root/sys/netinet6/raw_ip6.c
/*      $NetBSD: raw_ip6.c,v 1.185 2024/07/05 04:31:54 rin Exp $        */
/*      $KAME: raw_ip6.c,v 1.82 2001/07/23 18:57:56 jinmei Exp $        */

/*
 * Copyright (C) 1995, 1996, 1997, and 1998 WIDE Project.
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 * 1. Redistributions of source code must retain the above copyright
 *    notice, this list of conditions and the following disclaimer.
 * 2. Redistributions in binary form must reproduce the above copyright
 *    notice, this list of conditions and the following disclaimer in the
 *    documentation and/or other materials provided with the distribution.
 * 3. Neither the name of the project nor the names of its contributors
 *    may be used to endorse or promote products derived from this software
 *    without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE PROJECT AND CONTRIBUTORS ``AS IS'' AND
 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
 * ARE DISCLAIMED.  IN NO EVENT SHALL THE PROJECT OR CONTRIBUTORS BE LIABLE
 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
 * SUCH DAMAGE.
 */

/*
 * Copyright (c) 1982, 1986, 1988, 1993
 *      The Regents of the University of California.  All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 * 1. Redistributions of source code must retain the above copyright
 *    notice, this list of conditions and the following disclaimer.
 * 2. Redistributions in binary form must reproduce the above copyright
 *    notice, this list of conditions and the following disclaimer in the
 *    documentation and/or other materials provided with the distribution.
 * 3. Neither the name of the University nor the names of its contributors
 *    may be used to endorse or promote products derived from this software
 *    without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
 * ARE DISCLAIMED.  IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
 * SUCH DAMAGE.
 *
 *      @(#)raw_ip.c    8.2 (Berkeley) 1/4/94
 */

#include <sys/cdefs.h>
__KERNEL_RCSID(0, "$NetBSD: raw_ip6.c,v 1.185 2024/07/05 04:31:54 rin Exp $");

#ifdef _KERNEL_OPT
#include "opt_ipsec.h"
#include "opt_net_mpsafe.h"
#endif

#include <sys/param.h>
#include <sys/sysctl.h>
#include <sys/mbuf.h>
#include <sys/socket.h>
#include <sys/protosw.h>
#include <sys/socketvar.h>
#include <sys/systm.h>
#include <sys/proc.h>
#include <sys/kauth.h>
#include <sys/kmem.h>

#include <net/if.h>
#include <net/if_types.h>
#include <net/net_stats.h>

#include <netinet/in.h>
#include <netinet/in_var.h>
#include <netinet/ip6.h>
#include <netinet6/ip6_var.h>
#include <netinet6/ip6_private.h>
#include <netinet6/ip6_mroute.h>
#include <netinet/icmp6.h>
#include <netinet6/icmp6_private.h>
#include <netinet6/in6_pcb.h>
#include <netinet6/ip6protosw.h>
#include <netinet6/scope6_var.h>
#include <netinet6/raw_ip6.h>

#ifdef IPSEC
#include <netipsec/ipsec.h>
#include <netipsec/ipsec6.h>
#endif

#include "faith.h"
#if defined(NFAITH) && 0 < NFAITH
#include <net/if_faith.h>
#endif

extern struct inpcbtable rawcbtable;
struct  inpcbtable raw6cbtable;
#define ifatoia6(ifa)   ((struct in6_ifaddr *)(ifa))

/*
 * Raw interface to IP6 protocol.
 */

static percpu_t *rip6stat_percpu;

#define RIP6_STATINC(x)         _NET_STATINC(rip6stat_percpu, x)

static void sysctl_net_inet6_raw6_setup(struct sysctllog **);

/*
 * Initialize raw connection block queue.
 */
void
rip6_init(void)
{

        sysctl_net_inet6_raw6_setup(NULL);
        in6pcb_init(&raw6cbtable, 1, 1);

        rip6stat_percpu = percpu_alloc(sizeof(uint64_t) * RIP6_NSTATS);
}

static void
rip6_sbappendaddr(struct inpcb *last, struct ip6_hdr *ip6,
    const struct sockaddr *sa, int hlen, struct mbuf *n)
{
        struct mbuf *opts = NULL;

        if (last->inp_flags & IN6P_CONTROLOPTS ||
            SOOPT_TIMESTAMP(last->inp_socket->so_options))
                ip6_savecontrol(last, &opts, ip6, n);

        m_adj(n, hlen);

        if (sbappendaddr(&last->inp_socket->so_rcv, sa, n, opts) == 0) {
                soroverflow(last->inp_socket);
                m_freem(n);
                m_freem(opts);
                RIP6_STATINC(RIP6_STAT_FULLSOCK);
        } else {
                sorwakeup(last->inp_socket);
        }
}

/*
 * Setup generic address and protocol structures
 * for raw_input routine, then pass them along with
 * mbuf chain.
 */
int
rip6_input(struct mbuf **mp, int *offp, int proto)
{
        struct mbuf *m = *mp;
        struct ip6_hdr *ip6 = mtod(m, struct ip6_hdr *);
        struct inpcb *inp;
        struct inpcb *last = NULL;
        struct sockaddr_in6 rip6src;
        struct mbuf *n;

        RIP6_STATINC(RIP6_STAT_IPACKETS);

#if defined(NFAITH) && 0 < NFAITH
        if (faithprefix(&ip6->ip6_dst)) {
                /* send icmp6 host unreach? */
                m_freem(m);
                return IPPROTO_DONE;
        }
#endif

        sockaddr_in6_init(&rip6src, &ip6->ip6_src, 0, 0, 0);
        if (sa6_recoverscope(&rip6src) != 0) {
                /* XXX: should be impossible. */
                m_freem(m);
                return IPPROTO_DONE;
        }

        TAILQ_FOREACH(inp, &raw6cbtable.inpt_queue, inp_queue) {
                if (inp->inp_af != AF_INET6)
                        continue;
                if (in6p_ip6(inp).ip6_nxt &&
                    in6p_ip6(inp).ip6_nxt != proto)
                        continue;
                if (!IN6_IS_ADDR_UNSPECIFIED(&in6p_laddr(inp)) &&
                    !IN6_ARE_ADDR_EQUAL(&in6p_laddr(inp), &ip6->ip6_dst))
                        continue;
                if (!IN6_IS_ADDR_UNSPECIFIED(&in6p_faddr(inp)) &&
                    !IN6_ARE_ADDR_EQUAL(&in6p_faddr(inp), &ip6->ip6_src))
                        continue;
                if (in6p_cksum(inp) != -1) {
                        RIP6_STATINC(RIP6_STAT_ISUM);
                        /*
                         * Although in6_cksum() does not need the position of
                         * the checksum field for verification, enforce that it
                         * is located within the packet.  Userland has given
                         * a checksum offset, a packet too short for that is
                         * invalid.  Avoid overflow with user supplied offset.
                         */
                        if (m->m_pkthdr.len < *offp + 2 ||
                            m->m_pkthdr.len - *offp - 2 < in6p_cksum(inp) ||
                            in6_cksum(m, proto, *offp,
                            m->m_pkthdr.len - *offp)) {
                                RIP6_STATINC(RIP6_STAT_BADSUM);
                                continue;
                        }
                }

                if (last == NULL) {
                        ;
                }
#ifdef IPSEC
                else if (ipsec_used && ipsec_in_reject(m, last)) {
                        /* do not inject data into pcb */
                }
#endif
                else if ((n = m_copypacket(m, M_DONTWAIT)) != NULL) {
                        rip6_sbappendaddr(last, ip6, sin6tosa(&rip6src),
                            *offp, n);
                }

                last = inp;
        }

#ifdef IPSEC
        if (ipsec_used && last && ipsec_in_reject(m, last)) {
                m_freem(m);
                IP6_STATDEC(IP6_STAT_DELIVERED);
                /* do not inject data into pcb */
        } else
#endif
        if (last != NULL) {
                rip6_sbappendaddr(last, ip6, sin6tosa(&rip6src), *offp, m);
        } else {
                RIP6_STATINC(RIP6_STAT_NOSOCK);
                if (m->m_flags & M_MCAST)
                        RIP6_STATINC(RIP6_STAT_NOSOCKMCAST);
                if (proto == IPPROTO_NONE)
                        m_freem(m);
                else {
                        int s;
                        struct ifnet *rcvif = m_get_rcvif(m, &s);
                        const int prvnxt = ip6_get_prevhdr(m, *offp);
                        in6_ifstat_inc(rcvif, ifs6_in_protounknown);
                        m_put_rcvif(rcvif, &s);
                        icmp6_error(m, ICMP6_PARAM_PROB,
                            ICMP6_PARAMPROB_NEXTHEADER,
                            prvnxt);
                }
                IP6_STATDEC(IP6_STAT_DELIVERED);
        }
        return IPPROTO_DONE;
}

void *
rip6_ctlinput(int cmd, const struct sockaddr *sa, void *d)
{
        struct ip6_hdr *ip6;
        struct ip6ctlparam *ip6cp = NULL;
        const struct sockaddr_in6 *sa6_src = NULL;
        void *cmdarg;
        void (*notify)(struct inpcb *, int) = in6pcb_rtchange;
        int nxt;

        if (sa->sa_family != AF_INET6 ||
            sa->sa_len != sizeof(struct sockaddr_in6))
                return NULL;

        if ((unsigned)cmd >= PRC_NCMDS)
                return NULL;
        if (PRC_IS_REDIRECT(cmd))
                notify = in6pcb_rtchange, d = NULL;
        else if (cmd == PRC_HOSTDEAD)
                d = NULL;
        else if (cmd == PRC_MSGSIZE)
                ; /* special code is present, see below */
        else if (inet6ctlerrmap[cmd] == 0)
                return NULL;

        /* if the parameter is from icmp6, decode it. */
        if (d != NULL) {
                ip6cp = (struct ip6ctlparam *)d;
                ip6 = ip6cp->ip6c_ip6;
                cmdarg = ip6cp->ip6c_cmdarg;
                sa6_src = ip6cp->ip6c_src;
                nxt = ip6cp->ip6c_nxt;
        } else {
                ip6 = NULL;
                cmdarg = NULL;
                sa6_src = &sa6_any;
                nxt = -1;
        }

        if (ip6 && cmd == PRC_MSGSIZE) {
                const struct sockaddr_in6 *sa6 = (const struct sockaddr_in6 *)sa;
                int valid = 0;
                struct inpcb *inp;

                /*
                 * Check to see if we have a valid raw IPv6 socket
                 * corresponding to the address in the ICMPv6 message
                 * payload, and the protocol (ip6_nxt) meets the socket.
                 * XXX chase extension headers, or pass final nxt value
                 * from icmp6_notify_error()
                 */
                inp = NULL;
                inp = in6pcb_lookup(&raw6cbtable, &sa6->sin6_addr, 0,
                                             (const struct in6_addr *)&sa6_src->sin6_addr, 0, 0, 0);
#if 0
                if (!inp) {
                        /*
                         * As the use of sendto(2) is fairly popular,
                         * we may want to allow non-connected pcb too.
                         * But it could be too weak against attacks...
                         * We should at least check if the local
                         * address (= s) is really ours.
                         */
                        inp = in6pcb_lookup_bound(&raw6cbtable,
                            &sa6->sin6_addr, 0, 0);
                }
#endif

                if (inp && in6p_ip6(inp).ip6_nxt &&
                    in6p_ip6(inp).ip6_nxt == nxt)
                        valid++;

                /*
                 * Depending on the value of "valid" and routing table
                 * size (mtudisc_{hi,lo}wat), we will:
                 * - recalculate the new MTU and create the
                 *   corresponding routing entry, or
                 * - ignore the MTU change notification.
                 */
                icmp6_mtudisc_update((struct ip6ctlparam *)d, valid);

                /*
                 * regardless of if we called icmp6_mtudisc_update(),
                 * we need to call in6pcb_notify(), to notify path MTU
                 * change to the userland (RFC3542), because some
                 * unconnected sockets may share the same destination
                 * and want to know the path MTU.
                 */
        }

        (void) in6pcb_notify(&raw6cbtable, sa, 0,
            sin6tocsa(sa6_src), 0, cmd, cmdarg, notify);
        return NULL;
}

/*
 * Generate IPv6 header and pass packet to ip6_output.
 * Tack on options user may have setup with control call.
 */
int
rip6_output(struct mbuf *m, struct socket * const so,
    struct sockaddr_in6 * const dstsock, struct mbuf * const control)
{
        struct in6_addr *dst;
        struct ip6_hdr *ip6;
        struct inpcb *inp;
        u_int   plen = m->m_pkthdr.len;
        int error = 0;
        struct ip6_pktopts opt, *optp = NULL;
        struct ifnet *oifp = NULL;
        int type, code;         /* for ICMPv6 output statistics only */
        int scope_ambiguous = 0;
        int bound = curlwp_bind();
        struct psref psref;

        inp = sotoinpcb(so);

        dst = &dstsock->sin6_addr;
        if (control) {
                if ((error = ip6_setpktopts(control, &opt,
                    in6p_outputopts(inp),
                    kauth_cred_get(), so->so_proto->pr_protocol)) != 0) {
                        goto bad;
                }
                optp = &opt;
        } else
                optp = in6p_outputopts(inp);

        /*
         * Check and convert scope zone ID into internal form.
         * XXX: we may still need to determine the zone later.
         */
        if (!(so->so_state & SS_ISCONNECTED)) {
                if (dstsock->sin6_scope_id == 0 && !ip6_use_defzone)
                        scope_ambiguous = 1;
                if ((error = sa6_embedscope(dstsock, ip6_use_defzone)) != 0)
                        goto bad;
        }

        /*
         * For an ICMPv6 packet, we should know its type and code
         * to update statistics.
         */
        if (so->so_proto->pr_protocol == IPPROTO_ICMPV6) {
                struct icmp6_hdr *icmp6;
                if (m->m_len < sizeof(struct icmp6_hdr) &&
                    (m = m_pullup(m, sizeof(struct icmp6_hdr))) == NULL) {
                        error = ENOBUFS;
                        goto bad;
                }
                icmp6 = mtod(m, struct icmp6_hdr *);
                type = icmp6->icmp6_type;
                code = icmp6->icmp6_code;
        } else {
                type = 0;
                code = 0;
        }

        M_PREPEND(m, sizeof(*ip6), M_DONTWAIT);
        if (!m) {
                error = ENOBUFS;
                goto bad;
        }
        ip6 = mtod(m, struct ip6_hdr *);

        /*
         * Next header might not be ICMP6 but use its pseudo header anyway.
         */
        ip6->ip6_dst = *dst;

        /*
         * Source address selection.
         */
        error = in6_selectsrc(dstsock, optp, in6p_moptions(inp),
            &inp->inp_route, &in6p_laddr(inp), &oifp, &psref, &ip6->ip6_src);
        if (error != 0)
                goto bad;

        if (oifp && scope_ambiguous) {
                /*
                 * Application should provide a proper zone ID or the use of
                 * default zone IDs should be enabled.  Unfortunately, some
                 * applications do not behave as it should, so we need a
                 * workaround.  Even if an appropriate ID is not determined
                 * (when it's required), if we can determine the outgoing
                 * interface. determine the zone ID based on the interface.
                 */
                error = in6_setscope(&dstsock->sin6_addr, oifp, NULL);
                if (error != 0)
                        goto bad;
        }
        ip6->ip6_dst = dstsock->sin6_addr;

        /* fill in the rest of the IPv6 header fields */
        ip6->ip6_flow = in6p_flowinfo(inp) & IPV6_FLOWINFO_MASK;
        ip6->ip6_vfc  &= ~IPV6_VERSION_MASK;
        ip6->ip6_vfc  |= IPV6_VERSION;
        /* ip6_plen will be filled in ip6_output, so not fill it here. */
        ip6->ip6_nxt   = in6p_ip6(inp).ip6_nxt;
        ip6->ip6_hlim = in6pcb_selecthlim(inp, oifp);

        if_put(oifp, &psref);
        oifp = NULL;

        if (so->so_proto->pr_protocol == IPPROTO_ICMPV6 ||
            in6p_cksum(inp) != -1) {
                const uint8_t nxt = ip6->ip6_nxt;
                int off;
                u_int16_t sum;

                /* compute checksum */
                if (so->so_proto->pr_protocol == IPPROTO_ICMPV6)
                        off = offsetof(struct icmp6_hdr, icmp6_cksum);
                else
                        off = in6p_cksum(inp);
                if (plen < 2 || plen - 2 < off) {
                        error = EINVAL;
                        goto bad;
                }
                off += sizeof(struct ip6_hdr);

                sum = 0;
                m = m_copyback_cow(m, off, sizeof(sum), (void *)&sum,
                    M_DONTWAIT);
                if (m == NULL) {
                        error = ENOBUFS;
                        goto bad;
                }
                sum = in6_cksum(m, nxt, sizeof(*ip6), plen);
                m = m_copyback_cow(m, off, sizeof(sum), (void *)&sum,
                    M_DONTWAIT);
                if (m == NULL) {
                        error = ENOBUFS;
                        goto bad;
                }
        }

        {
                struct ifnet *ret_oifp = NULL;

                error = ip6_output(m, optp, &inp->inp_route, 0,
                    in6p_moptions(inp), inp, &ret_oifp);
                if (so->so_proto->pr_protocol == IPPROTO_ICMPV6) {
                        if (ret_oifp)
                                icmp6_ifoutstat_inc(ret_oifp, type, code);
                        ICMP6_STATINC(ICMP6_STAT_OUTHIST + type);
                } else
                        RIP6_STATINC(RIP6_STAT_OPACKETS);
        }

        goto freectl;

 bad:
        m_freem(m);

 freectl:
        if (control) {
                ip6_clearpktopts(&opt, -1);
                m_freem(control);
        }
        if_put(oifp, &psref);
        curlwp_bindx(bound);
        return error;
}

/*
 * Raw IPv6 socket option processing.
 */
int
rip6_ctloutput(int op, struct socket *so, struct sockopt *sopt)
{
        int error = 0;

        if (sopt->sopt_level == SOL_SOCKET && sopt->sopt_name == SO_NOHEADER) {
                int optval;

                /* need to fiddle w/ opt(IPPROTO_IPV6, IPV6_CHECKSUM)? */
                if (op == PRCO_GETOPT) {
                        optval = 1;
                        error = sockopt_set(sopt, &optval, sizeof(optval));
                } else if (op == PRCO_SETOPT) {
                        error = sockopt_getint(sopt, &optval);
                        if (error)
                                goto out;
                        if (optval == 0)
                                error = EINVAL;
                }

                goto out;
        } else if (sopt->sopt_level != IPPROTO_IPV6)
                return ip6_ctloutput(op, so, sopt);

        switch (sopt->sopt_name) {
        case MRT6_INIT:
        case MRT6_DONE:
        case MRT6_ADD_MIF:
        case MRT6_DEL_MIF:
        case MRT6_ADD_MFC:
        case MRT6_DEL_MFC:
        case MRT6_PIM:
                if (op == PRCO_SETOPT)
                        error = ip6_mrouter_set(so, sopt);
                else if (op == PRCO_GETOPT)
                        error = ip6_mrouter_get(so, sopt);
                else
                        error = EINVAL;
                break;
        case IPV6_CHECKSUM:
                return ip6_raw_ctloutput(op, so, sopt);
        default:
                return ip6_ctloutput(op, so, sopt);
        }
 out:
        return error;
}

extern  u_long rip6_sendspace;
extern  u_long rip6_recvspace;

int
rip6_attach(struct socket *so, int proto)
{
        struct inpcb *inp;
        int s, error;

        KASSERT(sotoinpcb(so) == NULL);
        sosetlock(so);

        error = kauth_authorize_network(kauth_cred_get(),
            KAUTH_NETWORK_SOCKET, KAUTH_REQ_NETWORK_SOCKET_RAWSOCK,
            KAUTH_ARG(AF_INET6),
            KAUTH_ARG(SOCK_RAW),
            KAUTH_ARG(so->so_proto->pr_protocol));
        if (error) {
                return error;
        }
        s = splsoftnet();
        error = soreserve(so, rip6_sendspace, rip6_recvspace);
        if (error) {
                splx(s);
                return error;
        }
        if ((error = inpcb_create(so, &raw6cbtable)) != 0) {
                splx(s);
                return error;
        }
        splx(s);
        inp = sotoinpcb(so);
        in6p_ip6(inp).ip6_nxt = proto;
        in6p_cksum(inp) = -1;

        in6p_icmp6filt(inp) = kmem_alloc(sizeof(struct icmp6_filter), KM_SLEEP);
        ICMP6_FILTER_SETPASSALL(in6p_icmp6filt(inp));
        KASSERT(solocked(so));
        return error;
}

static void
rip6_detach(struct socket *so)
{
        struct inpcb *inp = sotoinpcb(so);

        KASSERT(solocked(so));
        KASSERT(inp != NULL);

        if (so == ip6_mrouter) {
                ip6_mrouter_done();
        }
        /* xxx: RSVP */
        if (in6p_icmp6filt(inp) != NULL) {
                kmem_free(in6p_icmp6filt(inp), sizeof(struct icmp6_filter));
                in6p_icmp6filt(inp) = NULL;
        }
        inpcb_destroy(inp);
}

static int
rip6_accept(struct socket *so, struct sockaddr *nam)
{
        KASSERT(solocked(so));

        return EOPNOTSUPP;
}

static int
rip6_bind(struct socket *so, struct sockaddr *nam, struct lwp *l)
{
        struct inpcb *inp = sotoinpcb(so);
        struct sockaddr_in6 *addr = (struct sockaddr_in6 *)nam;
        struct ifaddr *ifa = NULL;
        int error = 0;
        int s;

        KASSERT(solocked(so));
        KASSERT(inp != NULL);
        KASSERT(nam != NULL);

        if (addr->sin6_len != sizeof(*addr))
                return EINVAL;
        if (IFNET_READER_EMPTY() || addr->sin6_family != AF_INET6)
                return EADDRNOTAVAIL;

        if ((error = sa6_embedscope(addr, ip6_use_defzone)) != 0)
                return error;

        /*
         * we don't support mapped address here, it would confuse
         * users so reject it
         */
        if (IN6_IS_ADDR_V4MAPPED(&addr->sin6_addr))
                return EADDRNOTAVAIL;
        s = pserialize_read_enter();
        if (!IN6_IS_ADDR_UNSPECIFIED(&addr->sin6_addr) &&
            (ifa = ifa_ifwithaddr(sin6tosa(addr))) == NULL) {
                error = EADDRNOTAVAIL;
                goto out;
        }
        if (ifa && (ifatoia6(ifa))->ia6_flags &
            (IN6_IFF_ANYCAST | IN6_IFF_DUPLICATED)) {
                error = EADDRNOTAVAIL;
                goto out;
        }

        in6p_laddr(inp) = addr->sin6_addr;
        error = 0;
out:
        pserialize_read_exit(s);
        return error;
}

static int
rip6_listen(struct socket *so, struct lwp *l)
{
        KASSERT(solocked(so));

        return EOPNOTSUPP;
}

static int
rip6_connect(struct socket *so, struct sockaddr *nam, struct lwp *l)
{
        struct inpcb *inp = sotoinpcb(so);
        struct sockaddr_in6 *addr = (struct sockaddr_in6 *)nam;
        struct in6_addr in6a;
        struct ifnet *ifp = NULL;
        int scope_ambiguous = 0;
        int error = 0;
        struct psref psref;
        int bound;

        KASSERT(solocked(so));
        KASSERT(inp != NULL);
        KASSERT(nam != NULL);

        if (IFNET_READER_EMPTY())
                return EADDRNOTAVAIL;
        if (addr->sin6_family != AF_INET6)
                return EAFNOSUPPORT;
        if (addr->sin6_len != sizeof(*addr))
                return EINVAL;

        /*
         * Application should provide a proper zone ID or the use of
         * default zone IDs should be enabled.  Unfortunately, some
         * applications do not behave as it should, so we need a
         * workaround.  Even if an appropriate ID is not determined,
         * we'll see if we can determine the outgoing interface.  If we
         * can, determine the zone ID based on the interface below.
         */
        if (addr->sin6_scope_id == 0 && !ip6_use_defzone)
                scope_ambiguous = 1;
        if ((error = sa6_embedscope(addr, ip6_use_defzone)) != 0)
                return error;

        bound = curlwp_bind();
        /* Source address selection. XXX: need pcblookup? */
        error = in6_selectsrc(addr, in6p_outputopts(inp),
            in6p_moptions(inp), &inp->inp_route,
            &in6p_laddr(inp), &ifp, &psref, &in6a);
        if (error != 0)
                goto out;
        /* XXX: see above */
        if (ifp && scope_ambiguous &&
            (error = in6_setscope(&addr->sin6_addr, ifp, NULL)) != 0) {
                goto out;
        }
        in6p_laddr(inp) = in6a;
        in6p_faddr(inp) = addr->sin6_addr;
        soisconnected(so);
out:
        if_put(ifp, &psref);
        curlwp_bindx(bound);
        return error;
}

static int
rip6_connect2(struct socket *so, struct socket *so2)
{
        KASSERT(solocked(so));

        return EOPNOTSUPP;
}

static int
rip6_disconnect(struct socket *so)
{
        struct inpcb *inp = sotoinpcb(so);

        KASSERT(solocked(so));
        KASSERT(inp != NULL);

        if ((so->so_state & SS_ISCONNECTED) == 0)
                return ENOTCONN;

        in6p_faddr(inp) = in6addr_any;
        so->so_state &= ~SS_ISCONNECTED;        /* XXX */
        return 0;
}

static int
rip6_shutdown(struct socket *so)
{
        KASSERT(solocked(so));

        /*
         * Mark the connection as being incapable of further input.
         */
        socantsendmore(so);
        return 0;
}

static int
rip6_abort(struct socket *so)
{
        KASSERT(solocked(so));

        soisdisconnected(so);
        rip6_detach(so);
        return 0;
}

static int
rip6_ioctl(struct socket *so, u_long cmd, void *nam, struct ifnet *ifp)
{
        return in6_control(so, cmd, nam, ifp);
}

static int
rip6_stat(struct socket *so, struct stat *ub)
{
        KASSERT(solocked(so));

        /* stat: don't bother with a blocksize */
        return 0;
}

static int
rip6_peeraddr(struct socket *so, struct sockaddr *nam)
{
        KASSERT(solocked(so));
        KASSERT(sotoinpcb(so) != NULL);
        KASSERT(nam != NULL);

        in6pcb_fetch_peeraddr(sotoinpcb(so), (struct sockaddr_in6 *)nam);
        return 0;
}

static int
rip6_sockaddr(struct socket *so, struct sockaddr *nam)
{
        KASSERT(solocked(so));
        KASSERT(sotoinpcb(so) != NULL);
        KASSERT(nam != NULL);

        in6pcb_fetch_sockaddr(sotoinpcb(so), (struct sockaddr_in6 *)nam);
        return 0;
}

static int
rip6_rcvd(struct socket *so, int flags, struct lwp *l)
{
        KASSERT(solocked(so));

        return EOPNOTSUPP;
}

static int
rip6_recvoob(struct socket *so, struct mbuf *m, int flags)
{
        KASSERT(solocked(so));

        return EOPNOTSUPP;
}

static int
rip6_send(struct socket *so, struct mbuf *m, struct sockaddr *nam,
    struct mbuf *control, struct lwp *l)
{
        struct inpcb *inp = sotoinpcb(so);
        struct sockaddr_in6 tmp;
        struct sockaddr_in6 *dst;
        int error = 0;

        KASSERT(solocked(so));
        KASSERT(inp != NULL);
        KASSERT(m != NULL);

        /*
         * Ship a packet out. The appropriate raw output
         * routine handles any messaging necessary.
         */

        /* always copy sockaddr to avoid overwrites */
        if (so->so_state & SS_ISCONNECTED) {
                if (nam) {
                        error = EISCONN;
                        goto release;
                }
                /* XXX */
                sockaddr_in6_init(&tmp, &in6p_faddr(inp), 0, 0, 0);
                dst = &tmp;
        } else {
                if (nam == NULL) {
                        error = ENOTCONN;
                        goto release;
                }
                tmp = *(struct sockaddr_in6 *)nam;
                dst = &tmp;

                if (dst->sin6_family != AF_INET6) {
                        error = EAFNOSUPPORT;
                        goto release;
                }
                if (dst->sin6_len != sizeof(*dst)) {
                        error = EINVAL;
                        goto release;
                }
        }
        error = rip6_output(m, so, dst, control);
        m = NULL;

release:
        m_freem(m);

        return error;
}

static int
rip6_sendoob(struct socket *so, struct mbuf *m, struct mbuf *control)
{
        KASSERT(solocked(so));

        m_freem(m);
        m_freem(control);

        return EOPNOTSUPP;
}

static int
rip6_purgeif(struct socket *so, struct ifnet *ifp)
{

        mutex_enter(softnet_lock);
        in6pcb_purgeif0(&raw6cbtable, ifp);
#ifdef NET_MPSAFE
        mutex_exit(softnet_lock);
#endif
        in6_purgeif(ifp);
#ifdef NET_MPSAFE
        mutex_enter(softnet_lock);
#endif
        in6pcb_purgeif(&raw6cbtable, ifp);
        mutex_exit(softnet_lock);

        return 0;
}

static int
sysctl_net_inet6_raw6_stats(SYSCTLFN_ARGS)
{

        return (NETSTAT_SYSCTL(rip6stat_percpu, RIP6_NSTATS));
}

static void
sysctl_net_inet6_raw6_setup(struct sysctllog **clog)
{

        sysctl_createv(clog, 0, NULL, NULL,
                       CTLFLAG_PERMANENT,
                       CTLTYPE_NODE, "inet6", NULL,
                       NULL, 0, NULL, 0,
                       CTL_NET, PF_INET6, CTL_EOL);
        sysctl_createv(clog, 0, NULL, NULL,
                       CTLFLAG_PERMANENT,
                       CTLTYPE_NODE, "raw6",
                       SYSCTL_DESCR("Raw IPv6 settings"),
                       NULL, 0, NULL, 0,
                       CTL_NET, PF_INET6, IPPROTO_RAW, CTL_EOL);

        sysctl_createv(clog, 0, NULL, NULL,
                       CTLFLAG_PERMANENT,
                       CTLTYPE_STRUCT, "pcblist",
                       SYSCTL_DESCR("Raw IPv6 control block list"),
                       sysctl_inpcblist, 0, &raw6cbtable, 0,
                       CTL_NET, PF_INET6, IPPROTO_RAW,
                       CTL_CREATE, CTL_EOL);
        sysctl_createv(clog, 0, NULL, NULL,
                       CTLFLAG_PERMANENT,
                       CTLTYPE_STRUCT, "stats",
                       SYSCTL_DESCR("Raw IPv6 statistics"),
                       sysctl_net_inet6_raw6_stats, 0, NULL, 0,
                       CTL_NET, PF_INET6, IPPROTO_RAW, RAW6CTL_STATS,
                       CTL_EOL);
}

PR_WRAP_USRREQS(rip6)
#define rip6_attach             rip6_attach_wrapper
#define rip6_detach             rip6_detach_wrapper
#define rip6_accept             rip6_accept_wrapper
#define rip6_bind               rip6_bind_wrapper
#define rip6_listen             rip6_listen_wrapper
#define rip6_connect            rip6_connect_wrapper
#define rip6_connect2           rip6_connect2_wrapper
#define rip6_disconnect         rip6_disconnect_wrapper
#define rip6_shutdown           rip6_shutdown_wrapper
#define rip6_abort              rip6_abort_wrapper
#define rip6_ioctl              rip6_ioctl_wrapper
#define rip6_stat               rip6_stat_wrapper
#define rip6_peeraddr           rip6_peeraddr_wrapper
#define rip6_sockaddr           rip6_sockaddr_wrapper
#define rip6_rcvd               rip6_rcvd_wrapper
#define rip6_recvoob            rip6_recvoob_wrapper
#define rip6_send               rip6_send_wrapper
#define rip6_sendoob            rip6_sendoob_wrapper
#define rip6_purgeif            rip6_purgeif_wrapper

const struct pr_usrreqs rip6_usrreqs = {
        .pr_attach      = rip6_attach,
        .pr_detach      = rip6_detach,
        .pr_accept      = rip6_accept,
        .pr_bind        = rip6_bind,
        .pr_listen      = rip6_listen,
        .pr_connect     = rip6_connect,
        .pr_connect2    = rip6_connect2,
        .pr_disconnect  = rip6_disconnect,
        .pr_shutdown    = rip6_shutdown,
        .pr_abort       = rip6_abort,
        .pr_ioctl       = rip6_ioctl,
        .pr_stat        = rip6_stat,
        .pr_peeraddr    = rip6_peeraddr,
        .pr_sockaddr    = rip6_sockaddr,
        .pr_rcvd        = rip6_rcvd,
        .pr_recvoob     = rip6_recvoob,
        .pr_send        = rip6_send,
        .pr_sendoob     = rip6_sendoob,
        .pr_purgeif     = rip6_purgeif,
};