freebsd-dev/sys/net/flowtable.c

1076 lines
28 KiB
C

/**************************************************************************
Copyright (c) 2008-2009, BitGravity Inc.
All rights reserved.
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are met:
1. Redistributions of source code must retain the above copyright notice,
this list of conditions and the following disclaimer.
2. Neither the name of the BitGravity Corporation nor the names of its
contributors may be used to endorse or promote products derived from
this software without specific prior written permission.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
POSSIBILITY OF SUCH DAMAGE.
***************************************************************************/
#include "opt_route.h"
#include "opt_mpath.h"
#include <sys/cdefs.h>
__FBSDID("$FreeBSD$");
#include <sys/param.h>
#include <sys/types.h>
#include <sys/bitstring.h>
#include <sys/callout.h>
#include <sys/kernel.h>
#include <sys/kthread.h>
#include <sys/limits.h>
#include <sys/malloc.h>
#include <sys/mbuf.h>
#include <sys/proc.h>
#include <sys/sched.h>
#include <sys/smp.h>
#include <sys/socket.h>
#include <sys/syslog.h>
#include <sys/sysctl.h>
#include <sys/vimage.h>
#include <net/if.h>
#include <net/if_llatbl.h>
#include <net/if_var.h>
#include <net/route.h>
#include <net/vnet.h>
#include <net/flowtable.h>
#include <netinet/in.h>
#include <netinet/in_systm.h>
#include <netinet/in_var.h>
#include <netinet/if_ether.h>
#include <netinet/ip.h>
#include <netinet/tcp.h>
#include <netinet/udp.h>
#include <netinet/sctp.h>
/*
* Taken from http://burtleburtle.net/bob/c/lookup3.c
*/
#define rot(x,k) (((x)<<(k)) | ((x)>>(32-(k))))
/*
-------------------------------------------------------------------------------
mix -- mix 3 32-bit values reversibly.
This is reversible, so any information in (a,b,c) before mix() is
still in (a,b,c) after mix().
If four pairs of (a,b,c) inputs are run through mix(), or through
mix() in reverse, there are at least 32 bits of the output that
are sometimes the same for one pair and different for another pair.
This was tested for:
* pairs that differed by one bit, by two bits, in any combination
of top bits of (a,b,c), or in any combination of bottom bits of
(a,b,c).
* "differ" is defined as +, -, ^, or ~^. For + and -, I transformed
the output delta to a Gray code (a^(a>>1)) so a string of 1's (as
is commonly produced by subtraction) look like a single 1-bit
difference.
* the base values were pseudorandom, all zero but one bit set, or
all zero plus a counter that starts at zero.
Some k values for my "a-=c; a^=rot(c,k); c+=b;" arrangement that
satisfy this are
4 6 8 16 19 4
9 15 3 18 27 15
14 9 3 7 17 3
Well, "9 15 3 18 27 15" didn't quite get 32 bits diffing
for "differ" defined as + with a one-bit base and a two-bit delta. I
used http://burtleburtle.net/bob/hash/avalanche.html to choose
the operations, constants, and arrangements of the variables.
This does not achieve avalanche. There are input bits of (a,b,c)
that fail to affect some output bits of (a,b,c), especially of a. The
most thoroughly mixed value is c, but it doesn't really even achieve
avalanche in c.
This allows some parallelism. Read-after-writes are good at doubling
the number of bits affected, so the goal of mixing pulls in the opposite
direction as the goal of parallelism. I did what I could. Rotates
seem to cost as much as shifts on every machine I could lay my hands
on, and rotates are much kinder to the top and bottom bits, so I used
rotates.
-------------------------------------------------------------------------------
*/
#define mix(a,b,c) \
{ \
a -= c; a ^= rot(c, 4); c += b; \
b -= a; b ^= rot(a, 6); a += c; \
c -= b; c ^= rot(b, 8); b += a; \
a -= c; a ^= rot(c,16); c += b; \
b -= a; b ^= rot(a,19); a += c; \
c -= b; c ^= rot(b, 4); b += a; \
}
/*
-------------------------------------------------------------------------------
final -- final mixing of 3 32-bit values (a,b,c) into c
Pairs of (a,b,c) values differing in only a few bits will usually
produce values of c that look totally different. This was tested for
* pairs that differed by one bit, by two bits, in any combination
of top bits of (a,b,c), or in any combination of bottom bits of
(a,b,c).
* "differ" is defined as +, -, ^, or ~^. For + and -, I transformed
the output delta to a Gray code (a^(a>>1)) so a string of 1's (as
is commonly produced by subtraction) look like a single 1-bit
difference.
* the base values were pseudorandom, all zero but one bit set, or
all zero plus a counter that starts at zero.
These constants passed:
14 11 25 16 4 14 24
12 14 25 16 4 14 24
and these came close:
4 8 15 26 3 22 24
10 8 15 26 3 22 24
11 8 15 26 3 22 24
-------------------------------------------------------------------------------
*/
#define final(a,b,c) \
{ \
c ^= b; c -= rot(b,14); \
a ^= c; a -= rot(c,11); \
b ^= a; b -= rot(a,25); \
c ^= b; c -= rot(b,16); \
a ^= c; a -= rot(c,4); \
b ^= a; b -= rot(a,14); \
c ^= b; c -= rot(b,24); \
}
/*
--------------------------------------------------------------------
This works on all machines. To be useful, it requires
-- that the key be an array of uint32_t's, and
-- that the length be the number of uint32_t's in the key
The function hashword() is identical to hashlittle() on little-endian
machines, and identical to hashbig() on big-endian machines,
except that the length has to be measured in uint32_ts rather than in
bytes. hashlittle() is more complicated than hashword() only because
hashlittle() has to dance around fitting the key bytes into registers.
--------------------------------------------------------------------
*/
static uint32_t hashword(
const uint32_t *k, /* the key, an array of uint32_t values */
size_t length, /* the length of the key, in uint32_ts */
uint32_t initval) /* the previous hash, or an arbitrary value */
{
uint32_t a,b,c;
/* Set up the internal state */
a = b = c = 0xdeadbeef + (((uint32_t)length)<<2) + initval;
/*------------------------------------------------- handle most of the key */
while (length > 3)
{
a += k[0];
b += k[1];
c += k[2];
mix(a,b,c);
length -= 3;
k += 3;
}
/*------------------------------------------- handle the last 3 uint32_t's */
switch(length) /* all the case statements fall through */
{
case 3 : c+=k[2];
case 2 : b+=k[1];
case 1 : a+=k[0];
final(a,b,c);
case 0: /* case 0: nothing left to add */
break;
}
/*------------------------------------------------------ report the result */
return c;
}
struct ipv4_tuple {
uint16_t ip_sport; /* source port */
uint16_t ip_dport; /* destination port */
in_addr_t ip_saddr; /* source address */
in_addr_t ip_daddr; /* destination address */
};
union ipv4_flow {
struct ipv4_tuple ipf_ipt;
uint32_t ipf_key[3];
};
struct ipv6_tuple {
uint16_t ip_sport; /* source port */
uint16_t ip_dport; /* destination port */
struct in6_addr ip_saddr; /* source address */
struct in6_addr ip_daddr; /* destination address */
};
union ipv6_flow {
struct ipv6_tuple ipf_ipt;
uint32_t ipf_key[9];
};
struct flentry {
volatile uint32_t f_fhash; /* hash flowing forward */
uint16_t f_flags; /* flow flags */
uint8_t f_pad; /* alignment */
uint8_t f_proto; /* protocol */
uint32_t f_uptime; /* uptime at last access */
struct flentry *f_next; /* pointer to collision entry */
volatile struct rtentry *f_rt; /* rtentry for flow */
volatile struct llentry *f_lle; /* llentry for flow */
};
struct flentry_v4 {
struct flentry fl_entry;
union ipv4_flow fl_flow;
};
struct flentry_v6 {
struct flentry fl_entry;
union ipv6_flow fl_flow;
};
#define fl_fhash fl_entry.fl_fhash
#define fl_flags fl_entry.fl_flags
#define fl_proto fl_entry.fl_proto
#define fl_uptime fl_entry.fl_uptime
#define fl_rt fl_entry.fl_rt
#define fl_lle fl_entry.fl_lle
#define SECS_PER_HOUR 3600
#define SECS_PER_DAY (24*SECS_PER_HOUR)
#define SYN_IDLE 300
#define UDP_IDLE 300
#define FIN_WAIT_IDLE 600
#define TCP_IDLE SECS_PER_DAY
typedef void fl_lock_t(struct flowtable *, uint32_t);
typedef void fl_rtalloc_t(struct route *, uint32_t, u_int);
union flentryp {
struct flentry **global;
struct flentry **pcpu[MAXCPU];
};
struct flowtable {
int ft_size;
int ft_lock_count;
uint32_t ft_flags;
uint32_t ft_collisions;
uint32_t ft_allocated;
uint32_t ft_misses;
uint64_t ft_hits;
uint32_t ft_udp_idle;
uint32_t ft_fin_wait_idle;
uint32_t ft_syn_idle;
uint32_t ft_tcp_idle;
fl_lock_t *ft_lock;
fl_lock_t *ft_unlock;
fl_rtalloc_t *ft_rtalloc;
struct mtx *ft_locks;
union flentryp ft_table;
bitstr_t *ft_masks[MAXCPU];
bitstr_t *ft_tmpmask;
struct flowtable *ft_next;
};
static struct proc *flowcleanerproc;
static struct flowtable *flow_list_head;
static uint32_t hashjitter;
static uma_zone_t ipv4_zone;
static uma_zone_t ipv6_zone;
/*
* TODO:
* - Make flowtable stats per-cpu, aggregated at sysctl call time,
* to avoid extra cache evictions caused by incrementing a shared
* counter
* - add IPv6 support to flow lookup
* - add sysctls to resize && flush flow tables
* - Add per flowtable sysctls for statistics and configuring timeouts
* - add saturation counter to rtentry to support per-packet load-balancing
* add flag to indicate round-robin flow, add list lookup from head
for flows
* - add sysctl / device node / syscall to support exporting and importing
* of flows with flag to indicate that a flow was imported so should
* not be considered for auto-cleaning
* - support explicit connection state (currently only ad-hoc for DSR)
*/
SYSCTL_NODE(_net_inet, OID_AUTO, flowtable, CTLFLAG_RD, NULL, "flowtable");
int flowtable_enable = 0;
SYSCTL_INT(_net_inet_flowtable, OID_AUTO, enable, CTLFLAG_RW,
&flowtable_enable, 0, "enable flowtable caching.");
static int flowtable_hits = 0;
SYSCTL_INT(_net_inet_flowtable, OID_AUTO, hits, CTLFLAG_RD,
&flowtable_hits, 0, "# flowtable hits.");
static int flowtable_lookups = 0;
SYSCTL_INT(_net_inet_flowtable, OID_AUTO, lookups, CTLFLAG_RD,
&flowtable_lookups, 0, "# flowtable lookups.");
static int flowtable_misses = 0;
SYSCTL_INT(_net_inet_flowtable, OID_AUTO, misses, CTLFLAG_RD,
&flowtable_misses, 0, "#flowtable misses.");
static int flowtable_frees = 0;
SYSCTL_INT(_net_inet_flowtable, OID_AUTO, frees, CTLFLAG_RD,
&flowtable_frees, 0, "#flows freed.");
static int flowtable_free_checks = 0;
SYSCTL_INT(_net_inet_flowtable, OID_AUTO, free_checks, CTLFLAG_RD,
&flowtable_free_checks, 0, "#flows free checks.");
static int flowtable_max_depth = 0;
SYSCTL_INT(_net_inet_flowtable, OID_AUTO, max_depth, CTLFLAG_RD,
&flowtable_max_depth, 0, "max collision list length.");
static int flowtable_collisions = 0;
SYSCTL_INT(_net_inet_flowtable, OID_AUTO, collisions, CTLFLAG_RD,
&flowtable_collisions, 0, "#flowtable collisions.");
/*
* XXX This does not end up updating timeouts at runtime
* and only reflects the value for the last table added :-/
*/
static int flowtable_syn_expire = SYN_IDLE;
SYSCTL_INT(_net_inet_flowtable, OID_AUTO, syn_expire, CTLFLAG_RW,
&flowtable_syn_expire, 0, "seconds after which to remove syn allocated flow.");
static int flowtable_udp_expire = UDP_IDLE;
SYSCTL_INT(_net_inet_flowtable, OID_AUTO, udp_expire, CTLFLAG_RW,
&flowtable_udp_expire, 0, "seconds after which to remove flow allocated to UDP.");
static int flowtable_fin_wait_expire = FIN_WAIT_IDLE;
SYSCTL_INT(_net_inet_flowtable, OID_AUTO, fin_wait_expire, CTLFLAG_RW,
&flowtable_fin_wait_expire, 0, "seconds after which to remove a flow in FIN_WAIT.");
static int flowtable_tcp_expire = TCP_IDLE;
SYSCTL_INT(_net_inet_flowtable, OID_AUTO, tcp_expire, CTLFLAG_RW,
&flowtable_tcp_expire, 0, "seconds after which to remove flow allocated to a TCP connection.");
/*
* Maximum number of flows that can be allocated of a given type.
*
* The table is allocated at boot time (for the pure caching case
* there is no reason why this could not be changed at runtime)
* and thus (currently) needs to be set with a tunable.
*/
static int nmbflows = 4096;
static int
sysctl_nmbflows(SYSCTL_HANDLER_ARGS)
{
int error, newnmbflows;
newnmbflows = nmbflows;
error = sysctl_handle_int(oidp, &newnmbflows, 0, req);
if (error == 0 && req->newptr) {
if (newnmbflows > nmbflows) {
nmbflows = newnmbflows;
uma_zone_set_max(ipv4_zone, nmbflows);
uma_zone_set_max(ipv6_zone, nmbflows);
} else
error = EINVAL;
}
return (error);
}
SYSCTL_PROC(_net_inet_flowtable, OID_AUTO, nmbflows, CTLTYPE_INT|CTLFLAG_RW,
&nmbflows, 0, sysctl_nmbflows, "IU", "Maximum number of flows allowed");
#ifndef RADIX_MPATH
static void
in_rtalloc_ign_wrapper(struct route *ro, uint32_t hash, u_int fib)
{
in_rtalloc_ign(ro, 0, fib);
}
#endif
static void
flowtable_global_lock(struct flowtable *table, uint32_t hash)
{
int lock_index = (hash)&(table->ft_lock_count - 1);
mtx_lock(&table->ft_locks[lock_index]);
}
static void
flowtable_global_unlock(struct flowtable *table, uint32_t hash)
{
int lock_index = (hash)&(table->ft_lock_count - 1);
mtx_unlock(&table->ft_locks[lock_index]);
}
static void
flowtable_pcpu_lock(struct flowtable *table, uint32_t hash)
{
critical_enter();
}
static void
flowtable_pcpu_unlock(struct flowtable *table, uint32_t hash)
{
critical_exit();
}
#define FL_ENTRY_INDEX(table, hash)((hash) % (table)->ft_size)
#define FL_ENTRY(table, hash) *flowtable_entry((table), (hash))
#define FL_ENTRY_LOCK(table, hash) (table)->ft_lock((table), (hash))
#define FL_ENTRY_UNLOCK(table, hash) (table)->ft_unlock((table), (hash))
#define FL_STALE (1<<8)
#define FL_IPV6 (1<<9)
static uint32_t
ipv4_flow_lookup_hash_internal(struct mbuf *m, struct route *ro,
uint32_t *key, uint16_t *flags, uint8_t *protop)
{
uint16_t sport = 0, dport = 0;
struct ip *ip = NULL;
uint8_t proto = 0;
int iphlen;
uint32_t hash;
struct sockaddr_in *sin;
struct tcphdr *th;
struct udphdr *uh;
struct sctphdr *sh;
if (flowtable_enable == 0)
return (0);
key[1] = key[0] = 0;
sin = (struct sockaddr_in *)&ro->ro_dst;
if (m != NULL) {
ip = mtod(m, struct ip *);
sin->sin_family = AF_INET;
sin->sin_len = sizeof(*sin);
sin->sin_addr = ip->ip_dst;
} else
*flags &= ~FL_HASH_PORTS;
key[2] = sin->sin_addr.s_addr;
if ((*flags & FL_HASH_PORTS) == 0)
goto skipports;
proto = ip->ip_p;
iphlen = ip->ip_hl << 2; /* XXX options? */
key[1] = ip->ip_src.s_addr;
switch (proto) {
case IPPROTO_TCP:
th = (struct tcphdr *)((caddr_t)ip + iphlen);
sport = ntohs(th->th_sport);
dport = ntohs(th->th_dport);
*flags |= th->th_flags;
if (*flags & TH_RST)
*flags |= FL_STALE;
break;
case IPPROTO_UDP:
uh = (struct udphdr *)((caddr_t)ip + iphlen);
sport = uh->uh_sport;
dport = uh->uh_dport;
break;
case IPPROTO_SCTP:
sh = (struct sctphdr *)((caddr_t)ip + iphlen);
sport = sh->src_port;
dport = sh->dest_port;
break;
default:
if (*flags & FL_HASH_PORTS)
goto noop;
/* no port - hence not a protocol we care about */
break;;
}
*protop = proto;
/*
* If this is a transmit route cache then
* hash all flows to a given destination to
* the same bucket
*/
if ((*flags & FL_HASH_PORTS) == 0)
proto = sport = dport = 0;
((uint16_t *)key)[0] = sport;
((uint16_t *)key)[1] = dport;
skipports:
hash = hashword(key, 3, hashjitter + proto);
if (m != NULL && (m->m_flags & M_FLOWID) == 0) {
m->m_flags |= M_FLOWID;
m->m_pkthdr.flowid = hash;
}
return (hash);
noop:
*protop = proto;
return (0);
}
static bitstr_t *
flowtable_mask(struct flowtable *ft)
{
bitstr_t *mask;
if (ft->ft_flags & FL_PCPU)
mask = ft->ft_masks[curcpu];
else
mask = ft->ft_masks[0];
return (mask);
}
static struct flentry **
flowtable_entry(struct flowtable *ft, uint32_t hash)
{
struct flentry **fle;
int index = (hash % ft->ft_size);
if (ft->ft_flags & FL_PCPU) {
KASSERT(&ft->ft_table.pcpu[curcpu][0] != NULL, ("pcpu not set"));
fle = &ft->ft_table.pcpu[curcpu][index];
} else {
KASSERT(&ft->ft_table.global[0] != NULL, ("global not set"));
fle = &ft->ft_table.global[index];
}
return (fle);
}
static int
flow_stale(struct flowtable *ft, struct flentry *fle)
{
time_t idle_time;
if ((fle->f_fhash == 0)
|| ((fle->f_rt->rt_flags & RTF_HOST) &&
((fle->f_rt->rt_flags & (RTF_UP))
!= (RTF_UP)))
|| (fle->f_rt->rt_ifp == NULL))
return (1);
idle_time = time_uptime - fle->f_uptime;
if ((fle->f_flags & FL_STALE) ||
((fle->f_flags & (TH_SYN|TH_ACK|TH_FIN)) == 0
&& (idle_time > ft->ft_udp_idle)) ||
((fle->f_flags & TH_FIN)
&& (idle_time > ft->ft_fin_wait_idle)) ||
((fle->f_flags & (TH_SYN|TH_ACK)) == TH_SYN
&& (idle_time > ft->ft_syn_idle)) ||
((fle->f_flags & (TH_SYN|TH_ACK)) == (TH_SYN|TH_ACK)
&& (idle_time > ft->ft_tcp_idle)) ||
((fle->f_rt->rt_flags & RTF_UP) == 0 ||
(fle->f_rt->rt_ifp == NULL)))
return (1);
return (0);
}
static void
flowtable_set_hashkey(struct flentry *fle, uint32_t *key)
{
uint32_t *hashkey;
int i, nwords;
if (fle->f_flags & FL_IPV6) {
nwords = 9;
hashkey = ((struct flentry_v4 *)fle)->fl_flow.ipf_key;
} else {
nwords = 3;
hashkey = ((struct flentry_v6 *)fle)->fl_flow.ipf_key;
}
for (i = 0; i < nwords; i++)
hashkey[i] = key[i];
}
static int
flowtable_insert(struct flowtable *ft, uint32_t hash, uint32_t *key,
uint8_t proto, struct route *ro, uint16_t flags)
{
struct flentry *fle, *fletail, *newfle, **flep;
int depth;
uma_zone_t flezone;
bitstr_t *mask;
flezone = (flags & FL_IPV6) ? ipv6_zone : ipv4_zone;
newfle = uma_zalloc(flezone, M_NOWAIT | M_ZERO);
if (newfle == NULL)
return (ENOMEM);
newfle->f_flags |= (flags & FL_IPV6);
FL_ENTRY_LOCK(ft, hash);
mask = flowtable_mask(ft);
flep = flowtable_entry(ft, hash);
fletail = fle = *flep;
if (fle == NULL) {
bit_set(mask, FL_ENTRY_INDEX(ft, hash));
*flep = fle = newfle;
goto skip;
}
depth = 0;
flowtable_collisions++;
/*
* find end of list and make sure that we were not
* preempted by another thread handling this flow
*/
while (fle != NULL) {
if (fle->f_fhash == hash && !flow_stale(ft, fle)) {
/*
* there was either a hash collision
* or we lost a race to insert
*/
FL_ENTRY_UNLOCK(ft, hash);
uma_zfree((newfle->f_flags & FL_IPV6) ?
ipv6_zone : ipv4_zone, newfle);
return (EEXIST);
}
/*
* re-visit this double condition XXX
*/
if (fletail->f_next != NULL)
fletail = fle->f_next;
depth++;
fle = fle->f_next;
}
if (depth > flowtable_max_depth)
flowtable_max_depth = depth;
fletail->f_next = newfle;
fle = newfle;
skip:
flowtable_set_hashkey(fle, key);
fle->f_proto = proto;
fle->f_rt = ro->ro_rt;
fle->f_lle = ro->ro_lle;
fle->f_fhash = hash;
fle->f_uptime = time_uptime;
FL_ENTRY_UNLOCK(ft, hash);
return (0);
}
static int
flowtable_key_equal(struct flentry *fle, uint32_t *key)
{
uint32_t *hashkey;
int i, nwords;
if (fle->f_flags & FL_IPV6) {
nwords = 9;
hashkey = ((struct flentry_v4 *)fle)->fl_flow.ipf_key;
} else {
nwords = 3;
hashkey = ((struct flentry_v6 *)fle)->fl_flow.ipf_key;
}
for (i = 0; i < nwords; i++)
if (hashkey[i] != key[i])
return (0);
return (1);
}
int
flowtable_lookup(struct flowtable *ft, struct mbuf *m, struct route *ro)
{
uint32_t key[9], hash;
struct flentry *fle;
uint16_t flags;
uint8_t proto = 0;
int error = 0, fib = 0;
struct rtentry *rt;
struct llentry *lle;
flags = ft->ft_flags;
ro->ro_rt = NULL;
ro->ro_lle = NULL;
/*
* The internal hash lookup is the only IPv4 specific bit
* remaining
*
* XXX BZ: to add IPv6 support just add a check for the
* address type in m and ro and an equivalent ipv6 lookup
* function - the rest of the code should automatically
* handle an ipv6 flow (note that m can be NULL in which
* case ro will be set)
*/
hash = ipv4_flow_lookup_hash_internal(m, ro, key,
&flags, &proto);
/*
* Ports are zero and this isn't a transmit cache
* - thus not a protocol for which we need to keep
* state
* FL_HASH_PORTS => key[0] != 0 for TCP || UDP || SCTP
*/
if (hash == 0 || (key[0] == 0 && (ft->ft_flags & FL_HASH_PORTS)))
return (ENOENT);
flowtable_lookups++;
FL_ENTRY_LOCK(ft, hash);
if ((fle = FL_ENTRY(ft, hash)) == NULL) {
FL_ENTRY_UNLOCK(ft, hash);
goto uncached;
}
keycheck:
rt = __DEVOLATILE(struct rtentry *, fle->f_rt);
lle = __DEVOLATILE(struct llentry *, fle->f_lle);
if ((rt != NULL)
&& fle->f_fhash == hash
&& flowtable_key_equal(fle, key)
&& (proto == fle->f_proto)
&& (rt->rt_flags & RTF_UP)
&& (rt->rt_ifp != NULL)) {
flowtable_hits++;
fle->f_uptime = time_uptime;
fle->f_flags |= flags;
ro->ro_rt = rt;
ro->ro_lle = lle;
FL_ENTRY_UNLOCK(ft, hash);
return (0);
} else if (fle->f_next != NULL) {
fle = fle->f_next;
goto keycheck;
}
FL_ENTRY_UNLOCK(ft, hash);
uncached:
flowtable_misses++;
/*
* This bit of code ends up locking the
* same route 3 times (just like ip_output + ether_output)
* - at lookup
* - in rt_check when called by arpresolve
* - dropping the refcount for the rtentry
*
* This could be consolidated to one if we wrote a variant
* of arpresolve with an rt_check variant that expected to
* receive the route locked
*/
if (m != NULL)
fib = M_GETFIB(m);
ft->ft_rtalloc(ro, hash, fib);
if (ro->ro_rt == NULL)
error = ENETUNREACH;
else {
struct llentry *lle = NULL;
struct sockaddr *l3addr;
struct rtentry *rt = ro->ro_rt;
struct ifnet *ifp = rt->rt_ifp;
if (rt->rt_flags & RTF_GATEWAY)
l3addr = rt->rt_gateway;
else
l3addr = &ro->ro_dst;
llentry_update(&lle, LLTABLE(ifp), l3addr, ifp);
ro->ro_lle = lle;
if (lle == NULL) {
RTFREE(rt);
ro->ro_rt = NULL;
return (ENOENT);
}
error = flowtable_insert(ft, hash, key, proto,
ro, flags);
if (error) {
RTFREE(rt);
LLE_FREE(lle);
ro->ro_rt = NULL;
ro->ro_lle = NULL;
}
}
return (error);
}
/*
* used by the bit_alloc macro
*/
#define calloc(count, size) malloc((count)*(size), M_DEVBUF, M_WAITOK|M_ZERO)
struct flowtable *
flowtable_alloc(int nentry, int flags)
{
struct flowtable *ft, *fttail;
int i;
if (hashjitter == 0)
hashjitter = arc4random();
KASSERT(nentry > 0, ("nentry must be > 0, is %d\n", nentry));
ft = malloc(sizeof(struct flowtable),
M_RTABLE, M_WAITOK | M_ZERO);
ft->ft_flags = flags;
ft->ft_size = nentry;
#ifdef RADIX_MPATH
ft->ft_rtalloc = rtalloc_mpath_fib;
#else
ft->ft_rtalloc = in_rtalloc_ign_wrapper;
#endif
if (flags & FL_PCPU) {
ft->ft_lock = flowtable_pcpu_lock;
ft->ft_unlock = flowtable_pcpu_unlock;
for (i = 0; i <= mp_maxid; i++) {
ft->ft_table.pcpu[i] =
malloc(nentry*sizeof(struct flentry *),
M_RTABLE, M_WAITOK | M_ZERO);
ft->ft_masks[i] = bit_alloc(nentry);
}
} else {
ft->ft_lock_count = 2*(powerof2(mp_maxid + 1) ? (mp_maxid + 1):
(fls(mp_maxid + 1) << 1));
ft->ft_lock = flowtable_global_lock;
ft->ft_unlock = flowtable_global_unlock;
ft->ft_table.global =
malloc(nentry*sizeof(struct flentry *),
M_RTABLE, M_WAITOK | M_ZERO);
ft->ft_locks = malloc(ft->ft_lock_count*sizeof(struct mtx),
M_RTABLE, M_WAITOK | M_ZERO);
for (i = 0; i < ft->ft_lock_count; i++)
mtx_init(&ft->ft_locks[i], "flow", NULL, MTX_DEF|MTX_DUPOK);
ft->ft_masks[0] = bit_alloc(nentry);
}
ft->ft_tmpmask = bit_alloc(nentry);
/*
* In the local transmit case the table truly is
* just a cache - so everything is eligible for
* replacement after 5s of non-use
*/
if (flags & FL_HASH_PORTS) {
ft->ft_udp_idle = flowtable_udp_expire;
ft->ft_syn_idle = flowtable_syn_expire;
ft->ft_fin_wait_idle = flowtable_fin_wait_expire;
ft->ft_tcp_idle = flowtable_fin_wait_expire;
} else {
ft->ft_udp_idle = ft->ft_fin_wait_idle =
ft->ft_syn_idle = ft->ft_tcp_idle = 30;
}
/*
* hook in to the cleaner list
*/
if (flow_list_head == NULL)
flow_list_head = ft;
else {
fttail = flow_list_head;
while (fttail->ft_next != NULL)
fttail = fttail->ft_next;
fttail->ft_next = ft;
}
return (ft);
}
static void
flowtable_setup(void *arg)
{
ipv4_zone = uma_zcreate("ip4flow", sizeof(struct flentry_v4), NULL,
NULL, NULL, NULL, 64, UMA_ZONE_MAXBUCKET);
ipv6_zone = uma_zcreate("ip6flow", sizeof(struct flentry_v6), NULL,
NULL, NULL, NULL, 64, UMA_ZONE_MAXBUCKET);
uma_zone_set_max(ipv4_zone, nmbflows);
uma_zone_set_max(ipv6_zone, nmbflows);
}
SYSINIT(flowtable_setup, SI_SUB_KTHREAD_INIT, SI_ORDER_ANY, flowtable_setup, NULL);
/*
* The rest of the code is devoted to garbage collection of expired entries.
* It is a new additon made necessary by the switch to dynamically allocating
* flow tables.
*
*/
static void
fle_free(struct flentry *fle)
{
struct rtentry *rt;
struct llentry *lle;
rt = __DEVOLATILE(struct rtentry *, fle->f_rt);
lle = __DEVOLATILE(struct llentry *, fle->f_lle);
RTFREE(rt);
LLE_FREE(lle);
uma_zfree((fle->f_flags & FL_IPV6) ? ipv6_zone : ipv4_zone, fle);
}
static void
flowtable_free_stale(struct flowtable *ft)
{
int curbit = 0, count;
struct flentry *fle, **flehead, *fleprev;
struct flentry *flefreehead, *flefreetail, *fletmp;
bitstr_t *mask, *tmpmask;
flefreehead = flefreetail = NULL;
mask = flowtable_mask(ft);
tmpmask = ft->ft_tmpmask;
memcpy(tmpmask, mask, ft->ft_size/8);
/*
* XXX Note to self, bit_ffs operates at the byte level
* and thus adds gratuitous overhead
*/
bit_ffs(tmpmask, ft->ft_size, &curbit);
while (curbit != -1) {
if (curbit >= ft->ft_size || curbit < -1) {
log(LOG_ALERT,
"warning: bad curbit value %d \n",
curbit);
break;
}
FL_ENTRY_LOCK(ft, curbit);
flehead = flowtable_entry(ft, curbit);
fle = fleprev = *flehead;
flowtable_free_checks++;
#ifdef DIAGNOSTIC
if (fle == NULL && curbit > 0) {
log(LOG_ALERT,
"warning bit=%d set, but no fle found\n",
curbit);
}
#endif
while (fle != NULL) {
if (!flow_stale(ft, fle)) {
fleprev = fle;
fle = fle->f_next;
continue;
}
/*
* delete head of the list
*/
if (fleprev == *flehead) {
fletmp = fleprev;
if (fle == fleprev) {
fleprev = *flehead = fle->f_next;
} else
fleprev = *flehead = fle;
fle = fle->f_next;
} else {
/*
* don't advance fleprev
*/
fletmp = fle;
fleprev->f_next = fle->f_next;
fle = fleprev->f_next;
}
if (flefreehead == NULL)
flefreehead = flefreetail = fletmp;
else {
flefreetail->f_next = fletmp;
flefreetail = fletmp;
}
fletmp->f_next = NULL;
}
if (*flehead == NULL)
bit_clear(mask, curbit);
FL_ENTRY_UNLOCK(ft, curbit);
bit_clear(tmpmask, curbit);
bit_ffs(tmpmask, ft->ft_size, &curbit);
}
count = 0;
while ((fle = flefreehead) != NULL) {
flefreehead = fle->f_next;
count++;
flowtable_frees++;
fle_free(fle);
}
if (bootverbose && count)
log(LOG_DEBUG, "freed %d flow entries\n", count);
}
static void
flowtable_cleaner(void)
{
struct flowtable *ft;
int i;
if (bootverbose)
log(LOG_INFO, "flowtable cleaner started\n");
while (1) {
ft = flow_list_head;
while (ft != NULL) {
if (ft->ft_flags & FL_PCPU) {
for (i = 0; i <= mp_maxid; i++) {
if (CPU_ABSENT(i))
continue;
thread_lock(curthread);
sched_bind(curthread, i);
thread_unlock(curthread);
flowtable_free_stale(ft);
thread_lock(curthread);
sched_unbind(curthread);
thread_unlock(curthread);
}
} else {
flowtable_free_stale(ft);
}
ft = ft->ft_next;
}
/*
* The 20 second interval between cleaning checks
* is arbitrary
*/
pause("flowcleanwait", 20*hz);
}
}
static struct kproc_desc flow_kp = {
"flowcleaner",
flowtable_cleaner,
&flowcleanerproc
};
SYSINIT(flowcleaner, SI_SUB_KTHREAD_IDLE, SI_ORDER_ANY, kproc_start, &flow_kp);