#include <sys/cdefs.h>
#define MBUF_PRIVATE
#include "opt_inet.h"
#include "opt_inet6.h"
#include <sys/param.h>
#include <sys/domain.h>
#include <sys/jail.h>
#include <sys/kernel.h>
#include <sys/lock.h>
#include <sys/malloc.h>
#include <sys/mbuf.h>
#include <sys/proc.h>
#include <sys/protosw.h>
#include <sys/signalvar.h>
#include <sys/socket.h>
#include <sys/socketvar.h>
#include <sys/sx.h>
#include <sys/syslog.h>
#include <sys/systm.h>
#include <sys/time.h>
#include <net/if.h>
#include <net/if_var.h>
#include <net/if_dl.h>
#include <net/if_llatbl.h>
#include <net/if_private.h>
#include <net/if_types.h>
#include <net/route.h>
#include <net/route/route_ctl.h>
#include <net/route/nhop.h>
#include <net/vnet.h>
#include <netinet/in.h>
#include <netinet/in_pcb.h>
#include <netinet/in_var.h>
#include <netinet/ip6.h>
#include <netinet/icmp6.h>
#include <netinet/tcp_var.h>
#include <netinet6/in6_fib.h>
#include <netinet6/in6_ifattach.h>
#include <netinet6/in6_pcb.h>
#include <netinet6/ip6_var.h>
#include <netinet6/scope6_var.h>
#include <netinet6/mld6_var.h>
#include <netinet6/nd6.h>
#include <netinet6/send.h>
extern ip6proto_ctlinput_t *ip6_ctlprotox[];
VNET_PCPUSTAT_DEFINE(struct icmp6stat, icmp6stat);
VNET_PCPUSTAT_SYSINIT(icmp6stat);
SYSCTL_VNET_PCPUSTAT(_net_inet6_icmp6, ICMPV6CTL_STATS, stats,
struct icmp6stat, icmp6stat,
"ICMPv6 statistics (struct icmp6stat, netinet/icmp6.h)");
#ifdef VIMAGE
VNET_PCPUSTAT_SYSUNINIT(icmp6stat);
#endif
VNET_DEFINE_STATIC(int, icmp6_rediraccept) = 1;
#define V_icmp6_rediraccept VNET(icmp6_rediraccept)
SYSCTL_INT(_net_inet6_icmp6, ICMPV6CTL_REDIRACCEPT, rediraccept,
CTLFLAG_VNET | CTLFLAG_RW, &VNET_NAME(icmp6_rediraccept), 0,
"Accept ICMPv6 redirect messages");
VNET_DEFINE_STATIC(int, icmp6_redirtimeout) = 10 * 60;
#define V_icmp6_redirtimeout VNET(icmp6_redirtimeout)
SYSCTL_INT(_net_inet6_icmp6, ICMPV6CTL_REDIRTIMEOUT, redirtimeout,
CTLFLAG_VNET | CTLFLAG_RW, &VNET_NAME(icmp6_redirtimeout), 0,
"Delay in seconds before expiring redirect route");
VNET_DEFINE_STATIC(int, icmp6_nodeinfo) = 0;
#define V_icmp6_nodeinfo VNET(icmp6_nodeinfo)
SYSCTL_INT(_net_inet6_icmp6, ICMPV6CTL_NODEINFO, nodeinfo,
CTLFLAG_VNET | CTLFLAG_RW, &VNET_NAME(icmp6_nodeinfo), 0,
"Mask of enabled RFC4620 node information query types");
VNET_DECLARE(struct inpcbinfo, ripcbinfo);
#define V_ripcbinfo VNET(ripcbinfo)
VNET_DECLARE(int, rip_bind_all_fibs);
#define V_rip_bind_all_fibs VNET(rip_bind_all_fibs)
static void icmp6_errcount(int, int);
static int icmp6_rip6_input(struct mbuf **, int);
static void icmp6_reflect(struct mbuf *, size_t);
static const char *icmp6_redirect_diag(struct in6_addr *,
struct in6_addr *, struct in6_addr *);
static struct mbuf *ni6_input(struct mbuf *, int, struct prison *);
static struct mbuf *ni6_nametodns(const char *, int, int);
static int ni6_dnsmatch(const char *, int, const char *, int);
static int ni6_addrs(struct icmp6_nodeinfo *, struct mbuf *,
struct ifnet **, struct in6_addr *);
static int ni6_store_addrs(struct icmp6_nodeinfo *, struct icmp6_nodeinfo *,
struct ifnet *, int);
static int icmp6_notify_error(struct mbuf **, int, int);
static void icmp6_mtudisc_update(struct ip6ctlparam *ip6cp);
void
kmod_icmp6stat_inc(int statnum)
{
counter_u64_add(VNET(icmp6stat)[statnum], 1);
}
static void
icmp6_errcount(int type, int code)
{
switch (type) {
case ICMP6_DST_UNREACH:
switch (code) {
case ICMP6_DST_UNREACH_NOROUTE:
ICMP6STAT_INC(icp6s_odst_unreach_noroute);
return;
case ICMP6_DST_UNREACH_ADMIN:
ICMP6STAT_INC(icp6s_odst_unreach_admin);
return;
case ICMP6_DST_UNREACH_BEYONDSCOPE:
ICMP6STAT_INC(icp6s_odst_unreach_beyondscope);
return;
case ICMP6_DST_UNREACH_ADDR:
ICMP6STAT_INC(icp6s_odst_unreach_addr);
return;
case ICMP6_DST_UNREACH_NOPORT:
ICMP6STAT_INC(icp6s_odst_unreach_noport);
return;
}
break;
case ICMP6_PACKET_TOO_BIG:
ICMP6STAT_INC(icp6s_opacket_too_big);
return;
case ICMP6_TIME_EXCEEDED:
switch (code) {
case ICMP6_TIME_EXCEED_TRANSIT:
ICMP6STAT_INC(icp6s_otime_exceed_transit);
return;
case ICMP6_TIME_EXCEED_REASSEMBLY:
ICMP6STAT_INC(icp6s_otime_exceed_reassembly);
return;
}
break;
case ICMP6_PARAM_PROB:
switch (code) {
case ICMP6_PARAMPROB_HEADER:
ICMP6STAT_INC(icp6s_oparamprob_header);
return;
case ICMP6_PARAMPROB_NEXTHEADER:
ICMP6STAT_INC(icp6s_oparamprob_nextheader);
return;
case ICMP6_PARAMPROB_OPTION:
ICMP6STAT_INC(icp6s_oparamprob_option);
return;
}
break;
case ND_REDIRECT:
ICMP6STAT_INC(icp6s_oredirect);
return;
}
ICMP6STAT_INC(icp6s_ounknown);
}
void
icmp6_error2(struct mbuf *m, int type, int code, int param,
struct ifnet *ifp)
{
struct ip6_hdr *ip6;
if (ifp == NULL)
return;
if (m->m_len < sizeof(struct ip6_hdr)) {
m = m_pullup(m, sizeof(struct ip6_hdr));
if (m == NULL) {
IP6STAT_INC(ip6s_exthdrtoolong);
return;
}
}
ip6 = mtod(m, struct ip6_hdr *);
if (in6_setscope(&ip6->ip6_src, ifp, NULL) != 0)
return;
if (in6_setscope(&ip6->ip6_dst, ifp, NULL) != 0)
return;
icmp6_error(m, type, code, param);
}
void
icmp6_error(struct mbuf *m, int type, int code, int param)
{
struct ip6_hdr *oip6, *nip6;
struct icmp6_hdr *icmp6;
struct epoch_tracker et;
u_int preplen;
int off;
int nxt;
ICMP6STAT_INC(icp6s_error);
icmp6_errcount(type, code);
#ifdef M_DECRYPTED
if (m->m_flags & M_DECRYPTED) {
ICMP6STAT_INC(icp6s_canterror);
goto freeit;
}
#endif
if (m->m_len < sizeof(struct ip6_hdr)) {
m = m_pullup(m, sizeof(struct ip6_hdr));
if (m == NULL) {
IP6STAT_INC(ip6s_exthdrtoolong);
return;
}
}
oip6 = mtod(m, struct ip6_hdr *);
if ((m->m_flags & (M_BCAST|M_MCAST) ||
IN6_IS_ADDR_MULTICAST(&oip6->ip6_dst)) &&
(type != ICMP6_PACKET_TOO_BIG &&
(type != ICMP6_PARAM_PROB ||
code != ICMP6_PARAMPROB_OPTION)))
goto freeit;
if (IN6_IS_ADDR_UNSPECIFIED(&oip6->ip6_src) ||
IN6_IS_ADDR_MULTICAST(&oip6->ip6_src))
goto freeit;
nxt = -1;
off = ip6_lasthdr(m, 0, IPPROTO_IPV6, &nxt);
if (off >= 0 && nxt == IPPROTO_ICMPV6) {
struct icmp6_hdr *icp;
if (m->m_len < off + sizeof(struct icmp6_hdr)) {
m = m_pullup(m, off + sizeof(struct icmp6_hdr));
if (m == NULL) {
IP6STAT_INC(ip6s_exthdrtoolong);
return;
}
}
oip6 = mtod(m, struct ip6_hdr *);
icp = (struct icmp6_hdr *)(mtod(m, caddr_t) + off);
if (icp->icmp6_type < ICMP6_ECHO_REQUEST ||
icp->icmp6_type == ND_REDIRECT) {
ICMP6STAT_INC(icp6s_canterror);
goto freeit;
} else {
}
} else {
}
if (icmp6_ratelimit(&oip6->ip6_src, type, code))
goto freeit;
if (m->m_pkthdr.len >= ICMPV6_PLD_MAXLEN)
m_adj(m, ICMPV6_PLD_MAXLEN - m->m_pkthdr.len);
preplen = sizeof(struct ip6_hdr) + sizeof(struct icmp6_hdr);
M_PREPEND(m, preplen, M_NOWAIT);
if (m == NULL) {
nd6log((LOG_DEBUG, "ENOBUFS in icmp6_error %d\n", __LINE__));
return;
}
nip6 = mtod(m, struct ip6_hdr *);
nip6->ip6_src = oip6->ip6_src;
nip6->ip6_dst = oip6->ip6_dst;
in6_clearscope(&oip6->ip6_src);
in6_clearscope(&oip6->ip6_dst);
icmp6 = (struct icmp6_hdr *)(nip6 + 1);
icmp6->icmp6_type = type;
icmp6->icmp6_code = code;
icmp6->icmp6_pptr = htonl((u_int32_t)param);
ICMP6STAT_INC2(icp6s_outhist, type);
NET_EPOCH_ENTER(et);
icmp6_reflect(m, sizeof(struct ip6_hdr));
NET_EPOCH_EXIT(et);
return;
freeit:
m_freem(m);
}
int
icmp6_errmap(const struct icmp6_hdr *icmp6)
{
switch (icmp6->icmp6_type) {
case ICMP6_DST_UNREACH:
switch (icmp6->icmp6_code) {
case ICMP6_DST_UNREACH_NOROUTE:
case ICMP6_DST_UNREACH_ADDR:
return (EHOSTUNREACH);
case ICMP6_DST_UNREACH_NOPORT:
case ICMP6_DST_UNREACH_ADMIN:
return (ECONNREFUSED);
case ICMP6_DST_UNREACH_BEYONDSCOPE:
return (ENOPROTOOPT);
default:
return (0);
}
case ICMP6_PACKET_TOO_BIG:
return (EMSGSIZE);
case ICMP6_TIME_EXCEEDED:
switch (icmp6->icmp6_code) {
case ICMP6_TIME_EXCEED_TRANSIT:
return (EHOSTUNREACH);
case ICMP6_TIME_EXCEED_REASSEMBLY:
return (0);
default:
return (0);
}
case ICMP6_PARAM_PROB:
switch (icmp6->icmp6_code) {
case ICMP6_PARAMPROB_NEXTHEADER:
return (ECONNREFUSED);
case ICMP6_PARAMPROB_HEADER:
case ICMP6_PARAMPROB_OPTION:
return (ENOPROTOOPT);
default:
return (0);
}
default:
return (0);
}
}
int
icmp6_input(struct mbuf **mp, int *offp, int proto)
{
struct mbuf *m, *n;
struct ifnet *ifp;
struct ip6_hdr *ip6, *nip6;
struct icmp6_hdr *icmp6, *nicmp6;
char ip6bufs[INET6_ADDRSTRLEN], ip6bufd[INET6_ADDRSTRLEN];
int code, error, icmp6len, ip6len, noff, off, sum;
NET_EPOCH_ASSERT();
m = *mp;
off = *offp;
if (m->m_len < off + sizeof(struct icmp6_hdr)) {
m = m_pullup(m, off + sizeof(struct icmp6_hdr));
if (m == NULL) {
IP6STAT_INC(ip6s_exthdrtoolong);
*mp = m;
return (IPPROTO_DONE);
}
}
icmp6len = m->m_pkthdr.len - off;
if (icmp6len < sizeof(struct icmp6_hdr)) {
ICMP6STAT_INC(icp6s_tooshort);
goto freeit;
}
ip6 = mtod(m, struct ip6_hdr *);
ifp = m->m_pkthdr.rcvif;
if (IN6_IS_ADDR_MULTICAST(&ip6->ip6_dst)) {
struct in6_multi *inm;
inm = in6m_lookup(ifp, &ip6->ip6_dst);
if (inm == NULL) {
IP6STAT_INC(ip6s_notmember);
in6_ifstat_inc(m->m_pkthdr.rcvif, ifs6_in_discard);
goto freeit;
}
}
icmp6 = (struct icmp6_hdr *)((caddr_t)ip6 + off);
code = icmp6->icmp6_code;
if ((sum = in6_cksum(m, IPPROTO_ICMPV6, off, icmp6len)) != 0) {
nd6log((LOG_ERR,
"ICMP6 checksum error(%d|%x) %s\n",
icmp6->icmp6_type, sum,
ip6_sprintf(ip6bufs, &ip6->ip6_src)));
ICMP6STAT_INC(icp6s_checksum);
goto freeit;
}
ICMP6STAT_INC2(icp6s_inhist, icmp6->icmp6_type);
icmp6_ifstat_inc(ifp, ifs6_in_msg);
if (icmp6->icmp6_type < ICMP6_INFOMSG_MASK)
icmp6_ifstat_inc(ifp, ifs6_in_error);
ip6len = sizeof(struct ip6_hdr) + ntohs(ip6->ip6_plen);
switch (icmp6->icmp6_type) {
case ICMP6_DST_UNREACH:
icmp6_ifstat_inc(ifp, ifs6_in_dstunreach);
switch (code) {
case ICMP6_DST_UNREACH_ADMIN:
icmp6_ifstat_inc(ifp, ifs6_in_adminprohib);
case ICMP6_DST_UNREACH_NOROUTE:
case ICMP6_DST_UNREACH_ADDR:
case ICMP6_DST_UNREACH_BEYONDSCOPE:
case ICMP6_DST_UNREACH_NOPORT:
goto deliver;
default:
goto badcode;
}
case ICMP6_PACKET_TOO_BIG:
icmp6_ifstat_inc(ifp, ifs6_in_pkttoobig);
goto deliver;
case ICMP6_TIME_EXCEEDED:
icmp6_ifstat_inc(ifp, ifs6_in_timeexceed);
switch (code) {
case ICMP6_TIME_EXCEED_TRANSIT:
case ICMP6_TIME_EXCEED_REASSEMBLY:
goto deliver;
default:
goto badcode;
}
case ICMP6_PARAM_PROB:
icmp6_ifstat_inc(ifp, ifs6_in_paramprob);
switch (code) {
case ICMP6_PARAMPROB_NEXTHEADER:
case ICMP6_PARAMPROB_HEADER:
case ICMP6_PARAMPROB_OPTION:
goto deliver;
default:
goto badcode;
}
case ICMP6_ECHO_REQUEST:
icmp6_ifstat_inc(ifp, ifs6_in_echo);
if (code != 0)
goto badcode;
if (icmp6_ratelimit(&ip6->ip6_src, ICMP6_ECHO_REPLY, 0))
break;
if ((n = m_copym(m, 0, M_COPYALL, M_NOWAIT)) == NULL) {
break;
}
if (!M_WRITABLE(n)
|| n->m_len < off + sizeof(struct icmp6_hdr)) {
struct mbuf *n0 = n;
int n0len;
CTASSERT(sizeof(*nip6) + sizeof(*nicmp6) <= MHLEN);
n = m_gethdr(M_NOWAIT, n0->m_type);
if (n == NULL) {
m_freem(n0);
break;
}
m_move_pkthdr(n, n0);
n0len = n0->m_pkthdr.len;
nip6 = mtod(n, struct ip6_hdr *);
bcopy(ip6, nip6, sizeof(struct ip6_hdr));
nicmp6 = (struct icmp6_hdr *)(nip6 + 1);
bcopy(icmp6, nicmp6, sizeof(struct icmp6_hdr));
noff = sizeof(struct ip6_hdr);
n->m_len = noff + sizeof(struct icmp6_hdr);
m_adj(n0, off + sizeof(struct icmp6_hdr));
n->m_pkthdr.len = n0len + (noff - off);
n->m_next = n0;
} else {
if (n->m_len < off + sizeof(*nicmp6)) {
n = m_pullup(n, off + sizeof(*nicmp6));
if (n == NULL) {
IP6STAT_INC(ip6s_exthdrtoolong);
break;
}
}
nicmp6 = (struct icmp6_hdr *)(mtod(n, caddr_t) + off);
noff = off;
}
if (n) {
nicmp6->icmp6_type = ICMP6_ECHO_REPLY;
nicmp6->icmp6_code = 0;
ICMP6STAT_INC(icp6s_reflect);
ICMP6STAT_INC2(icp6s_outhist, ICMP6_ECHO_REPLY);
icmp6_reflect(n, noff);
}
break;
case ICMP6_ECHO_REPLY:
icmp6_ifstat_inc(ifp, ifs6_in_echoreply);
if (code != 0)
goto badcode;
break;
case MLD_LISTENER_QUERY:
case MLD_LISTENER_REPORT:
case MLD_LISTENER_DONE:
case MLDV2_LISTENER_REPORT:
if ((ip6->ip6_hlim != 1) || (m->m_flags & M_RTALERT_MLD) == 0)
goto freeit;
if (mld_input(&m, off, icmp6len) != 0) {
*mp = NULL;
return (IPPROTO_DONE);
}
break;
case ICMP6_WRUREQUEST:
{
enum { WRU, FQDN } mode;
struct prison *pr;
if (!V_icmp6_nodeinfo)
break;
if (icmp6len == sizeof(struct icmp6_hdr) + 4)
mode = WRU;
else if (icmp6len >= sizeof(struct icmp6_nodeinfo))
mode = FQDN;
else
goto badlen;
pr = NULL;
sx_slock(&allprison_lock);
TAILQ_FOREACH(pr, &allprison, pr_list)
if (pr->pr_vnet == ifp->if_vnet)
break;
sx_sunlock(&allprison_lock);
if (pr == NULL)
pr = curthread->td_ucred->cr_prison;
if (mode == FQDN) {
if (m->m_len < off + sizeof(struct icmp6_nodeinfo)) {
m = m_pullup(m, off +
sizeof(struct icmp6_nodeinfo));
if (m == NULL) {
IP6STAT_INC(ip6s_exthdrtoolong);
*mp = m;
return (IPPROTO_DONE);
}
}
n = m_copym(m, 0, M_COPYALL, M_NOWAIT);
if (n)
n = ni6_input(n, off, pr);
noff = sizeof(struct ip6_hdr);
} else {
u_char *p;
int maxhlen, hlen;
if ((V_icmp6_nodeinfo & (ICMP6_NODEINFO_FQDNOK |
ICMP6_NODEINFO_TMPADDROK)) !=
(ICMP6_NODEINFO_FQDNOK | ICMP6_NODEINFO_TMPADDROK))
break;
if (code != 0)
goto badcode;
CTASSERT(sizeof(*nip6) + sizeof(*nicmp6) + 4 <= MHLEN);
n = m_gethdr(M_NOWAIT, m->m_type);
if (n == NULL) {
break;
}
if (!m_dup_pkthdr(n, m, M_NOWAIT)) {
m_free(n);
n = NULL;
break;
}
nip6 = mtod(n, struct ip6_hdr *);
bcopy(ip6, nip6, sizeof(struct ip6_hdr));
nicmp6 = (struct icmp6_hdr *)(nip6 + 1);
bcopy(icmp6, nicmp6, sizeof(struct icmp6_hdr));
p = (u_char *)(nicmp6 + 1);
bzero(p, 4);
maxhlen = M_TRAILINGSPACE(n) -
(sizeof(*nip6) + sizeof(*nicmp6) + 4);
mtx_lock(&pr->pr_mtx);
hlen = strlen(pr->pr_hostname);
if (maxhlen > hlen)
maxhlen = hlen;
bcopy(pr->pr_hostname, p + 4, maxhlen);
mtx_unlock(&pr->pr_mtx);
noff = sizeof(struct ip6_hdr);
n->m_pkthdr.len = n->m_len = sizeof(struct ip6_hdr) +
sizeof(struct icmp6_hdr) + 4 + maxhlen;
nicmp6->icmp6_type = ICMP6_WRUREPLY;
nicmp6->icmp6_code = 0;
}
if (n) {
ICMP6STAT_INC(icp6s_reflect);
ICMP6STAT_INC2(icp6s_outhist, ICMP6_WRUREPLY);
icmp6_reflect(n, noff);
}
break;
}
case ICMP6_WRUREPLY:
if (code != 0)
goto badcode;
break;
case ND_ROUTER_SOLICIT:
icmp6_ifstat_inc(ifp, ifs6_in_routersolicit);
if (code != 0)
goto badcode;
if (icmp6len < sizeof(struct nd_router_solicit))
goto badlen;
if (send_sendso_input_hook != NULL) {
if (m->m_len < off + icmp6len) {
m = m_pullup(m, off + icmp6len);
if (m == NULL) {
IP6STAT_INC(ip6s_exthdrtoolong);
*mp = NULL;
return (IPPROTO_DONE);
}
}
error = send_sendso_input_hook(m, ifp, SND_IN, ip6len);
if (error == 0) {
m = NULL;
goto freeit;
}
}
n = m_copym(m, 0, M_COPYALL, M_NOWAIT);
nd6_rs_input(m, off, icmp6len);
m = n;
if (m == NULL)
goto freeit;
break;
case ND_ROUTER_ADVERT:
icmp6_ifstat_inc(ifp, ifs6_in_routeradvert);
if (code != 0)
goto badcode;
if (icmp6len < sizeof(struct nd_router_advert))
goto badlen;
if (send_sendso_input_hook != NULL) {
error = send_sendso_input_hook(m, ifp, SND_IN, ip6len);
if (error == 0) {
m = NULL;
goto freeit;
}
}
n = m_copym(m, 0, M_COPYALL, M_NOWAIT);
nd6_ra_input(m, off, icmp6len);
m = n;
if (m == NULL)
goto freeit;
break;
case ND_NEIGHBOR_SOLICIT:
icmp6_ifstat_inc(ifp, ifs6_in_neighborsolicit);
if (code != 0)
goto badcode;
if (icmp6len < sizeof(struct nd_neighbor_solicit))
goto badlen;
if (send_sendso_input_hook != NULL) {
error = send_sendso_input_hook(m, ifp, SND_IN, ip6len);
if (error == 0) {
m = NULL;
goto freeit;
}
}
n = m_copym(m, 0, M_COPYALL, M_NOWAIT);
nd6_ns_input(m, off, icmp6len);
m = n;
if (m == NULL)
goto freeit;
break;
case ND_NEIGHBOR_ADVERT:
icmp6_ifstat_inc(ifp, ifs6_in_neighboradvert);
if (code != 0)
goto badcode;
if (icmp6len < sizeof(struct nd_neighbor_advert))
goto badlen;
if (send_sendso_input_hook != NULL) {
error = send_sendso_input_hook(m, ifp, SND_IN, ip6len);
if (error == 0) {
m = NULL;
goto freeit;
}
}
n = m_copym(m, 0, M_COPYALL, M_NOWAIT);
nd6_na_input(m, off, icmp6len);
m = n;
if (m == NULL)
goto freeit;
break;
case ND_REDIRECT:
icmp6_ifstat_inc(ifp, ifs6_in_redirect);
if (code != 0)
goto badcode;
if (icmp6len < sizeof(struct nd_redirect))
goto badlen;
if (send_sendso_input_hook != NULL) {
error = send_sendso_input_hook(m, ifp, SND_IN, ip6len);
if (error == 0) {
m = NULL;
goto freeit;
}
}
n = m_copym(m, 0, M_COPYALL, M_NOWAIT);
icmp6_redirect_input(m, off);
m = n;
if (m == NULL)
goto freeit;
break;
case ICMP6_ROUTER_RENUMBERING:
if (code != ICMP6_ROUTER_RENUMBERING_COMMAND &&
code != ICMP6_ROUTER_RENUMBERING_RESULT)
goto badcode;
if (icmp6len < sizeof(struct icmp6_router_renum))
goto badlen;
break;
default:
nd6log((LOG_DEBUG,
"icmp6_input: unknown type %d(src=%s, dst=%s, ifid=%d)\n",
icmp6->icmp6_type, ip6_sprintf(ip6bufs, &ip6->ip6_src),
ip6_sprintf(ip6bufd, &ip6->ip6_dst),
ifp ? ifp->if_index : 0));
if (icmp6->icmp6_type < ICMP6_ECHO_REQUEST) {
goto deliver;
} else {
break;
}
deliver:
if (icmp6_notify_error(&m, off, icmp6len) != 0) {
*mp = NULL;
return (IPPROTO_DONE);
}
break;
badcode:
ICMP6STAT_INC(icp6s_badcode);
break;
badlen:
ICMP6STAT_INC(icp6s_badlen);
break;
}
icmp6_rip6_input(&m, *offp);
*mp = m;
return (IPPROTO_DONE);
freeit:
m_freem(m);
*mp = NULL;
return (IPPROTO_DONE);
}
static int
icmp6_notify_error(struct mbuf **mp, int off, int icmp6len)
{
struct mbuf *m;
struct icmp6_hdr *icmp6;
struct ip6_hdr *eip6;
u_int32_t notifymtu;
struct sockaddr_in6 icmp6src, icmp6dst;
m = *mp;
if (icmp6len < sizeof(struct icmp6_hdr) + sizeof(struct ip6_hdr)) {
ICMP6STAT_INC(icp6s_tooshort);
goto freeit;
}
if (m->m_len < off + sizeof(*icmp6) + sizeof(struct ip6_hdr)) {
m = m_pullup(m, off + sizeof(*icmp6) + sizeof(struct ip6_hdr));
if (m == NULL) {
IP6STAT_INC(ip6s_exthdrtoolong);
*mp = m;
return (-1);
}
}
icmp6 = (struct icmp6_hdr *)(mtod(m, caddr_t) + off);
eip6 = (struct ip6_hdr *)(icmp6 + 1);
bzero(&icmp6dst, sizeof(icmp6dst));
{
u_int8_t nxt = eip6->ip6_nxt;
int eoff = off + sizeof(struct icmp6_hdr) +
sizeof(struct ip6_hdr);
struct ip6ctlparam ip6cp;
int icmp6type = icmp6->icmp6_type;
struct ip6_frag *fh;
struct ip6_rthdr *rth;
struct ip6_rthdr0 *rth0;
int rthlen;
while (1) {
struct ip6_ext *eh;
switch (nxt) {
case IPPROTO_HOPOPTS:
case IPPROTO_DSTOPTS:
case IPPROTO_AH:
if (m->m_len < eoff + sizeof(struct ip6_ext)) {
m = m_pullup(m, eoff +
sizeof(struct ip6_ext));
if (m == NULL) {
IP6STAT_INC(ip6s_exthdrtoolong);
*mp = m;
return (-1);
}
}
eh = (struct ip6_ext *)
(mtod(m, caddr_t) + eoff);
if (nxt == IPPROTO_AH)
eoff += (eh->ip6e_len + 2) << 2;
else
eoff += (eh->ip6e_len + 1) << 3;
nxt = eh->ip6e_nxt;
break;
case IPPROTO_ROUTING:
if (m->m_len < eoff + sizeof(*rth)) {
m = m_pullup(m, eoff + sizeof(*rth));
if (m == NULL) {
IP6STAT_INC(ip6s_exthdrtoolong);
*mp = m;
return (-1);
}
}
rth = (struct ip6_rthdr *)
(mtod(m, caddr_t) + eoff);
rthlen = (rth->ip6r_len + 1) << 3;
if (rth->ip6r_segleft &&
rth->ip6r_type == IPV6_RTHDR_TYPE_0) {
int hops;
if (m->m_len < eoff + rthlen) {
m = m_pullup(m, eoff + rthlen);
if (m == NULL) {
IP6STAT_INC(
ip6s_exthdrtoolong);
*mp = m;
return (-1);
}
}
rth0 = (struct ip6_rthdr0 *)
(mtod(m, caddr_t) + eoff);
if ((rth0->ip6r0_len % 2) == 0 &&
(hops = rth0->ip6r0_len/2))
icmp6dst.sin6_addr = *((struct in6_addr *)(rth0 + 1) + (hops - 1));
}
eoff += rthlen;
nxt = rth->ip6r_nxt;
break;
case IPPROTO_FRAGMENT:
if (m->m_len < eoff + sizeof(struct ip6_frag)) {
m = m_pullup(m, eoff +
sizeof(struct ip6_frag));
if (m == NULL) {
IP6STAT_INC(ip6s_exthdrtoolong);
*mp = m;
return (-1);
}
}
fh = (struct ip6_frag *)(mtod(m, caddr_t) +
eoff);
if (fh->ip6f_offlg & IP6F_OFF_MASK)
goto notify;
eoff += sizeof(struct ip6_frag);
nxt = fh->ip6f_nxt;
break;
default:
goto notify;
}
}
notify:
icmp6 = (struct icmp6_hdr *)(mtod(m, caddr_t) + off);
eip6 = (struct ip6_hdr *)(icmp6 + 1);
if (IN6_IS_ADDR_UNSPECIFIED(&eip6->ip6_src) ||
IN6_IS_ADDR_UNSPECIFIED(&eip6->ip6_dst))
goto freeit;
icmp6dst.sin6_len = sizeof(struct sockaddr_in6);
icmp6dst.sin6_family = AF_INET6;
if (IN6_IS_ADDR_UNSPECIFIED(&icmp6dst.sin6_addr))
icmp6dst.sin6_addr = eip6->ip6_dst;
if (in6_setscope(&icmp6dst.sin6_addr, m->m_pkthdr.rcvif, NULL))
goto freeit;
bzero(&icmp6src, sizeof(icmp6src));
icmp6src.sin6_len = sizeof(struct sockaddr_in6);
icmp6src.sin6_family = AF_INET6;
icmp6src.sin6_addr = eip6->ip6_src;
if (in6_setscope(&icmp6src.sin6_addr, m->m_pkthdr.rcvif, NULL))
goto freeit;
icmp6src.sin6_flowinfo =
(eip6->ip6_flow & IPV6_FLOWLABEL_MASK);
ip6cp.ip6c_m = m;
ip6cp.ip6c_icmp6 = icmp6;
ip6cp.ip6c_ip6 = (struct ip6_hdr *)(icmp6 + 1);
ip6cp.ip6c_off = eoff;
ip6cp.ip6c_finaldst = &icmp6dst;
ip6cp.ip6c_src = &icmp6src;
ip6cp.ip6c_nxt = nxt;
if (icmp6type == ICMP6_PACKET_TOO_BIG) {
notifymtu = ntohl(icmp6->icmp6_mtu);
ip6cp.ip6c_cmdarg = (void *)¬ifymtu;
icmp6_mtudisc_update(&ip6cp);
}
if (ip6_ctlprotox[nxt] != NULL)
ip6_ctlprotox[nxt](&ip6cp);
}
*mp = m;
return (0);
freeit:
m_freem(m);
*mp = NULL;
return (-1);
}
static void
icmp6_mtudisc_update(struct ip6ctlparam *ip6cp)
{
struct in6_addr *dst = &ip6cp->ip6c_finaldst->sin6_addr;
struct icmp6_hdr *icmp6 = ip6cp->ip6c_icmp6;
struct mbuf *m = ip6cp->ip6c_m;
u_int mtu = ntohl(icmp6->icmp6_mtu);
struct in_conninfo inc;
uint32_t max_mtu;
if (mtu < IPV6_MMTU)
return;
bzero(&inc, sizeof(inc));
inc.inc_fibnum = M_GETFIB(m);
inc.inc_flags |= INC_ISIPV6;
inc.inc6_faddr = *dst;
if (in6_setscope(&inc.inc6_faddr, m->m_pkthdr.rcvif, NULL))
return;
max_mtu = tcp_hc_getmtu(&inc);
if (max_mtu == 0)
max_mtu = tcp_maxmtu6(&inc, NULL);
if (mtu < max_mtu) {
tcp_hc_updatemtu(&inc, mtu);
ICMP6STAT_INC(icp6s_pmtuchg);
}
}
static struct mbuf *
ni6_input(struct mbuf *m, int off, struct prison *pr)
{
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;
struct ifnet *ifp = NULL;
struct in6_addr in6_subj;
struct ip6_hdr *ip6;
int oldfqdn = 0;
char *subj = NULL;
struct in6_ifaddr *ia6 = NULL;
ip6 = mtod(m, struct ip6_hdr *);
ni6 = (struct icmp6_nodeinfo *)(mtod(m, caddr_t) + off);
if ((V_icmp6_nodeinfo & ICMP6_NODEINFO_GLOBALOK) == 0 &&
!IN6_IS_ADDR_LOOPBACK(&ip6->ip6_src) &&
!IN6_IS_ADDR_LINKLOCAL(&ip6->ip6_src))
goto bad;
if (IN6_IS_ADDR_MULTICAST(&ip6->ip6_dst)) {
if (!IN6_IS_ADDR_MC_LINKLOCAL(&ip6->ip6_dst))
goto bad;
} else {
ia6 = in6ifa_ifwithaddr(&ip6->ip6_dst, 0 , false);
if (ia6 == NULL)
goto bad;
if ((ia6->ia6_flags & IN6_IFF_TEMPORARY) &&
!(V_icmp6_nodeinfo & ICMP6_NODEINFO_TMPADDROK)) {
nd6log((LOG_DEBUG, "ni6_input: ignore node info to "
"a temporary address in %s:%d",
__FILE__, __LINE__));
goto bad;
}
}
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:
if (ni6->ni_code == ICMP6_NI_SUBJ_FQDN && subjlen == 0)
break;
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
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(struct in6_addr))
goto bad;
m_copydata(m, off + sizeof(struct icmp6_nodeinfo),
subjlen, (caddr_t)&in6_subj);
if (in6_setscope(&in6_subj, m->m_pkthdr.rcvif, NULL))
goto bad;
subj = (char *)&in6_subj;
if (IN6_ARE_ADDR_EQUAL(&ip6->ip6_dst, &in6_subj))
break;
goto bad;
case ICMP6_NI_SUBJ_FQDN:
mtx_lock(&pr->pr_mtx);
n = ni6_nametodns(pr->pr_hostname,
strlen(pr->pr_hostname), 0);
mtx_unlock(&pr->pr_mtx);
if (!n || n->m_next || n->m_len == 0)
goto bad;
if (m->m_len < off + sizeof(struct icmp6_nodeinfo) +
subjlen) {
m = m_pullup(m, off +
sizeof(struct icmp6_nodeinfo) + subjlen);
if (m == NULL) {
IP6STAT_INC(ip6s_exthdrtoolong);
goto bad;
}
}
ni6 = (struct icmp6_nodeinfo *)(mtod(m, caddr_t) + off);
subj = (char *)(mtod(m, caddr_t) + off +
sizeof(struct icmp6_nodeinfo));
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:
default:
goto bad;
}
break;
}
switch (qtype) {
case NI_QTYPE_FQDN:
if ((V_icmp6_nodeinfo & ICMP6_NODEINFO_FQDNOK) == 0)
goto bad;
break;
case NI_QTYPE_NODEADDR:
case NI_QTYPE_IPV4ADDR:
if ((V_icmp6_nodeinfo & ICMP6_NODEINFO_NODEADDROK) == 0)
goto bad;
break;
}
switch (qtype) {
case NI_QTYPE_NOOP:
break;
case NI_QTYPE_SUPTYPES:
replylen += sizeof(u_int32_t);
break;
case NI_QTYPE_FQDN:
replylen += offsetof(struct ni_reply_fqdn, ni_fqdn_namelen);
break;
case NI_QTYPE_NODEADDR:
addrs = ni6_addrs(ni6, m, &ifp, (struct in6_addr *)subj);
if ((replylen += addrs * (sizeof(struct in6_addr) +
sizeof(u_int32_t))) > MCLBYTES)
replylen = MCLBYTES;
break;
case NI_QTYPE_IPV4ADDR:
break;
default:
qtype = NI_QTYPE_FQDN;
replylen += offsetof(struct ni_reply_fqdn, ni_fqdn_namelen);
oldfqdn++;
break;
}
if (replylen > MCLBYTES) {
goto bad;
}
if (replylen > MHLEN)
n = m_getcl(M_NOWAIT, m->m_type, M_PKTHDR);
else
n = m_gethdr(M_NOWAIT, m->m_type);
if (n == NULL) {
m_freem(m);
return (NULL);
}
m_move_pkthdr(n, m);
n->m_pkthdr.len = n->m_len = replylen;
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));
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);
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;
fqdn->ni_fqdn_ttl = 0;
mtx_lock(&pr->pr_mtx);
n->m_next = ni6_nametodns(pr->pr_hostname,
strlen(pr->pr_hostname), oldfqdn);
mtx_unlock(&pr->pr_mtx);
if (n->m_next == NULL)
goto bad;
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);
n->m_pkthdr.len = n->m_len = sizeof(struct ip6_hdr) +
sizeof(struct icmp6_nodeinfo) + copied;
break;
}
default:
break;
}
nni6->ni_type = ICMP6_NI_REPLY;
m_freem(m);
return (n);
bad:
m_freem(m);
if (n)
m_freem(n);
return (NULL);
}
static struct mbuf *
ni6_nametodns(const char *name, int namelen, int old)
{
struct mbuf *m;
char *cp, *ep;
const char *p, *q;
int i, len, nterm;
if (old)
len = namelen + 1;
else
len = MCLBYTES;
if (len > MLEN)
m = m_getcl(M_NOWAIT, MT_DATA, 0);
else
m = m_get(M_NOWAIT, MT_DATA);
if (m == NULL)
goto fail;
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 (namelen == 0)
return m;
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++;
if (cp + i + 1 >= ep)
goto fail;
if (i <= 0 || i >= 64)
goto fail;
*cp++ = i;
bcopy(p, cp, i);
cp += i;
p = q;
if (p < name + namelen && *p == '.')
p++;
}
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");
fail:
if (m)
m_freem(m);
return NULL;
}
static int
ni6_dnsmatch(const char *a, int alen, const char *b, int blen)
{
const char *a0, *b0;
int l;
if (alen == blen && bcmp(a, b, alen) == 0)
return 1;
a0 = a;
b0 = b;
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;
if (a[0] >= 64 || b[0] >= 64)
return 0;
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;
if (a[0] != b[0])
return 0;
l = a[0];
if (a - a0 + 1 + l > alen || b - b0 + 1 + l > blen)
return 0;
if (bcmp(a + 1, b + 1, l) != 0)
return 0;
a += 1 + l;
b += 1 + l;
}
if (a - a0 == alen && b - b0 == blen)
return 1;
else
return 0;
}
static int
ni6_addrs(struct icmp6_nodeinfo *ni6, struct mbuf *m, struct ifnet **ifpp,
struct in6_addr *subj)
{
struct ifnet *ifp;
struct in6_ifaddr *ifa6;
struct ifaddr *ifa;
int addrs = 0, addrsofif, iffound = 0;
int niflags = ni6->ni_flags;
NET_EPOCH_ASSERT();
if ((niflags & NI_NODEADDR_FLAG_ALL) == 0) {
switch (ni6->ni_code) {
case ICMP6_NI_SUBJ_IPV6:
if (subj == NULL)
return (0);
break;
default:
return (0);
}
}
CK_STAILQ_FOREACH(ifp, &V_ifnet, if_link) {
addrsofif = 0;
CK_STAILQ_FOREACH(ifa, &ifp->if_addrhead, ifa_link) {
if (ifa->ifa_addr->sa_family != AF_INET6)
continue;
ifa6 = (struct in6_ifaddr *)ifa;
if ((niflags & NI_NODEADDR_FLAG_ALL) == 0 &&
IN6_ARE_ADDR_EQUAL(subj, &ifa6->ia_addr.sin6_addr))
iffound = 1;
switch (in6_addrscope(&ifa6->ia_addr.sin6_addr)) {
case IPV6_ADDR_SCOPE_LINKLOCAL:
if ((niflags & NI_NODEADDR_FLAG_LINKLOCAL) == 0)
continue;
break;
case IPV6_ADDR_SCOPE_SITELOCAL:
if ((niflags & NI_NODEADDR_FLAG_SITELOCAL) == 0)
continue;
break;
case IPV6_ADDR_SCOPE_GLOBAL:
if ((niflags & NI_NODEADDR_FLAG_GLOBAL) == 0)
continue;
break;
default:
continue;
}
if ((ifa6->ia6_flags & IN6_IFF_ANYCAST) != 0 &&
(niflags & NI_NODEADDR_FLAG_ANYCAST) == 0)
continue;
if ((ifa6->ia6_flags & IN6_IFF_TEMPORARY) != 0 &&
(V_icmp6_nodeinfo & ICMP6_NODEINFO_TMPADDROK) == 0) {
continue;
}
addrsofif++;
}
if (iffound) {
*ifpp = ifp;
return (addrsofif);
}
addrs += addrsofif;
}
return (addrs);
}
static int
ni6_store_addrs(struct icmp6_nodeinfo *ni6, struct icmp6_nodeinfo *nni6,
struct ifnet *ifp0, int resid)
{
struct ifnet *ifp;
struct in6_ifaddr *ifa6;
struct ifaddr *ifa;
struct ifnet *ifp_dep = NULL;
int copied = 0, allow_deprecated = 0;
u_char *cp = (u_char *)(nni6 + 1);
int niflags = ni6->ni_flags;
u_int32_t ltime;
NET_EPOCH_ASSERT();
if (ifp0 == NULL && !(niflags & NI_NODEADDR_FLAG_ALL))
return (0);
ifp = ifp0 ? ifp0 : CK_STAILQ_FIRST(&V_ifnet);
again:
for (; ifp; ifp = CK_STAILQ_NEXT(ifp, if_link)) {
CK_STAILQ_FOREACH(ifa, &ifp->if_addrhead, ifa_link) {
if (ifa->ifa_addr->sa_family != AF_INET6)
continue;
ifa6 = (struct in6_ifaddr *)ifa;
if ((ifa6->ia6_flags & IN6_IFF_DEPRECATED) != 0 &&
allow_deprecated == 0) {
if (ifp_dep == NULL)
ifp_dep = ifp;
continue;
} else if ((ifa6->ia6_flags & IN6_IFF_DEPRECATED) == 0 &&
allow_deprecated != 0)
continue;
switch (in6_addrscope(&ifa6->ia_addr.sin6_addr)) {
case IPV6_ADDR_SCOPE_LINKLOCAL:
if ((niflags & NI_NODEADDR_FLAG_LINKLOCAL) == 0)
continue;
break;
case IPV6_ADDR_SCOPE_SITELOCAL:
if ((niflags & NI_NODEADDR_FLAG_SITELOCAL) == 0)
continue;
break;
case IPV6_ADDR_SCOPE_GLOBAL:
if ((niflags & NI_NODEADDR_FLAG_GLOBAL) == 0)
continue;
break;
default:
continue;
}
if ((ifa6->ia6_flags & IN6_IFF_ANYCAST) != 0 &&
(niflags & NI_NODEADDR_FLAG_ANYCAST) == 0)
continue;
if ((ifa6->ia6_flags & IN6_IFF_TEMPORARY) != 0 &&
(V_icmp6_nodeinfo & ICMP6_NODEINFO_TMPADDROK) == 0) {
continue;
}
if (resid < sizeof(struct in6_addr) +
sizeof(u_int32_t)) {
nni6->ni_flags |= NI_NODEADDR_FLAG_TRUNCATE;
return (copied);
}
if (ifa6->ia6_lifetime.ia6t_expire == 0)
ltime = ND6_INFINITE_LIFETIME;
else {
if (ifa6->ia6_lifetime.ia6t_expire >
time_uptime)
ltime = htonl(ifa6->ia6_lifetime.ia6t_expire - time_uptime);
else
ltime = 0;
}
bcopy(<ime, cp, sizeof(u_int32_t));
cp += sizeof(u_int32_t);
bcopy(&ifa6->ia_addr.sin6_addr, cp,
sizeof(struct in6_addr));
in6_clearscope((struct in6_addr *)cp);
cp += sizeof(struct in6_addr);
resid -= (sizeof(struct in6_addr) + sizeof(u_int32_t));
copied += (sizeof(struct in6_addr) + sizeof(u_int32_t));
}
if (ifp0)
break;
}
if (allow_deprecated == 0 && ifp_dep != NULL) {
ifp = ifp_dep;
allow_deprecated = 1;
goto again;
}
return (copied);
}
static bool
icmp6_rip6_match(const struct inpcb *inp, void *v)
{
struct ip6_hdr *ip6 = v;
if ((inp->inp_vflag & INP_IPV6) == 0)
return (false);
if (inp->inp_ip_p != IPPROTO_ICMPV6)
return (false);
if (!IN6_IS_ADDR_UNSPECIFIED(&inp->in6p_laddr) &&
!IN6_ARE_ADDR_EQUAL(&inp->in6p_laddr, &ip6->ip6_dst))
return (false);
if (!IN6_IS_ADDR_UNSPECIFIED(&inp->in6p_faddr) &&
!IN6_ARE_ADDR_EQUAL(&inp->in6p_faddr, &ip6->ip6_src))
return (false);
return (true);
}
static int
icmp6_rip6_input(struct mbuf **mp, int off)
{
struct mbuf *n, *m = *mp;
struct ip6_hdr *ip6 = mtod(m, struct ip6_hdr *);
struct inpcb_iterator inpi = INP_ITERATOR(&V_ripcbinfo,
INPLOOKUP_RLOCKPCB, icmp6_rip6_match, ip6);
struct inpcb *inp;
struct sockaddr_in6 fromsa;
struct icmp6_hdr *icmp6;
struct mbuf *opts = NULL;
int delivered = 0, fib;
icmp6 = (struct icmp6_hdr *)((caddr_t)ip6 + off);
bzero(&fromsa, sizeof(fromsa));
fromsa.sin6_family = AF_INET6;
fromsa.sin6_len = sizeof(struct sockaddr_in6);
fromsa.sin6_addr = ip6->ip6_src;
if (sa6_recoverscope(&fromsa)) {
m_freem(m);
*mp = NULL;
return (IPPROTO_DONE);
}
fib = M_GETFIB(m);
while ((inp = inp_next(&inpi)) != NULL) {
if (V_rip_bind_all_fibs == 0 && fib != inp->inp_inc.inc_fibnum)
continue;
if (ICMP6_FILTER_WILLBLOCK(icmp6->icmp6_type,
inp->in6p_icmp6filt))
continue;
if ((m->m_flags & M_EXT) && m->m_next == NULL &&
m->m_len <= MHLEN) {
n = m_get(M_NOWAIT, m->m_type);
if (n != NULL) {
if (m_dup_pkthdr(n, m, M_NOWAIT)) {
bcopy(m->m_data, n->m_data, m->m_len);
n->m_len = m->m_len;
} else {
m_free(n);
n = NULL;
}
}
} else
n = m_copym(m, 0, M_COPYALL, M_NOWAIT);
if (n == NULL)
continue;
if (inp->inp_flags & INP_CONTROLOPTS)
ip6_savecontrol(inp, n, &opts);
m_adj(n, off);
SOCKBUF_LOCK(&inp->inp_socket->so_rcv);
if (sbappendaddr_locked(&inp->inp_socket->so_rcv,
(struct sockaddr *)&fromsa, n, opts) == 0) {
soroverflow_locked(inp->inp_socket);
m_freem(n);
if (opts)
m_freem(opts);
} else {
sorwakeup_locked(inp->inp_socket);
delivered++;
}
opts = NULL;
}
m_freem(m);
*mp = NULL;
if (delivered == 0)
IP6STAT_DEC(ip6s_delivered);
return (IPPROTO_DONE);
}
static void
icmp6_reflect(struct mbuf *m, size_t off)
{
struct in6_addr src6, *srcp;
struct ip6_hdr *ip6;
struct icmp6_hdr *icmp6;
struct in6_ifaddr *ia = NULL;
struct ifnet *outif = NULL;
int plen;
int type, code, hlim;
if (off < sizeof(struct ip6_hdr)) {
nd6log((LOG_DEBUG,
"sanity fail: off=%lx, sizeof(ip6)=%lx in %s:%d\n",
(u_long)off, (u_long)sizeof(struct ip6_hdr),
__FILE__, __LINE__));
goto bad;
}
#ifdef DIAGNOSTIC
if (sizeof(struct ip6_hdr) + sizeof(struct icmp6_hdr) > MHLEN)
panic("assumption failed in icmp6_reflect");
#endif
if (off > sizeof(struct ip6_hdr)) {
size_t l;
struct ip6_hdr nip6;
l = off - sizeof(struct ip6_hdr);
m_copydata(m, 0, sizeof(nip6), (caddr_t)&nip6);
m_adj(m, l);
l = sizeof(struct ip6_hdr) + sizeof(struct icmp6_hdr);
if (m->m_len < l) {
if ((m = m_pullup(m, l)) == NULL)
return;
}
bcopy((caddr_t)&nip6, mtod(m, caddr_t), sizeof(nip6));
} else {
size_t l;
l = sizeof(struct ip6_hdr) + sizeof(struct icmp6_hdr);
if (m->m_len < l) {
if ((m = m_pullup(m, l)) == NULL)
return;
}
}
plen = m->m_pkthdr.len - sizeof(struct ip6_hdr);
ip6 = mtod(m, struct ip6_hdr *);
ip6->ip6_nxt = IPPROTO_ICMPV6;
icmp6 = (struct icmp6_hdr *)(ip6 + 1);
type = icmp6->icmp6_type;
code = icmp6->icmp6_code;
hlim = 0;
srcp = NULL;
if (__predict_false(IN6_IS_ADDR_UNSPECIFIED(&ip6->ip6_src))) {
nd6log((LOG_DEBUG,
"icmp6_reflect: source address is unspecified\n"));
goto bad;
}
if (!IN6_IS_ADDR_MULTICAST(&ip6->ip6_dst)) {
ia = in6ifa_ifwithaddr(&ip6->ip6_dst, 0 , false);
if (ia != NULL && !(ia->ia6_flags &
(IN6_IFF_ANYCAST|IN6_IFF_NOTREADY))) {
src6 = ia->ia_addr.sin6_addr;
srcp = &src6;
if (m->m_pkthdr.rcvif != NULL) {
hlim = ND_IFINFO(m->m_pkthdr.rcvif)->chlim;
} else
hlim = V_ip6_defhlim;
}
}
if (srcp == NULL) {
int error;
struct in6_addr dst6;
uint32_t scopeid;
in6_splitscope(&ip6->ip6_src, &dst6, &scopeid);
error = in6_selectsrc_addr(M_GETFIB(m), &dst6,
scopeid, NULL, &src6, &hlim);
if (error) {
char ip6buf[INET6_ADDRSTRLEN];
nd6log((LOG_DEBUG,
"icmp6_reflect: source can't be determined: "
"dst=%s, error=%d\n",
ip6_sprintf(ip6buf, &ip6->ip6_dst), error));
goto bad;
}
srcp = &src6;
}
ip6->ip6_dst = ip6->ip6_src;
ip6->ip6_src = *srcp;
ip6->ip6_flow = 0;
ip6->ip6_vfc &= ~IPV6_VERSION_MASK;
ip6->ip6_vfc |= IPV6_VERSION;
ip6->ip6_nxt = IPPROTO_ICMPV6;
ip6->ip6_hlim = hlim;
icmp6->icmp6_cksum = 0;
icmp6->icmp6_cksum = in6_cksum(m, IPPROTO_ICMPV6,
sizeof(struct ip6_hdr), plen);
m->m_flags &= ~(M_BCAST|M_MCAST);
m->m_pkthdr.rcvif = NULL;
ip6_output(m, NULL, NULL, 0, NULL, &outif, NULL);
if (outif)
icmp6_ifoutstat_inc(outif, type, code);
return;
bad:
m_freem(m);
return;
}
static const char *
icmp6_redirect_diag(struct in6_addr *src6, struct in6_addr *dst6,
struct in6_addr *tgt6)
{
static char buf[1024];
char ip6bufs[INET6_ADDRSTRLEN];
char ip6bufd[INET6_ADDRSTRLEN];
char ip6buft[INET6_ADDRSTRLEN];
snprintf(buf, sizeof(buf), "(src=%s dst=%s tgt=%s)",
ip6_sprintf(ip6bufs, src6), ip6_sprintf(ip6bufd, dst6),
ip6_sprintf(ip6buft, tgt6));
return buf;
}
void
icmp6_redirect_input(struct mbuf *m, int off)
{
struct ifnet *ifp;
struct ip6_hdr *ip6;
struct nd_redirect *nd_rd;
struct in6_addr src6, redtgt6, reddst6;
union nd_opts ndopts;
char ip6buf[INET6_ADDRSTRLEN];
char *lladdr;
int icmp6len, is_onlink, is_router, lladdrlen;
M_ASSERTPKTHDR(m);
KASSERT(m->m_pkthdr.rcvif != NULL, ("%s: no rcvif", __func__));
if (V_ip6_forwarding)
goto freeit;
if (!V_icmp6_rediraccept)
goto freeit;
if(m->m_flags & M_FRAGMENTED)
goto freeit;
ip6 = mtod(m, struct ip6_hdr *);
icmp6len = ntohs(ip6->ip6_plen);
if (m->m_len < off + icmp6len) {
m = m_pullup(m, off + icmp6len);
if (m == NULL) {
IP6STAT_INC(ip6s_exthdrtoolong);
return;
}
}
ip6 = mtod(m, struct ip6_hdr *);
nd_rd = (struct nd_redirect *)((caddr_t)ip6 + off);
ifp = m->m_pkthdr.rcvif;
redtgt6 = nd_rd->nd_rd_target;
reddst6 = nd_rd->nd_rd_dst;
if (in6_setscope(&redtgt6, ifp, NULL) ||
in6_setscope(&reddst6, ifp, NULL)) {
goto freeit;
}
src6 = ip6->ip6_src;
if (!IN6_IS_ADDR_LINKLOCAL(&src6)) {
nd6log((LOG_ERR,
"ICMP6 redirect sent from %s rejected; "
"must be from linklocal\n",
ip6_sprintf(ip6buf, &src6)));
goto bad;
}
if (__predict_false(ip6->ip6_hlim != 255)) {
ICMP6STAT_INC(icp6s_invlhlim);
nd6log((LOG_ERR,
"ICMP6 redirect sent from %s rejected; "
"hlim=%d (must be 255)\n",
ip6_sprintf(ip6buf, &src6), ip6->ip6_hlim));
goto bad;
}
{
struct nhop_object *nh;
struct in6_addr kdst;
uint32_t scopeid;
in6_splitscope(&reddst6, &kdst, &scopeid);
NET_EPOCH_ASSERT();
nh = fib6_lookup(ifp->if_fib, &kdst, scopeid, 0, 0);
if (nh != NULL) {
struct in6_addr nh_addr;
nh_addr = ifatoia6(nh->nh_ifa)->ia_addr.sin6_addr;
if ((nh->nh_flags & NHF_GATEWAY) == 0) {
nd6log((LOG_ERR,
"ICMP6 redirect rejected; no route "
"with inet6 gateway found for redirect dst: %s\n",
icmp6_redirect_diag(&src6, &reddst6, &redtgt6)));
goto bad;
}
nh_addr = nh->gw6_sa.sin6_addr;
if (IN6_ARE_ADDR_EQUAL(&src6, &nh_addr) == 0) {
nd6log((LOG_ERR,
"ICMP6 redirect rejected; "
"not equal to gw-for-src=%s (must be same): "
"%s\n",
ip6_sprintf(ip6buf, &nh_addr),
icmp6_redirect_diag(&src6, &reddst6, &redtgt6)));
goto bad;
}
} else {
nd6log((LOG_ERR,
"ICMP6 redirect rejected; "
"no route found for redirect dst: %s\n",
icmp6_redirect_diag(&src6, &reddst6, &redtgt6)));
goto bad;
}
}
if (IN6_IS_ADDR_MULTICAST(&reddst6)) {
nd6log((LOG_ERR,
"ICMP6 redirect rejected; "
"redirect dst must be unicast: %s\n",
icmp6_redirect_diag(&src6, &reddst6, &redtgt6)));
goto bad;
}
is_router = is_onlink = 0;
if (IN6_IS_ADDR_LINKLOCAL(&redtgt6))
is_router = 1;
if (bcmp(&redtgt6, &reddst6, sizeof(redtgt6)) == 0)
is_onlink = 1;
if (!is_router && !is_onlink) {
nd6log((LOG_ERR,
"ICMP6 redirect rejected; "
"neither router case nor onlink case: %s\n",
icmp6_redirect_diag(&src6, &reddst6, &redtgt6)));
goto bad;
}
icmp6len -= sizeof(*nd_rd);
nd6_option_init(nd_rd + 1, icmp6len, &ndopts);
if (nd6_options(&ndopts) < 0) {
nd6log((LOG_INFO, "%s: invalid ND option, rejected: %s\n",
__func__, icmp6_redirect_diag(&src6, &reddst6, &redtgt6)));
goto freeit;
}
lladdr = NULL;
lladdrlen = 0;
if (ndopts.nd_opts_tgt_lladdr) {
lladdr = (char *)(ndopts.nd_opts_tgt_lladdr + 1);
lladdrlen = ndopts.nd_opts_tgt_lladdr->nd_opt_len << 3;
}
if (lladdr && ((ifp->if_addrlen + 2 + 7) & ~7) != lladdrlen) {
nd6log((LOG_INFO, "%s: lladdrlen mismatch for %s "
"(if %d, icmp6 packet %d): %s\n",
__func__, ip6_sprintf(ip6buf, &redtgt6),
ifp->if_addrlen, lladdrlen - 2,
icmp6_redirect_diag(&src6, &reddst6, &redtgt6)));
goto bad;
}
nd6_cache_lladdr(ifp, &redtgt6, lladdr, lladdrlen, ND_REDIRECT,
is_onlink ? ND_REDIRECT_ONLINK : ND_REDIRECT_ROUTER);
{
struct sockaddr_in6 sdst;
struct sockaddr_in6 sgw;
struct sockaddr_in6 ssrc;
struct sockaddr *gw;
int rt_flags;
u_int fibnum;
bzero(&sdst, sizeof(sdst));
bzero(&ssrc, sizeof(ssrc));
sdst.sin6_family = ssrc.sin6_family = AF_INET6;
sdst.sin6_len = ssrc.sin6_len = sizeof(struct sockaddr_in6);
bcopy(&reddst6, &sdst.sin6_addr, sizeof(struct in6_addr));
bcopy(&src6, &ssrc.sin6_addr, sizeof(struct in6_addr));
rt_flags = 0;
if (is_router) {
bzero(&sgw, sizeof(sgw));
sgw.sin6_family = AF_INET6;
sgw.sin6_len = sizeof(struct sockaddr_in6);
bcopy(&redtgt6, &sgw.sin6_addr,
sizeof(struct in6_addr));
gw = (struct sockaddr *)&sgw;
rt_flags |= RTF_GATEWAY;
} else
gw = ifp->if_addr->ifa_addr;
for (fibnum = 0; fibnum < rt_numfibs; fibnum++)
rib_add_redirect(fibnum, (struct sockaddr *)&sdst, gw,
(struct sockaddr *)&ssrc, ifp, rt_flags,
V_icmp6_redirtimeout);
}
freeit:
m_freem(m);
return;
bad:
ICMP6STAT_INC(icp6s_badredirect);
m_freem(m);
}
void
icmp6_redirect_output(struct mbuf *m0, struct nhop_object *nh)
{
struct ifnet *ifp;
struct in6_addr ifp_ll6;
struct in6_addr *router_ll6;
struct ip6_hdr *sip6;
struct mbuf *m = NULL;
struct m_tag *mtag;
struct ip6_hdr *ip6;
struct nd_redirect *nd_rd;
struct llentry *ln = NULL;
size_t maxlen;
u_char *p;
struct ifnet *outif = NULL;
struct sockaddr_in6 src_sa;
icmp6_errcount(ND_REDIRECT, 0);
if (!V_ip6_forwarding)
goto fail;
if (!m0 || !nh || !(NH_IS_VALID(nh)) || !(ifp = nh->nh_ifp))
goto fail;
sip6 = mtod(m0, struct ip6_hdr *);
bzero(&src_sa, sizeof(src_sa));
src_sa.sin6_family = AF_INET6;
src_sa.sin6_len = sizeof(src_sa);
src_sa.sin6_addr = sip6->ip6_src;
if (nd6_is_addr_neighbor(&src_sa, ifp) == 0)
goto fail;
if (IN6_IS_ADDR_MULTICAST(&sip6->ip6_dst))
goto fail;
if (icmp6_ratelimit(&sip6->ip6_src, ND_REDIRECT, 0))
goto fail;
#if IPV6_MMTU >= MCLBYTES
# error assumption failed about IPV6_MMTU and MCLBYTES
#endif
m = m_getcl(M_NOWAIT, MT_DATA, M_PKTHDR);
if (m == NULL)
goto fail;
M_SETFIB(m, M_GETFIB(m0));
maxlen = M_TRAILINGSPACE(m);
maxlen = min(IPV6_MMTU, maxlen);
if (maxlen < sizeof(struct ip6_hdr) + sizeof(struct icmp6_hdr) +
((sizeof(struct nd_opt_hdr) + ifp->if_addrlen + 7) & ~7)) {
goto fail;
}
{
struct in6_ifaddr *ia;
if ((ia = in6ifa_ifpforlinklocal(ifp,
IN6_IFF_NOTREADY|
IN6_IFF_ANYCAST)) == NULL)
goto fail;
bcopy(&ia->ia_addr.sin6_addr, &ifp_ll6, sizeof(ifp_ll6));
ifa_free(&ia->ia_ifa);
}
if (nh->nh_flags & NHF_GATEWAY) {
struct sockaddr_in6 *sin6;
sin6 = &nh->gw6_sa;
router_ll6 = &sin6->sin6_addr;
if (!IN6_IS_ADDR_LINKLOCAL(router_ll6))
router_ll6 = (struct in6_addr *)NULL;
} else
router_ll6 = (struct in6_addr *)NULL;
ip6 = mtod(m, struct ip6_hdr *);
ip6->ip6_flow = 0;
ip6->ip6_vfc &= ~IPV6_VERSION_MASK;
ip6->ip6_vfc |= IPV6_VERSION;
ip6->ip6_nxt = IPPROTO_ICMPV6;
ip6->ip6_hlim = 255;
bcopy(&ifp_ll6, &ip6->ip6_src, sizeof(struct in6_addr));
bcopy(&sip6->ip6_src, &ip6->ip6_dst, sizeof(struct in6_addr));
nd_rd = (struct nd_redirect *)(ip6 + 1);
nd_rd->nd_rd_type = ND_REDIRECT;
nd_rd->nd_rd_code = 0;
nd_rd->nd_rd_reserved = 0;
if (nh->nh_flags & NHF_GATEWAY) {
if (!router_ll6)
goto fail;
bcopy(router_ll6, &nd_rd->nd_rd_target,
sizeof(nd_rd->nd_rd_target));
bcopy(&sip6->ip6_dst, &nd_rd->nd_rd_dst,
sizeof(nd_rd->nd_rd_dst));
} else {
bcopy(&sip6->ip6_dst, &nd_rd->nd_rd_target,
sizeof(nd_rd->nd_rd_target));
bcopy(&sip6->ip6_dst, &nd_rd->nd_rd_dst,
sizeof(nd_rd->nd_rd_dst));
}
p = (u_char *)(nd_rd + 1);
if (!router_ll6)
goto nolladdropt;
{
int len;
struct nd_opt_hdr *nd_opt;
char *lladdr;
ln = nd6_lookup(router_ll6, LLE_SF(AF_INET6, 0), ifp);
if (ln == NULL)
goto nolladdropt;
len = sizeof(*nd_opt) + ifp->if_addrlen;
len = (len + 7) & ~7;
if (len + (p - (u_char *)ip6) > maxlen)
goto nolladdropt;
if (ln->la_flags & LLE_VALID) {
nd_opt = (struct nd_opt_hdr *)p;
nd_opt->nd_opt_type = ND_OPT_TARGET_LINKADDR;
nd_opt->nd_opt_len = len >> 3;
lladdr = (char *)(nd_opt + 1);
bcopy(ln->ll_addr, lladdr, ifp->if_addrlen);
p += len;
}
}
nolladdropt:
if (ln != NULL)
LLE_RUNLOCK(ln);
m->m_pkthdr.len = m->m_len = p - (u_char *)ip6;
#ifdef M_DECRYPTED
if (m0->m_flags & M_DECRYPTED)
goto noredhdropt;
#endif
if (p - (u_char *)ip6 > maxlen)
goto noredhdropt;
{
int len;
struct nd_opt_rd_hdr *nd_opt_rh;
len = maxlen - (p - (u_char *)ip6);
len &= ~7;
if (m0->m_pkthdr.len != m0->m_len) {
if (m0->m_next) {
m_freem(m0->m_next);
m0->m_next = NULL;
}
m0->m_pkthdr.len = m0->m_len;
}
if (m0->m_next || m0->m_pkthdr.len != m0->m_len)
panic("assumption failed in %s:%d", __FILE__,
__LINE__);
if (len - sizeof(*nd_opt_rh) < m0->m_pkthdr.len) {
m0->m_pkthdr.len = m0->m_len = len -
sizeof(*nd_opt_rh);
} else {
size_t extra;
extra = m0->m_pkthdr.len % 8;
if (extra) {
if (8 - extra <= M_TRAILINGSPACE(m0)) {
bzero(m0->m_data + m0->m_len, 8 - extra);
m0->m_len += (8 - extra);
m0->m_pkthdr.len += (8 - extra);
} else {
m0->m_pkthdr.len -= extra;
m0->m_len -= extra;
}
}
len = m0->m_pkthdr.len + sizeof(*nd_opt_rh);
m0->m_pkthdr.len = m0->m_len = len -
sizeof(*nd_opt_rh);
}
nd_opt_rh = (struct nd_opt_rd_hdr *)p;
bzero(nd_opt_rh, sizeof(*nd_opt_rh));
nd_opt_rh->nd_opt_rh_type = ND_OPT_REDIRECTED_HEADER;
nd_opt_rh->nd_opt_rh_len = len >> 3;
p += sizeof(*nd_opt_rh);
m->m_pkthdr.len = m->m_len = p - (u_char *)ip6;
m_tag_delete_chain(m0, NULL);
m0->m_flags &= ~M_PKTHDR;
m->m_next = m0;
m->m_pkthdr.len = m->m_len + m0->m_len;
m0 = NULL;
}
noredhdropt:;
if (m0) {
m_freem(m0);
m0 = NULL;
}
in6_clearscope(&sip6->ip6_src);
in6_clearscope(&sip6->ip6_dst);
in6_clearscope(&nd_rd->nd_rd_target);
in6_clearscope(&nd_rd->nd_rd_dst);
ip6->ip6_plen = htons(m->m_pkthdr.len - sizeof(struct ip6_hdr));
nd_rd->nd_rd_cksum = 0;
nd_rd->nd_rd_cksum = in6_cksum(m, IPPROTO_ICMPV6,
sizeof(*ip6), ntohs(ip6->ip6_plen));
if (send_sendso_input_hook != NULL) {
mtag = m_tag_get(PACKET_TAG_ND_OUTGOING, sizeof(unsigned short),
M_NOWAIT);
if (mtag == NULL)
goto fail;
*(unsigned short *)(mtag + 1) = nd_rd->nd_rd_type;
m_tag_prepend(m, mtag);
}
ip6_output(m, NULL, NULL, 0, NULL, &outif, NULL);
if (outif) {
icmp6_ifstat_inc(outif, ifs6_out_msg);
icmp6_ifstat_inc(outif, ifs6_out_redirect);
}
ICMP6STAT_INC2(icp6s_outhist, ND_REDIRECT);
return;
fail:
if (m)
m_freem(m);
if (m0)
m_freem(m0);
}
int
icmp6_ctloutput(struct socket *so, struct sockopt *sopt)
{
int error = 0;
int optlen;
struct inpcb *inp = sotoinpcb(so);
int level, op, optname;
if (sopt) {
level = sopt->sopt_level;
op = sopt->sopt_dir;
optname = sopt->sopt_name;
optlen = sopt->sopt_valsize;
} else
level = op = optname = optlen = 0;
if (level != IPPROTO_ICMPV6) {
return EINVAL;
}
switch (op) {
case SOPT_SET:
switch (optname) {
case ICMP6_FILTER:
{
struct icmp6_filter ic6f;
if (optlen != sizeof(ic6f)) {
error = EMSGSIZE;
break;
}
error = sooptcopyin(sopt, &ic6f, optlen, optlen);
if (error == 0) {
INP_WLOCK(inp);
*inp->in6p_icmp6filt = ic6f;
INP_WUNLOCK(inp);
}
break;
}
default:
error = ENOPROTOOPT;
break;
}
break;
case SOPT_GET:
switch (optname) {
case ICMP6_FILTER:
{
struct icmp6_filter ic6f;
INP_RLOCK(inp);
ic6f = *inp->in6p_icmp6filt;
INP_RUNLOCK(inp);
error = sooptcopyout(sopt, &ic6f, sizeof(ic6f));
break;
}
default:
error = ENOPROTOOPT;
break;
}
break;
}
return (error);
}
static int sysctl_icmp6lim_and_jitter(SYSCTL_HANDLER_ARGS);
VNET_DEFINE_STATIC(u_int, icmp6errppslim) = 100;
#define V_icmp6errppslim VNET(icmp6errppslim)
SYSCTL_PROC(_net_inet6_icmp6, ICMPV6CTL_ERRPPSLIMIT, errppslimit,
CTLTYPE_UINT | CTLFLAG_VNET | CTLFLAG_RW, &VNET_NAME(icmp6errppslim), 0,
&sysctl_icmp6lim_and_jitter, "IU",
"Maximum number of ICMPv6 error/reply messages per second");
typedef enum {
RATELIM_PARAM_PROB = 0,
RATELIM_TOO_BIG,
RATELIM_UNREACH,
RATELIM_TEXCEED,
RATELIM_REDIR,
RATELIM_REPLY,
RATELIM_OTHER,
RATELIM_MAX
} ratelim_which;
static const char *icmp6_rate_descrs[RATELIM_MAX] = {
[RATELIM_PARAM_PROB] = "bad IPv6 header",
[RATELIM_TOO_BIG] = "packet too big",
[RATELIM_UNREACH] = "destination unreachable",
[RATELIM_TEXCEED] = "time exceeded",
[RATELIM_REPLY] = "echo reply",
[RATELIM_REDIR] = "neighbor discovery redirect",
[RATELIM_OTHER] = "(other)",
};
VNET_DEFINE_STATIC(int, icmp6lim_curr_jitter[RATELIM_MAX]) = {0};
#define V_icmp6lim_curr_jitter VNET(icmp6lim_curr_jitter)
VNET_DEFINE_STATIC(u_int, icmp6lim_jitter) = 8;
#define V_icmp6lim_jitter VNET(icmp6lim_jitter)
SYSCTL_PROC(_net_inet6_icmp6, OID_AUTO, icmp6lim_jitter, CTLTYPE_UINT |
CTLFLAG_VNET | CTLFLAG_RW, &VNET_NAME(icmp6lim_jitter), 0,
&sysctl_icmp6lim_and_jitter, "IU",
"Random errppslimit jitter adjustment limit");
VNET_DEFINE_STATIC(int, icmp6lim_output) = 1;
#define V_icmp6lim_output VNET(icmp6lim_output)
SYSCTL_INT(_net_inet6_icmp6, OID_AUTO, icmp6lim_output,
CTLFLAG_VNET | CTLFLAG_RW, &VNET_NAME(icmp6lim_output), 0,
"Enable logging of ICMPv6 response rate limiting");
static void
icmp6lim_new_jitter(int which)
{
KASSERT(which >= 0 && which < RATELIM_MAX,
("%s: which %d", __func__, which));
if (V_icmp6lim_jitter > 0)
V_icmp6lim_curr_jitter[which] =
arc4random_uniform(V_icmp6lim_jitter * 2 + 1) -
V_icmp6lim_jitter;
}
static int
sysctl_icmp6lim_and_jitter(SYSCTL_HANDLER_ARGS)
{
uint32_t new;
int error;
bool lim;
MPASS(oidp->oid_arg1 == &VNET_NAME(icmp6errppslim) ||
oidp->oid_arg1 == &VNET_NAME(icmp6lim_jitter));
lim = (oidp->oid_arg1 == &VNET_NAME(icmp6errppslim));
new = lim ? V_icmp6errppslim : V_icmp6lim_jitter;
error = sysctl_handle_int(oidp, &new, 0, req);
if (error == 0 && req->newptr) {
if (lim) {
if (new != 0 && new <= V_icmp6lim_jitter)
error = EINVAL;
else
V_icmp6errppslim = new;
} else {
if (new >= V_icmp6errppslim)
error = EINVAL;
else {
V_icmp6lim_jitter = new;
for (int i = 0; i < RATELIM_MAX; i++) {
icmp6lim_new_jitter(i);
}
}
}
}
MPASS(V_icmp6errppslim == 0 || V_icmp6errppslim > V_icmp6lim_jitter);
return (error);
}
VNET_DEFINE_STATIC(struct counter_rate *, icmp6_rates[RATELIM_MAX]);
#define V_icmp6_rates VNET(icmp6_rates)
static void
icmp6_ratelimit_init(void)
{
for (int i = 0; i < RATELIM_MAX; i++) {
V_icmp6_rates[i] = counter_rate_alloc(M_WAITOK, 1);
icmp6lim_new_jitter(i);
}
}
VNET_SYSINIT(icmp6_ratelimit, SI_SUB_PROTO_DOMAIN, SI_ORDER_ANY,
icmp6_ratelimit_init, NULL);
#ifdef VIMAGE
static void
icmp6_ratelimit_uninit(void)
{
for (int i = 0; i < RATELIM_MAX; i++)
counter_rate_free(V_icmp6_rates[i]);
}
VNET_SYSUNINIT(icmp6_ratelimit, SI_SUB_PROTO_DOMAIN, SI_ORDER_THIRD,
icmp6_ratelimit_uninit, NULL);
#endif
int
icmp6_ratelimit(const struct in6_addr *dst, const int type, const int code)
{
ratelim_which which;
int64_t pps;
if (V_icmp6errppslim == 0)
return (0);
switch (type) {
case ICMP6_PARAM_PROB:
which = RATELIM_PARAM_PROB;
break;
case ICMP6_PACKET_TOO_BIG:
which = RATELIM_TOO_BIG;
break;
case ICMP6_DST_UNREACH:
which = RATELIM_UNREACH;
break;
case ICMP6_TIME_EXCEEDED:
which = RATELIM_TEXCEED;
break;
case ND_REDIRECT:
which = RATELIM_REDIR;
break;
case ICMP6_ECHO_REPLY:
which = RATELIM_REPLY;
break;
default:
which = RATELIM_OTHER;
break;
};
pps = counter_ratecheck(V_icmp6_rates[which], V_icmp6errppslim +
V_icmp6lim_curr_jitter[which]);
if (pps > 0) {
if (V_icmp6lim_output)
log(LOG_NOTICE, "Limiting ICMPv6 %s output from %jd "
"to %d packets/sec\n", icmp6_rate_descrs[which],
(intmax_t )pps, V_icmp6errppslim +
V_icmp6lim_curr_jitter[which]);
icmp6lim_new_jitter(which);
}
if (pps == -1) {
ICMP6STAT_INC(icp6s_toofreq);
return (-1);
}
return (0);
}