📄 icmp6.c
字号:
* - joins NI group address at in6_ifattach() time only, does not cope
* with hostname changes by sethostname(3)
*/
#ifdef __FreeBSD__
#define hostnamelen strlen(hostname)
#endif
#ifndef offsetof /* XXX */
#define offsetof(type, member) ((size_t)(&((type *)0)->member))
#endif
static struct mbuf *
ni6_input(m, off)
struct mbuf *m;
int off;
{
struct icmp6_nodeinfo *ni6, *nni6;
struct mbuf *n = NULL;
u_int16_t qtype;
int subjlen;
int replylen = sizeof(struct ip6_hdr) + sizeof(struct icmp6_nodeinfo);
struct ni_reply_fqdn *fqdn;
int addrs; /* for NI_QTYPE_NODEADDR */
struct ifnet *ifp = NULL; /* for NI_QTYPE_NODEADDR */
struct sockaddr_in6 sin6; /* double meaning; ip6_dst and subjectaddr */
struct sockaddr_in6 sin6_d; /* XXX: we should retrieve this from m_aux */
struct ip6_hdr *ip6;
int oldfqdn = 0; /* if 1, return pascal string (03 draft) */
char *subj = NULL;
struct in6_ifaddr *ia6 = NULL;
int64_t zoneid;
ip6 = mtod(m, struct ip6_hdr *);
#ifndef PULLDOWN_TEST
ni6 = (struct icmp6_nodeinfo *)(mtod(m, caddr_t) + off);
#else
IP6_EXTHDR_GET(ni6, struct icmp6_nodeinfo *, m, off, sizeof(*ni6));
if (ni6 == NULL) {
/* m is already reclaimed */
return NULL;
}
#endif
/*
* Validate IPv6 destination address.
*
* The Responder must discard the Query without further processing
* unless it is one of the Responder's unicast or anycast addresses, or
* a link-local scope multicast address which the Responder has joined.
* [icmp-name-lookups-07, Section 4.]
*/
bzero(&sin6, sizeof(sin6));
sin6.sin6_family = AF_INET6;
sin6.sin6_len = sizeof(struct sockaddr_in6);
bcopy(&ip6->ip6_dst, &sin6.sin6_addr, sizeof(sin6.sin6_addr));
/* XXX scopeid */
if ((ia6 = (struct in6_ifaddr *)ifa_ifwithaddr((struct sockaddr *)&sin6)) != NULL) {
/* unicast/anycast, fine */
if ((ia6->ia6_flags & IN6_IFF_TEMPORARY) != 0 &&
(icmp6_nodeinfo & 4) == 0) {
nd6log((LOG_DEBUG, "ni6_input: ignore node info to "
"a temporary address in %s:%d",
__FILE__, __LINE__));
goto bad;
}
} else if (IN6_IS_ADDR_MC_LINKLOCAL(&sin6.sin6_addr))
; /* link-local multicast, fine */
else
goto bad;
/* validate query Subject field. */
qtype = ntohs(ni6->ni_qtype);
subjlen = m->m_pkthdr.len - off - sizeof(struct icmp6_nodeinfo);
switch (qtype) {
case NI_QTYPE_NOOP:
case NI_QTYPE_SUPTYPES:
/* 07 draft */
if (ni6->ni_code == ICMP6_NI_SUBJ_FQDN && subjlen == 0)
break;
/* FALLTHROUGH */
case NI_QTYPE_FQDN:
case NI_QTYPE_NODEADDR:
case NI_QTYPE_IPV4ADDR:
switch (ni6->ni_code) {
case ICMP6_NI_SUBJ_IPV6:
#if ICMP6_NI_SUBJ_IPV6 != 0
case 0:
#endif
/*
* backward compatibility - try to accept 03 draft
* format, where no Subject is present.
*/
if (qtype == NI_QTYPE_FQDN && ni6->ni_code == 0 &&
subjlen == 0) {
oldfqdn++;
break;
}
#if ICMP6_NI_SUBJ_IPV6 != 0
if (ni6->ni_code != ICMP6_NI_SUBJ_IPV6)
goto bad;
#endif
if (subjlen != sizeof(sin6.sin6_addr))
goto bad;
/*
* Validate Subject address.
*
* Not sure what exactly "address belongs to the node"
* means in the spec, is it just unicast, or what?
*
* At this moment we consider Subject address as
* "belong to the node" if the Subject address equals
* to the IPv6 destination address; validation for
* IPv6 destination address should have done enough
* check for us.
*
* We do not do proxy at this moment.
*/
/* m_pulldown instead of copy? */
m_copydata(m, off + sizeof(struct icmp6_nodeinfo),
subjlen, (caddr_t)&sin6.sin6_addr);
if ((zoneid = in6_addr2zoneid(m->m_pkthdr.rcvif,
&sin6.sin6_addr)) < 0)
goto bad;
sin6.sin6_scope_id = zoneid;
if (in6_embedscope(&sin6.sin6_addr, &sin6))
goto bad; /* XXX should not happen */
bzero(&sin6_d, sizeof(sin6_d));
sin6_d.sin6_family = AF_INET6; /* not used, actually */
sin6_d.sin6_len = sizeof(sin6_d); /* ditto */
sin6_d.sin6_addr = ip6->ip6_dst;
if ((zoneid = in6_addr2zoneid(m->m_pkthdr.rcvif,
&ip6->ip6_dst)) < 0)
goto bad;
sin6_d.sin6_scope_id = zoneid;
if (in6_embedscope(&sin6_d.sin6_addr, &sin6_d))
goto bad; /* XXX should not happen */
subj = (char *)&sin6;
if (SA6_ARE_ADDR_EQUAL(&sin6, &sin6_d))
break;
/*
* XXX if we are to allow other cases, we should really
* be careful about scope here.
* basically, we should disallow queries toward IPv6
* destination X with subject Y, if scope(X) > scope(Y).
* if we allow scope(X) > scope(Y), it will result in
* information leakage across scope boundary.
*/
goto bad;
case ICMP6_NI_SUBJ_FQDN:
/*
* Validate Subject name with gethostname(3).
*
* The behavior may need some debate, since:
* - we are not sure if the node has FQDN as
* hostname (returned by gethostname(3)).
* - the code does wildcard match for truncated names.
* however, we are not sure if we want to perform
* wildcard match, if gethostname(3) side has
* truncated hostname.
*/
n = ni6_nametodns(hostname, hostnamelen, 0);
if (!n || n->m_next || n->m_len == 0)
goto bad;
IP6_EXTHDR_GET(subj, char *, m,
off + sizeof(struct icmp6_nodeinfo), subjlen);
if (subj == NULL)
goto bad;
if (!ni6_dnsmatch(subj, subjlen, mtod(n, const char *),
n->m_len)) {
goto bad;
}
m_freem(n);
n = NULL;
break;
case ICMP6_NI_SUBJ_IPV4: /* XXX: to be implemented? */
default:
goto bad;
}
break;
}
/* refuse based on configuration. XXX ICMP6_NI_REFUSED? */
switch (qtype) {
case NI_QTYPE_FQDN:
if ((icmp6_nodeinfo & 1) == 0)
goto bad;
break;
case NI_QTYPE_NODEADDR:
case NI_QTYPE_IPV4ADDR:
if ((icmp6_nodeinfo & 2) == 0)
goto bad;
break;
}
/* guess reply length */
switch (qtype) {
case NI_QTYPE_NOOP:
break; /* no reply data */
case NI_QTYPE_SUPTYPES:
replylen += sizeof(u_int32_t);
break;
case NI_QTYPE_FQDN:
/* XXX will append an mbuf */
replylen += offsetof(struct ni_reply_fqdn, ni_fqdn_namelen);
break;
case NI_QTYPE_NODEADDR:
addrs = ni6_addrs(ni6, m, &ifp, subj);
if ((replylen += addrs * (sizeof(struct in6_addr) +
sizeof(u_int32_t))) > MCLBYTES)
replylen = MCLBYTES; /* XXX: will truncate pkt later */
break;
case NI_QTYPE_IPV4ADDR:
/* unsupported - should respond with unknown Qtype? */
goto bad;
default:
/*
* XXX: We must return a reply with the ICMP6 code
* `unknown Qtype' in this case. However we regard the case
* as an FQDN query for backward compatibility.
* Older versions set a random value to this field,
* so it rarely varies in the defined qtypes.
* But the mechanism is not reliable...
* maybe we should obsolete older versions.
*/
qtype = NI_QTYPE_FQDN;
/* XXX will append an mbuf */
replylen += offsetof(struct ni_reply_fqdn, ni_fqdn_namelen);
oldfqdn++;
break;
}
/* allocate an mbuf to reply. */
MGETHDR(n, M_DONTWAIT, m->m_type);
if (n == NULL) {
m_freem(m);
return(NULL);
}
#ifdef __OpenBSD__
M_DUP_PKTHDR(n, m); /* just for rcvif */
#else
M_COPY_PKTHDR(n, m); /* just for rcvif */
#endif
if (replylen > MHLEN) {
if (replylen > MCLBYTES) {
/*
* XXX: should we try to allocate more? But MCLBYTES
* is probably much larger than IPV6_MMTU...
*/
goto bad;
}
MCLGET(n, M_DONTWAIT);
if ((n->m_flags & M_EXT) == 0) {
goto bad;
}
}
n->m_pkthdr.len = n->m_len = replylen;
/* copy mbuf header and IPv6 + Node Information base headers */
bcopy(mtod(m, caddr_t), mtod(n, caddr_t), sizeof(struct ip6_hdr));
nni6 = (struct icmp6_nodeinfo *)(mtod(n, struct ip6_hdr *) + 1);
bcopy((caddr_t)ni6, (caddr_t)nni6, sizeof(struct icmp6_nodeinfo));
/* qtype dependent procedure */
switch (qtype) {
case NI_QTYPE_NOOP:
nni6->ni_code = ICMP6_NI_SUCCESS;
nni6->ni_flags = 0;
break;
case NI_QTYPE_SUPTYPES:
{
u_int32_t v;
nni6->ni_code = ICMP6_NI_SUCCESS;
nni6->ni_flags = htons(0x0000); /* raw bitmap */
/* supports NOOP, SUPTYPES, FQDN, and NODEADDR */
v = (u_int32_t)htonl(0x0000000f);
bcopy(&v, nni6 + 1, sizeof(u_int32_t));
break;
}
case NI_QTYPE_FQDN:
nni6->ni_code = ICMP6_NI_SUCCESS;
fqdn = (struct ni_reply_fqdn *)(mtod(n, caddr_t) +
sizeof(struct ip6_hdr) +
sizeof(struct icmp6_nodeinfo));
nni6->ni_flags = 0; /* XXX: meaningless TTL */
fqdn->ni_fqdn_ttl = 0; /* ditto. */
/*
* XXX do we really have FQDN in variable "hostname"?
*/
n->m_next = ni6_nametodns(hostname, hostnamelen, oldfqdn);
if (n->m_next == NULL)
goto bad;
/* XXX we assume that n->m_next is not a chain */
if (n->m_next->m_next != NULL)
goto bad;
n->m_pkthdr.len += n->m_next->m_len;
break;
case NI_QTYPE_NODEADDR:
{
int lenlim, copied;
nni6->ni_code = ICMP6_NI_SUCCESS;
n->m_pkthdr.len = n->m_len =
sizeof(struct ip6_hdr) + sizeof(struct icmp6_nodeinfo);
lenlim = M_TRAILINGSPACE(n);
copied = ni6_store_addrs(ni6, nni6, ifp, lenlim);
/* XXX: reset mbuf length */
n->m_pkthdr.len = n->m_len = sizeof(struct ip6_hdr) +
sizeof(struct icmp6_nodeinfo) + copied;
break;
}
default:
break; /* XXX impossible! */
}
nni6->ni_type = ICMP6_NI_REPLY;
m_freem(m);
return(n);
bad:
m_freem(m);
if (n)
m_freem(n);
return(NULL);
}
#undef hostnamelen
/*
* make a mbuf with DNS-encoded string. no compression support.
*
* XXX names with less than 2 dots (like "foo" or "foo.section") will be
* treated as truncated name (two \0 at the end). this is a wild guess.
*/
static struct mbuf *
ni6_nametodns(name, namelen, old)
const char *name;
int namelen;
int old; /* return pascal string if non-zero */
{
struct mbuf *m;
char *cp, *ep;
const char *p, *q;
int i, len, nterm;
if (old)
len = namelen + 1;
else
len = MCLBYTES;
/* because MAXHOSTNAMELEN is usually 256, we use cluster mbuf */
MGET(m, M_DONTWAIT, MT_DATA);
if (m && len > MLEN) {
MCLGET(m, M_DONTWAIT);
if ((m->m_flags & M_EXT) == 0)
goto fail;
}
if (!m)
goto fail;
m->m_next = NULL;
if (old) {
m->m_len = len;
*mtod(m, char *) = namelen;
bcopy(name, mtod(m, char *) + 1, namelen);
return m;
} else {
m->m_len = 0;
cp = mtod(m, char *);
ep = mtod(m, char *) + M_TRAILINGSPACE(m);
/* if not certain about my name, return empty buffer */
if (namelen == 0)
return m;
/*
* guess if it looks like shortened hostname, or FQDN.
* shortened hostname needs two trailing "\0".
*/
i = 0;
for (p = name; p < name + namelen; p++) {
if (*p && *p == '.')
i++;
}
if (i < 2)
nterm = 2;
else
nterm = 1;
p = name;
while (cp < ep && p < name + namelen) {
i = 0;
for (q = p; q < name + namelen && *q && *q != '.'; q++)
i++;
/* result does not fit into mbuf */
if (cp + i + 1 >= ep)
goto fail;
/*
* DNS label length restriction, RFC1035 page 8.
* "i == 0" case is included here to avoid returning
* 0-length label on "foo..bar".
*/
if (i <= 0 || i >= 64)
goto fail;
*cp++ = i;
bcopy(p, cp, i);
cp += i;
p = q;
if (p < name + namelen && *p == '.')
p++;
}
/* termination */
if (cp + nterm >= ep)
goto fail;
while (nterm-- > 0)
*cp++ = '\0';
m->m_len = cp - mtod(m, char *);
return m;
}
panic("should not reach here");
/* NOTREACHED */
fail:
if (m)
m_freem(m);
return NULL;
}
/*
* check if two DNS-encoded string matches. takes care of truncated
* form (with \0\0 at the end). no compression support.
* XXX upper/lowercase match (see RFC2065)
*/
static int
ni6_dnsmatch(a, alen, b, blen)
const char *a;
int alen;
const char *b;
int blen;
{
const char *a0, *b0;
int l;
/* simplest case - need validation? */
if (alen == blen && bcmp(a, b, alen) == 0)
return 1;
a0 = a;
b0 = b;
/* termination is mandatory */
if (alen < 2 || blen < 2)
return 0;
if (a0[alen - 1] != '\0' || b0[blen - 1] != '\0')
return 0;
alen--;
blen--;
while (a - a0 < alen && b - b0 < blen) {
if (a - a0 + 1 > alen || b - b0 + 1 > blen)
return 0;
if ((signed char)a[0] < 0 || (signed char)b[0] < 0)
return 0;
/* we don't support compression yet */
if (a[0] >= 64 || b[0] >= 64)
return 0;
/* truncated case */
if (a[0] == 0 && a - a0 == alen - 1)
return 1;
if (b[0] == 0 && b - b0 == blen - 1)
return 1;
if (a[0] == 0 || b[0] == 0)
return 0;
⌨️ 快捷键说明
复制代码
Ctrl + C
搜索代码
Ctrl + F
全屏模式
F11
切换主题
Ctrl + Shift + D
显示快捷键
?
增大字号
Ctrl + =
减小字号
Ctrl + -