Return to ip6_input.c CVS log | Up to [cvs.NetBSD.org] / src / sys / netinet6 |
Please note that diffs are not public domain; they are subject to the copyright notices on the relevant files. |
version 1.56, 2002/06/09 14:43:12 | version 1.78.2.2, 2006/12/30 20:50:38 | ||
---|---|---|---|
|
|
||
* 2. Redistributions in binary form must reproduce the above copyright | * 2. Redistributions in binary form must reproduce the above copyright | ||
* notice, this list of conditions and the following disclaimer in the | * notice, this list of conditions and the following disclaimer in the | ||
* documentation and/or other materials provided with the distribution. | * documentation and/or other materials provided with the distribution. | ||
* 3. All advertising materials mentioning features or use of this software | * 3. Neither the name of the University nor the names of its contributors | ||
* must display the following acknowledgement: | |||
* This product includes software developed by the University of | |||
* California, Berkeley and its contributors. | |||
* 4. Neither the name of the University nor the names of its contributors | |||
* may be used to endorse or promote products derived from this software | * may be used to endorse or promote products derived from this software | ||
* without specific prior written permission. | * without specific prior written permission. | ||
* | * | ||
|
|
||
__KERNEL_RCSID(0, "$NetBSD$"); | __KERNEL_RCSID(0, "$NetBSD$"); | ||
#include "opt_inet.h" | #include "opt_inet.h" | ||
#include "opt_inet6.h" | |||
#include "opt_ipsec.h" | #include "opt_ipsec.h" | ||
#include "opt_pfil_hooks.h" | #include "opt_pfil_hooks.h" | ||
|
|
||
#include <netinet6/ip6_var.h> | #include <netinet6/ip6_var.h> | ||
#include <netinet6/in6_pcb.h> | #include <netinet6/in6_pcb.h> | ||
#include <netinet/icmp6.h> | #include <netinet/icmp6.h> | ||
#include <netinet6/scope6_var.h> | |||
#include <netinet6/in6_ifattach.h> | #include <netinet6/in6_ifattach.h> | ||
#include <netinet6/nd6.h> | #include <netinet6/nd6.h> | ||
|
|
||
#include <netinet6/ip6protosw.h> | #include <netinet6/ip6protosw.h> | ||
/* we need it for NLOOP. */ | |||
#include "loop.h" | |||
#include "faith.h" | #include "faith.h" | ||
#include "gif.h" | #include "gif.h" | ||
#include "bpfilter.h" | |||
#if NGIF > 0 | #if NGIF > 0 | ||
#include <netinet6/in6_gif.h> | #include <netinet6/in6_gif.h> | ||
|
|
||
struct in6_ifaddr *in6_ifaddr; | struct in6_ifaddr *in6_ifaddr; | ||
struct ifqueue ip6intrq; | struct ifqueue ip6intrq; | ||
extern struct ifnet loif[NLOOP]; | extern struct callout in6_tmpaddrtimer_ch; | ||
int ip6_forward_srcrt; /* XXX */ | int ip6_forward_srcrt; /* XXX */ | ||
int ip6_sourcecheck; /* XXX */ | int ip6_sourcecheck; /* XXX */ | ||
int ip6_sourcecheck_interval; /* XXX */ | int ip6_sourcecheck_interval; /* XXX */ | ||
|
|
||
struct ip6stat ip6stat; | struct ip6stat ip6stat; | ||
static void ip6_init2 __P((void *)); | static void ip6_init2 __P((void *)); | ||
static struct m_tag *ip6_setdstifaddr __P((struct mbuf *, struct in6_ifaddr *)); | |||
static int ip6_hopopts_input __P((u_int32_t *, u_int32_t *, struct mbuf **, int *)); | static int ip6_hopopts_input __P((u_int32_t *, u_int32_t *, struct mbuf **, int *)); | ||
static struct mbuf *ip6_pullexthdr __P((struct mbuf *, size_t, int)); | |||
/* | /* | ||
* IP6 initialization: fill in IP6 protocol switch table. | * IP6 initialization: fill in IP6 protocol switch table. | ||
|
|
||
void | void | ||
ip6_init() | ip6_init() | ||
{ | { | ||
struct ip6protosw *pr; | const struct ip6protosw *pr; | ||
int i; | int i; | ||
pr = (struct ip6protosw *)pffindproto(PF_INET6, IPPROTO_RAW, SOCK_RAW); | pr = (const struct ip6protosw *)pffindproto(PF_INET6, IPPROTO_RAW, SOCK_RAW); | ||
if (pr == 0) | if (pr == 0) | ||
panic("ip6_init"); | panic("ip6_init"); | ||
for (i = 0; i < IPPROTO_MAX; i++) | for (i = 0; i < IPPROTO_MAX; i++) | ||
ip6_protox[i] = pr - inet6sw; | ip6_protox[i] = pr - inet6sw; | ||
for (pr = (struct ip6protosw *)inet6domain.dom_protosw; | for (pr = (const struct ip6protosw *)inet6domain.dom_protosw; | ||
pr < (struct ip6protosw *)inet6domain.dom_protoswNPROTOSW; pr++) | pr < (const struct ip6protosw *)inet6domain.dom_protoswNPROTOSW; pr++) | ||
if (pr->pr_domain->dom_family == PF_INET6 && | if (pr->pr_domain->dom_family == PF_INET6 && | ||
pr->pr_protocol && pr->pr_protocol != IPPROTO_RAW) | pr->pr_protocol && pr->pr_protocol != IPPROTO_RAW) | ||
ip6_protox[pr->pr_protocol] = pr - inet6sw; | ip6_protox[pr->pr_protocol] = pr - inet6sw; | ||
ip6intrq.ifq_maxlen = ip6qmaxlen; | ip6intrq.ifq_maxlen = ip6qmaxlen; | ||
scope6_init(); | |||
addrsel_policy_init(); | |||
nd6_init(); | nd6_init(); | ||
frag6_init(); | frag6_init(); | ||
ip6_flow_seq = arc4random(); | ip6_desync_factor = arc4random() % MAX_TEMP_DESYNC_FACTOR; | ||
ip6_init2((void *)0); | ip6_init2((void *)0); | ||
|
|
||
} | } | ||
static void | static void | ||
ip6_init2(dummy) | ip6_init2(void *dummy) | ||
void *dummy; | |||
{ | { | ||
/* nd6_timer_init */ | /* nd6_timer_init */ | ||
callout_init(&nd6_timer_ch); | callout_init(&nd6_timer_ch); | ||
callout_reset(&nd6_timer_ch, hz, nd6_timer, NULL); | callout_reset(&nd6_timer_ch, hz, nd6_timer, NULL); | ||
/* timer for regeneranation of temporary addresses randomize ID */ | |||
callout_init(&in6_tmpaddrtimer_ch); | |||
callout_reset(&in6_tmpaddrtimer_ch, | |||
(ip6_temp_preferred_lifetime - ip6_desync_factor - | |||
ip6_temp_regen_advance) * hz, | |||
in6_tmpaddrtimer, NULL); | |||
} | } | ||
/* | /* | ||
|
|
||
splx(s); | splx(s); | ||
if (m == 0) | if (m == 0) | ||
return; | return; | ||
/* drop the packet if IPv6 operation is disabled on the IF */ | |||
if ((ND_IFINFO(m->m_pkthdr.rcvif)->flags & ND6_IFF_IFDISABLED)) { | |||
m_freem(m); | |||
return; | |||
} | |||
ip6_input(m); | ip6_input(m); | ||
} | } | ||
} | } | ||
|
|
||
u_int32_t rtalert = ~0; | u_int32_t rtalert = ~0; | ||
int nxt, ours = 0; | int nxt, ours = 0; | ||
struct ifnet *deliverifp = NULL; | struct ifnet *deliverifp = NULL; | ||
int srcrt = 0; | |||
#ifdef IPSEC | #ifdef IPSEC | ||
/* | /* | ||
|
|
||
#endif | #endif | ||
/* | /* | ||
* make sure we don't have onion peering information into m_tag. | |||
*/ | |||
ip6_delaux(m); | |||
/* | |||
* mbuf statistics | * mbuf statistics | ||
*/ | */ | ||
if (m->m_flags & M_EXT) { | if (m->m_flags & M_EXT) { | ||
|
|
||
#define M2MMAX (sizeof(ip6stat.ip6s_m2m)/sizeof(ip6stat.ip6s_m2m[0])) | #define M2MMAX (sizeof(ip6stat.ip6s_m2m)/sizeof(ip6stat.ip6s_m2m[0])) | ||
if (m->m_next) { | if (m->m_next) { | ||
if (m->m_flags & M_LOOP) { | if (m->m_flags & M_LOOP) { | ||
ip6stat.ip6s_m2m[loif[0].if_index]++; /* XXX */ | ip6stat.ip6s_m2m[lo0ifp->if_index]++; /* XXX */ | ||
} else if (m->m_pkthdr.rcvif->if_index < M2MMAX) | } else if (m->m_pkthdr.rcvif->if_index < M2MMAX) | ||
ip6stat.ip6s_m2m[m->m_pkthdr.rcvif->if_index]++; | ip6stat.ip6s_m2m[m->m_pkthdr.rcvif->if_index]++; | ||
else | else | ||
|
|
||
in6_ifstat_inc(m->m_pkthdr.rcvif, ifs6_in_receive); | in6_ifstat_inc(m->m_pkthdr.rcvif, ifs6_in_receive); | ||
ip6stat.ip6s_total++; | ip6stat.ip6s_total++; | ||
#ifndef PULLDOWN_TEST | /* | ||
/* XXX is the line really necessary? */ | * If the IPv6 header is not aligned, slurp it up into a new | ||
IP6_EXTHDR_CHECK(m, 0, sizeof(struct ip6_hdr), /*nothing*/); | * mbuf with space for link headers, in the event we forward | ||
#endif | * it. Otherwise, if it is aligned, make sure the entire base | ||
* IPv6 header is in the first mbuf of the chain. | |||
if (m->m_len < sizeof(struct ip6_hdr)) { | */ | ||
struct ifnet *inifp; | if (IP6_HDR_ALIGNED_P(mtod(m, caddr_t)) == 0) { | ||
inifp = m->m_pkthdr.rcvif; | struct ifnet *inifp = m->m_pkthdr.rcvif; | ||
if ((m = m_copyup(m, sizeof(struct ip6_hdr), | |||
(max_linkhdr + 3) & ~3)) == NULL) { | |||
/* XXXJRT new stat, please */ | |||
ip6stat.ip6s_toosmall++; | |||
in6_ifstat_inc(inifp, ifs6_in_hdrerr); | |||
return; | |||
} | |||
} else if (__predict_false(m->m_len < sizeof(struct ip6_hdr))) { | |||
struct ifnet *inifp = m->m_pkthdr.rcvif; | |||
if ((m = m_pullup(m, sizeof(struct ip6_hdr))) == NULL) { | if ((m = m_pullup(m, sizeof(struct ip6_hdr))) == NULL) { | ||
ip6stat.ip6s_toosmall++; | ip6stat.ip6s_toosmall++; | ||
in6_ifstat_inc(inifp, ifs6_in_hdrerr); | in6_ifstat_inc(inifp, ifs6_in_hdrerr); | ||
|
|
||
if (1) | if (1) | ||
#endif | #endif | ||
{ | { | ||
struct in6_addr odst; | |||
odst = ip6->ip6_dst; | |||
if (pfil_run_hooks(&inet6_pfil_hook, &m, m->m_pkthdr.rcvif, | if (pfil_run_hooks(&inet6_pfil_hook, &m, m->m_pkthdr.rcvif, | ||
PFIL_IN) != 0) | PFIL_IN) != 0) | ||
return; | return; | ||
if (m == NULL) | if (m == NULL) | ||
return; | return; | ||
ip6 = mtod(m, struct ip6_hdr *); | ip6 = mtod(m, struct ip6_hdr *); | ||
srcrt = !IN6_ARE_ADDR_EQUAL(&odst, &ip6->ip6_dst); | |||
} | } | ||
#endif /* PFIL_HOOKS */ | #endif /* PFIL_HOOKS */ | ||
|
|
||
} | } | ||
#endif | #endif | ||
if (IN6_IS_ADDR_LOOPBACK(&ip6->ip6_src) || | /* | ||
IN6_IS_ADDR_LOOPBACK(&ip6->ip6_dst)) { | * Disambiguate address scope zones (if there is ambiguity). | ||
if (m->m_pkthdr.rcvif->if_flags & IFF_LOOPBACK) { | * We first make sure that the original source or destination address | ||
ours = 1; | * is not in our internal form for scoped addresses. Such addresses | ||
deliverifp = m->m_pkthdr.rcvif; | * are not necessarily invalid spec-wise, but we cannot accept them due | ||
goto hbhcheck; | * to the usage conflict. | ||
} else { | * in6_setscope() then also checks and rejects the cases where src or | ||
ip6stat.ip6s_badscope++; | * dst are the loopback address and the receiving interface | ||
in6_ifstat_inc(m->m_pkthdr.rcvif, ifs6_in_addrerr); | * is not loopback. | ||
goto bad; | */ | ||
} | if (__predict_false( | ||
} | m_makewritable(&m, 0, sizeof(struct ip6_hdr), M_DONTWAIT))) | ||
goto bad; | |||
/* drop packets if interface ID portion is already filled */ | ip6 = mtod(m, struct ip6_hdr *); | ||
if ((m->m_pkthdr.rcvif->if_flags & IFF_LOOPBACK) == 0) { | if (in6_clearscope(&ip6->ip6_src) || in6_clearscope(&ip6->ip6_dst)) { | ||
if (IN6_IS_SCOPE_LINKLOCAL(&ip6->ip6_src) && | ip6stat.ip6s_badscope++; /* XXX */ | ||
ip6->ip6_src.s6_addr16[1]) { | goto bad; | ||
ip6stat.ip6s_badscope++; | |||
goto bad; | |||
} | |||
if (IN6_IS_SCOPE_LINKLOCAL(&ip6->ip6_dst) && | |||
ip6->ip6_dst.s6_addr16[1]) { | |||
ip6stat.ip6s_badscope++; | |||
goto bad; | |||
} | |||
} | } | ||
if (in6_setscope(&ip6->ip6_src, m->m_pkthdr.rcvif, NULL) || | |||
if (IN6_IS_SCOPE_LINKLOCAL(&ip6->ip6_src)) | in6_setscope(&ip6->ip6_dst, m->m_pkthdr.rcvif, NULL)) { | ||
ip6->ip6_src.s6_addr16[1] | ip6stat.ip6s_badscope++; | ||
= htons(m->m_pkthdr.rcvif->if_index); | goto bad; | ||
if (IN6_IS_SCOPE_LINKLOCAL(&ip6->ip6_dst)) | |||
ip6->ip6_dst.s6_addr16[1] | |||
= htons(m->m_pkthdr.rcvif->if_index); | |||
/* | |||
* We use rt->rt_ifp to determine if the address is ours or not. | |||
* If rt_ifp is lo0, the address is ours. | |||
* The problem here is, rt->rt_ifp for fe80::%lo0/64 is set to lo0, | |||
* so any address under fe80::%lo0/64 will be mistakenly considered | |||
* local. The special case is supplied to handle the case properly | |||
* by actually looking at interface addresses | |||
* (using in6ifa_ifpwithaddr). | |||
*/ | |||
if ((m->m_pkthdr.rcvif->if_flags & IFF_LOOPBACK) != 0 && | |||
IN6_IS_ADDR_LINKLOCAL(&ip6->ip6_dst)) { | |||
if (!in6ifa_ifpwithaddr(m->m_pkthdr.rcvif, &ip6->ip6_dst)) { | |||
icmp6_error(m, ICMP6_DST_UNREACH, | |||
ICMP6_DST_UNREACH_ADDR, 0); | |||
/* m is already freed */ | |||
return; | |||
} | |||
ours = 1; | |||
deliverifp = m->m_pkthdr.rcvif; | |||
goto hbhcheck; | |||
} | } | ||
/* | /* | ||
|
|
||
/* | /* | ||
* Unicast check | * Unicast check | ||
*/ | */ | ||
if (ip6_forward_rt.ro_rt != NULL && | if (!IN6_ARE_ADDR_EQUAL(&ip6->ip6_dst, | ||
(ip6_forward_rt.ro_rt->rt_flags & RTF_UP) != 0 && | &((struct sockaddr_in6 *)(&ip6_forward_rt.ro_dst))->sin6_addr)) | ||
IN6_ARE_ADDR_EQUAL(&ip6->ip6_dst, | rtcache_free((struct route *)&ip6_forward_rt); | ||
&((struct sockaddr_in6 *)(&ip6_forward_rt.ro_dst))->sin6_addr)) | else | ||
rtcache_check((struct route *)&ip6_forward_rt); | |||
if (ip6_forward_rt.ro_rt != NULL) { | |||
/* XXX Revalidated route is accounted wrongly. */ | |||
ip6stat.ip6s_forward_cachehit++; | ip6stat.ip6s_forward_cachehit++; | ||
else { | } else { | ||
struct sockaddr_in6 *dst6; | struct sockaddr_in6 *dst6; | ||
if (ip6_forward_rt.ro_rt) { | ip6stat.ip6s_forward_cachemiss++; | ||
/* route is down or destination is different */ | |||
ip6stat.ip6s_forward_cachemiss++; | |||
RTFREE(ip6_forward_rt.ro_rt); | |||
ip6_forward_rt.ro_rt = 0; | |||
} | |||
bzero(&ip6_forward_rt.ro_dst, sizeof(struct sockaddr_in6)); | bzero(&ip6_forward_rt.ro_dst, sizeof(struct sockaddr_in6)); | ||
dst6 = (struct sockaddr_in6 *)&ip6_forward_rt.ro_dst; | dst6 = (struct sockaddr_in6 *)&ip6_forward_rt.ro_dst; | ||
|
|
||
dst6->sin6_family = AF_INET6; | dst6->sin6_family = AF_INET6; | ||
dst6->sin6_addr = ip6->ip6_dst; | dst6->sin6_addr = ip6->ip6_dst; | ||
rtalloc((struct route *)&ip6_forward_rt); | rtcache_init((struct route *)&ip6_forward_rt); | ||
} | } | ||
#define rt6_key(r) ((struct sockaddr_in6 *)((r)->rt_nodes->rn_key)) | #define rt6_key(r) ((struct sockaddr_in6 *)((r)->rt_nodes->rn_key)) | ||
|
|
||
* But we think it's even useful in some situations, e.g. when using | * But we think it's even useful in some situations, e.g. when using | ||
* a special daemon which wants to intercept the packet. | * a special daemon which wants to intercept the packet. | ||
*/ | */ | ||
if (ip6_forward_rt.ro_rt && | if (ip6_forward_rt.ro_rt != NULL && | ||
(ip6_forward_rt.ro_rt->rt_flags & | (ip6_forward_rt.ro_rt->rt_flags & | ||
(RTF_HOST|RTF_GATEWAY)) == RTF_HOST && | (RTF_HOST|RTF_GATEWAY)) == RTF_HOST && | ||
!(ip6_forward_rt.ro_rt->rt_flags & RTF_CLONED) && | !(ip6_forward_rt.ro_rt->rt_flags & RTF_CLONED) && | ||
|
|
||
*/ | */ | ||
#if defined(NFAITH) && 0 < NFAITH | #if defined(NFAITH) && 0 < NFAITH | ||
if (ip6_keepfaith) { | if (ip6_keepfaith) { | ||
if (ip6_forward_rt.ro_rt && ip6_forward_rt.ro_rt->rt_ifp | if (ip6_forward_rt.ro_rt != NULL && | ||
&& ip6_forward_rt.ro_rt->rt_ifp->if_type == IFT_FAITH) { | ip6_forward_rt.ro_rt->rt_ifp != NULL && | ||
ip6_forward_rt.ro_rt->rt_ifp->if_type == IFT_FAITH) { | |||
/* XXX do we need more sanity checks? */ | /* XXX do we need more sanity checks? */ | ||
ours = 1; | ours = 1; | ||
deliverifp = ip6_forward_rt.ro_rt->rt_ifp; /* faith */ | deliverifp = ip6_forward_rt.ro_rt->rt_ifp; /* faith */ | ||
|
|
||
* working right. | * working right. | ||
*/ | */ | ||
struct ifaddr *ifa; | struct ifaddr *ifa; | ||
for (ifa = m->m_pkthdr.rcvif->if_addrlist.tqh_first; | TAILQ_FOREACH(ifa, &m->m_pkthdr.rcvif->if_addrlist, ifa_list) { | ||
ifa; | |||
ifa = ifa->ifa_list.tqe_next) { | |||
if (ifa->ifa_addr == NULL) | if (ifa->ifa_addr == NULL) | ||
continue; /* just for safety */ | continue; /* just for safety */ | ||
if (ifa->ifa_addr->sa_family != AF_INET6) | if (ifa->ifa_addr->sa_family != AF_INET6) | ||
|
|
||
hbhcheck: | hbhcheck: | ||
/* | /* | ||
* record address information into m_tag, if we don't have one yet. | |||
* note that we are unable to record it, if the address is not listed | |||
* as our interface address (e.g. multicast addresses, addresses | |||
* within FAITH prefixes and such). | |||
*/ | |||
if (deliverifp && !ip6_getdstifaddr(m)) { | |||
struct in6_ifaddr *ia6; | |||
ia6 = in6_ifawithifp(deliverifp, &ip6->ip6_dst); | |||
if (ia6) { | |||
if (!ip6_setdstifaddr(m, ia6)) { | |||
/* | |||
* XXX maybe we should drop the packet here, | |||
* as we could not provide enough information | |||
* to the upper layers. | |||
*/ | |||
} | |||
} | |||
} | |||
/* | |||
* Process Hop-by-Hop options header if it's contained. | * Process Hop-by-Hop options header if it's contained. | ||
* m may be modified in ip6_hopopts_input(). | * m may be modified in ip6_hopopts_input(). | ||
* If a JumboPayload option is included, plen will also be modified. | * If a JumboPayload option is included, plen will also be modified. | ||
|
|
||
if (ip6->ip6_plen == 0 && plen == 0) { | if (ip6->ip6_plen == 0 && plen == 0) { | ||
/* | /* | ||
* Note that if a valid jumbo payload option is | * Note that if a valid jumbo payload option is | ||
* contained, ip6_hoptops_input() must set a valid | * contained, ip6_hopopts_input() must set a valid | ||
* (non-zero) payload length to the variable plen. | * (non-zero) payload length to the variable plen. | ||
*/ | */ | ||
ip6stat.ip6s_badoptions++; | ip6stat.ip6s_badoptions++; | ||
|
|
||
(caddr_t)&ip6->ip6_plen - (caddr_t)ip6); | (caddr_t)&ip6->ip6_plen - (caddr_t)ip6); | ||
return; | return; | ||
} | } | ||
#ifndef PULLDOWN_TEST | |||
/* ip6_hopopts_input() ensures that mbuf is contiguous */ | |||
hbh = (struct ip6_hbh *)(ip6 + 1); | |||
#else | |||
IP6_EXTHDR_GET(hbh, struct ip6_hbh *, m, sizeof(struct ip6_hdr), | IP6_EXTHDR_GET(hbh, struct ip6_hbh *, m, sizeof(struct ip6_hdr), | ||
sizeof(struct ip6_hbh)); | sizeof(struct ip6_hbh)); | ||
if (hbh == NULL) { | if (hbh == NULL) { | ||
ip6stat.ip6s_tooshort++; | ip6stat.ip6s_tooshort++; | ||
return; | return; | ||
} | } | ||
#endif | KASSERT(IP6_HDR_ALIGNED_P(hbh)); | ||
nxt = hbh->ip6h_nxt; | nxt = hbh->ip6h_nxt; | ||
/* | /* | ||
|
|
||
return; | return; | ||
} | } | ||
} else if (!ours) { | } else if (!ours) { | ||
ip6_forward(m, 0); | ip6_forward(m, srcrt); | ||
return; | return; | ||
} | } | ||
|
|
||
} | } | ||
/* | /* | ||
* set/grab in6_ifaddr correspond to IPv6 destination address. | |||
*/ | |||
static struct m_tag * | |||
ip6_setdstifaddr(m, ia6) | |||
struct mbuf *m; | |||
struct in6_ifaddr *ia6; | |||
{ | |||
struct m_tag *mtag; | |||
mtag = ip6_addaux(m); | |||
if (mtag) | |||
((struct ip6aux *)(mtag + 1))->ip6a_dstia6 = ia6; | |||
return mtag; /* NULL if failed to set */ | |||
} | |||
struct in6_ifaddr * | |||
ip6_getdstifaddr(m) | |||
struct mbuf *m; | |||
{ | |||
struct m_tag *mtag; | |||
mtag = ip6_findaux(m); | |||
if (mtag) | |||
return ((struct ip6aux *)(mtag + 1))->ip6a_dstia6; | |||
else | |||
return NULL; | |||
} | |||
/* | |||
* Hop-by-Hop options header processing. If a valid jumbo payload option is | * Hop-by-Hop options header processing. If a valid jumbo payload option is | ||
* included, the real payload length will be stored in plenp. | * included, the real payload length will be stored in plenp. | ||
*/ | */ | ||
|
|
||
struct mbuf *m = *mp; | struct mbuf *m = *mp; | ||
int off = *offp, hbhlen; | int off = *offp, hbhlen; | ||
struct ip6_hbh *hbh; | struct ip6_hbh *hbh; | ||
u_int8_t *opt; | |||
/* validation of the length of the header */ | /* validation of the length of the header */ | ||
#ifndef PULLDOWN_TEST | |||
IP6_EXTHDR_CHECK(m, off, sizeof(*hbh), -1); | |||
hbh = (struct ip6_hbh *)(mtod(m, caddr_t) + off); | |||
hbhlen = (hbh->ip6h_len + 1) << 3; | |||
IP6_EXTHDR_CHECK(m, off, hbhlen, -1); | |||
hbh = (struct ip6_hbh *)(mtod(m, caddr_t) + off); | |||
#else | |||
IP6_EXTHDR_GET(hbh, struct ip6_hbh *, m, | IP6_EXTHDR_GET(hbh, struct ip6_hbh *, m, | ||
sizeof(struct ip6_hdr), sizeof(struct ip6_hbh)); | sizeof(struct ip6_hdr), sizeof(struct ip6_hbh)); | ||
if (hbh == NULL) { | if (hbh == NULL) { | ||
|
|
||
ip6stat.ip6s_tooshort++; | ip6stat.ip6s_tooshort++; | ||
return -1; | return -1; | ||
} | } | ||
#endif | KASSERT(IP6_HDR_ALIGNED_P(hbh)); | ||
off += hbhlen; | off += hbhlen; | ||
hbhlen -= sizeof(struct ip6_hbh); | hbhlen -= sizeof(struct ip6_hbh); | ||
opt = (u_int8_t *)hbh + sizeof(struct ip6_hbh); | |||
if (ip6_process_hopopts(m, (u_int8_t *)hbh + sizeof(struct ip6_hbh), | if (ip6_process_hopopts(m, (u_int8_t *)hbh + sizeof(struct ip6_hbh), | ||
hbhlen, rtalertp, plenp) < 0) | hbhlen, rtalertp, plenp) < 0) | ||
return(-1); | return (-1); | ||
*offp = off; | *offp = off; | ||
*mp = m; | *mp = m; | ||
return(0); | return (0); | ||
} | } | ||
/* | /* | ||
|
|
||
switch (IP6OPT_TYPE(*optp)) { | switch (IP6OPT_TYPE(*optp)) { | ||
case IP6OPT_TYPE_SKIP: /* ignore the option */ | case IP6OPT_TYPE_SKIP: /* ignore the option */ | ||
return((int)*(optp + 1)); | return ((int)*(optp + 1)); | ||
case IP6OPT_TYPE_DISCARD: /* silently discard */ | case IP6OPT_TYPE_DISCARD: /* silently discard */ | ||
m_freem(m); | m_freem(m); | ||
return(-1); | return (-1); | ||
case IP6OPT_TYPE_FORCEICMP: /* send ICMP even if multicasted */ | case IP6OPT_TYPE_FORCEICMP: /* send ICMP even if multicasted */ | ||
ip6stat.ip6s_badoptions++; | ip6stat.ip6s_badoptions++; | ||
icmp6_error(m, ICMP6_PARAM_PROB, ICMP6_PARAMPROB_OPTION, off); | icmp6_error(m, ICMP6_PARAM_PROB, ICMP6_PARAMPROB_OPTION, off); | ||
return(-1); | return (-1); | ||
case IP6OPT_TYPE_ICMP: /* send ICMP if not multicasted */ | case IP6OPT_TYPE_ICMP: /* send ICMP if not multicasted */ | ||
ip6stat.ip6s_badoptions++; | ip6stat.ip6s_badoptions++; | ||
ip6 = mtod(m, struct ip6_hdr *); | ip6 = mtod(m, struct ip6_hdr *); | ||
|
|
||
else | else | ||
icmp6_error(m, ICMP6_PARAM_PROB, | icmp6_error(m, ICMP6_PARAM_PROB, | ||
ICMP6_PARAMPROB_OPTION, off); | ICMP6_PARAMPROB_OPTION, off); | ||
return(-1); | return (-1); | ||
} | } | ||
m_freem(m); /* XXX: NOTREACHED */ | m_freem(m); /* XXX: NOTREACHED */ | ||
return(-1); | return (-1); | ||
} | } | ||
/* | /* | ||
|
|
||
struct ip6_hdr *ip6; | struct ip6_hdr *ip6; | ||
struct mbuf *m; | struct mbuf *m; | ||
{ | { | ||
struct proc *p = curproc; /* XXX */ | #ifdef RFC2292 | ||
int privileged; | #define IS2292(x, y) ((in6p->in6p_flags & IN6P_RFC2292) ? (x) : (y)) | ||
#else | |||
privileged = 0; | #define IS2292(x, y) (y) | ||
if (p && !suser(p->p_ucred, &p->p_acflag)) | #endif | ||
privileged++; | |||
#ifdef SO_TIMESTAMP | #ifdef SO_TIMESTAMP | ||
if (in6p->in6p_socket->so_options & SO_TIMESTAMP) { | if (in6p->in6p_socket->so_options & SO_TIMESTAMP) { | ||
|
|
||
microtime(&tv); | microtime(&tv); | ||
*mp = sbcreatecontrol((caddr_t) &tv, sizeof(tv), | *mp = sbcreatecontrol((caddr_t) &tv, sizeof(tv), | ||
SCM_TIMESTAMP, SOL_SOCKET); | SCM_TIMESTAMP, SOL_SOCKET); | ||
if (*mp) | if (*mp) | ||
mp = &(*mp)->m_next; | mp = &(*mp)->m_next; | ||
} | } | ||
#endif | #endif | ||
if (in6p->in6p_flags & IN6P_RECVDSTADDR) { | |||
*mp = sbcreatecontrol((caddr_t) &ip6->ip6_dst, | |||
sizeof(struct in6_addr), IPV6_RECVDSTADDR, | |||
IPPROTO_IPV6); | |||
if (*mp) | |||
mp = &(*mp)->m_next; | |||
} | |||
#ifdef noyet | /* some OSes call this logic with IPv4 packet, for SO_TIMESTAMP */ | ||
/* options were tossed above */ | if ((ip6->ip6_vfc & IPV6_VERSION_MASK) != IPV6_VERSION) | ||
if (in6p->in6p_flags & IN6P_RECVOPTS) | return; | ||
/* broken */ | |||
/* ip6_srcroute doesn't do what we want here, need to fix */ | |||
if (in6p->in6p_flags & IPV6P_RECVRETOPTS) | |||
/* broken */ | |||
#endif | |||
/* RFC 2292 sec. 5 */ | /* RFC 2292 sec. 5 */ | ||
if ((in6p->in6p_flags & IN6P_PKTINFO) != 0) { | if ((in6p->in6p_flags & IN6P_PKTINFO) != 0) { | ||
struct in6_pktinfo pi6; | struct in6_pktinfo pi6; | ||
bcopy(&ip6->ip6_dst, &pi6.ipi6_addr, sizeof(struct in6_addr)); | bcopy(&ip6->ip6_dst, &pi6.ipi6_addr, sizeof(struct in6_addr)); | ||
if (IN6_IS_SCOPE_LINKLOCAL(&pi6.ipi6_addr)) | in6_clearscope(&pi6.ipi6_addr); /* XXX */ | ||
pi6.ipi6_addr.s6_addr16[1] = 0; | pi6.ipi6_ifindex = m->m_pkthdr.rcvif ? | ||
pi6.ipi6_ifindex = (m && m->m_pkthdr.rcvif) | m->m_pkthdr.rcvif->if_index : 0; | ||
? m->m_pkthdr.rcvif->if_index | |||
: 0; | |||
*mp = sbcreatecontrol((caddr_t) &pi6, | *mp = sbcreatecontrol((caddr_t) &pi6, | ||
sizeof(struct in6_pktinfo), IPV6_PKTINFO, | sizeof(struct in6_pktinfo), | ||
IPPROTO_IPV6); | IS2292(IPV6_2292PKTINFO, IPV6_PKTINFO), IPPROTO_IPV6); | ||
if (*mp) | if (*mp) | ||
mp = &(*mp)->m_next; | mp = &(*mp)->m_next; | ||
} | } | ||
if (in6p->in6p_flags & IN6P_HOPLIMIT) { | if (in6p->in6p_flags & IN6P_HOPLIMIT) { | ||
int hlim = ip6->ip6_hlim & 0xff; | int hlim = ip6->ip6_hlim & 0xff; | ||
*mp = sbcreatecontrol((caddr_t) &hlim, | |||
sizeof(int), IPV6_HOPLIMIT, IPPROTO_IPV6); | *mp = sbcreatecontrol((caddr_t) &hlim, sizeof(int), | ||
IS2292(IPV6_2292HOPLIMIT, IPV6_HOPLIMIT), IPPROTO_IPV6); | |||
if (*mp) | |||
mp = &(*mp)->m_next; | |||
} | |||
if ((in6p->in6p_flags & IN6P_TCLASS) != 0) { | |||
u_int32_t flowinfo; | |||
int tclass; | |||
flowinfo = (u_int32_t)ntohl(ip6->ip6_flow & IPV6_FLOWINFO_MASK); | |||
flowinfo >>= 20; | |||
tclass = flowinfo & 0xff; | |||
*mp = sbcreatecontrol((caddr_t)&tclass, sizeof(tclass), | |||
IPV6_TCLASS, IPPROTO_IPV6); | |||
if (*mp) | if (*mp) | ||
mp = &(*mp)->m_next; | mp = &(*mp)->m_next; | ||
} | } | ||
/* IN6P_NEXTHOP - for outgoing packet only */ | |||
/* | /* | ||
* IPV6_HOPOPTS socket option. We require super-user privilege | * IPV6_HOPOPTS socket option. Recall that we required super-user | ||
* for the option, but it might be too strict, since there might | * privilege for the option (see ip6_ctloutput), but it might be too | ||
* be some hop-by-hop options which can be returned to normal user. | * strict, since there might be some hop-by-hop options which can be | ||
* See RFC 2292 section 6. | * returned to normal user. | ||
* See also RFC3542 section 8 (or RFC2292 section 6). | |||
*/ | */ | ||
if ((in6p->in6p_flags & IN6P_HOPOPTS) != 0 && privileged) { | if ((in6p->in6p_flags & IN6P_HOPOPTS) != 0) { | ||
/* | /* | ||
* Check if a hop-by-hop options header is contatined in the | * Check if a hop-by-hop options header is contatined in the | ||
* received packet, and if so, store the options as ancillary | * received packet, and if so, store the options as ancillary | ||
|
|
||
* just after the IPv6 header, which fact is assured through | * just after the IPv6 header, which fact is assured through | ||
* the IPv6 input processing. | * the IPv6 input processing. | ||
*/ | */ | ||
struct ip6_hdr *ip6 = mtod(m, struct ip6_hdr *); | struct ip6_hdr *xip6 = mtod(m, struct ip6_hdr *); | ||
if (ip6->ip6_nxt == IPPROTO_HOPOPTS) { | if (xip6->ip6_nxt == IPPROTO_HOPOPTS) { | ||
struct ip6_hbh *hbh; | struct ip6_hbh *hbh; | ||
int hbhlen; | int hbhlen; | ||
struct mbuf *ext; | |||
#ifndef PULLDOWN_TEST | ext = ip6_pullexthdr(m, sizeof(struct ip6_hdr), | ||
hbh = (struct ip6_hbh *)(ip6 + 1); | xip6->ip6_nxt); | ||
hbhlen = (hbh->ip6h_len + 1) << 3; | if (ext == NULL) { | ||
#else | |||
IP6_EXTHDR_GET(hbh, struct ip6_hbh *, m, | |||
sizeof(struct ip6_hdr), sizeof(struct ip6_hbh)); | |||
if (hbh == NULL) { | |||
ip6stat.ip6s_tooshort++; | ip6stat.ip6s_tooshort++; | ||
return; | return; | ||
} | } | ||
hbh = mtod(ext, struct ip6_hbh *); | |||
hbhlen = (hbh->ip6h_len + 1) << 3; | hbhlen = (hbh->ip6h_len + 1) << 3; | ||
IP6_EXTHDR_GET(hbh, struct ip6_hbh *, m, | if (hbhlen != ext->m_len) { | ||
sizeof(struct ip6_hdr), hbhlen); | m_freem(ext); | ||
if (hbh == NULL) { | |||
ip6stat.ip6s_tooshort++; | ip6stat.ip6s_tooshort++; | ||
return; | return; | ||
} | } | ||
#endif | |||
/* | /* | ||
* XXX: We copy whole the header even if a jumbo | * XXX: We copy whole the header even if a jumbo | ||
* payload option is included, which option is to | * payload option is included, which option is to | ||
* be removed before returning in the RFC 2292. | * be removed before returning in the RFC 2292. | ||
* But it's too painful operation... | * Note: this constraint is removed in RFC3542. | ||
*/ | */ | ||
*mp = sbcreatecontrol((caddr_t)hbh, hbhlen, | *mp = sbcreatecontrol((caddr_t)hbh, hbhlen, | ||
IPV6_HOPOPTS, IPPROTO_IPV6); | IS2292(IPV6_2292HOPOPTS, IPV6_HOPOPTS), | ||
IPPROTO_IPV6); | |||
if (*mp) | if (*mp) | ||
mp = &(*mp)->m_next; | mp = &(*mp)->m_next; | ||
m_freem(ext); | |||
} | } | ||
} | } | ||
/* IPV6_DSTOPTS and IPV6_RTHDR socket options */ | /* IPV6_DSTOPTS and IPV6_RTHDR socket options */ | ||
if (in6p->in6p_flags & (IN6P_DSTOPTS | IN6P_RTHDR)) { | if (in6p->in6p_flags & (IN6P_DSTOPTS | IN6P_RTHDR)) { | ||
struct ip6_hdr *ip6 = mtod(m, struct ip6_hdr *); | struct ip6_hdr *xip6 = mtod(m, struct ip6_hdr *); | ||
int nxt = ip6->ip6_nxt, off = sizeof(struct ip6_hdr);; | int nxt = xip6->ip6_nxt, off = sizeof(struct ip6_hdr); | ||
/* | /* | ||
* Search for destination options headers or routing | * Search for destination options headers or routing | ||
|
|
||
* Note that the order of the headers remains in | * Note that the order of the headers remains in | ||
* the chain of ancillary data. | * the chain of ancillary data. | ||
*/ | */ | ||
while (1) { /* is explicit loop prevention necessary? */ | for (;;) { /* is explicit loop prevention necessary? */ | ||
struct ip6_ext *ip6e; | struct ip6_ext *ip6e = NULL; | ||
int elen; | int elen; | ||
struct mbuf *ext = NULL; | |||
#ifndef PULLDOWN_TEST | /* | ||
ip6e = (struct ip6_ext *)(mtod(m, caddr_t) + off); | * if it is not an extension header, don't try to | ||
if (nxt == IPPROTO_AH) | * pull it from the chain. | ||
elen = (ip6e->ip6e_len + 2) << 2; | */ | ||
else | switch (nxt) { | ||
elen = (ip6e->ip6e_len + 1) << 3; | case IPPROTO_DSTOPTS: | ||
#else | case IPPROTO_ROUTING: | ||
IP6_EXTHDR_GET(ip6e, struct ip6_ext *, m, off, | case IPPROTO_HOPOPTS: | ||
sizeof(struct ip6_ext)); | case IPPROTO_AH: /* is it possible? */ | ||
if (ip6e == NULL) { | break; | ||
default: | |||
goto loopend; | |||
} | |||
ext = ip6_pullexthdr(m, off, nxt); | |||
if (ext == NULL) { | |||
ip6stat.ip6s_tooshort++; | ip6stat.ip6s_tooshort++; | ||
return; | return; | ||
} | } | ||
ip6e = mtod(ext, struct ip6_ext *); | |||
if (nxt == IPPROTO_AH) | if (nxt == IPPROTO_AH) | ||
elen = (ip6e->ip6e_len + 2) << 2; | elen = (ip6e->ip6e_len + 2) << 2; | ||
else | else | ||
elen = (ip6e->ip6e_len + 1) << 3; | elen = (ip6e->ip6e_len + 1) << 3; | ||
IP6_EXTHDR_GET(ip6e, struct ip6_ext *, m, off, elen); | if (elen != ext->m_len) { | ||
if (ip6e == NULL) { | m_freem(ext); | ||
ip6stat.ip6s_tooshort++; | ip6stat.ip6s_tooshort++; | ||
return; | return; | ||
} | } | ||
#endif | KASSERT(IP6_HDR_ALIGNED_P(ip6e)); | ||
switch (nxt) { | switch (nxt) { | ||
case IPPROTO_DSTOPTS: | case IPPROTO_DSTOPTS: | ||
if (!in6p->in6p_flags & IN6P_DSTOPTS) | if (!in6p->in6p_flags & IN6P_DSTOPTS) | ||
break; | break; | ||
/* | |||
* We also require super-user privilege for | |||
* the option. | |||
* See the comments on IN6_HOPOPTS. | |||
*/ | |||
if (!privileged) | |||
break; | |||
*mp = sbcreatecontrol((caddr_t)ip6e, elen, | *mp = sbcreatecontrol((caddr_t)ip6e, elen, | ||
IPV6_DSTOPTS, | IS2292(IPV6_2292DSTOPTS, IPV6_DSTOPTS), | ||
IPPROTO_IPV6); | IPPROTO_IPV6); | ||
if (*mp) | if (*mp) | ||
mp = &(*mp)->m_next; | mp = &(*mp)->m_next; | ||
break; | break; | ||
|
|
||
break; | break; | ||
*mp = sbcreatecontrol((caddr_t)ip6e, elen, | *mp = sbcreatecontrol((caddr_t)ip6e, elen, | ||
IPV6_RTHDR, | IS2292(IPV6_2292RTHDR, IPV6_RTHDR), | ||
IPPROTO_IPV6); | IPPROTO_IPV6); | ||
if (*mp) | if (*mp) | ||
mp = &(*mp)->m_next; | mp = &(*mp)->m_next; | ||
break; | break; | ||
case IPPROTO_UDP: | case IPPROTO_HOPOPTS: | ||
case IPPROTO_TCP: | case IPPROTO_AH: /* is it possible? */ | ||
case IPPROTO_ICMPV6: | break; | ||
default: | default: | ||
/* | /* | ||
* stop search if we encounter an upper | * other cases have been filtered in the above. | ||
* layer protocol headers. | * none will visit this case. here we supply | ||
* the code just in case (nxt overwritten or | |||
* other cases). | |||
*/ | */ | ||
m_freem(ext); | |||
goto loopend; | goto loopend; | ||
case IPPROTO_HOPOPTS: | |||
case IPPROTO_AH: /* is it possible? */ | |||
break; | |||
} | } | ||
/* proceed with the next header. */ | /* proceed with the next header. */ | ||
off += elen; | off += elen; | ||
nxt = ip6e->ip6e_nxt; | nxt = ip6e->ip6e_nxt; | ||
ip6e = NULL; | |||
m_freem(ext); | |||
ext = NULL; | |||
} | } | ||
loopend: | loopend: | ||
; | ; | ||
} | |||
} | |||
#undef IS2292 | |||
void | |||
ip6_notify_pmtu(struct in6pcb *in6p, struct sockaddr_in6 *dst, uint32_t *mtu) | |||
{ | |||
struct socket *so; | |||
struct mbuf *m_mtu; | |||
struct ip6_mtuinfo mtuctl; | |||
so = in6p->in6p_socket; | |||
if (mtu == NULL) | |||
return; | |||
#ifdef DIAGNOSTIC | |||
if (so == NULL) /* I believe this is impossible */ | |||
panic("ip6_notify_pmtu: socket is NULL"); | |||
#endif | |||
memset(&mtuctl, 0, sizeof(mtuctl)); /* zero-clear for safety */ | |||
mtuctl.ip6m_mtu = *mtu; | |||
mtuctl.ip6m_addr = *dst; | |||
if (sa6_recoverscope(&mtuctl.ip6m_addr)) | |||
return; | |||
if ((m_mtu = sbcreatecontrol((caddr_t)&mtuctl, sizeof(mtuctl), | |||
IPV6_PATHMTU, IPPROTO_IPV6)) == NULL) | |||
return; | |||
if (sbappendaddr(&so->so_rcv, (struct sockaddr *)dst, NULL, m_mtu) | |||
== 0) { | |||
m_freem(m_mtu); | |||
/* XXX: should count statistics */ | |||
} else | |||
sorwakeup(so); | |||
return; | |||
} | |||
/* | |||
* pull single extension header from mbuf chain. returns single mbuf that | |||
* contains the result, or NULL on error. | |||
*/ | |||
static struct mbuf * | |||
ip6_pullexthdr(m, off, nxt) | |||
struct mbuf *m; | |||
size_t off; | |||
int nxt; | |||
{ | |||
struct ip6_ext ip6e; | |||
size_t elen; | |||
struct mbuf *n; | |||
#ifdef DIAGNOSTIC | |||
switch (nxt) { | |||
case IPPROTO_DSTOPTS: | |||
case IPPROTO_ROUTING: | |||
case IPPROTO_HOPOPTS: | |||
case IPPROTO_AH: /* is it possible? */ | |||
break; | |||
default: | |||
printf("ip6_pullexthdr: invalid nxt=%d\n", nxt); | |||
} | } | ||
if ((in6p->in6p_flags & IN6P_HOPOPTS) && privileged) { | #endif | ||
/* to be done */ | |||
m_copydata(m, off, sizeof(ip6e), (caddr_t)&ip6e); | |||
if (nxt == IPPROTO_AH) | |||
elen = (ip6e.ip6e_len + 2) << 2; | |||
else | |||
elen = (ip6e.ip6e_len + 1) << 3; | |||
MGET(n, M_DONTWAIT, MT_DATA); | |||
if (n && elen >= MLEN) { | |||
MCLGET(n, M_DONTWAIT); | |||
if ((n->m_flags & M_EXT) == 0) { | |||
m_free(n); | |||
n = NULL; | |||
} | |||
} | } | ||
if ((in6p->in6p_flags & IN6P_DSTOPTS) && privileged) { | if (!n) | ||
/* to be done */ | return NULL; | ||
n->m_len = 0; | |||
if (elen >= M_TRAILINGSPACE(n)) { | |||
m_free(n); | |||
return NULL; | |||
} | } | ||
/* IN6P_RTHDR - to be done */ | |||
m_copydata(m, off, elen, mtod(n, caddr_t)); | |||
n->m_len = elen; | |||
return n; | |||
} | } | ||
/* | /* | ||
|
|
||
* carefully. Moreover, it will not be used in the near future when | * carefully. Moreover, it will not be used in the near future when | ||
* we develop `neater' mechanism to process extension headers. | * we develop `neater' mechanism to process extension headers. | ||
*/ | */ | ||
char * | u_int8_t * | ||
ip6_get_prevhdr(m, off) | ip6_get_prevhdr(m, off) | ||
struct mbuf *m; | struct mbuf *m; | ||
int off; | int off; | ||
|
|
||
struct ip6_hdr *ip6 = mtod(m, struct ip6_hdr *); | struct ip6_hdr *ip6 = mtod(m, struct ip6_hdr *); | ||
if (off == sizeof(struct ip6_hdr)) | if (off == sizeof(struct ip6_hdr)) | ||
return(&ip6->ip6_nxt); | return (&ip6->ip6_nxt); | ||
else { | else { | ||
int len, nxt; | int len, nxt; | ||
struct ip6_ext *ip6e = NULL; | struct ip6_ext *ip6e = NULL; | ||
|
|
||
nxt = ip6e->ip6e_nxt; | nxt = ip6e->ip6e_nxt; | ||
} | } | ||
if (ip6e) | if (ip6e) | ||
return(&ip6e->ip6e_nxt); | return (&ip6e->ip6e_nxt); | ||
else | else | ||
return NULL; | return NULL; | ||
} | } | ||
|
|
||
switch (proto) { | switch (proto) { | ||
case IPPROTO_IPV6: | case IPPROTO_IPV6: | ||
/* do not chase beyond intermediate IPv6 headers */ | |||
if (off != 0) | |||
return -1; | |||
if (m->m_pkthdr.len < off + sizeof(ip6)) | if (m->m_pkthdr.len < off + sizeof(ip6)) | ||
return -1; | return -1; | ||
m_copydata(m, off, sizeof(ip6), (caddr_t)&ip6); | m_copydata(m, off, sizeof(ip6), (caddr_t)&ip6); | ||
|
|
||
if (m->m_pkthdr.len < off + sizeof(fh)) | if (m->m_pkthdr.len < off + sizeof(fh)) | ||
return -1; | return -1; | ||
m_copydata(m, off, sizeof(fh), (caddr_t)&fh); | m_copydata(m, off, sizeof(fh), (caddr_t)&fh); | ||
if ((ntohs(fh.ip6f_offlg) & IP6F_OFF_MASK) != 0) | if ((fh.ip6f_offlg & IP6F_OFF_MASK) != 0) | ||
return -1; | return -1; | ||
if (nxtp) | if (nxtp) | ||
*nxtp = fh.ip6f_nxt; | *nxtp = fh.ip6f_nxt; | ||
|
|
||
default: | default: | ||
return -1; | return -1; | ||
} | } | ||
return -1; | |||
} | } | ||
/* | /* | ||
|
|
||
nxt = -1; | nxt = -1; | ||
nxtp = &nxt; | nxtp = &nxt; | ||
} | } | ||
while (1) { | for (;;) { | ||
newoff = ip6_nexthdr(m, off, proto, nxtp); | newoff = ip6_nexthdr(m, off, proto, nxtp); | ||
if (newoff < 0) | if (newoff < 0) | ||
return off; | return off; | ||
|
|
||
} | } | ||
} | } | ||
struct m_tag * | |||
ip6_addaux(m) | |||
struct mbuf *m; | |||
{ | |||
struct m_tag *mtag; | |||
mtag = m_tag_find(m, PACKET_TAG_INET6, NULL); | |||
if (!mtag) { | |||
mtag = m_tag_get(PACKET_TAG_INET6, sizeof(struct ip6aux), | |||
M_NOWAIT); | |||
if (mtag) { | |||
m_tag_prepend(m, mtag); | |||
bzero(mtag + 1, sizeof(struct ip6aux)); | |||
} | |||
} | |||
return mtag; | |||
} | |||
struct m_tag * | |||
ip6_findaux(m) | |||
struct mbuf *m; | |||
{ | |||
struct m_tag *mtag; | |||
mtag = m_tag_find(m, PACKET_TAG_INET6, NULL); | |||
return mtag; | |||
} | |||
void | |||
ip6_delaux(m) | |||
struct mbuf *m; | |||
{ | |||
struct m_tag *mtag; | |||
mtag = m_tag_find(m, PACKET_TAG_INET6, NULL); | |||
if (mtag) | |||
m_tag_delete(m, mtag); | |||
} | |||
/* | /* | ||
* System control for IP6 | * System control for IP6 | ||
*/ | */ | ||
|
|
||
ENOPROTOOPT | ENOPROTOOPT | ||
}; | }; | ||
int | SYSCTL_SETUP(sysctl_net_inet6_ip6_setup, "sysctl net.inet6.ip6 subtree setup") | ||
ip6_sysctl(name, namelen, oldp, oldlenp, newp, newlen) | |||
int *name; | |||
u_int namelen; | |||
void *oldp; | |||
size_t *oldlenp; | |||
void *newp; | |||
size_t newlen; | |||
{ | { | ||
int old, error; | #ifdef RFC2292 | ||
#define IS2292(x, y) ((in6p->in6p_flags & IN6P_RFC2292) ? (x) : (y)) | |||
/* All sysctl names at this level are terminal. */ | |||
if (namelen != 1) | |||
return ENOTDIR; | |||
switch (name[0]) { | |||
case IPV6CTL_FORWARDING: | |||
return sysctl_int(oldp, oldlenp, newp, newlen, | |||
&ip6_forwarding); | |||
case IPV6CTL_SENDREDIRECTS: | |||
return sysctl_int(oldp, oldlenp, newp, newlen, | |||
&ip6_sendredirects); | |||
case IPV6CTL_DEFHLIM: | |||
return sysctl_int(oldp, oldlenp, newp, newlen, &ip6_defhlim); | |||
case IPV6CTL_MAXFRAGPACKETS: | |||
return sysctl_int(oldp, oldlenp, newp, newlen, | |||
&ip6_maxfragpackets); | |||
case IPV6CTL_ACCEPT_RTADV: | |||
return sysctl_int(oldp, oldlenp, newp, newlen, | |||
&ip6_accept_rtadv); | |||
case IPV6CTL_KEEPFAITH: | |||
return sysctl_int(oldp, oldlenp, newp, newlen, &ip6_keepfaith); | |||
case IPV6CTL_LOG_INTERVAL: | |||
return sysctl_int(oldp, oldlenp, newp, newlen, | |||
&ip6_log_interval); | |||
case IPV6CTL_HDRNESTLIMIT: | |||
return sysctl_int(oldp, oldlenp, newp, newlen, | |||
&ip6_hdrnestlimit); | |||
case IPV6CTL_DAD_COUNT: | |||
return sysctl_int(oldp, oldlenp, newp, newlen, &ip6_dad_count); | |||
case IPV6CTL_AUTO_FLOWLABEL: | |||
return sysctl_int(oldp, oldlenp, newp, newlen, | |||
&ip6_auto_flowlabel); | |||
case IPV6CTL_DEFMCASTHLIM: | |||
return sysctl_int(oldp, oldlenp, newp, newlen, | |||
&ip6_defmcasthlim); | |||
#if NGIF > 0 | |||
case IPV6CTL_GIF_HLIM: | |||
return sysctl_int(oldp, oldlenp, newp, newlen, | |||
&ip6_gif_hlim); | |||
#endif | |||
case IPV6CTL_KAME_VERSION: | |||
return sysctl_rdstring(oldp, oldlenp, newp, __KAME_VERSION); | |||
case IPV6CTL_USE_DEPRECATED: | |||
return sysctl_int(oldp, oldlenp, newp, newlen, | |||
&ip6_use_deprecated); | |||
case IPV6CTL_RR_PRUNE: | |||
return sysctl_int(oldp, oldlenp, newp, newlen, &ip6_rr_prune); | |||
case IPV6CTL_V6ONLY: | |||
#ifdef INET6_BINDV6ONLY | |||
return sysctl_rdint(oldp, oldlenp, newp, ip6_v6only); | |||
#else | #else | ||
return sysctl_int(oldp, oldlenp, newp, newlen, &ip6_v6only); | #define IS2292(x, y) (y) | ||
#endif | |||
case IPV6CTL_ANONPORTMIN: | |||
old = ip6_anonportmin; | |||
error = sysctl_int(oldp, oldlenp, newp, newlen, | |||
&ip6_anonportmin); | |||
if (ip6_anonportmin >= ip6_anonportmax || ip6_anonportmin < 0 || | |||
ip6_anonportmin > 65535 | |||
#ifndef IPNOPRIVPORTS | |||
|| ip6_anonportmin < IPV6PORT_RESERVED | |||
#endif | #endif | ||
) { | |||
ip6_anonportmin = old; | sysctl_createv(clog, 0, NULL, NULL, | ||
return (EINVAL); | CTLFLAG_PERMANENT, | ||
} | CTLTYPE_NODE, "net", NULL, | ||
return (error); | NULL, 0, NULL, 0, | ||
case IPV6CTL_ANONPORTMAX: | CTL_NET, CTL_EOL); | ||
old = ip6_anonportmax; | sysctl_createv(clog, 0, NULL, NULL, | ||
error = sysctl_int(oldp, oldlenp, newp, newlen, | CTLFLAG_PERMANENT, | ||
&ip6_anonportmax); | CTLTYPE_NODE, "inet6", | ||
if (ip6_anonportmin >= ip6_anonportmax || ip6_anonportmax < 0 || | SYSCTL_DESCR("PF_INET6 related settings"), | ||
ip6_anonportmax > 65535 | NULL, 0, NULL, 0, | ||
#ifndef IPNOPRIVPORTS | CTL_NET, PF_INET6, CTL_EOL); | ||
|| ip6_anonportmax < IPV6PORT_RESERVED | sysctl_createv(clog, 0, NULL, NULL, | ||
#endif | CTLFLAG_PERMANENT, | ||
) { | CTLTYPE_NODE, "ip6", | ||
ip6_anonportmax = old; | SYSCTL_DESCR("IPv6 related settings"), | ||
return (EINVAL); | NULL, 0, NULL, 0, | ||
} | CTL_NET, PF_INET6, IPPROTO_IPV6, CTL_EOL); | ||
return (error); | |||
sysctl_createv(clog, 0, NULL, NULL, | |||
CTLFLAG_PERMANENT|CTLFLAG_READWRITE, | |||
CTLTYPE_INT, "forwarding", | |||
SYSCTL_DESCR("Enable forwarding of INET6 datagrams"), | |||
NULL, 0, &ip6_forwarding, 0, | |||
CTL_NET, PF_INET6, IPPROTO_IPV6, | |||
IPV6CTL_FORWARDING, CTL_EOL); | |||
sysctl_createv(clog, 0, NULL, NULL, | |||
CTLFLAG_PERMANENT|CTLFLAG_READWRITE, | |||
CTLTYPE_INT, "redirect", | |||
SYSCTL_DESCR("Enable sending of ICMPv6 redirect messages"), | |||
NULL, 0, &ip6_sendredirects, 0, | |||
CTL_NET, PF_INET6, IPPROTO_IPV6, | |||
IPV6CTL_SENDREDIRECTS, CTL_EOL); | |||
sysctl_createv(clog, 0, NULL, NULL, | |||
CTLFLAG_PERMANENT|CTLFLAG_READWRITE, | |||
CTLTYPE_INT, "hlim", | |||
SYSCTL_DESCR("Hop limit for an INET6 datagram"), | |||
NULL, 0, &ip6_defhlim, 0, | |||
CTL_NET, PF_INET6, IPPROTO_IPV6, | |||
IPV6CTL_DEFHLIM, CTL_EOL); | |||
#ifdef notyet | |||
sysctl_createv(clog, 0, NULL, NULL, | |||
CTLFLAG_PERMANENT|CTLFLAG_READWRITE, | |||
CTLTYPE_INT, "mtu", NULL, | |||
NULL, 0, &, 0, | |||
CTL_NET, PF_INET6, IPPROTO_IPV6, | |||
IPV6CTL_DEFMTU, CTL_EOL); | |||
#endif | |||
#ifdef __no_idea__ | |||
sysctl_createv(clog, 0, NULL, NULL, | |||
CTLFLAG_PERMANENT|CTLFLAG_READWRITE, | |||
CTLTYPE_INT, "forwsrcrt", NULL, | |||
NULL, 0, &?, 0, | |||
CTL_NET, PF_INET6, IPPROTO_IPV6, | |||
IPV6CTL_FORWSRCRT, CTL_EOL); | |||
sysctl_createv(clog, 0, NULL, NULL, | |||
CTLFLAG_PERMANENT|CTLFLAG_READWRITE, | |||
CTLTYPE_STRUCT, "mrtstats", NULL, | |||
NULL, 0, &?, sizeof(?), | |||
CTL_NET, PF_INET6, IPPROTO_IPV6, | |||
IPV6CTL_MRTSTATS, CTL_EOL); | |||
sysctl_createv(clog, 0, NULL, NULL, | |||
CTLFLAG_PERMANENT|CTLFLAG_READWRITE, | |||
CTLTYPE_?, "mrtproto", NULL, | |||
NULL, 0, &?, sizeof(?), | |||
CTL_NET, PF_INET6, IPPROTO_IPV6, | |||
IPV6CTL_MRTPROTO, CTL_EOL); | |||
#endif | |||
sysctl_createv(clog, 0, NULL, NULL, | |||
CTLFLAG_PERMANENT|CTLFLAG_READWRITE, | |||
CTLTYPE_INT, "maxfragpackets", | |||
SYSCTL_DESCR("Maximum number of fragments to buffer " | |||
"for reassembly"), | |||
NULL, 0, &ip6_maxfragpackets, 0, | |||
CTL_NET, PF_INET6, IPPROTO_IPV6, | |||
IPV6CTL_MAXFRAGPACKETS, CTL_EOL); | |||
#ifdef __no_idea__ | |||
sysctl_createv(clog, 0, NULL, NULL, | |||
CTLFLAG_PERMANENT|CTLFLAG_READWRITE, | |||
CTLTYPE_INT, "sourcecheck", NULL, | |||
NULL, 0, &?, 0, | |||
CTL_NET, PF_INET6, IPPROTO_IPV6, | |||
IPV6CTL_SOURCECHECK, CTL_EOL); | |||
sysctl_createv(clog, 0, NULL, NULL, | |||
CTLFLAG_PERMANENT|CTLFLAG_READWRITE, | |||
CTLTYPE_INT, "sourcecheck_logint", NULL, | |||
NULL, 0, &?, 0, | |||
CTL_NET, PF_INET6, IPPROTO_IPV6, | |||
IPV6CTL_SOURCECHECK_LOGINT, CTL_EOL); | |||
#endif | |||
sysctl_createv(clog, 0, NULL, NULL, | |||
CTLFLAG_PERMANENT|CTLFLAG_READWRITE, | |||
CTLTYPE_INT, "accept_rtadv", | |||
SYSCTL_DESCR("Accept router advertisements"), | |||
NULL, 0, &ip6_accept_rtadv, 0, | |||
CTL_NET, PF_INET6, IPPROTO_IPV6, | |||
IPV6CTL_ACCEPT_RTADV, CTL_EOL); | |||
sysctl_createv(clog, 0, NULL, NULL, | |||
CTLFLAG_PERMANENT|CTLFLAG_READWRITE, | |||
CTLTYPE_INT, "keepfaith", | |||
SYSCTL_DESCR("Activate faith interface"), | |||
NULL, 0, &ip6_keepfaith, 0, | |||
CTL_NET, PF_INET6, IPPROTO_IPV6, | |||
IPV6CTL_KEEPFAITH, CTL_EOL); | |||
sysctl_createv(clog, 0, NULL, NULL, | |||
CTLFLAG_PERMANENT|CTLFLAG_READWRITE, | |||
CTLTYPE_INT, "log_interval", | |||
SYSCTL_DESCR("Minumum interval between logging " | |||
"unroutable packets"), | |||
NULL, 0, &ip6_log_interval, 0, | |||
CTL_NET, PF_INET6, IPPROTO_IPV6, | |||
IPV6CTL_LOG_INTERVAL, CTL_EOL); | |||
sysctl_createv(clog, 0, NULL, NULL, | |||
CTLFLAG_PERMANENT|CTLFLAG_READWRITE, | |||
CTLTYPE_INT, "hdrnestlimit", | |||
SYSCTL_DESCR("Maximum number of nested IPv6 headers"), | |||
NULL, 0, &ip6_hdrnestlimit, 0, | |||
CTL_NET, PF_INET6, IPPROTO_IPV6, | |||
IPV6CTL_HDRNESTLIMIT, CTL_EOL); | |||
sysctl_createv(clog, 0, NULL, NULL, | |||
CTLFLAG_PERMANENT|CTLFLAG_READWRITE, | |||
CTLTYPE_INT, "dad_count", | |||
SYSCTL_DESCR("Number of Duplicate Address Detection " | |||
"probes to send"), | |||
NULL, 0, &ip6_dad_count, 0, | |||
CTL_NET, PF_INET6, IPPROTO_IPV6, | |||
IPV6CTL_DAD_COUNT, CTL_EOL); | |||
sysctl_createv(clog, 0, NULL, NULL, | |||
CTLFLAG_PERMANENT|CTLFLAG_READWRITE, | |||
CTLTYPE_INT, "auto_flowlabel", | |||
SYSCTL_DESCR("Assign random IPv6 flow labels"), | |||
NULL, 0, &ip6_auto_flowlabel, 0, | |||
CTL_NET, PF_INET6, IPPROTO_IPV6, | |||
IPV6CTL_AUTO_FLOWLABEL, CTL_EOL); | |||
sysctl_createv(clog, 0, NULL, NULL, | |||
CTLFLAG_PERMANENT|CTLFLAG_READWRITE, | |||
CTLTYPE_INT, "defmcasthlim", | |||
SYSCTL_DESCR("Default multicast hop limit"), | |||
NULL, 0, &ip6_defmcasthlim, 0, | |||
CTL_NET, PF_INET6, IPPROTO_IPV6, | |||
IPV6CTL_DEFMCASTHLIM, CTL_EOL); | |||
#if NGIF > 0 | |||
sysctl_createv(clog, 0, NULL, NULL, | |||
CTLFLAG_PERMANENT|CTLFLAG_READWRITE, | |||
CTLTYPE_INT, "gifhlim", | |||
SYSCTL_DESCR("Default hop limit for a gif tunnel datagram"), | |||
NULL, 0, &ip6_gif_hlim, 0, | |||
CTL_NET, PF_INET6, IPPROTO_IPV6, | |||
IPV6CTL_GIF_HLIM, CTL_EOL); | |||
#endif /* NGIF */ | |||
sysctl_createv(clog, 0, NULL, NULL, | |||
CTLFLAG_PERMANENT, | |||
CTLTYPE_STRING, "kame_version", | |||
SYSCTL_DESCR("KAME Version"), | |||
NULL, 0, __UNCONST(__KAME_VERSION), 0, | |||
CTL_NET, PF_INET6, IPPROTO_IPV6, | |||
IPV6CTL_KAME_VERSION, CTL_EOL); | |||
sysctl_createv(clog, 0, NULL, NULL, | |||
CTLFLAG_PERMANENT|CTLFLAG_READWRITE, | |||
CTLTYPE_INT, "use_deprecated", | |||
SYSCTL_DESCR("Allow use of deprecated addresses as " | |||
"source addresses"), | |||
NULL, 0, &ip6_use_deprecated, 0, | |||
CTL_NET, PF_INET6, IPPROTO_IPV6, | |||
IPV6CTL_USE_DEPRECATED, CTL_EOL); | |||
sysctl_createv(clog, 0, NULL, NULL, | |||
CTLFLAG_PERMANENT|CTLFLAG_READWRITE, | |||
CTLTYPE_INT, "rr_prune", NULL, | |||
NULL, 0, &ip6_rr_prune, 0, | |||
CTL_NET, PF_INET6, IPPROTO_IPV6, | |||
IPV6CTL_RR_PRUNE, CTL_EOL); | |||
sysctl_createv(clog, 0, NULL, NULL, | |||
CTLFLAG_PERMANENT | |||
#ifndef INET6_BINDV6ONLY | |||
|CTLFLAG_READWRITE, | |||
#endif | |||
CTLTYPE_INT, "v6only", | |||
SYSCTL_DESCR("Disallow PF_INET6 sockets from connecting " | |||
"to PF_INET sockets"), | |||
NULL, 0, &ip6_v6only, 0, | |||
CTL_NET, PF_INET6, IPPROTO_IPV6, | |||
IPV6CTL_V6ONLY, CTL_EOL); | |||
sysctl_createv(clog, 0, NULL, NULL, | |||
CTLFLAG_PERMANENT|CTLFLAG_READWRITE, | |||
CTLTYPE_INT, "anonportmin", | |||
SYSCTL_DESCR("Lowest ephemeral port number to assign"), | |||
sysctl_net_inet_ip_ports, 0, &ip6_anonportmin, 0, | |||
CTL_NET, PF_INET6, IPPROTO_IPV6, | |||
IPV6CTL_ANONPORTMIN, CTL_EOL); | |||
sysctl_createv(clog, 0, NULL, NULL, | |||
CTLFLAG_PERMANENT|CTLFLAG_READWRITE, | |||
CTLTYPE_INT, "anonportmax", | |||
SYSCTL_DESCR("Highest ephemeral port number to assign"), | |||
sysctl_net_inet_ip_ports, 0, &ip6_anonportmax, 0, | |||
CTL_NET, PF_INET6, IPPROTO_IPV6, | |||
IPV6CTL_ANONPORTMAX, CTL_EOL); | |||
#ifndef IPNOPRIVPORTS | #ifndef IPNOPRIVPORTS | ||
case IPV6CTL_LOWPORTMIN: | sysctl_createv(clog, 0, NULL, NULL, | ||
old = ip6_lowportmin; | CTLFLAG_PERMANENT|CTLFLAG_READWRITE, | ||
error = sysctl_int(oldp, oldlenp, newp, newlen, | CTLTYPE_INT, "lowportmin", | ||
&ip6_lowportmin); | SYSCTL_DESCR("Lowest privileged ephemeral port number " | ||
if (ip6_lowportmin >= ip6_lowportmax || | "to assign"), | ||
ip6_lowportmin > IPV6PORT_RESERVEDMAX || | sysctl_net_inet_ip_ports, 0, &ip6_lowportmin, 0, | ||
ip6_lowportmin < IPV6PORT_RESERVEDMIN) { | CTL_NET, PF_INET6, IPPROTO_IPV6, | ||
ip6_lowportmin = old; | IPV6CTL_LOWPORTMIN, CTL_EOL); | ||
return (EINVAL); | sysctl_createv(clog, 0, NULL, NULL, | ||
} | CTLFLAG_PERMANENT|CTLFLAG_READWRITE, | ||
return (error); | CTLTYPE_INT, "lowportmax", | ||
case IPV6CTL_LOWPORTMAX: | SYSCTL_DESCR("Highest privileged ephemeral port number " | ||
old = ip6_lowportmax; | "to assign"), | ||
error = sysctl_int(oldp, oldlenp, newp, newlen, | sysctl_net_inet_ip_ports, 0, &ip6_lowportmax, 0, | ||
&ip6_lowportmax); | CTL_NET, PF_INET6, IPPROTO_IPV6, | ||
if (ip6_lowportmin >= ip6_lowportmax || | IPV6CTL_LOWPORTMAX, CTL_EOL); | ||
ip6_lowportmax > IPV6PORT_RESERVEDMAX || | #endif /* IPNOPRIVPORTS */ | ||
ip6_lowportmax < IPV6PORT_RESERVEDMIN) { | sysctl_createv(clog, 0, NULL, NULL, | ||
ip6_lowportmax = old; | CTLFLAG_PERMANENT|CTLFLAG_READWRITE, | ||
return (EINVAL); | CTLTYPE_INT, "use_tempaddr", | ||
} | SYSCTL_DESCR("Use temporary address"), | ||
return (error); | NULL, 0, &ip6_use_tempaddr, 0, | ||
#endif | CTL_NET, PF_INET6, IPPROTO_IPV6, | ||
case IPV6CTL_MAXFRAGS: | CTL_CREATE, CTL_EOL); | ||
return sysctl_int(oldp, oldlenp, newp, newlen, &ip6_maxfrags); | sysctl_createv(clog, 0, NULL, NULL, | ||
default: | CTLFLAG_PERMANENT|CTLFLAG_READWRITE, | ||
return EOPNOTSUPP; | CTLTYPE_INT, "temppltime", | ||
} | SYSCTL_DESCR("preferred lifetime of a temporary address"), | ||
/* NOTREACHED */ | NULL, 0, &ip6_temp_preferred_lifetime, 0, | ||
CTL_NET, PF_INET6, IPPROTO_IPV6, | |||
CTL_CREATE, CTL_EOL); | |||
sysctl_createv(clog, 0, NULL, NULL, | |||
CTLFLAG_PERMANENT|CTLFLAG_READWRITE, | |||
CTLTYPE_INT, "tempvltime", | |||
SYSCTL_DESCR("valid lifetime of a temporary address"), | |||
NULL, 0, &ip6_temp_valid_lifetime, 0, | |||
CTL_NET, PF_INET6, IPPROTO_IPV6, | |||
CTL_CREATE, CTL_EOL); | |||
sysctl_createv(clog, 0, NULL, NULL, | |||
CTLFLAG_PERMANENT|CTLFLAG_READWRITE, | |||
CTLTYPE_INT, "maxfrags", | |||
SYSCTL_DESCR("Maximum fragments in reassembly queue"), | |||
NULL, 0, &ip6_maxfrags, 0, | |||
CTL_NET, PF_INET6, IPPROTO_IPV6, | |||
IPV6CTL_MAXFRAGS, CTL_EOL); | |||
sysctl_createv(clog, 0, NULL, NULL, | |||
CTLFLAG_PERMANENT, | |||
CTLTYPE_STRUCT, "stats", | |||
SYSCTL_DESCR("IPv6 statistics"), | |||
NULL, 0, &ip6stat, sizeof(ip6stat), | |||
CTL_NET, PF_INET6, IPPROTO_IPV6, | |||
IPV6CTL_STATS, CTL_EOL); | |||
sysctl_createv(clog, 0, NULL, NULL, | |||
CTLFLAG_PERMANENT|CTLFLAG_READWRITE, | |||
CTLTYPE_INT, "use_defaultzone", | |||
SYSCTL_DESCR("Whether to use the default scope zones"), | |||
NULL, 0, &ip6_use_defzone, 0, | |||
CTL_NET, PF_INET6, IPPROTO_IPV6, | |||
IPV6CTL_USE_DEFAULTZONE, CTL_EOL); | |||
sysctl_createv(clog, 0, NULL, NULL, | |||
CTLFLAG_PERMANENT|CTLFLAG_READWRITE, | |||
CTLTYPE_INT, "mcast_pmtu", | |||
SYSCTL_DESCR("Enable pMTU discovery for multicast packet"), | |||
NULL, 0, &ip6_mcast_pmtu, 0, | |||
CTL_NET, PF_INET6, IPPROTO_IPV6, | |||
CTL_CREATE, CTL_EOL); | |||
} | } |