#include "opt_inet.h"
#include "opt_mpls.h"
#include <sys/param.h>
#include <sys/systm.h>
#include <sys/malloc.h>
#include <sys/mbuf.h>
#include <sys/socket.h>
#include <sys/domain.h>
#include <sys/kernel.h>
#include <sys/sysctl.h>
#include <sys/globaldata.h>
#include <sys/thread.h>
#include <net/if.h>
#include <net/if_var.h>
#include <net/route.h>
#include <net/netisr.h>
#include <netinet/in.h>
#include <net/ip_mroute/ip_mroute.h>
#include <sys/thread2.h>
#include <sys/msgport2.h>
#include <net/netmsg2.h>
#include <net/netisr2.h>
#ifdef MPLS
#include <netproto/mpls/mpls.h>
#endif
static struct rtstatistics rtstatistics_percpu[MAXCPU] __cachealign;
#define rtstat rtstatistics_percpu[mycpuid]
struct radix_node_head *rt_tables[MAXCPU][AF_MAX+1];
static void rt_maskedcopy (struct sockaddr *, struct sockaddr *,
struct sockaddr *);
static void rtable_init(void);
static void rtinit_rtrequest_callback(int, int, struct rt_addrinfo *,
struct rtentry *, void *);
static void rtredirect_msghandler(netmsg_t msg);
static void rtrequest1_msghandler(netmsg_t msg);
static void rtsearch_msghandler(netmsg_t msg);
static void rtmask_add_msghandler(netmsg_t msg);
static int rt_setshims(struct rtentry *, struct sockaddr **);
SYSCTL_NODE(_net, OID_AUTO, route, CTLFLAG_RW, 0, "Routing");
#ifdef ROUTE_DEBUG
static int route_debug = 1;
SYSCTL_INT(_net_route, OID_AUTO, route_debug, CTLFLAG_RW,
&route_debug, 0, "");
#endif
u_long route_kmalloc_limit = 0;
TUNABLE_ULONG("net.route.kmalloc_limit", &route_kmalloc_limit);
void
route_init(void)
{
int cpu;
if (route_kmalloc_limit)
kmalloc_raise_limit(M_RTABLE, route_kmalloc_limit);
for (cpu = 0; cpu < netisr_ncpus; ++cpu)
bzero(&rtstatistics_percpu[cpu], sizeof(struct rtstatistics));
rn_init();
rtable_init();
}
static void
rtable_init_oncpu(netmsg_t msg)
{
struct domain *dom;
int cpu = mycpuid;
ASSERT_NETISR_NCPUS(cpu);
SLIST_FOREACH(dom, &domains, dom_next) {
if (dom->dom_rtattach) {
dom->dom_rtattach(
(void **)&rt_tables[cpu][dom->dom_family],
dom->dom_rtoffset);
}
}
netisr_forwardmsg(&msg->base, cpu + 1);
}
static void
rtable_init(void)
{
struct netmsg_base msg;
netmsg_init(&msg, NULL, &curthread->td_msgport, 0, rtable_init_oncpu);
netisr_domsg_global(&msg);
}
static int
sysctl_rtstatistics(SYSCTL_HANDLER_ARGS)
{
int cpu, error = 0;
for (cpu = 0; cpu < netisr_ncpus; ++cpu) {
if ((error = SYSCTL_OUT(req, &rtstatistics_percpu[cpu],
sizeof(struct rtstatistics))))
break;
if ((error = SYSCTL_IN(req, &rtstatistics_percpu[cpu],
sizeof(struct rtstatistics))))
break;
}
return (error);
}
SYSCTL_PROC(_net_route, OID_AUTO, stats, (CTLTYPE_OPAQUE|CTLFLAG_RW),
0, 0, sysctl_rtstatistics, "S,rtstatistics", "Routing statistics");
void
rtalloc(struct route *ro)
{
rtalloc_ign(ro, 0UL);
}
void
rtalloc_ign(struct route *ro, u_long ignoreflags)
{
if (ro->ro_rt != NULL) {
if (ro->ro_rt->rt_ifp != NULL && ro->ro_rt->rt_flags & RTF_UP)
return;
rtfree(ro->ro_rt);
ro->ro_rt = NULL;
}
ro->ro_rt = _rtlookup(&ro->ro_dst, ignoreflags);
}
struct rtentry *
_rtlookup(struct sockaddr *dst, u_long ignore)
{
struct radix_node_head *rnh = rt_tables[mycpuid][dst->sa_family];
struct rtentry *rt;
ASSERT_NETISR_NCPUS(mycpuid);
if (rnh == NULL)
goto unreach;
rt = (struct rtentry *) rnh->rnh_matchaddr(dst, rnh);
if (rt == NULL)
goto unreach;
if ((rt->rt_flags & ~ignore & (RTF_CLONING | RTF_PRCLONING)) != 0) {
struct rtentry *clonedroute;
int error;
clonedroute = rt;
error = rtrequest(RTM_RESOLVE, dst, NULL, NULL, 0,
&clonedroute);
if (error != 0) {
rt_dstmsg(RTM_MISS, dst, error);
rt->rt_refcnt++;
return (rt);
}
if (clonedroute->rt_flags & RTF_XRESOLVE)
rt_dstmsg(RTM_RESOLVE, dst, 0);
return (clonedroute);
}
rt->rt_refcnt++;
return (rt);
unreach:
rtstat.rts_unreach++;
rt_dstmsg(RTM_MISS, dst, 0);
return (NULL);
}
void
rtfree(struct rtentry *rt)
{
ASSERT_NETISR_NCPUS(rt->rt_cpuid);
KASSERT(rt->rt_refcnt > 0, ("rtfree: rt_refcnt %ld", rt->rt_refcnt));
--rt->rt_refcnt;
if (rt->rt_refcnt == 0) {
struct radix_node_head *rnh =
rt_tables[mycpuid][rt_key(rt)->sa_family];
if (rnh->rnh_close)
rnh->rnh_close((struct radix_node *)rt, rnh);
if (!(rt->rt_flags & RTF_UP)) {
if (rt->rt_ifa != NULL)
IFAFREE(rt->rt_ifa);
if (rt->rt_parent != NULL)
RTFREE(rt->rt_parent);
R_Free(rt_key(rt));
R_Free(rt);
}
}
}
static void
rtfree_async_dispatch(netmsg_t msg)
{
struct rtentry *rt = msg->lmsg.u.ms_resultp;
rtfree(rt);
netisr_replymsg(&msg->base, 0);
}
void
rtfree_async(struct rtentry *rt)
{
struct netmsg_base *msg;
if (IN_NETISR_NCPUS(rt->rt_cpuid)) {
rtfree(rt);
return;
}
KASSERT(rt->rt_refcnt > 0,
("rtfree_async: rt_refcnt %ld", rt->rt_refcnt));
msg = kmalloc(sizeof(*msg), M_LWKTMSG, M_INTWAIT);
netmsg_init(msg, NULL, &netisr_afree_rport, 0, rtfree_async_dispatch);
msg->lmsg.u.ms_resultp = rt;
netisr_sendmsg(msg, rt->rt_cpuid);
}
int
rtredirect_oncpu(struct sockaddr *dst, struct sockaddr *gateway,
struct sockaddr *netmask, int flags, struct sockaddr *src)
{
struct rtentry *rt = NULL;
struct rt_addrinfo rtinfo;
struct ifaddr *ifa;
u_long *stat = NULL;
int error;
ASSERT_NETISR_NCPUS(mycpuid);
if ((ifa = ifa_ifwithnet(gateway)) == NULL) {
error = ENETUNREACH;
goto out;
}
if (!(flags & RTF_DONE) &&
(rt = rtpurelookup(dst)) != NULL &&
(!sa_equal(src, rt->rt_gateway) || rt->rt_ifa != ifa)) {
error = EINVAL;
goto done;
}
if (ifa_ifwithaddr(gateway)) {
error = EHOSTUNREACH;
goto done;
}
if (rt == NULL)
goto create;
if ((rt_mask(rt) != NULL && rt_mask(rt)->sa_len < 2)) {
rtfree(rt);
goto create;
}
if (!(rt->rt_flags & RTF_GATEWAY)) {
error = EHOSTUNREACH;
goto done;
}
if (!(rt->rt_flags & RTF_HOST) && (flags & RTF_HOST)) {
create:
flags |= RTF_GATEWAY | RTF_DYNAMIC;
bzero(&rtinfo, sizeof(struct rt_addrinfo));
rtinfo.rti_info[RTAX_DST] = dst;
rtinfo.rti_info[RTAX_GATEWAY] = gateway;
rtinfo.rti_info[RTAX_NETMASK] = netmask;
rtinfo.rti_flags = flags;
rtinfo.rti_ifa = ifa;
rt = NULL;
error = rtrequest1(RTM_ADD, &rtinfo, &rt);
if (rt != NULL)
flags = rt->rt_flags;
stat = &rtstat.rts_dynamic;
} else {
rt->rt_flags |= RTF_MODIFIED;
flags |= RTF_MODIFIED;
rt_setgate(rt, rt_key(rt), gateway);
if (mycpuid == 0)
rt_rtmsg(RTM_CHANGE, rt, rt->rt_ifp, 0);
error = 0;
stat = &rtstat.rts_newgateway;
}
done:
if (rt != NULL)
rtfree(rt);
out:
if (error != 0)
rtstat.rts_badredirect++;
else if (stat != NULL)
(*stat)++;
return error;
}
struct netmsg_rtredirect {
struct netmsg_base base;
struct sockaddr *dst;
struct sockaddr *gateway;
struct sockaddr *netmask;
int flags;
struct sockaddr *src;
};
void
rtredirect(struct sockaddr *dst, struct sockaddr *gateway,
struct sockaddr *netmask, int flags, struct sockaddr *src)
{
struct rt_addrinfo rtinfo;
int error;
struct netmsg_rtredirect msg;
netmsg_init(&msg.base, NULL, &curthread->td_msgport,
0, rtredirect_msghandler);
msg.dst = dst;
msg.gateway = gateway;
msg.netmask = netmask;
msg.flags = flags;
msg.src = src;
error = netisr_domsg_global(&msg.base);
bzero(&rtinfo, sizeof(struct rt_addrinfo));
rtinfo.rti_info[RTAX_DST] = dst;
rtinfo.rti_info[RTAX_GATEWAY] = gateway;
rtinfo.rti_info[RTAX_NETMASK] = netmask;
rtinfo.rti_info[RTAX_AUTHOR] = src;
rt_missmsg(RTM_REDIRECT, &rtinfo, flags, error);
}
static void
rtredirect_msghandler(netmsg_t msg)
{
struct netmsg_rtredirect *rmsg = (void *)msg;
rtredirect_oncpu(rmsg->dst, rmsg->gateway, rmsg->netmask,
rmsg->flags, rmsg->src);
netisr_forwardmsg(&msg->base, mycpuid + 1);
}
int
rtioctl(u_long req, caddr_t data, struct ucred *cred)
{
#ifdef INET
return mrt_ioctl ? mrt_ioctl(req, data) : EOPNOTSUPP;
#else
return ENXIO;
#endif
}
struct ifaddr *
ifa_ifwithroute(int flags, struct sockaddr *dst, struct sockaddr *gateway)
{
struct ifaddr *ifa;
if (!(flags & RTF_GATEWAY)) {
ifa = NULL;
if (flags & RTF_HOST) {
ifa = ifa_ifwithdstaddr(dst);
}
if (ifa == NULL)
ifa = ifa_ifwithaddr(gateway);
} else {
ifa = ifa_ifwithdstaddr(gateway);
}
if (ifa == NULL)
ifa = ifa_ifwithnet(gateway);
if (ifa == NULL) {
struct rtentry *rt;
rt = rtpurelookup(gateway);
if (rt == NULL)
return (NULL);
rt->rt_refcnt--;
if ((ifa = rt->rt_ifa) == NULL)
return (NULL);
}
if (ifa->ifa_addr->sa_family != dst->sa_family) {
struct ifaddr *oldifa = ifa;
ifa = ifaof_ifpforaddr(dst, ifa->ifa_ifp);
if (ifa == NULL)
ifa = oldifa;
}
return (ifa);
}
static int rt_fixdelete (struct radix_node *, void *);
static int rt_fixchange (struct radix_node *, void *);
struct rtfc_arg {
struct rtentry *rt0;
struct radix_node_head *rnh;
};
int
rt_getifa(struct rt_addrinfo *rtinfo)
{
struct sockaddr *gateway = rtinfo->rti_info[RTAX_GATEWAY];
struct sockaddr *dst = rtinfo->rti_info[RTAX_DST];
struct sockaddr *ifaaddr = rtinfo->rti_info[RTAX_IFA];
int flags = rtinfo->rti_flags;
if (rtinfo->rti_ifp == NULL) {
struct sockaddr *ifpaddr;
ifpaddr = rtinfo->rti_info[RTAX_IFP];
if (ifpaddr != NULL && ifpaddr->sa_family == AF_LINK) {
struct ifaddr *ifa;
ifa = ifa_ifwithnet(ifpaddr);
if (ifa != NULL)
rtinfo->rti_ifp = ifa->ifa_ifp;
}
}
if (rtinfo->rti_ifa == NULL && ifaaddr != NULL)
rtinfo->rti_ifa = ifa_ifwithaddr(ifaaddr);
if (rtinfo->rti_ifa == NULL) {
struct sockaddr *sa;
if (ifaaddr != NULL)
sa = ifaaddr;
else if ((flags & RTF_GATEWAY) != 0 &&
gateway->sa_family == dst->sa_family)
sa = gateway;
else
sa = dst;
KKASSERT(sa != NULL);
if (rtinfo->rti_ifp != NULL) {
rtinfo->rti_ifa = ifaof_ifpforaddr(sa, rtinfo->rti_ifp);
if (rtinfo->rti_ifa == NULL &&
gateway != NULL && gateway != sa)
rtinfo->rti_ifa =
ifaof_ifpforaddr(gateway, rtinfo->rti_ifp);
} else if (dst != NULL && gateway != NULL) {
rtinfo->rti_ifa = ifa_ifwithroute(flags, dst, gateway);
} else {
rtinfo->rti_ifa = ifa_ifwithroute(flags, sa, sa);
}
}
if (rtinfo->rti_ifa == NULL)
return (ENETUNREACH);
if (rtinfo->rti_ifp == NULL)
rtinfo->rti_ifp = rtinfo->rti_ifa->ifa_ifp;
return (0);
}
int
rtrequest(
int req,
struct sockaddr *dst,
struct sockaddr *gateway,
struct sockaddr *netmask,
int flags,
struct rtentry **ret_nrt)
{
struct rt_addrinfo rtinfo;
bzero(&rtinfo, sizeof(struct rt_addrinfo));
rtinfo.rti_info[RTAX_DST] = dst;
rtinfo.rti_info[RTAX_GATEWAY] = gateway;
rtinfo.rti_info[RTAX_NETMASK] = netmask;
rtinfo.rti_flags = flags;
return rtrequest1(req, &rtinfo, ret_nrt);
}
int
rtrequest_global(
int req,
struct sockaddr *dst,
struct sockaddr *gateway,
struct sockaddr *netmask,
int flags)
{
struct rt_addrinfo rtinfo;
bzero(&rtinfo, sizeof(struct rt_addrinfo));
rtinfo.rti_info[RTAX_DST] = dst;
rtinfo.rti_info[RTAX_GATEWAY] = gateway;
rtinfo.rti_info[RTAX_NETMASK] = netmask;
rtinfo.rti_flags = flags;
return rtrequest1_global(req, &rtinfo, NULL, NULL, RTREQ_PRIO_NORM);
}
struct netmsg_rtq {
struct netmsg_base base;
int req;
struct rt_addrinfo *rtinfo;
rtrequest1_callback_func_t callback;
void *arg;
};
int
rtrequest1_global(int req, struct rt_addrinfo *rtinfo,
rtrequest1_callback_func_t callback, void *arg, boolean_t req_prio)
{
struct netmsg_rtq msg;
int flags = 0;
if (req_prio)
flags = MSGF_PRIORITY;
netmsg_init(&msg.base, NULL, &curthread->td_msgport, flags,
rtrequest1_msghandler);
msg.base.lmsg.ms_error = -1;
msg.req = req;
msg.rtinfo = rtinfo;
msg.callback = callback;
msg.arg = arg;
return (netisr_domsg_global(&msg.base));
}
static void
rtrequest1_msghandler(netmsg_t msg)
{
struct netmsg_rtq *rmsg = (void *)msg;
struct rt_addrinfo rtinfo;
struct rtentry *rt = NULL;
int error;
rtinfo = *rmsg->rtinfo;
error = rtrequest1(rmsg->req, &rtinfo, &rt);
if (rt)
--rt->rt_refcnt;
if (rmsg->callback)
rmsg->callback(rmsg->req, error, &rtinfo, rt, rmsg->arg);
if (rmsg->base.lmsg.ms_error < 0 || error == 0)
rmsg->base.lmsg.ms_error = error;
if (error && rmsg->req != RTM_DELETE) {
if (mycpuid != 0) {
panic("rtrequest1_msghandler: rtrequest table req %d, "
"failed on cpu%d, error %d\n",
rmsg->req, mycpuid, error);
}
netisr_replymsg(&rmsg->base, error);
} else {
netisr_forwardmsg_error(&rmsg->base, mycpuid + 1,
rmsg->base.lmsg.ms_error);
}
}
int
rtrequest1(int req, struct rt_addrinfo *rtinfo, struct rtentry **ret_nrt)
{
struct sockaddr *dst = rtinfo->rti_info[RTAX_DST];
struct rtentry *rt;
struct radix_node *rn;
struct radix_node_head *rnh;
struct ifaddr *ifa;
struct sockaddr *ndst;
int error = 0;
ASSERT_NETISR_NCPUS(mycpuid);
#define gotoerr(x) { error = x ; goto bad; }
#ifdef ROUTE_DEBUG
if (route_debug)
rt_addrinfo_print(req, rtinfo);
#endif
crit_enter();
if ((rnh = rt_tables[mycpuid][dst->sa_family]) == NULL)
gotoerr(EAFNOSUPPORT);
if (rtinfo->rti_flags & RTF_HOST) {
rtinfo->rti_info[RTAX_NETMASK] = NULL;
rtinfo->rti_flags &= ~(RTF_CLONING | RTF_PRCLONING);
}
switch (req) {
case RTM_DELETE:
rn = rnh->rnh_deladdr(rtinfo->rti_info[RTAX_DST],
rtinfo->rti_info[RTAX_NETMASK], rnh);
if (rn == NULL)
gotoerr(ESRCH);
KASSERT(!(rn->rn_flags & (RNF_ACTIVE | RNF_ROOT)),
("rnh_deladdr returned flags 0x%x", rn->rn_flags));
rt = (struct rtentry *)rn;
++rt->rt_refcnt;
if ((rt->rt_flags & (RTF_CLONING | RTF_PRCLONING)) &&
rt_mask(rt) != NULL) {
rnh->rnh_walktree_from(rnh, rt_key(rt), rt_mask(rt),
rt_fixdelete, rt);
}
if (rt->rt_gwroute != NULL) {
RTFREE(rt->rt_gwroute);
rt->rt_gwroute = NULL;
}
rt->rt_flags &= ~RTF_UP;
#ifdef ROUTE_DEBUG
if (route_debug)
rt_print(rtinfo, rt);
#endif
if ((ifa = rt->rt_ifa) && ifa->ifa_rtrequest)
ifa->ifa_rtrequest(RTM_DELETE, rt);
KASSERT(rt->rt_refcnt >= 0,
("rtrequest1(DELETE): refcnt %ld", rt->rt_refcnt));
if (ret_nrt != NULL) {
*ret_nrt = rt;
} else {
rtfree(rt);
}
break;
case RTM_RESOLVE:
if (ret_nrt == NULL || (rt = *ret_nrt) == NULL)
gotoerr(EINVAL);
if (!(rt->rt_ifp->if_flags & IFF_UP))
gotoerr(ENETDOWN);
KASSERT(rt->rt_cpuid == mycpuid,
("rt resolve rt_cpuid %d, mycpuid %d",
rt->rt_cpuid, mycpuid));
ifa = rt->rt_ifa;
rtinfo->rti_flags =
rt->rt_flags & ~(RTF_CLONING | RTF_PRCLONING | RTF_STATIC);
rtinfo->rti_flags |= RTF_WASCLONED;
rtinfo->rti_info[RTAX_GATEWAY] = rt->rt_gateway;
if ((rtinfo->rti_info[RTAX_NETMASK] = rt->rt_genmask) == NULL)
rtinfo->rti_flags |= RTF_HOST;
rtinfo->rti_info[RTAX_MPLS1] = rt->rt_shim[0];
rtinfo->rti_info[RTAX_MPLS2] = rt->rt_shim[1];
rtinfo->rti_info[RTAX_MPLS3] = rt->rt_shim[2];
goto makeroute;
case RTM_ADD:
KASSERT((!(rtinfo->rti_flags & RTF_GATEWAY) ||
rtinfo->rti_info[RTAX_GATEWAY] != NULL),
("rtrequest: GATEWAY but no gateway"));
if (rtinfo->rti_ifa == NULL &&
(error = rt_getifa(rtinfo)) != 0)
gotoerr(error);
ifa = rtinfo->rti_ifa;
makeroute:
R_Malloc(rt, struct rtentry *, sizeof(struct rtentry));
if (rt == NULL) {
if (req == RTM_ADD)
kprintf("%s: alloc rtentry failed on cpu%d\n",
__func__, mycpuid);
gotoerr(ENOBUFS);
}
bzero(rt, sizeof(struct rtentry));
rt->rt_flags = RTF_UP | rtinfo->rti_flags;
rt->rt_cpuid = mycpuid;
error = rt_setgate(rt, dst, rtinfo->rti_info[RTAX_GATEWAY]);
if (error != 0) {
R_Free(rt);
gotoerr(error);
}
ndst = rt_key(rt);
if (rtinfo->rti_info[RTAX_NETMASK] != NULL)
rt_maskedcopy(dst, ndst,
rtinfo->rti_info[RTAX_NETMASK]);
else
bcopy(dst, ndst, dst->sa_len);
if (rtinfo->rti_info[RTAX_MPLS1] != NULL)
rt_setshims(rt, rtinfo->rti_info);
IFAREF(ifa);
rt->rt_ifa = ifa;
rt->rt_ifp = ifa->ifa_ifp;
rn = rnh->rnh_addaddr(ndst, rtinfo->rti_info[RTAX_NETMASK],
rnh, rt->rt_nodes);
if (rn == NULL) {
struct rtentry *oldrt;
oldrt = rtpurelookup(ndst);
if (oldrt != NULL) {
--oldrt->rt_refcnt;
if (oldrt->rt_flags & RTF_WASCLONED) {
rtrequest(RTM_DELETE, rt_key(oldrt),
oldrt->rt_gateway,
rt_mask(oldrt),
oldrt->rt_flags, NULL);
rn = rnh->rnh_addaddr(ndst,
rtinfo->rti_info[RTAX_NETMASK],
rnh, rt->rt_nodes);
}
}
}
ifa = rt->rt_ifa;
if (rn == NULL) {
if (rt->rt_gwroute != NULL)
rtfree(rt->rt_gwroute);
IFAFREE(ifa);
R_Free(rt_key(rt));
R_Free(rt);
gotoerr(EEXIST);
}
if (req == RTM_RESOLVE) {
rt->rt_rmx = (*ret_nrt)->rt_rmx;
rt->rt_rmx.rmx_pksent = 0;
if ((*ret_nrt)->rt_flags &
(RTF_CLONING | RTF_PRCLONING)) {
rt->rt_parent = *ret_nrt;
(*ret_nrt)->rt_refcnt++;
}
}
if (ifa->ifa_rtrequest != NULL)
ifa->ifa_rtrequest(req, rt);
if (req == RTM_ADD && !(rt->rt_flags & RTF_HOST) &&
rt_mask(rt) != NULL) {
struct rtfc_arg arg = { rt, rnh };
rnh->rnh_walktree_from(rnh, rt_key(rt), rt_mask(rt),
rt_fixchange, &arg);
}
#ifdef ROUTE_DEBUG
if (route_debug)
rt_print(rtinfo, rt);
#endif
if (ret_nrt != NULL) {
rt->rt_refcnt++;
*ret_nrt = rt;
}
break;
case RTM_GET:
rn = rnh->rnh_lookup(rtinfo->rti_info[RTAX_DST],
rtinfo->rti_info[RTAX_NETMASK], rnh);
if (rn == NULL)
gotoerr(ESRCH);
if (ret_nrt != NULL) {
rt = (struct rtentry *)rn;
rt->rt_refcnt++;
*ret_nrt = rt;
}
break;
default:
error = EOPNOTSUPP;
}
bad:
#ifdef ROUTE_DEBUG
if (route_debug) {
if (error)
kprintf("rti %p failed error %d\n", rtinfo, error);
else
kprintf("rti %p succeeded\n", rtinfo);
}
#endif
crit_exit();
return (error);
}
static int
rt_fixdelete(struct radix_node *rn, void *vp)
{
struct rtentry *rt = (struct rtentry *)rn;
struct rtentry *rt0 = vp;
if (rt->rt_parent == rt0 &&
!(rt->rt_flags & (RTF_PINNED | RTF_CLONING | RTF_PRCLONING))) {
return rtrequest(RTM_DELETE, rt_key(rt), NULL, rt_mask(rt),
rt->rt_flags, NULL);
}
return 0;
}
#ifdef DEBUG
static int rtfcdebug = 0;
#endif
static int
rt_fixchange(struct radix_node *rn, void *vp)
{
struct rtentry *rt = (struct rtentry *)rn;
struct rtfc_arg *ap = vp;
struct rtentry *rt0 = ap->rt0;
struct radix_node_head *rnh = ap->rnh;
u_char *xk1, *xm1, *xk2, *xmp;
int i, len, mlen;
#ifdef DEBUG
if (rtfcdebug)
kprintf("rt_fixchange: rt %p, rt0 %p\n", rt, rt0);
#endif
if (rt->rt_parent == NULL ||
(rt->rt_flags & (RTF_PINNED | RTF_CLONING | RTF_PRCLONING))) {
#ifdef DEBUG
if (rtfcdebug) kprintf("no parent, pinned or cloning\n");
#endif
return 0;
}
if (rt->rt_parent == rt0) {
#ifdef DEBUG
if (rtfcdebug) kprintf("parent match\n");
#endif
return rtrequest(RTM_DELETE, rt_key(rt), NULL, rt_mask(rt),
rt->rt_flags, NULL);
}
len = imin(rt_key(rt0)->sa_len, rt_key(rt)->sa_len);
xk1 = (u_char *)rt_key(rt0);
xm1 = (u_char *)rt_mask(rt0);
xk2 = (u_char *)rt_key(rt);
xmp = (u_char *)rt_mask(rt->rt_parent);
mlen = rt_key(rt->rt_parent)->sa_len;
if (mlen > rt_key(rt0)->sa_len) {
#ifdef DEBUG
if (rtfcdebug)
kprintf("rt_fixchange: inserting a less "
"specific route\n");
#endif
return 0;
}
for (i = rnh->rnh_treetop->rn_offset; i < mlen; i++) {
if ((xmp[i] & ~(xmp[i] ^ xm1[i])) != xmp[i]) {
#ifdef DEBUG
if (rtfcdebug)
kprintf("rt_fixchange: inserting a less "
"specific route\n");
#endif
return 0;
}
}
for (i = rnh->rnh_treetop->rn_offset; i < len; i++) {
if ((xk2[i] & xm1[i]) != xk1[i]) {
#ifdef DEBUG
if (rtfcdebug) kprintf("no match\n");
#endif
return 0;
}
}
#ifdef DEBUG
if (rtfcdebug) kprintf("deleting\n");
#endif
return rtrequest(RTM_DELETE, rt_key(rt), NULL, rt_mask(rt),
rt->rt_flags, NULL);
}
int
rt_setgate(struct rtentry *rt0, struct sockaddr *dst, struct sockaddr *gate)
{
char *space, *oldspace;
int dlen = RT_ROUNDUP(dst->sa_len), glen = RT_ROUNDUP(gate->sa_len);
struct rtentry *rt = rt0;
struct radix_node_head *rnh = rt_tables[mycpuid][dst->sa_family];
ASSERT_NETISR_NCPUS(mycpuid);
if (((rt0->rt_flags & (RTF_HOST | RTF_GATEWAY | RTF_LLINFO)) ==
(RTF_HOST | RTF_GATEWAY)) &&
dst->sa_len == gate->sa_len &&
sa_equal(dst, gate)) {
if (rt_key(rt0) != NULL)
rtrequest(RTM_DELETE, rt_key(rt0), rt0->rt_gateway,
rt_mask(rt0), rt0->rt_flags, NULL);
return EADDRNOTAVAIL;
}
if (rt->rt_gateway == NULL ||
glen > RT_ROUNDUP(rt->rt_gateway->sa_len)) {
oldspace = (char *)rt_key(rt);
R_Malloc(space, char *, dlen + glen);
if (space == NULL)
return ENOBUFS;
rt->rt_nodes->rn_key = space;
} else {
space = (char *)rt_key(rt);
oldspace = NULL;
}
rt->rt_gateway = (struct sockaddr *)(space + dlen);
bcopy(gate, rt->rt_gateway, glen);
if (oldspace != NULL) {
bcopy(dst, space, dlen);
R_Free(oldspace);
}
if (rt->rt_gwroute != NULL) {
RTFREE(rt->rt_gwroute);
rt->rt_gwroute = NULL;
}
if (rt->rt_flags & RTF_GATEWAY) {
rt->rt_gwroute = _rtlookup(gate, RTF_PRCLONING);
if (rt->rt_gwroute == rt) {
rt->rt_gwroute = NULL;
--rt->rt_refcnt;
return EDQUOT;
}
}
if (!(rt->rt_flags & RTF_HOST) && rt_mask(rt) != NULL) {
struct rtfc_arg arg = { rt, rnh };
rnh->rnh_walktree_from(rnh, rt_key(rt), rt_mask(rt),
rt_fixchange, &arg);
}
return 0;
}
static void
rt_maskedcopy(
struct sockaddr *src,
struct sockaddr *dst,
struct sockaddr *netmask)
{
u_char *cp1 = (u_char *)src;
u_char *cp2 = (u_char *)dst;
u_char *cp3 = (u_char *)netmask;
u_char *cplim = cp2 + *cp3;
u_char *cplim2 = cp2 + *cp1;
*cp2++ = *cp1++; *cp2++ = *cp1++;
cp3 += 2;
if (cplim > cplim2)
cplim = cplim2;
while (cp2 < cplim)
*cp2++ = *cp1++ & *cp3++;
if (cp2 < cplim2)
bzero(cp2, cplim2 - cp2);
}
int
rt_llroute(struct sockaddr *dst, struct rtentry *rt0, struct rtentry **drt)
{
struct rtentry *up_rt, *rt;
ASSERT_NETISR_NCPUS(mycpuid);
if (!(rt0->rt_flags & RTF_UP)) {
up_rt = rtlookup(dst);
if (up_rt == NULL)
return (EHOSTUNREACH);
up_rt->rt_refcnt--;
} else
up_rt = rt0;
if (up_rt->rt_flags & RTF_GATEWAY) {
if (up_rt->rt_gwroute == NULL) {
up_rt->rt_gwroute = rtlookup(up_rt->rt_gateway);
if (up_rt->rt_gwroute == NULL)
return (EHOSTUNREACH);
} else if (!(up_rt->rt_gwroute->rt_flags & RTF_UP)) {
rtfree(up_rt->rt_gwroute);
up_rt->rt_gwroute = rtlookup(up_rt->rt_gateway);
if (up_rt->rt_gwroute == NULL)
return (EHOSTUNREACH);
}
rt = up_rt->rt_gwroute;
} else
rt = up_rt;
if (rt->rt_flags & RTF_REJECT &&
(rt->rt_rmx.rmx_expire == 0 ||
time_uptime < rt->rt_rmx.rmx_expire))
return (rt->rt_flags & RTF_HOST ? EHOSTDOWN : EHOSTUNREACH);
*drt = rt;
return 0;
}
struct rt_purgecloned_arg {
struct ifnet *ifp;
int family;
};
static int
rt_purgecloned_callback(struct radix_node *rn, void *xap)
{
struct rtentry *rt = (struct rtentry *)rn;
struct rt_purgecloned_arg *arg = xap;
if (rt->rt_ifp == arg->ifp && rt->rt_flags & RTF_WASCLONED)
rtrequest(RTM_DELETE, rt_key(rt), NULL, rt_mask(rt), 0, NULL);
return 0;
}
void
rt_purgecloned(struct ifnet *ifp, int af)
{
struct radix_node_head *rnh;
struct rt_purgecloned_arg arg = {
.ifp = ifp,
.family = af,
};
ASSERT_NETISR0;
if ((rnh = rt_tables[mycpuid][af]) != NULL)
rnh->rnh_walktree(rnh, rt_purgecloned_callback, &arg);
}
static int
rt_setshims(struct rtentry *rt, struct sockaddr **rt_shim){
int i;
for (i=0; i<3; i++) {
struct sockaddr *shim = rt_shim[RTAX_MPLS1 + i];
int shimlen;
if (shim == NULL)
break;
shimlen = RT_ROUNDUP(shim->sa_len);
R_Malloc(rt->rt_shim[i], struct sockaddr *, shimlen);
bcopy(shim, rt->rt_shim[i], shimlen);
}
return 0;
}
#ifdef ROUTE_DEBUG
void
rt_print(struct rt_addrinfo *rtinfo, struct rtentry *rt)
{
kprintf("rti %p cpu %d route %p flags %08lx: ",
rtinfo, mycpuid, rt, rt->rt_flags);
sockaddr_print(rt_key(rt));
kprintf(" mask ");
sockaddr_print(rt_mask(rt));
kprintf(" gw ");
sockaddr_print(rt->rt_gateway);
kprintf(" ifp \"%s\"", rt->rt_ifp ? rt->rt_ifp->if_xname : "?");
kprintf(" ifa %p\n", rt->rt_ifa);
}
void
rt_addrinfo_print(int cmd, struct rt_addrinfo *rti)
{
int didit = 0;
int i;
#ifdef ROUTE_DEBUG
if (cmd == RTM_DELETE && route_debug > 1)
print_backtrace(-1);
#endif
switch(cmd) {
case RTM_ADD:
kprintf("ADD ");
break;
case RTM_RESOLVE:
kprintf("RES ");
break;
case RTM_DELETE:
kprintf("DEL ");
break;
default:
kprintf("C%02d ", cmd);
break;
}
kprintf("rti %p cpu %d flags %08x ", rti, mycpuid, rti->rti_flags);
for (i = 0; i < RTAX_MAX; ++i) {
if (rti->rti_info[i] == NULL)
continue;
if (didit)
kprintf(", ");
switch (i) {
case RTAX_DST:
kprintf("(DST ");
break;
case RTAX_GATEWAY:
kprintf("(GWY ");
break;
case RTAX_NETMASK:
kprintf("(MSK ");
break;
case RTAX_GENMASK:
kprintf("(GEN ");
break;
case RTAX_IFP:
kprintf("(IFP ");
break;
case RTAX_IFA:
kprintf("(IFA ");
break;
case RTAX_AUTHOR:
kprintf("(AUT ");
break;
case RTAX_BRD:
kprintf("(BRD ");
break;
default:
kprintf("(?%02d ", i);
break;
}
sockaddr_print(rti->rti_info[i]);
kprintf(")");
didit = 1;
}
kprintf(" ifp \"%s\"", rti->rti_ifp ? rti->rti_ifp->if_xname : "?");
kprintf(" ifa %p\n", rti->rti_ifa);
}
void
sockaddr_print(const struct sockaddr *sa)
{
const struct sockaddr_in *sa4;
const struct sockaddr_in6 *sa6;
int len;
int i;
if (sa == NULL) {
kprintf("NULL");
return;
}
switch (sa->sa_family) {
case AF_INET:
sa4 = (const struct sockaddr_in *)sa;
kprintf("INET %d %d.%d.%d.%d",
ntohs(sa4->sin_port),
(ntohl(sa4->sin_addr.s_addr) >> 24) & 255,
(ntohl(sa4->sin_addr.s_addr) >> 16) & 255,
(ntohl(sa4->sin_addr.s_addr) >> 8) & 255,
(ntohl(sa4->sin_addr.s_addr) >> 0) & 255
);
break;
case AF_INET6:
sa6 = (const struct sockaddr_in6 *)sa;
kprintf("INET6 %d %04x:%04x%04x:%04x:%04x:%04x:%04x:%04x",
ntohs(sa6->sin6_port),
ntohs(sa6->sin6_addr.s6_addr16[0]),
ntohs(sa6->sin6_addr.s6_addr16[1]),
ntohs(sa6->sin6_addr.s6_addr16[2]),
ntohs(sa6->sin6_addr.s6_addr16[3]),
ntohs(sa6->sin6_addr.s6_addr16[4]),
ntohs(sa6->sin6_addr.s6_addr16[5]),
ntohs(sa6->sin6_addr.s6_addr16[6]),
ntohs(sa6->sin6_addr.s6_addr16[7])
);
break;
default:
kprintf("AF%d ", sa->sa_family);
len = sa->sa_len - offsetof(struct sockaddr, sa_data[0]);
while (len > 0 && sa->sa_data[len-1] == 0)
--len;
for (i = 0; i < len; ++i) {
if (i)
kprintf(".");
kprintf("%d", (unsigned char)sa->sa_data[i]);
}
break;
}
}
#endif
int
rtinit(struct ifaddr *ifa, int cmd, int flags)
{
struct sockaddr *dst, *deldst, *netmask;
struct mbuf *m = NULL;
struct radix_node_head *rnh;
struct radix_node *rn;
struct rt_addrinfo rtinfo;
int error;
ASSERT_NETISR0;
if (flags & RTF_HOST) {
dst = ifa->ifa_dstaddr;
netmask = NULL;
} else {
dst = ifa->ifa_addr;
netmask = ifa->ifa_netmask;
}
if (cmd == RTM_DELETE) {
if (netmask != NULL) {
m = m_get(M_NOWAIT, MT_SONAME);
if (m == NULL)
return (ENOBUFS);
mbuftrackid(m, 34);
deldst = mtod(m, struct sockaddr *);
rt_maskedcopy(dst, deldst, netmask);
dst = deldst;
}
if ((rnh = rt_tables[mycpuid][dst->sa_family]) == NULL ||
(rn = rnh->rnh_lookup(dst, netmask, rnh)) == NULL ||
((struct rtentry *)rn)->rt_ifa != ifa ||
!sa_equal((const struct sockaddr *)rn->rn_key, dst)) {
if (m != NULL)
m_free(m);
return (flags & RTF_HOST ? EHOSTUNREACH : ENETUNREACH);
}
#if 0
else {
return (flags & RTF_HOST ? EHOSTUNREACH : ENETUNREACH);
}
#endif
}
bzero(&rtinfo, sizeof(struct rt_addrinfo));
rtinfo.rti_info[RTAX_DST] = dst;
rtinfo.rti_info[RTAX_GATEWAY] = ifa->ifa_addr;
rtinfo.rti_info[RTAX_NETMASK] = netmask;
rtinfo.rti_flags = flags | ifa->ifa_flags;
rtinfo.rti_ifa = ifa;
error = rtrequest1_global(cmd, &rtinfo, rtinit_rtrequest_callback, ifa,
RTREQ_PRIO_HIGH);
if (m != NULL)
m_free(m);
return (error);
}
static void
rtinit_rtrequest_callback(int cmd, int error,
struct rt_addrinfo *rtinfo, struct rtentry *rt,
void *arg)
{
struct ifaddr *ifa = arg;
if (error == 0 && rt) {
if (mycpuid == 0)
rt_newaddrmsg(cmd, ifa, error, rt);
if (cmd == RTM_DELETE) {
if (rt->rt_refcnt == 0) {
++rt->rt_refcnt;
rtfree(rt);
}
}
}
}
struct netmsg_rts {
struct netmsg_base base;
int req;
struct rt_addrinfo *rtinfo;
rtsearch_callback_func_t callback;
void *arg;
boolean_t exact_match;
int found_cnt;
};
int
rtsearch_global(int req, struct rt_addrinfo *rtinfo,
rtsearch_callback_func_t callback, void *arg, boolean_t exact_match,
boolean_t req_prio)
{
struct netmsg_rts msg;
int flags = 0;
if (req_prio)
flags = MSGF_PRIORITY;
netmsg_init(&msg.base, NULL, &curthread->td_msgport, flags,
rtsearch_msghandler);
msg.req = req;
msg.rtinfo = rtinfo;
msg.callback = callback;
msg.arg = arg;
msg.exact_match = exact_match;
msg.found_cnt = 0;
return (netisr_domsg_global(&msg.base));
}
static void
rtsearch_msghandler(netmsg_t msg)
{
struct netmsg_rts *rmsg = (void *)msg;
struct rt_addrinfo rtinfo;
struct radix_node_head *rnh;
struct rtentry *rt;
int error;
ASSERT_NETISR_NCPUS(mycpuid);
rtinfo = *rmsg->rtinfo;
if ((rnh = rt_tables[mycpuid][rtinfo.rti_dst->sa_family]) == NULL) {
if (mycpuid != 0)
panic("partially initialized routing tables");
netisr_replymsg(&rmsg->base, EAFNOSUPPORT);
return;
}
if (rtinfo.rti_flags & RTF_HOST) {
rtinfo.rti_netmask = NULL;
rtinfo.rti_flags &= ~(RTF_CLONING | RTF_PRCLONING);
}
rt = (struct rtentry *)
rnh->rnh_lookup(rtinfo.rti_dst, rtinfo.rti_netmask, rnh);
if (rt != NULL && rmsg->exact_match &&
((rt->rt_flags ^ rtinfo.rti_flags) & RTF_HOST))
rt = NULL;
if (rt == NULL) {
error = 0;
} else {
rmsg->found_cnt++;
rt->rt_refcnt++;
error = rmsg->callback(rmsg->req, &rtinfo, rt, rmsg->arg,
rmsg->found_cnt);
rt->rt_refcnt--;
if (error == EJUSTRETURN) {
netisr_replymsg(&rmsg->base, 0);
return;
}
}
if (error) {
KKASSERT(rmsg->found_cnt > 0);
if (rmsg->req != RTM_GET && rmsg->found_cnt > 1) {
panic("rtsearch_msghandler: unrecoverable error "
"cpu %d", mycpuid);
}
netisr_replymsg(&rmsg->base, error);
} else {
if (rmsg->found_cnt == 0) {
error = ESRCH;
}
netisr_forwardmsg_error(&rmsg->base, mycpuid + 1, error);
}
}
int
rtmask_add_global(struct sockaddr *mask, boolean_t req_prio)
{
struct netmsg_base msg;
int flags = 0;
if (req_prio)
flags = MSGF_PRIORITY;
netmsg_init(&msg, NULL, &curthread->td_msgport, flags,
rtmask_add_msghandler);
msg.lmsg.u.ms_resultp = mask;
return (netisr_domsg_global(&msg));
}
struct sockaddr *
_rtmask_lookup(struct sockaddr *mask, boolean_t search)
{
struct radix_node *n;
#define clen(s) (*(const u_char *)(s))
n = rn_addmask(mask, search, true, rn_cpumaskhead(mycpuid));
if (n != NULL &&
mask->sa_len >= clen(n->rn_key) &&
bcmp((const u_char *)mask + 1,
n->rn_key + 1, clen(n->rn_key) - 1) == 0) {
return __DECONST(struct sockaddr *, n->rn_key);
} else {
return NULL;
}
#undef clen
}
static void
rtmask_add_msghandler(netmsg_t msg)
{
struct sockaddr *mask = msg->lmsg.u.ms_resultp;
ASSERT_NETISR_NCPUS(mycpuid);
if (rtmask_lookup(mask) == NULL) {
netisr_replymsg(&msg->base, ENOBUFS);
return;
}
netisr_forwardmsg(&msg->base, mycpuid + 1);
}
SYSINIT(route, SI_SUB_PROTO_DOMAIN, SI_ORDER_THIRD, route_init, 0);
struct rtchange_arg {
struct ifaddr *old_ifa;
struct ifaddr *new_ifa;
struct rtentry *rt;
int changed;
};
static void
rtchange_ifa(struct rtentry *rt, struct rtchange_arg *ap)
{
if (rt->rt_ifa->ifa_rtrequest != NULL)
rt->rt_ifa->ifa_rtrequest(RTM_DELETE, rt);
IFAFREE(rt->rt_ifa);
IFAREF(ap->new_ifa);
rt->rt_ifa = ap->new_ifa;
rt->rt_ifp = ap->new_ifa->ifa_ifp;
if (rt->rt_ifa->ifa_rtrequest != NULL)
rt->rt_ifa->ifa_rtrequest(RTM_ADD, rt);
ap->changed = 1;
}
static int
rtchange_callback(struct radix_node *rn, void *xap)
{
struct rtchange_arg *ap = xap;
struct rtentry *rt = (struct rtentry *)rn;
if (rt->rt_ifa == ap->old_ifa) {
if (rt->rt_flags & (RTF_CLONING | RTF_PRCLONING)) {
ap->rt = rt;
return EJUSTRETURN;
}
rtchange_ifa(rt, ap);
}
return 0;
}
struct netmsg_rtchange {
struct netmsg_base base;
struct ifaddr *old_ifa;
struct ifaddr *new_ifa;
int changed;
};
static void
rtchange_dispatch(netmsg_t msg)
{
struct netmsg_rtchange *rmsg = (void *)msg;
struct radix_node_head *rnh;
struct rtchange_arg arg;
int cpu;
cpu = mycpuid;
ASSERT_NETISR_NCPUS(cpu);
memset(&arg, 0, sizeof(arg));
arg.old_ifa = rmsg->old_ifa;
arg.new_ifa = rmsg->new_ifa;
rnh = rt_tables[cpu][AF_INET];
for (;;) {
int error;
KKASSERT(arg.rt == NULL);
error = rnh->rnh_walktree(rnh, rtchange_callback, &arg);
if (arg.rt != NULL) {
struct rtentry *rt;
rt = arg.rt;
arg.rt = NULL;
rtchange_ifa(rt, &arg);
} else {
break;
}
}
if (arg.changed)
rmsg->changed = 1;
netisr_forwardmsg(&rmsg->base, cpu + 1);
}
int
rtchange(struct ifaddr *old_ifa, struct ifaddr *new_ifa)
{
struct netmsg_rtchange msg;
netmsg_init(&msg.base, NULL, &curthread->td_msgport, MSGF_PRIORITY,
rtchange_dispatch);
msg.old_ifa = old_ifa;
msg.new_ifa = new_ifa;
msg.changed = 0;
netisr_domsg_global(&msg.base);
if (msg.changed) {
old_ifa->ifa_flags &= ~IFA_ROUTE;
new_ifa->ifa_flags |= IFA_ROUTE;
return 0;
} else {
return ENOENT;
}
}