#include "opt_inet.h"
#include "opt_inet6.h"
#include <sys/param.h>
#include <sys/systm.h>
#include <sys/callout.h>
#include <sys/malloc.h>
#include <sys/mbuf.h>
#include <sys/socket.h>
#include <sys/sockio.h>
#include <sys/time.h>
#include <sys/kernel.h>
#include <sys/protosw.h>
#include <sys/errno.h>
#include <sys/syslog.h>
#include <sys/queue.h>
#include <sys/sysctl.h>
#include <sys/mutex.h>
#include <sys/resourcevar.h>
#include <sys/thread2.h>
#include <sys/mutex2.h>
#include <net/if.h>
#include <net/if_dl.h>
#include <net/if_types.h>
#include <net/route.h>
#include <net/netisr2.h>
#include <net/netmsg2.h>
#include <netinet/in.h>
#include <netinet/if_ether.h>
#include <netinet6/in6_var.h>
#include <netinet6/in6_ifattach.h>
#include <netinet/ip6.h>
#include <netinet6/ip6_var.h>
#include <netinet6/nd6.h>
#include <netinet/icmp6.h>
#include <net/net_osdep.h>
#define ND6_SLOWTIMER_INTERVAL (60 * 60)
#define ND6_RECALC_REACHTM_INTERVAL (60 * 120)
#define SIN6(s) ((struct sockaddr_in6 *)s)
#define SDL(s) ((struct sockaddr_dl *)s)
#define ND6_IFP_MATCHES(ifp, ifa_ifp) \
((ifp) == NULL || \
(ifa_ifp) == (ifp) || \
(((ifp)->if_flags & IFF_ISBRIDGE) && \
(ifa_ifp)->if_bridge == (ifp)->if_softc) \
)
#define ND6_RTENTRY_IS_NEIGHBOR(rt, ifp) \
(((rt)->rt_flags & RTF_GATEWAY) == 0 && \
((rt)->rt_flags & RTF_LLINFO) != 0 && \
(rt)->rt_gateway->sa_family == AF_LINK && \
(rt)->rt_llinfo && \
ND6_IFP_MATCHES((ifp), (rt)->rt_ifa->ifa_ifp) \
)
#define ND6_RTENTRY_IS_LLCLONING(rt) \
(((rt)->rt_flags & (RTF_PRCLONING | RTF_LLINFO)) == \
(RTF_PRCLONING | RTF_LLINFO) || \
((rt)->rt_flags & RTF_CLONING))
int nd6_prune = 1;
int nd6_delay = 5;
int nd6_umaxtries = 3;
int nd6_mmaxtries = 3;
int nd6_useloopback = 1;
int nd6_gctimer = (60 * 60 * 24);
int nd6_maxndopt = 10;
int nd6_maxnudhint = 0;
#ifdef ND6_DEBUG
int nd6_debug = 1;
#else
int nd6_debug = 0;
#endif
static int nd6_inuse, nd6_allocated;
struct llinfo_nd6 llinfo_nd6 = {&llinfo_nd6, &llinfo_nd6};
struct nd_drhead nd_defrouter;
struct nd_prhead nd_prefix = { 0 };
struct mtx nd6_mtx = MTX_INITIALIZER("nd6");
int nd6_recalc_reachtm_interval = ND6_RECALC_REACHTM_INTERVAL;
static struct sockaddr_in6 all1_sa;
static void nd6_setmtu0 (struct ifnet *, struct nd_ifinfo *);
static int regen_tmpaddr (struct in6_ifaddr *);
static void nd6_slowtimo(void *);
static void nd6_slowtimo_dispatch(netmsg_t);
static void nd6_timer(void *);
static void nd6_timer_dispatch(netmsg_t);
static struct callout nd6_slowtimo_ch;
static struct netmsg_base nd6_slowtimo_netmsg;
static struct callout nd6_timer_ch;
static struct netmsg_base nd6_timer_netmsg;
void
nd6_init(void)
{
static int nd6_init_done = 0;
int i;
if (nd6_init_done) {
log(LOG_NOTICE, "nd6_init called more than once(ignored)\n");
return;
}
all1_sa.sin6_family = AF_INET6;
all1_sa.sin6_len = sizeof(struct sockaddr_in6);
for (i = 0; i < sizeof(all1_sa.sin6_addr); i++)
all1_sa.sin6_addr.s6_addr[i] = 0xff;
TAILQ_INIT(&nd_defrouter);
nd6_init_done = 1;
callout_init_mp(&nd6_slowtimo_ch);
netmsg_init(&nd6_slowtimo_netmsg, NULL, &netisr_adone_rport,
MSGF_PRIORITY, nd6_slowtimo_dispatch);
callout_reset_bycpu(&nd6_slowtimo_ch, ND6_SLOWTIMER_INTERVAL * hz,
nd6_slowtimo, NULL, 0);
}
struct nd_ifinfo *
nd6_ifattach(struct ifnet *ifp)
{
struct nd_ifinfo *nd;
nd = kmalloc(sizeof(*nd), M_IP6NDP, M_WAITOK | M_ZERO);
nd->initialized = 1;
nd->chlim = IPV6_DEFHLIM;
nd->basereachable = REACHABLE_TIME;
nd->reachable = ND_COMPUTE_RTIME(nd->basereachable);
nd->retrans = RETRANS_TIMER;
nd->flags = ND6_IFF_PERFORMNUD;
if (ip6_auto_linklocal || (ifp->if_flags & IFF_LOOPBACK))
nd->flags |= ND6_IFF_AUTO_LINKLOCAL;
if (ip6_accept_rtadv && !(ifp->if_flags & IFF_LOOPBACK))
nd->flags |= ND6_IFF_ACCEPT_RTADV;
nd6_setmtu0(ifp, nd);
return nd;
}
void
nd6_ifdetach(struct nd_ifinfo *nd)
{
kfree(nd, M_IP6NDP);
}
void
nd6_setmtu(struct ifnet *ifp)
{
nd6_setmtu0(ifp, ND_IFINFO(ifp));
}
struct netmsg_nd6setmtu {
struct netmsg_base nmsg;
struct ifnet *ifp;
struct nd_ifinfo *ndi;
};
static void
nd6_setmtu0_dispatch(netmsg_t msg)
{
struct netmsg_nd6setmtu *nmsg = (struct netmsg_nd6setmtu *)msg;
struct ifnet *ifp = nmsg->ifp;
struct nd_ifinfo *ndi = nmsg->ndi;
uint32_t omaxmtu;
omaxmtu = ndi->maxmtu;
switch (ifp->if_type) {
case IFT_ETHER:
ndi->maxmtu = MIN(ETHERMTU, ifp->if_mtu);
break;
case IFT_IEEE1394:
ndi->maxmtu = MIN(ETHERMTU, ifp->if_mtu);
break;
#ifdef IFT_IEEE80211
case IFT_IEEE80211:
ndi->maxmtu = MIN(ETHERMTU, ifp->if_mtu);
break;
#endif
default:
ndi->maxmtu = ifp->if_mtu;
break;
}
if (omaxmtu >= IPV6_MMTU && ndi->maxmtu < IPV6_MMTU) {
log(LOG_NOTICE, "nd6_setmtu0: "
"new link MTU on %s (%lu) is too small for IPv6\n",
if_name(ifp), (unsigned long)ndi->maxmtu);
}
if (ndi->maxmtu > in6_maxmtu)
in6_setmaxmtu();
lwkt_replymsg(&nmsg->nmsg.lmsg, 0);
}
void
nd6_setmtu0(struct ifnet *ifp, struct nd_ifinfo *ndi)
{
struct netmsg_nd6setmtu nmsg;
netmsg_init(&nmsg.nmsg, NULL, &curthread->td_msgport, 0,
nd6_setmtu0_dispatch);
nmsg.ifp = ifp;
nmsg.ndi = ndi;
lwkt_domsg(netisr_cpuport(0), &nmsg.nmsg.lmsg, 0);
}
void
nd6_option_init(void *opt, int icmp6len, union nd_opts *ndopts)
{
bzero(ndopts, sizeof(*ndopts));
ndopts->nd_opts_search = (struct nd_opt_hdr *)opt;
ndopts->nd_opts_last
= (struct nd_opt_hdr *)(((u_char *)opt) + icmp6len);
if (icmp6len == 0) {
ndopts->nd_opts_done = 1;
ndopts->nd_opts_search = NULL;
}
}
struct nd_opt_hdr *
nd6_option(union nd_opts *ndopts)
{
struct nd_opt_hdr *nd_opt;
int olen;
if (!ndopts)
panic("ndopts == NULL in nd6_option");
if (!ndopts->nd_opts_last)
panic("uninitialized ndopts in nd6_option");
if (!ndopts->nd_opts_search)
return NULL;
if (ndopts->nd_opts_done)
return NULL;
nd_opt = ndopts->nd_opts_search;
if ((caddr_t)&nd_opt->nd_opt_len >= (caddr_t)ndopts->nd_opts_last) {
bzero(ndopts, sizeof(*ndopts));
return NULL;
}
olen = nd_opt->nd_opt_len << 3;
if (olen == 0) {
bzero(ndopts, sizeof(*ndopts));
return NULL;
}
ndopts->nd_opts_search = (struct nd_opt_hdr *)((caddr_t)nd_opt + olen);
if (ndopts->nd_opts_search > ndopts->nd_opts_last) {
bzero(ndopts, sizeof(*ndopts));
return NULL;
} else if (ndopts->nd_opts_search == ndopts->nd_opts_last) {
ndopts->nd_opts_done = 1;
ndopts->nd_opts_search = NULL;
}
return nd_opt;
}
int
nd6_options(union nd_opts *ndopts)
{
struct nd_opt_hdr *nd_opt;
int i = 0;
if (!ndopts)
panic("ndopts == NULL in nd6_options");
if (!ndopts->nd_opts_last)
panic("uninitialized ndopts in nd6_options");
if (!ndopts->nd_opts_search)
return 0;
while (1) {
nd_opt = nd6_option(ndopts);
if (!nd_opt && !ndopts->nd_opts_last) {
icmp6stat.icp6s_nd_badopt++;
bzero(ndopts, sizeof(*ndopts));
return -1;
}
if (!nd_opt)
goto skip1;
switch (nd_opt->nd_opt_type) {
case ND_OPT_SOURCE_LINKADDR:
case ND_OPT_TARGET_LINKADDR:
case ND_OPT_MTU:
case ND_OPT_REDIRECTED_HEADER:
if (ndopts->nd_opt_array[nd_opt->nd_opt_type]) {
nd6log((LOG_INFO,
"duplicated ND6 option found (type=%d)\n",
nd_opt->nd_opt_type));
} else {
ndopts->nd_opt_array[nd_opt->nd_opt_type]
= nd_opt;
}
break;
case ND_OPT_PREFIX_INFORMATION:
if (ndopts->nd_opt_array[nd_opt->nd_opt_type] == 0) {
ndopts->nd_opt_array[nd_opt->nd_opt_type]
= nd_opt;
}
ndopts->nd_opts_pi_end =
(struct nd_opt_prefix_info *)nd_opt;
break;
default:
nd6log((LOG_DEBUG,
"nd6_options: unsupported option %d - "
"option ignored\n", nd_opt->nd_opt_type));
}
skip1:
i++;
if (i > nd6_maxndopt) {
icmp6stat.icp6s_nd_toomanyopt++;
nd6log((LOG_INFO, "too many loop in nd opt\n"));
break;
}
if (ndopts->nd_opts_done)
break;
}
return 0;
}
static void
nd6_timer_dispatch(netmsg_t nmsg)
{
struct llinfo_nd6 *ln;
struct nd_defrouter *dr;
struct nd_prefix *pr;
struct ifnet *ifp;
struct in6_ifaddr *ia6, *nia6;
ASSERT_NETISR0;
crit_enter();
lwkt_replymsg(&nmsg->lmsg, 0);
crit_exit();
mtx_lock(&nd6_mtx);
ln = llinfo_nd6.ln_next;
while (ln && ln != &llinfo_nd6) {
struct rtentry *rt;
struct sockaddr_in6 *dst;
struct llinfo_nd6 *next = ln->ln_next;
struct nd_ifinfo *ndi = NULL;
if ((rt = ln->ln_rt) == NULL) {
ln = next;
continue;
}
if ((ifp = rt->rt_ifp) == NULL) {
ln = next;
continue;
}
ndi = ND_IFINFO(ifp);
dst = (struct sockaddr_in6 *)rt_key(rt);
if (ln->ln_expire > time_uptime) {
ln = next;
continue;
}
if (!rt)
panic("rt=0 in nd6_timer(ln=%p)", ln);
if (rt->rt_llinfo && (struct llinfo_nd6 *)rt->rt_llinfo != ln)
panic("rt_llinfo(%p) is not equal to ln(%p)",
rt->rt_llinfo, ln);
if (!dst)
panic("dst=0 in nd6_timer(ln=%p)", ln);
switch (ln->ln_state) {
case ND6_LLINFO_WAITDELETE:
next = nd6_free(rt);
break;
case ND6_LLINFO_INCOMPLETE:
if (ln->ln_asked++ >= nd6_mmaxtries) {
struct mbuf *m = ln->ln_hold;
if (m) {
if (rt->rt_ifp) {
m->m_pkthdr.rcvif = rt->rt_ifp;
}
m_adj(m, ETHER_HDR_LEN);
icmp6_error(m, ICMP6_DST_UNREACH,
ICMP6_DST_UNREACH_ADDR, 0);
ln->ln_hold = NULL;
}
ln->ln_state = ND6_LLINFO_WAITDELETE;
rt_rtmsg(RTM_MISS, rt, rt->rt_ifp, 0);
}
ln->ln_expire = time_uptime +
ND_IFINFO(ifp)->retrans / 1000;
nd6_ns_output(ifp, NULL, &dst->sin6_addr,
ln, 0);
break;
case ND6_LLINFO_REACHABLE:
if (ln->ln_expire) {
ln->ln_state = ND6_LLINFO_STALE;
ln->ln_expire = time_uptime + nd6_gctimer;
}
break;
case ND6_LLINFO_STALE:
if (ln->ln_expire)
next = nd6_free(rt);
break;
case ND6_LLINFO_DELAY:
if (ndi && (ndi->flags & ND6_IFF_PERFORMNUD)) {
ln->ln_asked = 1;
ln->ln_state = ND6_LLINFO_PROBE;
ln->ln_expire = time_uptime +
ndi->retrans / 1000;
nd6_ns_output(ifp, &dst->sin6_addr,
&dst->sin6_addr,
ln, 0);
} else {
ln->ln_state = ND6_LLINFO_STALE;
ln->ln_expire = time_uptime + nd6_gctimer;
}
break;
case ND6_LLINFO_PROBE:
if (ln->ln_asked < nd6_umaxtries) {
ln->ln_asked++;
ln->ln_expire = time_uptime +
ND_IFINFO(ifp)->retrans / 1000;
nd6_ns_output(ifp, &dst->sin6_addr,
&dst->sin6_addr, ln, 0);
} else {
rt_rtmsg(RTM_MISS, rt, rt->rt_ifp, 0);
next = nd6_free(rt);
}
break;
}
ln = next;
}
dr = TAILQ_FIRST(&nd_defrouter);
while (dr) {
if (dr->expire && dr->expire < time_uptime) {
struct nd_defrouter *t;
t = TAILQ_NEXT(dr, dr_entry);
defrtrlist_del(dr);
dr = t;
} else {
dr = TAILQ_NEXT(dr, dr_entry);
}
}
addrloop:
for (ia6 = in6_ifaddr; ia6; ia6 = nia6) {
nia6 = ia6->ia_next;
if (IFA6_IS_INVALID(ia6)) {
int regen = 0;
if (ip6_use_tempaddr &&
(ia6->ia6_flags & IN6_IFF_TEMPORARY)) {
if (regen_tmpaddr(ia6) == 0)
regen = 1;
}
in6_purgeaddr(&ia6->ia_ifa);
if (regen)
goto addrloop;
continue;
}
if (IFA6_IS_DEPRECATED(ia6)) {
int oldflags = ia6->ia6_flags;
if ((oldflags & IN6_IFF_DEPRECATED) == 0) {
ia6->ia6_flags |= IN6_IFF_DEPRECATED;
in6_newaddrmsg((struct ifaddr *)ia6);
}
if (ip6_use_tempaddr &&
(ia6->ia6_flags & IN6_IFF_TEMPORARY) &&
!(oldflags & IN6_IFF_DEPRECATED)) {
if (regen_tmpaddr(ia6) == 0) {
goto addrloop;
}
}
} else {
if (ia6->ia6_flags & IN6_IFF_DEPRECATED) {
ia6->ia6_flags &= ~IN6_IFF_DEPRECATED;
in6_newaddrmsg((struct ifaddr *)ia6);
}
}
}
pr = nd_prefix.lh_first;
while (pr) {
if (pr->ndpr_expire && pr->ndpr_expire < time_uptime) {
struct nd_prefix *t;
t = pr->ndpr_next;
prelist_remove(pr);
pr = t;
} else
pr = pr->ndpr_next;
}
mtx_unlock(&nd6_mtx);
callout_reset(&nd6_timer_ch, nd6_prune * hz, nd6_timer, NULL);
}
static void
nd6_timer(void *arg __unused)
{
struct lwkt_msg *lmsg = &nd6_timer_netmsg.lmsg;
KASSERT(mycpuid == 0, ("not on cpu0"));
crit_enter();
if (lmsg->ms_flags & MSGF_DONE)
lwkt_sendmsg_oncpu(netisr_cpuport(0), lmsg);
crit_exit();
}
void
nd6_timer_init(void)
{
callout_init_mp(&nd6_timer_ch);
netmsg_init(&nd6_timer_netmsg, NULL, &netisr_adone_rport,
MSGF_PRIORITY, nd6_timer_dispatch);
callout_reset_bycpu(&nd6_timer_ch, hz, nd6_timer, NULL, 0);
}
static int
regen_tmpaddr(struct in6_ifaddr *ia6)
{
struct ifaddr_container *ifac;
struct ifnet *ifp;
struct in6_ifaddr *public_ifa6 = NULL;
ifp = ia6->ia_ifa.ifa_ifp;
TAILQ_FOREACH(ifac, &ifp->if_addrheads[mycpuid], ifa_link) {
struct ifaddr *ifa = ifac->ifa;
struct in6_ifaddr *it6;
if (ifa->ifa_addr->sa_family != AF_INET6)
continue;
it6 = (struct in6_ifaddr *)ifa;
if (!(it6->ia6_flags & IN6_IFF_AUTOCONF))
continue;
if (it6->ia6_ndpr == NULL || it6->ia6_ndpr != ia6->ia6_ndpr)
continue;
if ((it6->ia6_flags & IN6_IFF_TEMPORARY) &&
!IFA6_IS_DEPRECATED(it6)) {
public_ifa6 = NULL;
break;
}
if (!IFA6_IS_DEPRECATED(it6))
public_ifa6 = it6;
}
if (public_ifa6 != NULL) {
int e;
if ((e = in6_tmpifadd(public_ifa6, 0)) != 0) {
log(LOG_NOTICE, "regen_tmpaddr: failed to create a new"
" tmp addr,errno=%d\n", e);
return (-1);
}
return (0);
}
return (-1);
}
void
nd6_purge(struct ifnet *ifp)
{
struct llinfo_nd6 *ln, *nln;
struct nd_defrouter *dr, *ndr, drany;
struct nd_prefix *pr, *npr;
if ((dr = TAILQ_FIRST(&nd_defrouter)) != NULL) {
for (dr = TAILQ_NEXT(dr, dr_entry); dr; dr = ndr) {
ndr = TAILQ_NEXT(dr, dr_entry);
if (dr->ifp == ifp)
defrtrlist_del(dr);
}
dr = TAILQ_FIRST(&nd_defrouter);
if (dr->ifp == ifp)
defrtrlist_del(dr);
}
for (pr = nd_prefix.lh_first; pr; pr = npr) {
npr = pr->ndpr_next;
if (pr->ndpr_ifp == ifp) {
prelist_remove(pr);
}
}
if (nd6_defifindex == ifp->if_index)
nd6_setdefaultiface(0);
if (!ip6_forwarding &&
(ND_IFINFO(ifp)->flags & ND6_IFF_ACCEPT_RTADV)) {
bzero(&drany, sizeof(drany));
defrouter_delreq(&drany, 0);
defrouter_select();
}
ln = llinfo_nd6.ln_next;
while (ln && ln != &llinfo_nd6) {
struct rtentry *rt;
struct sockaddr_dl *sdl;
nln = ln->ln_next;
rt = ln->ln_rt;
if (rt && rt->rt_gateway &&
rt->rt_gateway->sa_family == AF_LINK) {
sdl = (struct sockaddr_dl *)rt->rt_gateway;
if (sdl->sdl_index == ifp->if_index)
nln = nd6_free(rt);
}
ln = nln;
}
}
struct rtentry *
nd6_lookup(struct in6_addr *addr6, int create, struct ifnet *ifp)
{
struct rtentry *rt;
struct sockaddr_in6 sin6;
bzero(&sin6, sizeof(sin6));
sin6.sin6_len = sizeof(struct sockaddr_in6);
sin6.sin6_family = AF_INET6;
sin6.sin6_addr = *addr6;
if (create)
rt = rtlookup((struct sockaddr *)&sin6);
else
rt = rtpurelookup((struct sockaddr *)&sin6);
if (rt && !(rt->rt_flags & RTF_LLINFO)) {
if (create) {
--rt->rt_refcnt;
rt = NULL;
}
}
if (!rt) {
if (create && ifp) {
int e;
struct ifaddr *ifa;
ifa = ifaof_ifpforaddr((struct sockaddr *)&sin6, ifp);
if (ifa == NULL)
return (NULL);
if ((e = rtrequest(RTM_ADD, (struct sockaddr *)&sin6,
ifa->ifa_addr, (struct sockaddr *)&all1_sa,
(ifa->ifa_flags | RTF_HOST | RTF_LLINFO) &
~RTF_CLONING, &rt)) != 0) {
log(LOG_ERR,
"nd6_lookup: failed to add route for a "
"neighbor(%s), errno=%d\n",
ip6_sprintf(addr6), e);
}
if (rt == NULL)
return (NULL);
if (rt->rt_llinfo) {
struct llinfo_nd6 *ln =
(struct llinfo_nd6 *)rt->rt_llinfo;
ln->ln_state = ND6_LLINFO_NOSTATE;
}
} else
return (NULL);
}
rt->rt_refcnt--;
if (!ND6_RTENTRY_IS_NEIGHBOR(rt, ifp)) {
if (create) {
log(LOG_DEBUG,
"nd6_lookup: failed to lookup %s (if = %s)\n",
ip6_sprintf(addr6), ifp ? if_name(ifp) : "unspec");
}
return (NULL);
}
return (rt);
}
static struct rtentry *
nd6_neighbor_lookup(struct in6_addr *addr6, struct ifnet *ifp)
{
struct rtentry *rt;
struct sockaddr_in6 sin6;
bzero(&sin6, sizeof(sin6));
sin6.sin6_len = sizeof(struct sockaddr_in6);
sin6.sin6_family = AF_INET6;
sin6.sin6_addr = *addr6;
rt = rtpurelookup((struct sockaddr *)&sin6);
if (rt == NULL)
return (NULL);
rt->rt_refcnt--;
if (!ND6_RTENTRY_IS_NEIGHBOR(rt, ifp)) {
if (nd6_onlink_ns_rfc4861 &&
(ND6_RTENTRY_IS_LLCLONING(rt) ||
(rt->rt_parent != NULL &&
ND6_RTENTRY_IS_LLCLONING(rt->rt_parent)))) {
} else {
rt = NULL;
}
}
return (rt);
}
int
nd6_is_addr_neighbor(struct sockaddr_in6 *addr, struct ifnet *ifp)
{
struct ifaddr_container *ifac;
#define IFADDR6(a) ((((struct in6_ifaddr *)(a))->ia_addr).sin6_addr)
#define IFMASK6(a) ((((struct in6_ifaddr *)(a))->ia_prefixmask).sin6_addr)
if (IN6_IS_ADDR_LINKLOCAL(&addr->sin6_addr) &&
ntohs(*(u_int16_t *)&addr->sin6_addr.s6_addr[2]) == ifp->if_index)
return (1);
TAILQ_FOREACH(ifac, &ifp->if_addrheads[mycpuid], ifa_link) {
struct ifaddr *ifa = ifac->ifa;
if (ifa->ifa_addr->sa_family != AF_INET6)
continue;
if (IN6_ARE_MASKED_ADDR_EQUAL(&IFADDR6(ifa), &addr->sin6_addr,
&IFMASK6(ifa)))
return (1);
}
if (nd6_neighbor_lookup(&addr->sin6_addr, ifp) != NULL)
return (1);
return (0);
#undef IFADDR6
#undef IFMASK6
}
struct llinfo_nd6 *
nd6_free(struct rtentry *rt)
{
struct llinfo_nd6 *ln = (struct llinfo_nd6 *)rt->rt_llinfo, *next;
struct in6_addr in6 = ((struct sockaddr_in6 *)rt_key(rt))->sin6_addr;
struct nd_defrouter *dr;
if (!ip6_forwarding &&
(ND_IFINFO(rt->rt_ifp)->flags & ND6_IFF_ACCEPT_RTADV)) {
mtx_lock(&nd6_mtx);
dr = defrouter_lookup(
&((struct sockaddr_in6 *)rt_key(rt))->sin6_addr,
rt->rt_ifp);
if (ln->ln_router || dr) {
rt6_flush(&in6, rt->rt_ifp);
}
if (dr) {
ln->ln_state = ND6_LLINFO_INCOMPLETE;
pfxlist_onlink_check();
if (dr == TAILQ_FIRST(&nd_defrouter)) {
TAILQ_REMOVE(&nd_defrouter, dr, dr_entry);
TAILQ_INSERT_TAIL(&nd_defrouter, dr, dr_entry);
defrouter_select();
}
}
mtx_unlock(&nd6_mtx);
}
next = ln->ln_next;
rtrequest(RTM_DELETE, rt_key(rt), NULL, rt_mask(rt), 0, NULL);
return (next);
}
void
nd6_nud_hint(struct rtentry *rt, struct in6_addr *dst6, int force)
{
struct llinfo_nd6 *ln;
if (!rt) {
if (!dst6)
return;
if (!(rt = nd6_lookup(dst6, 0, NULL)))
return;
}
if ((rt->rt_flags & RTF_GATEWAY) ||
!(rt->rt_flags & RTF_LLINFO) ||
rt->rt_llinfo == NULL || rt->rt_gateway == NULL ||
rt->rt_gateway->sa_family != AF_LINK) {
return;
}
ln = (struct llinfo_nd6 *)rt->rt_llinfo;
if (ln->ln_state < ND6_LLINFO_REACHABLE)
return;
if (!force) {
ln->ln_byhint++;
if (ln->ln_byhint > nd6_maxnudhint)
return;
}
ln->ln_state = ND6_LLINFO_REACHABLE;
if (ln->ln_expire)
ln->ln_expire = time_uptime +
ND_IFINFO(rt->rt_ifp)->reachable;
}
void
nd6_rtrequest(int req, struct rtentry *rt)
{
struct sockaddr *gate = rt->rt_gateway;
struct llinfo_nd6 *ln = (struct llinfo_nd6 *)rt->rt_llinfo;
static struct sockaddr_dl null_sdl = {sizeof(null_sdl), AF_LINK};
struct ifnet *ifp = rt->rt_ifp;
struct ifaddr *ifa;
if ((rt->rt_flags & RTF_GATEWAY))
return;
if (nd6_need_cache(ifp) == 0 && !(rt->rt_flags & RTF_HOST)) {
return;
}
if (req == RTM_RESOLVE &&
(nd6_need_cache(ifp) == 0 ||
!nd6_is_addr_neighbor((struct sockaddr_in6 *)rt_key(rt), ifp))) {
rt->rt_flags &= ~RTF_LLINFO;
return;
}
switch (req) {
case RTM_ADD:
if (rt->rt_flags & (RTF_CLONING | RTF_LLINFO)) {
rt_setgate(rt, rt_key(rt),
(struct sockaddr *)&null_sdl);
gate = rt->rt_gateway;
SDL(gate)->sdl_type = ifp->if_type;
SDL(gate)->sdl_index = ifp->if_index;
if (ln)
ln->ln_expire = time_uptime;
if (ln && ln->ln_expire == 0) {
ln->ln_expire = 1;
}
if ((rt->rt_flags & RTF_CLONING))
break;
}
#if 0
if ((rt->rt_flags & RTF_ANNOUNCE) && mycpuid == 0) {
nd6_na_output(ifp,
&SIN6(rt_key(rt))->sin6_addr,
&SIN6(rt_key(rt))->sin6_addr,
ip6_forwarding ? ND_NA_FLAG_ROUTER : 0,
1, NULL);
}
#endif
case RTM_RESOLVE:
if ((ifp->if_flags & (IFF_POINTOPOINT | IFF_LOOPBACK)) == 0) {
if (gate->sa_family != AF_LINK ||
gate->sa_len < sizeof(null_sdl)) {
log(LOG_DEBUG,
"nd6_rtrequest: bad gateway value: %s\n",
if_name(ifp));
break;
}
SDL(gate)->sdl_type = ifp->if_type;
SDL(gate)->sdl_index = ifp->if_index;
}
if (ln != NULL)
break;
R_Malloc(ln, struct llinfo_nd6 *, sizeof(*ln));
rt->rt_llinfo = (caddr_t)ln;
if (!ln) {
log(LOG_DEBUG, "nd6_rtrequest: malloc failed\n");
break;
}
nd6_inuse++;
nd6_allocated++;
bzero(ln, sizeof(*ln));
ln->ln_rt = rt;
if (req == RTM_ADD) {
ln->ln_state = ND6_LLINFO_REACHABLE;
ln->ln_byhint = 0;
} else {
ln->ln_state = ND6_LLINFO_NOSTATE;
ln->ln_expire = time_uptime;
}
rt->rt_flags |= RTF_LLINFO;
ln->ln_next = llinfo_nd6.ln_next;
llinfo_nd6.ln_next = ln;
ln->ln_prev = &llinfo_nd6;
ln->ln_next->ln_prev = ln;
ifa = (struct ifaddr *)in6ifa_ifpwithaddr(rt->rt_ifp,
&SIN6(rt_key(rt))->sin6_addr);
if (ifa) {
caddr_t macp = nd6_ifptomac(ifp);
ln->ln_expire = 0;
ln->ln_state = ND6_LLINFO_REACHABLE;
ln->ln_byhint = 0;
if (macp) {
bcopy(macp, LLADDR(SDL(gate)), ifp->if_addrlen);
SDL(gate)->sdl_alen = ifp->if_addrlen;
}
if (nd6_useloopback) {
rt->rt_ifp = loif;
if (ifa != rt->rt_ifa) {
IFAFREE(rt->rt_ifa);
IFAREF(ifa);
rt->rt_ifa = ifa;
}
}
} else if (rt->rt_flags & RTF_ANNOUNCE) {
ln->ln_expire = 0;
ln->ln_state = ND6_LLINFO_REACHABLE;
ln->ln_byhint = 0;
if ((ifp->if_flags & IFF_MULTICAST) && mycpuid == 0) {
struct in6_addr llsol;
int error;
llsol = SIN6(rt_key(rt))->sin6_addr;
llsol.s6_addr16[0] = htons(0xff02);
llsol.s6_addr16[1] = htons(ifp->if_index);
llsol.s6_addr32[1] = 0;
llsol.s6_addr32[2] = htonl(1);
llsol.s6_addr8[12] = 0xff;
if (!in6_addmulti(&llsol, ifp, &error)) {
nd6log((LOG_ERR, "%s: failed to join "
"%s (errno=%d)\n", if_name(ifp),
ip6_sprintf(&llsol), error));
}
}
}
break;
case RTM_DELETE:
if (!ln)
break;
if ((rt->rt_flags & RTF_ANNOUNCE) &&
(ifp->if_flags & IFF_MULTICAST) && mycpuid == 0) {
struct in6_addr llsol;
struct in6_multi *in6m;
llsol = SIN6(rt_key(rt))->sin6_addr;
llsol.s6_addr16[0] = htons(0xff02);
llsol.s6_addr16[1] = htons(ifp->if_index);
llsol.s6_addr32[1] = 0;
llsol.s6_addr32[2] = htonl(1);
llsol.s6_addr8[12] = 0xff;
in6m = IN6_LOOKUP_MULTI(&llsol, ifp);
if (in6m)
in6_delmulti(in6m);
}
nd6_inuse--;
ln->ln_next->ln_prev = ln->ln_prev;
ln->ln_prev->ln_next = ln->ln_next;
ln->ln_prev = NULL;
rt->rt_llinfo = 0;
rt->rt_flags &= ~RTF_LLINFO;
if (ln->ln_hold)
m_freem(ln->ln_hold);
R_Free(ln);
}
}
int
nd6_ioctl(u_long cmd, caddr_t data, struct ifnet *ifp)
{
struct in6_drlist *drl = (struct in6_drlist *)data;
struct in6_prlist *prl = (struct in6_prlist *)data;
struct in6_ndireq *ndi = (struct in6_ndireq *)data;
struct in6_nbrinfo *nbi = (struct in6_nbrinfo *)data;
struct in6_ndifreq *ndif = (struct in6_ndifreq *)data;
struct nd_defrouter *dr, any;
struct nd_prefix *pr;
struct rtentry *rt;
int i = 0, error = 0;
switch (cmd) {
case SIOCGDRLST_IN6:
bzero(drl, sizeof(*drl));
mtx_lock(&nd6_mtx);
dr = TAILQ_FIRST(&nd_defrouter);
while (dr && i < DRLSTSIZ) {
drl->defrouter[i].rtaddr = dr->rtaddr;
if (IN6_IS_ADDR_LINKLOCAL(&drl->defrouter[i].rtaddr)) {
drl->defrouter[i].rtaddr.s6_addr16[1] = 0;
} else
log(LOG_ERR,
"default router list contains a "
"non-linklocal address(%s)\n",
ip6_sprintf(&drl->defrouter[i].rtaddr));
drl->defrouter[i].flags = dr->flags;
drl->defrouter[i].rtlifetime = dr->rtlifetime;
drl->defrouter[i].expire = dr->expire;
drl->defrouter[i].if_index = dr->ifp->if_index;
i++;
dr = TAILQ_NEXT(dr, dr_entry);
}
mtx_unlock(&nd6_mtx);
break;
case SIOCGPRLST_IN6:
bzero(prl, sizeof(*prl));
mtx_lock(&nd6_mtx);
pr = nd_prefix.lh_first;
while (pr && i < PRLSTSIZ) {
struct nd_pfxrouter *pfr;
int j;
in6_embedscope(&prl->prefix[i].prefix,
&pr->ndpr_prefix, NULL, NULL);
prl->prefix[i].raflags = pr->ndpr_raf;
prl->prefix[i].prefixlen = pr->ndpr_plen;
prl->prefix[i].vltime = pr->ndpr_vltime;
prl->prefix[i].pltime = pr->ndpr_pltime;
prl->prefix[i].if_index = pr->ndpr_ifp->if_index;
prl->prefix[i].expire = pr->ndpr_expire;
pfr = pr->ndpr_advrtrs.lh_first;
j = 0;
while (pfr) {
if (j < DRLSTSIZ) {
#define RTRADDR prl->prefix[i].advrtr[j]
RTRADDR = pfr->router->rtaddr;
if (IN6_IS_ADDR_LINKLOCAL(&RTRADDR)) {
RTRADDR.s6_addr16[1] = 0;
} else
log(LOG_ERR,
"a router(%s) advertises "
"a prefix with "
"non-link local address\n",
ip6_sprintf(&RTRADDR));
#undef RTRADDR
}
j++;
pfr = pfr->pfr_next;
}
prl->prefix[i].advrtrs = j;
prl->prefix[i].origin = PR_ORIG_RA;
i++;
pr = pr->ndpr_next;
}
mtx_unlock(&nd6_mtx);
break;
case OSIOCGIFINFO_IN6:
bzero(&ndi->ndi, sizeof(ndi->ndi));
ndi->ndi.linkmtu = IN6_LINKMTU(ifp);
ndi->ndi.maxmtu = ND_IFINFO(ifp)->maxmtu;
ndi->ndi.basereachable = ND_IFINFO(ifp)->basereachable;
ndi->ndi.reachable = ND_IFINFO(ifp)->reachable;
ndi->ndi.retrans = ND_IFINFO(ifp)->retrans;
ndi->ndi.flags = ND_IFINFO(ifp)->flags;
ndi->ndi.recalctm = ND_IFINFO(ifp)->recalctm;
ndi->ndi.chlim = ND_IFINFO(ifp)->chlim;
break;
case SIOCGIFINFO_IN6:
ndi->ndi = *ND_IFINFO(ifp);
ndi->ndi.linkmtu = IN6_LINKMTU(ifp);
break;
case SIOCSIFINFO_IN6:
if (ndi->ndi.linkmtu != 0) {
if (ndi->ndi.linkmtu < IPV6_MMTU ||
ndi->ndi.linkmtu > IN6_LINKMTU(ifp)) {
error = EINVAL;
break;
}
ND_IFINFO(ifp)->linkmtu = ndi->ndi.linkmtu;
}
if (ndi->ndi.basereachable != 0) {
int obasereachable = ND_IFINFO(ifp)->basereachable;
ND_IFINFO(ifp)->basereachable = ndi->ndi.basereachable;
if (ndi->ndi.basereachable != obasereachable)
ND_IFINFO(ifp)->reachable =
ND_COMPUTE_RTIME(ndi->ndi.basereachable);
}
if (ndi->ndi.retrans != 0)
ND_IFINFO(ifp)->retrans = ndi->ndi.retrans;
if (ndi->ndi.chlim != 0)
ND_IFINFO(ifp)->chlim = ndi->ndi.chlim;
case SIOCSIFINFO_FLAGS:
if ((ndi->ndi.flags & ND6_IFF_AUTO_LINKLOCAL) &&
!(ND_IFINFO(ifp)->flags & ND6_IFF_AUTO_LINKLOCAL)) {
ND_IFINFO(ifp)->flags |= ND6_IFF_AUTO_LINKLOCAL;
in6_ifattach(ifp, NULL);
}
ND_IFINFO(ifp)->flags = ndi->ndi.flags;
break;
case SIOCSNDFLUSH_IN6:
bzero(&any, sizeof(any));
defrouter_delreq(&any, 0);
defrouter_select();
break;
case SIOCSPFXFLUSH_IN6:
{
struct nd_prefix *pr, *next;
mtx_lock(&nd6_mtx);
for (pr = nd_prefix.lh_first; pr; pr = next) {
struct in6_ifaddr *ia, *ia_next;
next = pr->ndpr_next;
if (IN6_IS_ADDR_LINKLOCAL(&pr->ndpr_prefix.sin6_addr))
continue;
for (ia = in6_ifaddr; ia; ia = ia_next) {
ia_next = ia->ia_next;
if (!(ia->ia6_flags & IN6_IFF_AUTOCONF))
continue;
if (ia->ia6_ndpr == pr)
in6_purgeaddr(&ia->ia_ifa);
}
prelist_remove(pr);
}
mtx_unlock(&nd6_mtx);
break;
}
case SIOCSRTRFLUSH_IN6:
{
struct nd_defrouter *dr, *next;
mtx_lock(&nd6_mtx);
if ((dr = TAILQ_FIRST(&nd_defrouter)) != NULL) {
for (dr = TAILQ_NEXT(dr, dr_entry); dr; dr = next) {
next = TAILQ_NEXT(dr, dr_entry);
defrtrlist_del(dr);
}
defrtrlist_del(TAILQ_FIRST(&nd_defrouter));
}
mtx_unlock(&nd6_mtx);
break;
}
case SIOCGNBRINFO_IN6:
{
struct llinfo_nd6 *ln;
struct in6_addr nb_addr = nbi->addr;
if (IN6_IS_ADDR_LINKLOCAL(&nbi->addr) ||
IN6_IS_ADDR_MC_LINKLOCAL(&nbi->addr)) {
u_int16_t *idp = (u_int16_t *)&nb_addr.s6_addr[2];
if (*idp == 0)
*idp = htons(ifp->if_index);
}
mtx_lock(&nd6_mtx);
if ((rt = nd6_lookup(&nb_addr, 0, ifp)) == NULL) {
error = EINVAL;
mtx_unlock(&nd6_mtx);
break;
}
ln = (struct llinfo_nd6 *)rt->rt_llinfo;
nbi->state = ln->ln_state;
nbi->asked = ln->ln_asked;
nbi->isrouter = ln->ln_router;
nbi->expire = ln->ln_expire;
mtx_unlock(&nd6_mtx);
break;
}
case SIOCGDEFIFACE_IN6:
ndif->ifindex = nd6_defifindex;
break;
case SIOCSDEFIFACE_IN6:
return (nd6_setdefaultiface(ndif->ifindex));
}
return (error);
}
struct rtentry *
nd6_cache_lladdr(struct ifnet *ifp, struct in6_addr *from, char *lladdr,
int lladdrlen,
int type,
int code )
{
struct rtentry *rt = NULL;
struct llinfo_nd6 *ln = NULL;
int is_newentry;
struct sockaddr_dl *sdl = NULL;
int do_update;
int olladdr;
int llchange;
int newstate = 0;
if (!ifp)
panic("ifp == NULL in nd6_cache_lladdr");
if (!from)
panic("from == NULL in nd6_cache_lladdr");
if (IN6_IS_ADDR_UNSPECIFIED(from))
return NULL;
rt = nd6_lookup(from, 0, ifp);
if (!rt) {
#if 0
if (!lladdr || !lladdrlen)
return NULL;
#endif
rt = nd6_lookup(from, 1, ifp);
is_newentry = 1;
} else {
if (rt->rt_flags & RTF_STATIC)
return NULL;
is_newentry = 0;
}
if (!rt)
return NULL;
if ((rt->rt_flags & (RTF_GATEWAY | RTF_LLINFO)) != RTF_LLINFO) {
fail:
nd6_free(rt);
return NULL;
}
ln = (struct llinfo_nd6 *)rt->rt_llinfo;
if (!ln)
goto fail;
if (!rt->rt_gateway)
goto fail;
if (rt->rt_gateway->sa_family != AF_LINK)
goto fail;
sdl = SDL(rt->rt_gateway);
olladdr = (sdl->sdl_alen) ? 1 : 0;
if (olladdr && lladdr) {
if (bcmp(lladdr, LLADDR(sdl), ifp->if_addrlen))
llchange = 1;
else
llchange = 0;
} else
llchange = 0;
if (lladdr) {
sdl->sdl_alen = ifp->if_addrlen;
bcopy(lladdr, LLADDR(sdl), ifp->if_addrlen);
}
if (!is_newentry) {
if ((!olladdr && lladdr) ||
(olladdr && lladdr && llchange)) {
do_update = 1;
newstate = ND6_LLINFO_STALE;
} else {
do_update = 0;
}
} else {
do_update = 1;
if (!lladdr)
newstate = ND6_LLINFO_NOSTATE;
else
newstate = ND6_LLINFO_STALE;
}
if (do_update) {
ln->ln_state = newstate;
if (ln->ln_state == ND6_LLINFO_STALE) {
ln->ln_expire = time_uptime + nd6_gctimer;
if (ln->ln_hold) {
nd6_output(ifp, ifp, ln->ln_hold,
(struct sockaddr_in6 *)rt_key(rt), rt);
ln->ln_hold = NULL;
}
} else if (ln->ln_state == ND6_LLINFO_INCOMPLETE) {
ln->ln_expire = time_uptime;
}
}
switch (type & 0xff) {
case ND_NEIGHBOR_SOLICIT:
if (is_newentry)
ln->ln_router = 0;
break;
case ND_REDIRECT:
if (code == ND_REDIRECT_ROUTER)
ln->ln_router = 1;
else if (is_newentry)
ln->ln_router = 0;
break;
case ND_ROUTER_SOLICIT:
ln->ln_router = 0;
break;
case ND_ROUTER_ADVERT:
if ((!is_newentry && (olladdr || lladdr)) ||
(is_newentry && lladdr)) {
ln->ln_router = 1;
}
break;
}
if (llchange || lladdr)
rt_rtmsg(llchange ? RTM_CHANGE : RTM_ADD, rt, rt->rt_ifp, 0);
if (do_update && ln->ln_router && !ip6_forwarding &&
(ND_IFINFO(ifp)->flags & ND6_IFF_ACCEPT_RTADV))
defrouter_select();
return rt;
}
static void
nd6_slowtimo(void *arg __unused)
{
struct lwkt_msg *lmsg = &nd6_slowtimo_netmsg.lmsg;
KASSERT(mycpuid == 0, ("not on cpu0"));
crit_enter();
if (lmsg->ms_flags & MSGF_DONE)
lwkt_sendmsg_oncpu(netisr_cpuport(0), lmsg);
crit_exit();
}
static void
nd6_slowtimo_dispatch(netmsg_t nmsg)
{
const struct ifnet_array *arr;
struct nd_ifinfo *nd6if;
int i;
ASSERT_NETISR0;
crit_enter();
lwkt_replymsg(&nmsg->lmsg, 0);
crit_exit();
arr = ifnet_array_get();
mtx_lock(&nd6_mtx);
for (i = 0; i < arr->ifnet_count; ++i) {
struct ifnet *ifp = arr->ifnet_arr[i];
if (ifp->if_afdata[AF_INET6] == NULL)
continue;
nd6if = ND_IFINFO(ifp);
if (nd6if->basereachable &&
(nd6if->recalctm -= ND6_SLOWTIMER_INTERVAL) <= 0) {
nd6if->recalctm = nd6_recalc_reachtm_interval;
nd6if->reachable = ND_COMPUTE_RTIME(nd6if->basereachable);
}
}
mtx_unlock(&nd6_mtx);
callout_reset(&nd6_slowtimo_ch, ND6_SLOWTIMER_INTERVAL * hz,
nd6_slowtimo, NULL);
}
int
nd6_output(struct ifnet *ifp, struct ifnet *origifp, struct mbuf *m,
struct sockaddr_in6 *dst, struct rtentry *rt)
{
int error;
if (ifp->if_flags & IFF_LOOPBACK)
error = ifp->if_output(origifp, m, (struct sockaddr *)dst, rt);
else
error = ifp->if_output(ifp, m, (struct sockaddr *)dst, rt);
return error;
}
int
nd6_resolve(struct ifnet *ifp, struct rtentry *rt0, struct mbuf *m,
struct sockaddr *dst0, u_char *desten)
{
struct sockaddr_in6 *dst = SIN6(dst0);
struct rtentry *rt = NULL;
struct llinfo_nd6 *ln = NULL;
int error;
if (m->m_flags & M_MCAST) {
switch (ifp->if_type) {
case IFT_ETHER:
#ifdef IFT_L2VLAN
case IFT_L2VLAN:
#endif
#ifdef IFT_IEEE80211
case IFT_IEEE80211:
#endif
ETHER_MAP_IPV6_MULTICAST(&dst->sin6_addr,
desten);
return 0;
case IFT_IEEE1394:
bcopy(ifp->if_broadcastaddr, desten, ifp->if_addrlen);
return 0;
default:
error = EAFNOSUPPORT;
goto bad;
}
}
if (rt0 != NULL) {
error = rt_llroute(dst0, rt0, &rt);
if (error != 0)
goto bad;
ln = rt->rt_llinfo;
}
if (ln == NULL && nd6_is_addr_neighbor(dst, ifp)) {
rt = nd6_lookup(&dst->sin6_addr, 1, ifp);
if (rt != NULL)
ln = rt->rt_llinfo;
}
if (ln == NULL || rt == NULL) {
if (!(ifp->if_flags & IFF_POINTOPOINT) &&
!(ND_IFINFO(ifp)->flags & ND6_IFF_PERFORMNUD)) {
log(LOG_DEBUG,
"nd6_output: can't allocate llinfo for %s "
"(ln=%p, rt=%p)\n",
ip6_sprintf(&dst->sin6_addr), ln, rt);
error = ENOBUFS;
goto bad;
}
return 0;
}
if ((ifp->if_flags & IFF_POINTOPOINT) &&
ln->ln_state < ND6_LLINFO_REACHABLE) {
ln->ln_state = ND6_LLINFO_STALE;
ln->ln_expire = time_uptime + nd6_gctimer;
}
if (ln->ln_state == ND6_LLINFO_STALE) {
ln->ln_asked = 0;
ln->ln_state = ND6_LLINFO_DELAY;
ln->ln_expire = time_uptime + nd6_delay;
}
if (ln->ln_state > ND6_LLINFO_INCOMPLETE) {
struct sockaddr_dl *sdl = SDL(rt->rt_gateway);
if (sdl->sdl_family == AF_LINK && sdl->sdl_alen != 0) {
bcopy(LLADDR(sdl), desten, sdl->sdl_alen);
return 0;
} else {
static struct krate krate_nce = { .freq = 1 };
krateprintf(&krate_nce,
"nd6_resolve: incomplete neighbor cache "
"ln=%p sdl=%p\n", ln, sdl);
}
}
if (ln->ln_hold)
m_freem(ln->ln_hold);
ln->ln_hold = m;
if (ln->ln_state == ND6_LLINFO_NOSTATE ||
ln->ln_state == ND6_LLINFO_WAITDELETE) {
ln->ln_state = ND6_LLINFO_INCOMPLETE;
ln->ln_expire = 1;
ln->ln_asked = 0;
}
if (ln->ln_expire && ln->ln_expire < time_uptime && ln->ln_asked == 0) {
ln->ln_asked++;
ln->ln_expire = time_uptime + ND_IFINFO(ifp)->retrans / 1000;
nd6_ns_output(ifp, NULL, &dst->sin6_addr, ln, 0);
}
if (ln->ln_asked >= nd6_mmaxtries)
return (rt != NULL && rt->rt_flags & RTF_GATEWAY) ?
EHOSTUNREACH : EHOSTDOWN;
return EWOULDBLOCK;
bad:
m_freem(m);
return error;
}
int
nd6_need_cache(struct ifnet *ifp)
{
switch (ifp->if_type) {
case IFT_ETHER:
case IFT_IEEE1394:
#ifdef IFT_L2VLAN
case IFT_L2VLAN:
#endif
#ifdef IFT_IEEE80211
case IFT_IEEE80211:
#endif
#ifdef IFT_CARP
case IFT_CARP:
#endif
case IFT_GIF:
return (1);
default:
return (0);
}
}
static int nd6_sysctl_drlist(SYSCTL_HANDLER_ARGS);
static int nd6_sysctl_prlist(SYSCTL_HANDLER_ARGS);
#ifdef SYSCTL_DECL
SYSCTL_DECL(_net_inet6_icmp6);
#endif
SYSCTL_NODE(_net_inet6_icmp6, ICMPV6CTL_ND6_DRLIST, nd6_drlist,
CTLFLAG_RD, nd6_sysctl_drlist, "List default routers");
SYSCTL_NODE(_net_inet6_icmp6, ICMPV6CTL_ND6_PRLIST, nd6_prlist,
CTLFLAG_RD, nd6_sysctl_prlist, "List prefixes");
static int
nd6_sysctl_drlist(SYSCTL_HANDLER_ARGS)
{
int error;
char buf[1024];
struct in6_defrouter *d, *de;
struct nd_defrouter *dr;
if (req->newptr)
return EPERM;
error = 0;
for (dr = TAILQ_FIRST(&nd_defrouter); dr;
dr = TAILQ_NEXT(dr, dr_entry)) {
d = (struct in6_defrouter *)buf;
de = (struct in6_defrouter *)(buf + sizeof(buf));
if (d + 1 <= de) {
bzero(d, sizeof(*d));
d->rtaddr.sin6_family = AF_INET6;
d->rtaddr.sin6_len = sizeof(d->rtaddr);
if (in6_recoverscope(&d->rtaddr, &dr->rtaddr,
dr->ifp) != 0)
log(LOG_ERR,
"scope error in "
"default router list (%s)\n",
ip6_sprintf(&dr->rtaddr));
d->flags = dr->flags;
d->rtlifetime = dr->rtlifetime;
d->expire = dr->expire;
d->if_index = dr->ifp->if_index;
} else
panic("buffer too short");
error = SYSCTL_OUT(req, buf, sizeof(*d));
if (error)
break;
}
return error;
}
static int
nd6_sysctl_prlist(SYSCTL_HANDLER_ARGS)
{
int error;
char buf[1024];
struct in6_prefix *p, *pe;
struct nd_prefix *pr;
if (req->newptr)
return EPERM;
error = 0;
for (pr = nd_prefix.lh_first; pr; pr = pr->ndpr_next) {
u_short advrtrs;
size_t advance;
struct sockaddr_in6 *sin6, *s6;
struct nd_pfxrouter *pfr;
p = (struct in6_prefix *)buf;
pe = (struct in6_prefix *)(buf + sizeof(buf));
if (p + 1 <= pe) {
bzero(p, sizeof(*p));
sin6 = (struct sockaddr_in6 *)(p + 1);
p->prefix = pr->ndpr_prefix;
if (in6_recoverscope(&p->prefix,
&p->prefix.sin6_addr, pr->ndpr_ifp) != 0)
log(LOG_ERR,
"scope error in prefix list (%s)\n",
ip6_sprintf(&p->prefix.sin6_addr));
p->raflags = pr->ndpr_raf;
p->prefixlen = pr->ndpr_plen;
p->vltime = pr->ndpr_vltime;
p->pltime = pr->ndpr_pltime;
p->if_index = pr->ndpr_ifp->if_index;
p->expire = pr->ndpr_expire;
p->refcnt = pr->ndpr_refcnt;
p->flags = pr->ndpr_stateflags;
p->origin = PR_ORIG_RA;
advrtrs = 0;
for (pfr = pr->ndpr_advrtrs.lh_first; pfr;
pfr = pfr->pfr_next) {
if ((void *)&sin6[advrtrs + 1] > (void *)pe) {
advrtrs++;
continue;
}
s6 = &sin6[advrtrs];
bzero(s6, sizeof(*s6));
s6->sin6_family = AF_INET6;
s6->sin6_len = sizeof(*sin6);
if (in6_recoverscope(s6, &pfr->router->rtaddr,
pfr->router->ifp) != 0)
log(LOG_ERR,
"scope error in "
"prefix list (%s)\n",
ip6_sprintf(&pfr->router->rtaddr));
advrtrs++;
}
p->advrtrs = advrtrs;
} else {
panic("buffer too short");
}
advance = sizeof(*p) + sizeof(*sin6) * advrtrs;
error = SYSCTL_OUT(req, buf, advance);
if (error)
break;
}
return error;
}