#include "opt_carp.h"
#include <sys/param.h>
#include <sys/systm.h>
#include <sys/kernel.h>
#include <sys/sysctl.h>
#include <sys/socket.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/if_var.h>
#ifdef CARP
#include <net/if_types.h>
#endif
#include <net/netmsg2.h>
#include <net/netisr2.h>
#include <netinet/in.h>
#include <netinet/in_var.h>
#include <netinet/ip_var.h>
#include <netinet/ip_flow.h>
#define RTPRF_EXPIRING RTF_PROTO3
struct in_rtq_pcpu {
struct radix_node_head *rnh;
struct callout timo_ch;
struct netmsg_base timo_nmsg;
time_t lastdrain;
int draining;
struct netmsg_base drain_nmsg;
} __cachealign;
static void in_rtqtimo(void *);
static struct in_rtq_pcpu in_rtq_pcpu[MAXCPU];
static struct radix_node *
in_addroute(const void *key, const void *mask, struct radix_node_head *head,
struct radix_node *nodes)
{
struct rtentry *rt = (struct rtentry *)nodes;
struct sockaddr_in *sin = (struct sockaddr_in *)rt_key(rt);
struct radix_node *ret;
struct in_ifaddr_container *iac;
struct in_ifaddr *ia;
if (IN_MULTICAST(ntohl(sin->sin_addr.s_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 | RTF_LOCAL)) == RTF_HOST) {
if (in_broadcast(sin->sin_addr, rt->rt_ifp)) {
rt->rt_flags |= RTF_BROADCAST;
} else if (satosin(rt->rt_ifa->ifa_addr)->sin_addr.s_addr ==
sin->sin_addr.s_addr) {
rt->rt_flags |= RTF_LOCAL;
} else {
LIST_FOREACH(iac, INADDR_HASH(sin->sin_addr.s_addr),
ia_hash) {
ia = iac->ia;
if (sin->sin_addr.s_addr ==
ia->ia_addr.sin_addr.s_addr) {
rt->rt_flags |= RTF_LOCAL;
IFAREF(&ia->ia_ifa);
IFAFREE(rt->rt_ifa);
rt->rt_ifa = &ia->ia_ifa;
rt->rt_ifp = rt->rt_ifa->ifa_ifp;
break;
}
}
}
}
if (rt->rt_rmx.rmx_mtu == 0 &&
!(rt->rt_rmx.rmx_locks & RTV_MTU) &&
rt->rt_ifp != NULL)
rt->rt_rmx.rmx_mtu = rt->rt_ifp->if_mtu;
ret = rn_addroute(key, mask, head, nodes);
if (ret == NULL && (rt->rt_flags & RTF_HOST)) {
struct rtentry *oldrt;
oldrt = rtpurelookup((struct sockaddr *)sin);
if (oldrt != NULL) {
--oldrt->rt_refcnt;
if ((oldrt->rt_flags & RTF_LLINFO) &&
(oldrt->rt_flags & RTF_HOST) &&
oldrt->rt_gateway &&
oldrt->rt_gateway->sa_family == AF_LINK) {
rtrequest(RTM_DELETE, rt_key(oldrt),
oldrt->rt_gateway, rt_mask(oldrt),
oldrt->rt_flags, NULL);
ret = rn_addroute(key, mask, head, nodes);
}
}
}
if (ret != NULL &&
(rt->rt_flags &
(RTF_MULTICAST | RTF_BROADCAST | RTF_WASCLONED)) == 0)
ipflow_flush_oncpu();
return ret;
}
static struct radix_node *
in_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_EXPIRING) {
rt->rt_flags &= ~RTPRF_EXPIRING;
rt->rt_rmx.rmx_expire = 0;
}
}
return rn;
}
static int rtq_reallyold = 60*60;
SYSCTL_INT(_net_inet_ip, IPCTL_RTEXPIRE, rtexpire, CTLFLAG_RW,
&rtq_reallyold , 0,
"Default expiration time on cloned routes");
static int rtq_minreallyold = 10;
SYSCTL_INT(_net_inet_ip, IPCTL_RTMINEXPIRE, rtminexpire, CTLFLAG_RW,
&rtq_minreallyold , 0,
"Minimum time to attempt to hold onto cloned routes");
static int rtq_toomany = 128;
SYSCTL_INT(_net_inet_ip, IPCTL_RTMAXCACHE, rtmaxcache, CTLFLAG_RW,
&rtq_toomany , 0, "Upper limit on cloned routes");
static void
in_closeroute(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_EXPIRING)) != RTF_WASCLONED)
return;
if (rtq_reallyold != 0) {
rt->rt_flags |= RTPRF_EXPIRING;
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 draining;
int killed;
int found;
int updating;
time_t nextstop;
};
static int
in_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_EXPIRING) {
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, "in_rtqkill: error %d\n", err);
else
ap->killed++;
} else {
if (ap->updating &&
(int)(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
in_rtqtimo_dispatch(netmsg_t nmsg)
{
struct rtqk_arg arg;
struct timeval atv;
static time_t last_adjusted_timeout = 0;
struct in_rtq_pcpu *pcpu = &in_rtq_pcpu[mycpuid];
struct radix_node_head *rnh = pcpu->rnh;
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, in_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, "in_rtqtimo: adjusted rtq_reallyold to %d\n",
rtq_reallyold);
#endif
arg.found = arg.killed = 0;
arg.updating = 1;
rnh->rnh_walktree(rnh, in_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(&pcpu->timo_ch, tvtohz_high(&atv), in_rtqtimo, NULL);
}
static void
in_rtqtimo(void *arg __unused)
{
int cpuid = mycpuid;
struct lwkt_msg *lmsg = &in_rtq_pcpu[cpuid].timo_nmsg.lmsg;
crit_enter();
if (lmsg->ms_flags & MSGF_DONE)
lwkt_sendmsg_oncpu(netisr_cpuport(cpuid), lmsg);
crit_exit();
}
static void
in_rtqdrain_oncpu(struct in_rtq_pcpu *pcpu)
{
struct radix_node_head *rnh = rt_tables[mycpuid][AF_INET];
struct rtqk_arg arg;
ASSERT_NETISR_NCPUS(mycpuid);
arg.found = arg.killed = 0;
arg.rnh = rnh;
arg.nextstop = 0;
arg.draining = 1;
arg.updating = 0;
rnh->rnh_walktree(rnh, in_rtqkill, &arg);
pcpu->lastdrain = time_uptime;
}
static void
in_rtqdrain_dispatch(netmsg_t nmsg)
{
struct in_rtq_pcpu *pcpu = &in_rtq_pcpu[mycpuid];
crit_enter();
lwkt_replymsg(&nmsg->lmsg, 0);
crit_exit();
in_rtqdrain_oncpu(pcpu);
pcpu->draining = 0;
}
static void
in_rtqdrain_ipi(void *arg __unused)
{
int cpu = mycpuid;
struct lwkt_msg *msg = &in_rtq_pcpu[cpu].drain_nmsg.lmsg;
crit_enter();
if (msg->ms_flags & MSGF_DONE)
lwkt_sendmsg_oncpu(netisr_cpuport(cpu), msg);
crit_exit();
}
void
in_rtqdrain(void)
{
cpumask_t mask;
int cpu;
CPUMASK_ASSBMASK(mask, netisr_ncpus);
CPUMASK_ANDMASK(mask, smp_active_mask);
cpu = mycpuid;
if (IN_NETISR_NCPUS(cpu)) {
in_rtqdrain_oncpu(&in_rtq_pcpu[cpu]);
CPUMASK_NANDBIT(mask, cpu);
}
for (cpu = 0; cpu < netisr_ncpus; ++cpu) {
struct in_rtq_pcpu *pcpu = &in_rtq_pcpu[cpu];
if (!CPUMASK_TESTBIT(mask, cpu))
continue;
if (pcpu->draining || pcpu->lastdrain == time_uptime) {
CPUMASK_NANDBIT(mask, cpu);
continue;
}
pcpu->draining = 1;
}
if (CPUMASK_TESTNZERO(mask))
lwkt_send_ipiq_mask(mask, in_rtqdrain_ipi, NULL);
}
int
in_inithead(void **head, int off)
{
struct radix_node_head *rnh;
struct in_rtq_pcpu *pcpu;
int cpuid = mycpuid;
rnh = *head;
KKASSERT(rnh == rt_tables[cpuid][AF_INET]);
if (!rn_inithead(&rnh, rn_cpumaskhead(cpuid), off))
return 0;
*head = rnh;
rnh->rnh_addaddr = in_addroute;
rnh->rnh_matchaddr = in_matchroute;
rnh->rnh_close = in_closeroute;
pcpu = &in_rtq_pcpu[cpuid];
pcpu->rnh = rnh;
callout_init_mp(&pcpu->timo_ch);
netmsg_init(&pcpu->timo_nmsg, NULL, &netisr_adone_rport, MSGF_PRIORITY,
in_rtqtimo_dispatch);
netmsg_init(&pcpu->drain_nmsg, NULL, &netisr_adone_rport, MSGF_PRIORITY,
in_rtqdrain_dispatch);
in_rtqtimo(NULL);
return 1;
}
struct in_ifadown_arg {
struct radix_node_head *rnh;
struct ifaddr *ifa;
int del;
};
static int
in_ifadownkill(struct radix_node *rn, void *xap)
{
struct in_ifadown_arg *ap = xap;
struct rtentry *rt = (struct rtentry *)rn;
int err;
if (rt->rt_ifa == ap->ifa &&
(ap->del || !(rt->rt_flags & RTF_STATIC))) {
rt->rt_flags &= ~(RTF_CLONING | RTF_PRCLONING);
err = rtrequest(RTM_DELETE, rt_key(rt), rt->rt_gateway,
rt_mask(rt), rt->rt_flags, NULL);
if (err)
log(LOG_WARNING, "in_ifadownkill: error %d\n", err);
}
return 0;
}
struct netmsg_ifadown {
struct netmsg_base base;
struct ifaddr *ifa;
int del;
};
static void
in_ifadown_dispatch(netmsg_t msg)
{
struct netmsg_ifadown *rmsg = (void *)msg;
struct radix_node_head *rnh;
struct ifaddr *ifa = rmsg->ifa;
struct in_ifadown_arg arg;
int cpu;
cpu = mycpuid;
ASSERT_NETISR_NCPUS(cpu);
arg.rnh = rnh = rt_tables[cpu][AF_INET];
arg.ifa = ifa;
arg.del = rmsg->del;
rnh->rnh_walktree(rnh, in_ifadownkill, &arg);
ifa->ifa_flags &= ~IFA_ROUTE;
netisr_forwardmsg(&msg->base, cpu + 1);
}
int
in_ifadown_force(struct ifaddr *ifa, int delete)
{
struct netmsg_ifadown msg;
if (ifa->ifa_addr->sa_family != AF_INET)
return 1;
netmsg_init(&msg.base, NULL, &curthread->td_msgport, MSGF_PRIORITY,
in_ifadown_dispatch);
msg.ifa = ifa;
msg.del = delete;
netisr_domsg_global(&msg.base);
return 0;
}
int
in_ifadown(struct ifaddr *ifa, int delete)
{
#ifdef CARP
if (ifa->ifa_ifp->if_type == IFT_CARP)
return 0;
#endif
return in_ifadown_force(ifa, delete);
}