#include <sys/param.h>
#include <sys/systm.h>
#include <sys/kernel.h>
#include <sys/sysctl.h>
#include <sys/queue.h>
#include <sys/socket.h>
#include <sys/socketvar.h>
#include <sys/mbuf.h>
#include <sys/syslog.h>
#include <sys/globaldata.h>
#include <sys/thread2.h>
#include <net/if.h>
#include <net/route.h>
#include <net/netisr2.h>
#include <net/netmsg2.h>
#include <netinet/in.h>
#include <netinet/ip_var.h>
#include <netinet/in_var.h>
#include <netinet/ip6.h>
#include <netinet6/ip6_var.h>
#include <netinet/icmp6.h>
#include <netinet6/nd6.h>
#include <netinet/tcp.h>
#include <netinet/tcp_seq.h>
#include <netinet/tcp_timer.h>
#include <netinet/tcp_var.h>
struct in6_rttimo_ctx {
struct callout timo_ch;
struct netmsg_base timo_nmsg;
struct radix_node_head *timo_rnh;
} __cachealign;
static struct in6_rttimo_ctx in6_rtqtimo_ctx[MAXCPU];
static struct in6_rttimo_ctx in6_mtutimo_ctx[MAXCPU];
extern int in6_inithead (void **head, int off);
#define RTPRF_OURS RTF_PROTO3
static struct radix_node *
in6_addroute(const void *key, const void *mask, struct radix_node_head *head,
struct radix_node *nodes)
{
struct rtentry *rt = (struct rtentry *)nodes;
struct sockaddr_in6 *sin6 = (struct sockaddr_in6 *)rt_key(rt);
struct radix_node *ret;
if (IN6_IS_ADDR_MULTICAST(&sin6->sin6_addr))
rt->rt_flags |= RTF_MULTICAST;
if (!(rt->rt_flags & (RTF_HOST | RTF_CLONING | RTF_MULTICAST))) {
rt->rt_flags |= RTF_PRCLONING;
}
if (rt->rt_flags & RTF_HOST) {
if (IN6_ARE_ADDR_EQUAL(&satosin6(rt->rt_ifa->ifa_addr)
->sin6_addr,
&sin6->sin6_addr)) {
rt->rt_flags |= RTF_LOCAL;
}
}
if (!rt->rt_rmx.rmx_mtu && !(rt->rt_rmx.rmx_locks & RTV_MTU) &&
rt->rt_ifp != NULL)
rt->rt_rmx.rmx_mtu = IN6_LINKMTU(rt->rt_ifp);
ret = rn_addroute(key, mask, head, nodes);
if (ret == NULL && rt->rt_flags & RTF_HOST) {
struct rtentry *rt2;
rt2 = rtpurelookup((struct sockaddr *)sin6);
if (rt2 != NULL) {
--rt2->rt_refcnt;
if (rt2->rt_flags & RTF_LLINFO &&
rt2->rt_flags & RTF_HOST &&
rt2->rt_gateway &&
rt2->rt_gateway->sa_family == AF_LINK) {
rtrequest(RTM_DELETE, rt_key(rt2),
rt2->rt_gateway, rt_mask(rt2),
rt2->rt_flags, NULL);
ret = rn_addroute(key, mask, head, nodes);
}
}
} else if (ret == NULL && rt->rt_flags & RTF_CLONING) {
struct rtentry *rt2;
rt2 = rtpurelookup((struct sockaddr *)sin6);
if (rt2 != NULL) {
if ((rt2->rt_flags & (RTF_CLONING|RTF_HOST|RTF_GATEWAY))
== RTF_CLONING &&
rt2->rt_gateway &&
rt2->rt_gateway->sa_family == AF_LINK &&
rt2->rt_ifp == rt->rt_ifp) {
ret = rt2->rt_nodes;
}
--rt2->rt_refcnt;
}
}
return ret;
}
static struct radix_node *
in6_matchroute(const void *key, struct radix_node_head *head)
{
struct radix_node *rn = rn_match(key, head);
struct rtentry *rt = (struct rtentry *)rn;
if (rt != NULL && rt->rt_refcnt == 0) {
if (rt->rt_flags & RTPRF_OURS) {
rt->rt_flags &= ~RTPRF_OURS;
rt->rt_rmx.rmx_expire = 0;
}
}
return rn;
}
SYSCTL_DECL(_net_inet6_ip6);
static int rtq_reallyold = 60*60;
SYSCTL_INT(_net_inet6_ip6, IPV6CTL_RTEXPIRE, rtexpire,
CTLFLAG_RW, &rtq_reallyold , 0, "Default expiration time on cloned routes");
static int rtq_minreallyold = 10;
SYSCTL_INT(_net_inet6_ip6, IPV6CTL_RTMINEXPIRE, rtminexpire, CTLFLAG_RW,
&rtq_minreallyold , 0, "Minimum time to attempt to hold onto cloned routes");
static int rtq_toomany = 128;
SYSCTL_INT(_net_inet6_ip6, IPV6CTL_RTMAXCACHE, rtmaxcache,
CTLFLAG_RW, &rtq_toomany , 0, "Upper limit on cloned routes");
static void
in6_clsroute(struct radix_node *rn, struct radix_node_head *head)
{
struct rtentry *rt = (struct rtentry *)rn;
if (!(rt->rt_flags & RTF_UP))
return;
if ((rt->rt_flags & (RTF_LLINFO | RTF_HOST)) != RTF_HOST)
return;
if ((rt->rt_flags & (RTF_WASCLONED | RTPRF_OURS)) != RTF_WASCLONED)
return;
if (rtq_reallyold != 0) {
rt->rt_flags |= RTPRF_OURS;
rt->rt_rmx.rmx_expire = time_uptime + rtq_reallyold;
} else {
rtrequest(RTM_DELETE, rt_key(rt), rt->rt_gateway, rt_mask(rt),
rt->rt_flags, &rt);
}
}
struct rtqk_arg {
struct radix_node_head *rnh;
int mode;
int updating;
int draining;
int killed;
int found;
time_t nextstop;
};
static int
in6_rtqkill(struct radix_node *rn, void *rock)
{
struct rtqk_arg *ap = rock;
struct rtentry *rt = (struct rtentry *)rn;
int err;
if (rt->rt_flags & RTPRF_OURS) {
ap->found++;
if (ap->draining || rt->rt_rmx.rmx_expire <= time_uptime) {
if (rt->rt_refcnt > 0)
panic("rtqkill route really not free");
err = rtrequest(RTM_DELETE, rt_key(rt), rt->rt_gateway,
rt_mask(rt), rt->rt_flags, NULL);
if (err)
log(LOG_WARNING, "in6_rtqkill: error %d", err);
else
ap->killed++;
} else {
if (ap->updating &&
(rt->rt_rmx.rmx_expire - time_uptime >
rtq_reallyold)) {
rt->rt_rmx.rmx_expire =
time_uptime + rtq_reallyold;
}
ap->nextstop = lmin(ap->nextstop,
rt->rt_rmx.rmx_expire);
}
}
return 0;
}
#define RTQ_TIMEOUT 60*10
static int rtq_timeout = RTQ_TIMEOUT;
static void
in6_rtqtimo(void *arg __unused)
{
int cpuid = mycpuid;
struct lwkt_msg *lmsg = &in6_rtqtimo_ctx[cpuid].timo_nmsg.lmsg;
crit_enter();
if (lmsg->ms_flags & MSGF_DONE)
lwkt_sendmsg_oncpu(netisr_cpuport(cpuid), lmsg);
crit_exit();
}
static void
in6_rtqtimo_dispatch(netmsg_t nmsg)
{
struct in6_rttimo_ctx *ctx = &in6_rtqtimo_ctx[mycpuid];
struct radix_node_head *rnh = ctx->timo_rnh;
struct rtqk_arg arg;
struct timeval atv;
static time_t last_adjusted_timeout = 0;
ASSERT_NETISR_NCPUS(mycpuid);
crit_enter();
lwkt_replymsg(&nmsg->lmsg, 0);
crit_exit();
arg.found = arg.killed = 0;
arg.rnh = rnh;
arg.nextstop = time_uptime + rtq_timeout;
arg.draining = arg.updating = 0;
rnh->rnh_walktree(rnh, in6_rtqkill, &arg);
if ((arg.found - arg.killed > rtq_toomany)
&& (int)(time_uptime - last_adjusted_timeout) >= rtq_timeout
&& rtq_reallyold > rtq_minreallyold) {
rtq_reallyold = 2*rtq_reallyold / 3;
if (rtq_reallyold < rtq_minreallyold) {
rtq_reallyold = rtq_minreallyold;
}
last_adjusted_timeout = time_uptime;
#ifdef DIAGNOSTIC
log(LOG_DEBUG, "in6_rtqtimo: adjusted rtq_reallyold to %d",
rtq_reallyold);
#endif
arg.found = arg.killed = 0;
arg.updating = 1;
rnh->rnh_walktree(rnh, in6_rtqkill, &arg);
}
atv.tv_usec = 0;
atv.tv_sec = arg.nextstop - time_uptime;
if ((int)atv.tv_sec < 1) {
atv.tv_sec = 1;
arg.nextstop = time_uptime + atv.tv_sec;
}
if ((int)atv.tv_sec > rtq_timeout) {
atv.tv_sec = rtq_timeout;
arg.nextstop = time_uptime + atv.tv_sec;
}
callout_reset(&ctx->timo_ch, tvtohz_high(&atv), in6_rtqtimo, NULL);
}
struct mtuex_arg {
struct radix_node_head *rnh;
time_t nextstop;
};
static int
in6_mtuexpire(struct radix_node *rn, void *rock)
{
struct rtentry *rt = (struct rtentry *)rn;
struct mtuex_arg *ap = rock;
if (!rt)
panic("rt == NULL in in6_mtuexpire");
if (rt->rt_rmx.rmx_expire && !(rt->rt_flags & RTF_PROBEMTU)) {
if (rt->rt_rmx.rmx_expire <= time_uptime) {
rt->rt_flags |= RTF_PROBEMTU;
} else {
ap->nextstop = lmin(ap->nextstop,
rt->rt_rmx.rmx_expire);
}
}
return 0;
}
#define MTUTIMO_DEFAULT (60*1)
static void
in6_mtutimo(void *arg __unused)
{
int cpuid = mycpuid;
struct lwkt_msg *lmsg = &in6_mtutimo_ctx[cpuid].timo_nmsg.lmsg;
crit_enter();
if (lmsg->ms_flags & MSGF_DONE)
lwkt_sendmsg_oncpu(netisr_cpuport(cpuid), lmsg);
crit_exit();
}
static void
in6_mtutimo_dispatch(netmsg_t nmsg)
{
struct in6_rttimo_ctx *ctx = &in6_mtutimo_ctx[mycpuid];
struct radix_node_head *rnh = ctx->timo_rnh;
struct mtuex_arg arg;
struct timeval atv;
ASSERT_NETISR_NCPUS(mycpuid);
crit_enter();
lwkt_replymsg(&nmsg->lmsg, 0);
crit_exit();
arg.rnh = rnh;
arg.nextstop = time_uptime + MTUTIMO_DEFAULT;
rnh->rnh_walktree(rnh, in6_mtuexpire, &arg);
atv.tv_usec = 0;
atv.tv_sec = arg.nextstop - time_uptime;
if ((int)atv.tv_sec < 1) {
atv.tv_sec = 1;
arg.nextstop = time_uptime + atv.tv_sec;
}
if ((int)atv.tv_sec > rtq_timeout) {
atv.tv_sec = rtq_timeout;
arg.nextstop = time_uptime + atv.tv_sec;
}
callout_reset(&ctx->timo_ch, tvtohz_high(&atv), in6_mtutimo, NULL);
}
#if 0
void
in6_rtqdrain(void)
{
struct radix_node_head *rnh = rt_tables[mycpuid][AF_INET6];
struct rtqk_arg arg;
arg.found = arg.killed = 0;
arg.rnh = rnh;
arg.nextstop = 0;
arg.draining = 1;
arg.updating = 0;
crit_enter();
rnh->rnh_walktree(rnh, in6_rtqkill, &arg);
crit_exit();
}
#endif
int
in6_inithead(void **head, int off)
{
struct radix_node_head *rnh;
struct in6_rttimo_ctx *ctx;
int cpuid = mycpuid;
rnh = *head;
KKASSERT(rnh == rt_tables[cpuid][AF_INET6]);
if (!rn_inithead(&rnh, rn_cpumaskhead(cpuid), off))
return 0;
*head = rnh;
rnh->rnh_addaddr = in6_addroute;
rnh->rnh_matchaddr = in6_matchroute;
rnh->rnh_close = in6_clsroute;
ctx = &in6_rtqtimo_ctx[cpuid];
ctx->timo_rnh = rnh;
callout_init_mp(&ctx->timo_ch);
netmsg_init(&ctx->timo_nmsg, NULL, &netisr_adone_rport, MSGF_PRIORITY,
in6_rtqtimo_dispatch);
ctx = &in6_mtutimo_ctx[cpuid];
ctx->timo_rnh = rnh;
callout_init_mp(&ctx->timo_ch);
netmsg_init(&ctx->timo_nmsg, NULL, &netisr_adone_rport, MSGF_PRIORITY,
in6_mtutimo_dispatch);
in6_rtqtimo(NULL);
in6_mtutimo(NULL);
return 1;
}