blob: 1f9c014c8903bab39e819b94da57380df145e444 [file] [log] [blame]
/*
* Fastpath Learner
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU FP_ERR( Public License
* as published by the Free Software Foundation; either version
* 2 of the License, or (at your option) any later version.
*/
#define pr_fmt(fmt) "mfp" " learner:%s:%d: " fmt, __func__, __LINE__
#include <br_private.h>
#include <net/addrconf.h>
#include <linux/inetdevice.h>
#include "fp_common.h"
#include "fp_database.h"
#include "fp_device.h"
#include "fp_core.h"
#include "fp_netlink.h"
#define RTMGRP_IPV4_ROUTE 0x40
#define RTMGRP_IPV4_RULE 0x80
#define RTMGRP_IPV6_ROUTE 0x400
#define RTNETLINK_GRP (RTMGRP_IPV4_ROUTE | RTMGRP_IPV4_RULE | RTMGRP_IPV6_ROUTE)
#define NFLGRP2MASK(group) ((((group) > NFNLGRP_NONE) && \
((group) < __NFNLGRP_MAX)) ? \
(0x1UL << ((group) - 1)) : 0)
#define NFNETLINK_GRP \
NFLGRP2MASK(NFNLGRP_CONNTRACK_NEW) | \
NFLGRP2MASK(NFNLGRP_CONNTRACK_UPDATE) | \
NFLGRP2MASK(NFNLGRP_CONNTRACK_DESTROY) | \
NFLGRP2MASK(NFNLGRP_CONNTRACK_EXP_NEW) | \
NFLGRP2MASK(NFNLGRP_CONNTRACK_EXP_UPDATE) | \
NFLGRP2MASK(NFNLGRP_CONNTRACK_EXP_DESTROY)
/* ipv6 special flags always rejected (RTF values > 64K) */
#define RT6_REJECT_MASK ~(RTF_UP | RTF_GATEWAY | RTF_HOST | \
RTF_REINSTATE | RTF_DYNAMIC | RTF_MODIFIED | \
RTF_DEFAULT | RTF_ADDRCONF | RTF_CACHE)
#define DEFAULT_LOOKUPS_DELAY_MS (5)
#define DEFAULT_LOOKUPS_RETRIES (10)
#define NETIF_INVALID(x) (!(x) || !netif_device_present(x) || \
!netif_running(x) || !netif_carrier_ok(x))
static inline struct net_device *
get_netdev_from_br(struct net_device *br, struct nf_conntrack_tuple *tuple);
static bool fp_learner_wq = FP_LEARNER_WQ_DEFAULT;
struct policy_entry {
struct list_head list;
unsigned int port;
};
struct fp_learner {
spinlock_t lock;
struct list_head work_items_list;
struct list_head policy_list;
struct workqueue_struct *wq;
struct work_struct update_work;
struct socket *rt_nl_sock;
struct socket *nf_nl_sock;
struct notifier_block netdev_notifier;
struct notifier_block netevent_notifier;
struct notifier_block inet6addr_notifier;
unsigned int lookups_retries;
unsigned int lookups_delay;
unsigned int fp_rmmoding;
};
struct learner_work {
struct list_head list;
struct fp_learner *priv;
struct delayed_work work;
/* add new connection data*/
struct nf_conn *ct;
};
struct nf_conn *
__get_conntrack_from_nlmsg(struct sk_buff *skb, struct nlmsghdr *nlh);
struct nf_conntrack_expect *
__get_expect_from_nlmsg(struct sk_buff *skb, struct nlmsghdr *nlh);
void learner_nc_dump_conntrack_tuple(char *msg, struct nf_conn *ct)
{
struct nf_conntrack_tuple *orig_tuple =
&ct->tuplehash[IP_CT_DIR_ORIGINAL].tuple;
struct nf_conntrack_tuple *reply_tuple =
&ct->tuplehash[IP_CT_DIR_REPLY].tuple;
char buf[MAX_DEBUG_PRINT_SIZE];
int len = 0;
if (msg)
len = sprintf(buf, "%s", msg);
len += sprintf(buf + len, "tuple orig:\n");
len += fp_dump_tuple(buf + len, orig_tuple);
len += sprintf(buf + len, "\ntuple reply:\n");
len += fp_dump_tuple(buf + len, reply_tuple);
pr_err("%s\n", buf);
}
static inline bool policy_check_port(u_int8_t protocol, __be16 port)
{
if (protocol == IPPROTO_UDP) {
switch (ntohs(port)) {
case 53: /* DNS */
case 67: /* bootps */
case 68: /* bootpc */
case 69: /* Trivial File Transfer Protocol (TFTP) */
case 135: /* DHCP server, DNS server and WINS. Also used by DCOM */
case 137: /* NetBIOS NetBIOS Name Service */
case 138: /* NetBIOS NetBIOS Datagram Service */
case 139: /* NetBIOS NetBIOS Session Service */
case 161: /* SNMP */
case 162: /* SNMPTRAP */
case 199: /* SMUX, SNMP Unix Multiplexer */
case 517: /* Talk */
case 518: /* NTalk */
case 546: /* DHCPv6 client*/
case 547: /* DHCPv6 server*/
case 953: /* Domain Name System (DNS) RNDC Service */
case 1719: /* H.323 Registration and alternate communication */
case 1723: /* Microsoft Point-to-Point Tunneling Protocol (PPTP) */
case 5060: /* Session Initiation Protocol (SIP) */
case 5353: /* Multicast DNS (mDNS) */
case 6566: /* SANE (Scanner Access Now Easy) */
case 20480: /* emwavemsg (emWave Message Service) */
return false;
}
} else { /* TCP */
switch (ntohs(port)) {
case 21: /* FTP control (command) */
case 53: /* DNS */
case 135: /* DHCP server, DNS server and WINS. Also used by DCOM */
case 137: /* NetBIOS NetBIOS Name Service */
case 138: /* NetBIOS NetBIOS Datagram Service */
case 139: /* NetBIOS NetBIOS Session Service */
case 162: /* SNMPTRAP */
case 199: /* SMUX, SNMP Unix Multiplexer */
case 546: /* DHCPv6 client*/
case 547: /* DHCPv6 server*/
case 953: /* Domain Name System (DNS) RNDC Service */
case 1720: /* H.323 Call signalling */
case 1723: /* Microsoft Point-to-Point Tunneling Protocol (PPTP) */
case 5060: /* Session Initiation Protocol (SIP) */
case 6566: /* SANE (Scanner Access Now Easy) */
case 6667: /* Internet Relay Chat (IRC) */
case 20480: /* emwavemsg (emWave Message Service) */
return false;
}
}
return true;
}
static bool learner_policy_check(struct fp_learner *priv, struct nf_conn *ct)
{
const struct nf_conntrack_l4proto *l4proto;
struct policy_entry *itr;
struct nf_conntrack_tuple *orig_tuple;
orig_tuple = &ct->tuplehash[IP_CT_DIR_ORIGINAL].tuple;
l4proto = nf_ct_l4proto_find(nf_ct_protonum(ct));
NF_CT_ASSERT(l4proto);
if (!l4proto->l4proto)
goto fail;
/* check protocol is UDP/TCP */
if (l4proto->l4proto != IPPROTO_UDP &&
l4proto->l4proto != IPPROTO_TCP)
goto fail;
if (!policy_check_port(l4proto->l4proto, orig_tuple->dst.u.all))
goto fail;
if (!policy_check_port(l4proto->l4proto, orig_tuple->src.u.all))
goto fail;
/* Check dynamic policy */
spin_lock_bh(&priv->lock);
list_for_each_entry(itr, &priv->policy_list, list)
if (itr && ((itr->port == ntohs(orig_tuple->dst.u.all)) ||
(itr->port == ntohs(orig_tuple->src.u.all)))) {
spin_unlock_bh(&priv->lock);
goto fail;
}
spin_unlock_bh(&priv->lock);
return true;
fail:
pr_debug("connection %p failed police check\n", ct);
return false;
}
static inline void flowi_init(struct flowi *fl, int iif,
__u8 scope, __u8 proto,
__be32 daddr, __be32 saddr,
__be16 dport, __be16 sport,
__u32 mark)
{
memset(fl, 0, sizeof(*fl));
#if LINUX_VERSION_CODE >= KERNEL_VERSION(2, 6, 39)
fl->flowi_iif = iif;
fl->flowi_scope = scope;
fl->flowi_proto = proto;
fl->u.ip4.daddr = daddr;
fl->u.ip4.saddr = saddr;
fl->u.ip4.fl4_dport = dport;
fl->u.ip4.fl4_sport = sport;
#ifdef CONFIG_NF_CONNTRACK_MARK
fl->flowi_mark = mark;
#endif
#else
fl->iif = iif;
fl->fl4_scope = scope;
fl->proto = proto;
fl->fl4_dst = daddr;
fl->fl4_src = saddr;
fl->fl_ip_dport = dport;
fl->fl_ip_sport = sport;
#endif
}
static inline bool invert_tuple(struct nf_conntrack_tuple *inverse,
struct nf_conntrack_tuple *orig)
{
return nf_ct_invert_tuple(inverse, orig);
}
static inline bool ipv6_check_special_addr(const struct in6_addr *addr)
{
int addr_type = ipv6_addr_type(addr);
/* TODO: check if we need to filter other types - such as Link Local */
return ((addr_type & IPV6_ADDR_MULTICAST) ||
(addr_type & IPV6_ADDR_LOOPBACK) ||
(addr_type & IPV6_ADDR_ANY));
}
static struct net_device *fp_get_route_ipv6(struct nf_conn *ct,
struct nf_conntrack_tuple *tuple,
unsigned int *route)
{
struct net_device *dev = NULL;
struct flowi6 fl6 = {
.flowi6_oif = 0,
.daddr = tuple->dst.u3.in6,
};
int flags = RT6_LOOKUP_F_IFACE;
struct fib6_result res = {};
int ret = 0;
if (ipv6_check_special_addr(&tuple->dst.u3.in6) ||
ipv6_check_special_addr(&tuple->src.u3.in6)) {
pr_debug("Filter special address (saddr=%pI6c, daddr=%pI6c)\n",
&tuple->src.u3.in6, &tuple->dst.u3.in6);
return NULL;
}
//if (&tuple->src.u3.in6) {
memcpy(&fl6.saddr, &tuple->src.u3.in6, sizeof(tuple->src.u3.in6));
flags |= RT6_LOOKUP_F_HAS_SADDR;
//}
#ifdef CONFIG_NF_CONNTRACK_MARK
fl6.flowi6_mark = ct->mark;
#endif
ret = ip6_route_lookup_fastpath(nf_ct_net(ct), &fl6, &res, flags);
if (ret){
pr_debug("rt6_lookup failed\n");
goto out;
}
/* check if route is usable*/
if (res.fib6_flags & RTF_UP) {
if (res.fib6_flags & RT6_REJECT_MASK) {
pr_debug("route rejected (rt6i_flags = 0x%08x)\n", res.fib6_flags);
goto out;
}
/* accepted in fastpath */
dev = res.nh->fib_nh_dev;
*route = res.fib6_flags;
}
out:
return dev;
}
static inline bool ipv4_check_special_addr(const __be32 addr)
{
/* Filter multicast, broadcast, loopback and zero net*/
return (ipv4_is_loopback(addr) || ipv4_is_multicast(addr) ||
ipv4_is_lbcast(addr) || ipv4_is_zeronet(addr));
}
static inline struct net_device *fp_get_dev_by_ipaddr(struct nf_conntrack_tuple *tuple)
{
struct net *net;
struct net_device *dev;
struct in_device *in_dev;
struct in_ifaddr *ifa;
for_each_net(net) {
for_each_netdev(net, dev) {
in_dev = __in_dev_get_rcu(dev);
if (!in_dev)
continue;
in_dev_for_each_ifa_rcu(ifa, in_dev) {
if (tuple->src.u3.ip == ifa->ifa_local)
return dev;
}
}
}
return NULL;
}
static struct net_device *fp_get_route_ipv4(struct nf_conn *ct,
struct nf_conntrack_tuple *tuple,
unsigned int *route)
{
struct fib_result res;
struct flowi flp;
struct net_device *dev = NULL;
if (ipv4_check_special_addr(tuple->dst.u3.ip) ||
ipv4_check_special_addr(tuple->src.u3.ip)) {
pr_debug("Filter special address (saddr=%pI4, daddr=%pI4)\n",
&tuple->src.u3.ip, &tuple->dst.u3.ip);
return NULL;
}
#ifdef CONFIG_NF_CONNTRACK_MARK
flowi_init(&flp, 0, 0, tuple->dst.protonum, tuple->dst.u3.ip,
tuple->src.u3.ip, tuple->src.u.all, tuple->dst.u.all, ct->mark);
#else
flowi_init(&flp, 0, 0, tuple->dst.protonum, tuple->dst.u3.ip,
tuple->src.u3.ip, tuple->src.u.all, tuple->dst.u.all, 0);
#endif
rcu_read_lock_bh();
if (rt4_lookup(nf_ct_net(ct), &flp, &res) < 0) {
pr_debug("Getting route failed\n");
rcu_read_unlock_bh();
return NULL;
}
if (res.type == RTN_BROADCAST) {
pr_err("Route = RTN_BROADCAST\n");
goto out;
}
if (res.type == RTN_MULTICAST) {
pr_err("Route = RTN_MULTICAST\n");
goto out;
}
if (res.type == RTN_LOCAL) {
pr_debug("Route = RTN_LOCAL\n");
goto out;
}
*route = res.type;
dev = res.fi->fib_nh->fib_nh_dev;
if (NF_CT_NAT(ct))
dev = fp_get_dev_by_ipaddr(tuple) ? fp_get_dev_by_ipaddr(tuple) : dev;
out:
ip4_rt_put(&res);
rcu_read_unlock_bh();
return dev;
}
static struct fp_net_device *fp_get_route(struct nf_conn *ct,
struct nf_conntrack_tuple *tuple,
u32 *route, int retries, int delay)
{
struct fp_net_device *fdev;
struct net_device *dev, *br = NULL;
dev = (tuple->src.l3num == AF_INET6) ?
fp_get_route_ipv6(ct, tuple, route) :
fp_get_route_ipv4(ct, tuple, route);
if (!dev)
return NULL;
if (dev->priv_flags & IFF_EBRIDGE) {
br = dev;
do {
dev = get_netdev_from_br(br, tuple);
if (dev)
break;
if (delay)
msleep(delay);
} while (retries--);
if (!dev) {
pr_debug("Unable to get net device from bridge IP\n");
return NULL;
}
}
if (dev->reg_state != NETREG_REGISTERED) {
pr_debug("device %s not registred (reg_state=%d)\n", dev->name,
dev->reg_state);
return NULL;
}
if (unlikely(NETIF_INVALID(dev)) || !(dev->flags & IFF_UP)) {
pr_debug("dev (%s) state invalid (state: %lu) or is not up (flags: 0x%x)\n", dev->name, dev->state, dev->flags);
return NULL;
}
fdev = fpdev_get_if(dev);
if (!fdev) {
pr_err("no fastpath device for %s\n", dev->name);
return NULL;
}
fdev->br = br;
return fdev;
}
static inline int ipv4_gw_addr(struct nf_conn *ct, struct net_device *dev,
__be32 saddr, __be32 daddr, __be32 *gw)
{
struct fib_result res;
int ret = 0;
struct flowi flp;
#ifdef CONFIG_NF_CONNTRACK_MARK
flowi_init(&flp, dev->ifindex, RT_SCOPE_UNIVERSE, 0, daddr, saddr, 0, 0, ct->mark);
#else
flowi_init(&flp, dev->ifindex, RT_SCOPE_UNIVERSE, 0, daddr, saddr, 0, 0, 0);
#endif
rcu_read_lock_bh();
ret = rt4_lookup(dev_net(dev), &flp, &res);
if (ret != 0) {
pr_err("rt4_lookup failed, ret = %d\n", ret);
rcu_read_unlock_bh();
return ret;
}
if (res.type == RTN_BROADCAST || res.type == RTN_MULTICAST ||
res.type == RTN_LOCAL) {
pr_debug("gw not found - res.type = %d\n", res.type);
ret = -EFAULT;
} else {
*gw = res.fi->fib_nh->fib_nh_gw4;
pr_debug("gw found (%pI4)\n", gw);
}
ip4_rt_put(&res);
rcu_read_unlock_bh();
return ret;
}
static inline int ipv6_gw_addr(struct nf_conn *ct, struct net_device *dev, struct in6_addr *saddr,
struct in6_addr *daddr, struct in6_addr *gw)
{
int ret = 0;
struct flowi6 fl6 = {
.flowi6_oif = 0,
.daddr = *daddr,
};
int flags = RT6_LOOKUP_F_IFACE;
struct fib6_result res = {};
if (saddr) {
memcpy(&fl6.saddr, saddr, sizeof(*saddr));
flags |= RT6_LOOKUP_F_HAS_SADDR;
}
#ifdef CONFIG_NF_CONNTRACK_MARK
fl6.flowi6_mark = ct->mark;
#endif
ret = ip6_route_lookup_fastpath(dev_net(dev), &fl6, &res, flags);
if (ret) {
pr_err("rt6_lookup failed\n");
ret = -ENETUNREACH;
goto out;
}
/* check if route is usable*/
if (res.fib6_flags & RTF_UP) {
if (res.nh->fib_nh_gw_family)
*gw = res.nh->fib_nh_gw6;
} else {
pr_debug("gw found but route is not up\n");
ret = -EFAULT;
}
out:
return ret;
}
/* copied from br_fdb.c */
static inline
struct net_bridge_fdb_entry *fp_br_fdb_find(struct hlist_head *head,
const unsigned char *addr)
{
struct net_bridge_fdb_entry *fdb;
hlist_for_each_entry_rcu(fdb, head, fdb_node) {
if (ether_addr_equal(fdb->key.addr.addr, addr))
return fdb;
}
return NULL;
}
static inline
struct net_device *fp_br_get_netdev_by_mac(struct net_bridge *br,
const unsigned char *mac)
{
unsigned int i;
struct net_bridge_fdb_entry *fdb;
BUG_ON(!br);
rcu_read_lock_bh();
for (i = 0; i < BR_HASH_SIZE; i++) {
fdb = fp_br_fdb_find(&br->fdb_list, mac);
if (fdb) {
pr_debug("br: %s fdb[%u]: %pIM , port:%s\n",
br->dev->name, i, fdb->key.addr.addr,
fdb->dst->dev->name);
rcu_read_unlock_bh();
return fdb->dst->dev;
}
}
rcu_read_unlock_bh();
pr_debug("no match found in fdb (%pM)\n", mac);
return NULL;
}
static inline
struct net_device *get_netdev_from_br(struct net_device *br,
struct nf_conntrack_tuple *tuple)
{
struct neighbour *neigh;
struct neigh_table *tbl;
struct net_device *dev = NULL;
BUG_ON(!tuple);
tbl = (tuple->src.l3num == AF_INET6) ? &nd_tbl : &arp_tbl;
neigh = neigh_lookup(tbl, tuple->dst.u3.all, br);
if (neigh) {
dev = fp_br_get_netdev_by_mac(netdev_priv(br), neigh->ha);
neigh_release(neigh);
}
return dev;
}
static int fp_hh_init(struct nf_conn *ct, struct nf_conntrack_tuple *t,
struct fp_net_device *dst, struct hh_cache *hh)
{
struct net_device *dev = dst->br ? dst->br : dst->dev;
__be16 prot;
struct neighbour *n;
const struct header_ops *header_ops;
if (is_vlan_dev(dev))
header_ops = vlan_dev_real_dev(dev)->header_ops;
else
header_ops = dev->header_ops;
memset(hh, 0, sizeof(*hh));
if (!header_ops) {
pr_debug("device %s has no header ops\n", dev->name);
return 0; /* device does not have L2 header*/
}
if (!header_ops->cache || !header_ops->cache_update) {
pr_debug("device %s has no header cache ops\n", dev->name);
return -ENOTSUPP;
}
if (t->src.l3num == AF_INET) {
__be32 gw;
prot = htons(ETH_P_IP);
n = __ipv4_neigh_lookup(dev, t->dst.u3.ip);
if (!n) {
if (ipv4_gw_addr(ct, dev, t->src.u3.ip, t->dst.u3.ip, &gw))
goto not_found;
n = __ipv4_neigh_lookup(dev, gw);
if (!n)
goto not_found;
}
} else if (t->src.l3num == AF_INET6) {
struct in6_addr gw6;
prot = htons(ETH_P_IPV6);
n = __ipv6_neigh_lookup(dev, &t->dst.u3.in6);
if (!n) {
if (ipv6_gw_addr(ct, dev, &t->src.u3.in6, &t->dst.u3.in6, &gw6))
goto not_found;
n = __ipv6_neigh_lookup(dev, &t->dst.u3.in6);
if (!n)
goto not_found;
}
} else {
BUG();
}
if (n->nud_state & NUD_VALID) {
int err = header_ops->cache(n, hh, prot);
neigh_release(n);
pr_debug("device %s hh_cache initialized: hh_len=%d, hh_data=%pM\n",
dev->name, hh->hh_len, hh->hh_data);
return err;
}
pr_debug("neighbour state invalid (%02x)\n", n->nud_state);
neigh_release(n);
not_found:
/* we get here in 2 cases, both are NOT considered as error:
* 1. Neighbour lookup failed - we will be notified when the neighbour
* will be finally created
* 2. Neighbour state not valid - we will be notified when the neighbour
* state changes
* Both are handled by netdev_event - where the entry's hh_cache will be
* updated. Untill this happens, all packets matching this entry will be
* classified as slow by the fp_classifier.
*/
pr_debug("No neighbour found or neighbour state invalid\n");
return 0;
}
static struct fpdb_entry *connection_to_entry(struct fp_learner *priv,
struct nf_conn *ct,
enum ip_conntrack_dir dir,
gfp_t flags)
{
struct fp_net_device *dst = NULL, *src = NULL;
struct nf_conntrack_tuple *orig_tuple, *reply_tuple;
struct nf_conntrack_tuple orig_tuple_inverse, reply_tuple_inverse;
struct fpdb_entry *entry;
struct hh_cache hh;
unsigned int in_route_type, out_route_type;
int retries = flags != GFP_ATOMIC ? priv->lookups_retries : 0;
int delay = flags != GFP_ATOMIC ? priv->lookups_delay : 0;
if (unlikely(priv->fp_rmmoding))
goto failed;
/* For reply connections -> switch tuples */
if (dir == IP_CT_DIR_REPLY) {
orig_tuple = &ct->tuplehash[IP_CT_DIR_REPLY].tuple;
reply_tuple = &ct->tuplehash[IP_CT_DIR_ORIGINAL].tuple;
} else {
orig_tuple = &ct->tuplehash[IP_CT_DIR_ORIGINAL].tuple;
reply_tuple = &ct->tuplehash[IP_CT_DIR_REPLY].tuple;
}
if (NF_CT_NAT(ct)) {
if (!invert_tuple(&orig_tuple_inverse, orig_tuple)) {
pr_err("Inverting tuple failed\n");
goto failed;
}
if (!invert_tuple(&reply_tuple_inverse, reply_tuple)) {
pr_err("Inverting tuple failed\n");
goto failed;
}
orig_tuple = &reply_tuple_inverse;
reply_tuple = &orig_tuple_inverse;
pr_debug( "NAT connection was detected\n");
}
/* Check destination route */
dst = fp_get_route(ct, orig_tuple, &in_route_type, retries, delay);
if (!dst) {
pr_debug("Connection routing failed\n");
goto failed;
}
/* Check source route */
src = fp_get_route(ct, reply_tuple, &out_route_type, retries, delay);
if (!src) {
pr_debug("Connection routing failed (local)\n");
goto failed;
}
if (fp_hh_init(ct, orig_tuple, dst, &hh)) {
pr_debug("fp_hh_init failed \n");
goto failed;
}
entry = fpdb_alloc(flags);
if (!entry) {
pr_debug("Allocating entry failed\n");
goto failed;
}
/* Restore the original tuples */
if (dir == IP_CT_DIR_REPLY) {
orig_tuple = &ct->tuplehash[IP_CT_DIR_REPLY].tuple;
reply_tuple = &ct->tuplehash[IP_CT_DIR_ORIGINAL].tuple;
} else {
orig_tuple = &ct->tuplehash[IP_CT_DIR_ORIGINAL].tuple;
reply_tuple = &ct->tuplehash[IP_CT_DIR_REPLY].tuple;
}
/*
* if interface is going down, and we are updating an entry refering
* to this interface, we might accidently route the connection to its
* source device. Block this entry until it is updated again.
*/
if (src->dev == dst->dev) {
pr_debug("Connection created with src == dst for (%s)\n",
src->dev->name);
entry->block = 1;
}
/* Fill in entry */
entry->dir = dir;
entry->in_tuple = *orig_tuple;
entry->out_tuple = *reply_tuple;
entry->ct = ct;
entry->out_dev = dst;
entry->in_dev = src;
entry->hit_counter = 0;
entry->debug.in_route_type = in_route_type;
entry->debug.out_route_type = out_route_type;
entry->hh = hh;
/* Succced */
pr_debug("connection added (ct=%p, dir=%d)\n", ct, dir);
FP_DEBUG_DUMP_CONTRACK(NULL, ct);
return entry;
failed:
/* Failed */
fpdev_put(src);
fpdev_put(dst);
pr_debug("connection refused (ct=%p, dir=%d)\n", ct, dir);
FP_DEBUG_DUMP_CONTRACK(NULL, ct);
return NULL;
}
static inline int __add_new_connection(struct fp_learner *priv,
struct nf_conn *ct, gfp_t flags)
{
struct nf_conn_fastpath *fastpath = nfct_fastpath(ct);
struct fpdb_entry *e;
rcu_read_lock_bh();
/* original fastpath connection */
if (!fastpath) {
e = connection_to_entry(priv, ct, IP_CT_DIR_ORIGINAL, flags);
if (!e) {
rcu_read_unlock_bh();
return -EINVAL;
}
set_bit(IPS_FASTPATH_BIT, &ct->status);
fastpath = nf_ct_ext_add(ct, NF_CT_EXT_FASTPATH, flags);
BUG_ON(!fastpath);
fastpath->fpd_el[IP_CT_DIR_ORIGINAL] = e;
fastpath->fpd_el[IP_CT_DIR_REPLY] = NULL;
fpdb_add(e);
goto del_entry;
}
/* reply fastpath connection */
BUG_ON(!test_bit(IPS_FASTPATH_BIT, &ct->status));
if (fastpath->fpd_el[IP_CT_DIR_REPLY] == NULL &&
test_bit(IPS_SEEN_REPLY_BIT, &ct->status)) {
e = connection_to_entry(priv, ct, IP_CT_DIR_REPLY, flags);
if (!e) {
rcu_read_unlock_bh();
return -EINVAL;
}
fastpath->fpd_el[IP_CT_DIR_REPLY] = e;
fpdb_add(e);
goto del_entry;
}
rcu_read_unlock_bh();
return 0;
del_entry:
if (unlikely((NETIF_INVALID(e->in_dev->dev)) ||
!(e->in_dev->dev->flags & IFF_UP) || priv->fp_rmmoding)) {
pr_err("in_dev (%s) state invalid or is rmmoding, del entry!\n", e->in_dev->dev->name);
fpdb_del_by_dev(e->in_dev->dev);
}
if (unlikely((NETIF_INVALID(e->out_dev->dev)) ||
!(e->out_dev->dev->flags & IFF_UP) || priv->fp_rmmoding)) {
pr_err("out_dev (%s) state invalid or is rmmoding, del entry!\n", e->out_dev->dev->name);
fpdb_del_by_dev(e->out_dev->dev);
}
rcu_read_unlock_bh();
return 0;
}
static void new_connection_work(struct work_struct *w)
{
struct learner_work *work;
work = container_of(w, struct learner_work, work.work);
BUG_ON(!work);
__add_new_connection(work->priv, work->ct, GFP_KERNEL);
/* release work */
spin_lock_bh(&work->priv->lock);
list_del(&work->list);
spin_unlock_bh(&work->priv->lock);
kfree(work);
}
static inline int add_new_connection_work(struct fp_learner *priv,
struct nf_conn *ct)
{
struct learner_work *work;
if (!learner_policy_check(priv, ct))
return -EINVAL;
work = kzalloc(sizeof(*work), GFP_ATOMIC);
if (!work)
return -ENOMEM;
work->ct = ct;
work->priv = priv;
INIT_LIST_HEAD(&work->list);
INIT_DELAYED_WORK(&work->work, new_connection_work);
spin_lock_bh(&priv->lock);
list_add_tail(&work->list, &priv->work_items_list);
spin_unlock_bh(&priv->lock);
queue_delayed_work(priv->wq, &work->work, 0);
return 0;
}
static inline int add_new_connection_noblock(struct fp_learner *priv,
struct nf_conn *ct)
{
if (!learner_policy_check(priv, ct))
return -EINVAL;
return __add_new_connection(priv, ct, GFP_ATOMIC);
}
static inline int add_new_connection(struct fp_learner *priv,
struct nf_conn *ct)
{
if (fp_learner_wq)
return add_new_connection_work(priv, ct);
else
return add_new_connection_noblock(priv, ct);
}
/* check if this connection is waiting in our workqueue
* and cancle it if it is.
*/
static inline int
new_connection_cancle(struct fp_learner *priv, struct nf_conn *ct)
{
struct learner_work *work;
if (!fp_learner_wq)
return 0;
spin_lock_bh(&priv->lock);
list_for_each_entry(work, &priv->work_items_list, list) {
if (work->ct == ct) {
if (cancel_delayed_work(&work->work)) {
pr_debug("cancle connection add %p\n", ct);
list_del(&work->list);
kfree(work);
}
break;
}
}
spin_unlock_bh(&priv->lock);
return 0;
}
static int learner_ct_event(struct fp_learner *priv, struct nf_conn *ct,
unsigned int type, unsigned int flags)
{
if (type == IPCTNL_MSG_CT_DELETE) {
pr_debug("delete connection (%p)\n", ct);
return new_connection_cancle(priv, ct);
} else if (type == IPCTNL_MSG_CT_NEW) {
pr_debug("new connection (%p)\n", ct);
return add_new_connection(priv, ct);
}
pr_debug("Unhandled type=%u\n", type);
FP_DEBUG_DUMP_CONTRACK(NULL, ct);
return -ENOTSUPP;
}
static int fpdev_del_gb6(struct net_device *dev)
{
struct fp_net_device *fpdev;
fpdev = fpdev_get_if(dev);
if (unlikely(!fpdev))
return 0;
memset(&fpdev->ll6addr, 0, sizeof(struct in6_addr));
memset(&fpdev->gb6addr, 0, sizeof(struct in6_addr));
fpdev->prefixlen = 0;
fpdev->mtu = 0;
fpdev_clear_ll6(fpdev);
fpdev_clear_gb6(fpdev);
fpdev_clear_mtu(fpdev);
fpdev_put(fpdev);
return 0;
}
/**
* handle netdevice events.
*
* NETDEV_REGISTER
* new net_device is registered. A fastpath device is created
* and associated to it.
*
* NETDEV_UNREGISTER
* net_device unregistered, delete the associated fastpath device. In
* addition, remove all conntracks related to this device - this will
* cause all the related fastpath database entries to be deleted thus allowing
* the device to be safely removed.
*
* @note We can safely ignore NETDEV_UP / NETDEV_DOWN since it is
* checked in the classifier anyway. Regarding other events -
* will be added in the future if needed.
* @param dev
* @param event
*
* @return NOTIFY_DONE
*/
static int
__learner_netdev_event(struct net_device *dev, unsigned long event)
{
switch (event) {
case NETDEV_REGISTER:
pr_debug("received netdev (%s) register, event %lu, state: 0x%lx, flags: 0x%x, invalid: %d\n",
dev->name, event, dev->state, dev->flags, NETIF_INVALID(dev));
fpdev_add_if(dev);
break;
case NETDEV_UNREGISTER:
printk(KERN_DEBUG "received netdev (%s) unregister, event %lu, state: 0x%lx, flags: 0x%x, invalid: %d\n",
dev->name, event, dev->state, dev->flags, NETIF_INVALID(dev));
fpdb_del_by_dev(dev);
fpdb_iterate(&fpdb_del_block_entry_by_dev, (void *)dev);
fpdev_del_if(dev);
break;
case NETDEV_DOWN:
fpdev_del_gb6(dev);
break;
default:
pr_debug("ignoring netdev %s event %lu, state: 0x%lx, flags: 0x%x, invalid: %d\n",
dev->name, event, dev->state, dev->flags, NETIF_INVALID(dev));
}
return NOTIFY_DONE;
}
/* main dispatcher for netdev events - bridge and loopback ignored */
static int learner_netdev_event(struct notifier_block *nb,
unsigned long event, void *ptr)
{
struct net_device *dev;
#if LINUX_VERSION_CODE < KERNEL_VERSION(3, 11, 0)
dev = ptr;
#else
dev = ((struct netdev_notifier_info*)ptr)->dev;
#endif
if ((dev->priv_flags & IFF_EBRIDGE) || (dev->flags & IFF_LOOPBACK))
return NOTIFY_DONE;
return __learner_netdev_event(dev, event);
}
static void learner_netdev_cleanup(struct notifier_block *nb)
{
#if LINUX_VERSION_CODE < KERNEL_VERSION(3, 4, 0)
struct net_device *dev;
struct net *net;
rtnl_lock();
for_each_net(net) {
for_each_netdev(net, dev) {
if (dev->flags & IFF_UP) {
nb->notifier_call(nb, NETDEV_GOING_DOWN, dev);
nb->notifier_call(nb, NETDEV_DOWN, dev);
}
nb->notifier_call(nb, NETDEV_UNREGISTER, dev);
nb->notifier_call(nb, NETDEV_UNREGISTER_BATCH, dev);
}
}
rtnl_unlock();
#endif
}
static int fp_inet6addr_event(struct notifier_block *nb,
unsigned long event, void *ptr)
{
struct inet6_ifaddr *ifa = (struct inet6_ifaddr *)ptr;
struct net_device *dev = ifa->idev->dev;
struct fp_net_device *fpdev;
int addr_type;
if (event != NETDEV_UP)
return NOTIFY_DONE;
addr_type = ipv6_addr_type(&ifa->addr);
if (!(addr_type & IPV6_ADDR_LINKLOCAL))
return NOTIFY_DONE;
fpdev = fpdev_get_if(dev);
if (unlikely(!fpdev))
return NOTIFY_DONE;
if (!fpdev_is_ll6_set(fpdev)) {
memcpy(&fpdev->ll6addr, &ifa->addr, sizeof(ifa->addr));
fpdev_set_ll6(fpdev);
}
fpdev_put(fpdev);
return NOTIFY_DONE;
}
static int update_entry(struct fpdb_entry *e, void *data)
{
struct fpdb_entry *ne = NULL;
struct fp_learner *fpl = (struct fp_learner *)data;
struct nf_conn_fastpath *fastpath;
spin_lock_bh(&e->lock);
fpdb_lock_bh();
/* Exit if CT destroied, in case fpdb get a wrong ct info */
if (e->state == ENTRY_DYING)
goto done;
fastpath = nfct_fastpath(e->ct);
if (unlikely(!fastpath))
goto done;
ne = connection_to_entry(fpl, e->ct, e->dir, GFP_ATOMIC);
if (!ne) {
/* The connection may become local but we do not want
to remove it from STACK so just block it */
e->block = 1;
} else {
if (ne->out_dev == e->out_dev &&
ne->in_dev == e->in_dev &&
nf_ct_tuple_equal(&ne->in_tuple, &e->in_tuple) &&
nf_ct_tuple_equal(&ne->out_tuple, &e->out_tuple) &&
!memcmp(&ne->hh, &e->hh, sizeof(struct hh_cache))) {
pr_debug("new fp entry equal old,no update\n");
fpdb_free(ne);
goto done;
}
/* if the old connection is blocked keep it blocked */
/* if ne->block is 1 and e->block is 0, there will be issue --yhuang 20160617*/
if (ne->block != 1)
ne->block = e->block;
if (ne->dir == IP_CT_DIR_REPLY)
fastpath->fpd_el[IP_CT_DIR_REPLY] = ne;
else
fastpath->fpd_el[IP_CT_DIR_ORIGINAL] = ne;
fpdb_replace(e, ne);
}
done:
fpdb_unlock_bh();
spin_unlock_bh(&e->lock);
if (ne) {
if (unlikely((NETIF_INVALID(ne->in_dev->dev)) ||
!(ne->in_dev->dev->flags & IFF_UP) || fpl->fp_rmmoding)) {
pr_err("in_dev (%s) state invalid or rmmoding, del!\n",
ne->in_dev->dev->name);
fpdb_del_by_dev(ne->in_dev->dev);
}
if (unlikely((NETIF_INVALID(ne->out_dev->dev)) ||
!(ne->out_dev->dev->flags & IFF_UP) || fpl->fp_rmmoding)) {
pr_err("out_dev (%s) state invalid or rmmoding, del!\n",
ne->out_dev->dev->name);
fpdb_del_by_dev(ne->out_dev->dev);
}
}
return 0;
}
static int block_entry(struct fpdb_entry *e, void *ptr)
{
spin_lock_bh(&e->lock);
e->block = 1;
spin_unlock_bh(&e->lock);
return 0;
}
static void learner_ct_update_work(struct work_struct *work)
{
struct fp_learner *fpl = container_of(work,
struct fp_learner, update_work);
fpdb_iterate(&update_entry, (void *)fpl);
}
void __learner_ct_update_all(struct fp_learner *fpl)
{
schedule_work(&fpl->update_work);
}
static int learner_netevent(struct notifier_block *nb, unsigned long event, void *ctx)
{
struct fp_learner *fpl = container_of(nb, struct fp_learner, netevent_notifier);
BUG_ON(!fpl);
if (event == NETEVENT_NEIGH_UPDATE) {
struct neighbour *n = ctx;
pr_debug("neighbor update received (state=%d, dev=%s)\n",
n->nud_state, n->dev->name);
__learner_ct_update_all(fpl);
} else if (event == NETEVENT_REDIRECT) {
__learner_ct_update_all(fpl);
pr_debug("neighbor redirect received\n");
} else {
pr_debug("mfp received netevent %lu, which no need to update ct\n", event);
WARN_ON(1);
}
return 0;
}
static void learner_rtnetlink_rcv(struct nlmsghdr *nlh, void *ptr)
{
struct fp_learner *fpl = (struct fp_learner *)ptr;
switch (nlh->nlmsg_type) {
case RTM_NEWROUTE:
case RTM_DELROUTE:
pr_debug( "%s\n",
nlh->nlmsg_type == RTM_NEWROUTE ? "RTM_NEWROUTE" :
"RTM_DELROUTE");
__learner_ct_update_all(fpl);
break;
case RTM_NEWTFILTER:
pr_debug( "RTM_NEWTFILTER\n");
/* TODO: check if we need update in this case*/
break;
case RTM_DELTFILTER:
pr_debug( "RTM_DELTFILTER\n");
/* TODO: check if we need update in this case*/
break;
case RTM_GETTFILTER:
pr_debug( "RTM_GETTFILTER\n");
break;
}
pr_debug("handle routing netlink message, type=%d\n", nlh->nlmsg_type);
//TODO - add support for update_all_connections
}
static void learner_nfnetlink_rcv(struct sk_buff *skb,
struct nlmsghdr *nlh, void *ptr)
{
struct fp_learner *priv = (struct fp_learner *)ptr;
unsigned int type = NFNL_MSG_TYPE(nlh->nlmsg_type);
unsigned int ssid = NFNL_SUBSYS_ID(nlh->nlmsg_type);
struct nf_conn *ct;
struct nf_conntrack_expect *exp;
unsigned int flags = nlh->nlmsg_flags;
int ret;
if (ssid == NFNL_SUBSYS_CTNETLINK) {
ct = __get_conntrack_from_nlmsg(skb, nlh);
if (ct == NULL) {
pr_debug("can't get nf conn type=%u, ssid=%u\n", type, ssid);
return;
}
pr_debug("found CTNETLINK connection %p, type=%u, ssid=%u\n", ct, type, ssid);
} else if (ssid == NFNL_SUBSYS_CTNETLINK_EXP) {
exp = __get_expect_from_nlmsg(skb, nlh);
if (exp == NULL) {
pr_err("can't get expect\n");
return;
}
ct = exp->master;
pr_debug("found CTNETLINK_EXP exp %p, master connection %p, type=%u, ssid=%u\n", exp, ct, type, ssid);
} else {
pr_err("unexpected ssid (%d)\n", ssid);
return;
}
/* dispatch events */
ret = learner_ct_event(priv, ct, type, flags);
if (ret < 0)
pr_debug("learner_ct_event failed with error code %d\n"
"ct=%p, type=%u, flags=%u\n", ret, ct, type, flags);
}
/* Receive message from netlink and pass information to relevant function. */
static void learner_nl_data_ready(struct sock *sk)
{
int ret = 0;
int len;
struct sk_buff *skb;
struct nlmsghdr *nlh;
BUG_ON(!sk);
pr_debug("got a message (socket protocol=%d)\n", sk->sk_protocol);
while ((skb = skb_recv_datagram(sk, 0, 1, &ret)) == NULL) {
if (ret == -EAGAIN || ret == -ENOBUFS) {
pr_err("recvfrom() error %d\n", -ret);
return;
}
}
len = skb->len;
for (nlh = (struct nlmsghdr *)skb->data; NLMSG_OK(nlh, len);
nlh = NLMSG_NEXT(nlh, len)) {
pr_debug("nlmsg_len %u, nlmsg_type %u\n", nlh->nlmsg_len, nlh->nlmsg_type);
/* Finish of reading. */
if (nlh->nlmsg_type == NFNL_MSG_TYPE(NLMSG_DONE))
goto out;
/* Error handling. */
if (nlh->nlmsg_type == NFNL_MSG_TYPE(NLMSG_ERROR)) {
pr_err("nl message error\n");
goto out;
}
if (sk->sk_protocol == NETLINK_ROUTE) {
learner_rtnetlink_rcv(nlh, sk->sk_user_data);
} else if (sk->sk_protocol == NETLINK_NETFILTER) {
learner_nfnetlink_rcv(skb, nlh, sk->sk_user_data);
} else {
pr_err("unrecognized sk_protocol (%u)\n", sk->sk_protocol);
goto out;
}
}
out:
skb_orphan(skb);
kfree_skb(skb);
return;
}
static int learner_nl_open(void *priv, struct socket **s, int proto, int groups)
{
struct socket *sock;
struct sockaddr_nl addr;
int rc, val = 1;
rc = sock_create_kern(&init_net, AF_NETLINK , SOCK_RAW, proto, &sock);
if (rc < 0) {
pr_err("create err (rc=%d)\n", rc);
return rc;
}
memset((void *)&addr, 0, sizeof(addr));
addr.nl_family = AF_NETLINK;
addr.nl_pid = 0;
addr.nl_groups = groups;
sock->sk->sk_user_data = priv;
sock->sk->sk_data_ready = learner_nl_data_ready;
sock->sk->sk_allocation = GFP_ATOMIC;
rc = kernel_bind(sock, (struct sockaddr *)&addr, sizeof(addr));
if (rc < 0) {
pr_err("bind err (rc=%d)\n", rc);
goto sock_err;
}
rc = kernel_setsockopt(sock, SOL_NETLINK, NETLINK_NO_ENOBUFS, (char *)&val, sizeof(val));
if (rc < 0) {
pr_err("setsockopt err (rc=%d)", rc);
goto sock_err;
}
pr_debug("netlink socket opened (proto=%u, groups=%u)\n", proto, groups);
*s = sock;
return 0;
sock_err:
kernel_sock_shutdown(sock, SHUT_RDWR);
sock_release(sock);
return rc;
}
static void learner_nl_close(struct socket *sk)
{
BUG_ON(!sk);
kernel_sock_shutdown(sk, SHUT_RDWR);
sock_release(sk);
}
static unsigned int fp_learner_nf_hook(void *priv,
struct sk_buff *skb,
const struct nf_hook_state *state)
{
enum ip_conntrack_info ctinfo;
struct nf_conn *ct = nf_ct_get(skb, &ctinfo);
struct nf_conn_fastpath *f;
struct fpdb_entry *e;
struct net_device *el_src, *el_dst;
if (!ct)
goto out;
f = nfct_fastpath(ct);
if (!f)
goto out;
rcu_read_lock_bh();
e = rcu_dereference(f->fpd_el[CTINFO2DIR(ctinfo)]);
/* Here we can not clear block simply. We need to check */
/* whether src dev equals to dst dev yhuang 20160622*/
if (unlikely(e && (e->block == true))) {
if ((e->state != ENTRY_ALIVE)
|| (e->dir >= IP_CT_DIR_MAX)) {
rcu_read_unlock_bh();
goto out;
}
/* skb->dev already set to destiniation device */
el_src = e->in_dev->dev;
el_dst = e->out_dev->dev;
if(unlikely((el_src == NULL) || (el_dst == NULL))) {
rcu_read_unlock_bh();
goto out;
}
if (unlikely(NETIF_INVALID(el_src) || NETIF_INVALID(el_dst))) {
rcu_read_unlock_bh();
goto out;
}
if (likely(el_src != el_dst)) {
spin_lock_bh(&e->lock);
e->block = 0;
spin_unlock_bh(&e->lock);
pr_debug("unblock entry:ct=%x dir=%d bucket=%x %x %x\n",
(unsigned int)e->ct, e->dir, e->bucket,
(unsigned int)(&e->in_tuple),
(unsigned int)(&e->out_tuple));
}
}
rcu_read_unlock_bh();
out:
return NF_ACCEPT;
}
/** learner's netfilter hook */
static struct nf_hook_ops nf_learner_hook_data[] __read_mostly = {
{
.hook = fp_learner_nf_hook,
.pf = NFPROTO_IPV4,
.hooknum = NF_INET_POST_ROUTING,
.priority = NF_IP_PRI_LAST,
},
{
.hook = fp_learner_nf_hook,
.pf = NFPROTO_IPV6,
.hooknum = NF_INET_POST_ROUTING,
.priority = NF_IP_PRI_LAST,
},
};
void __learner_ct_block_all(struct fastpath_module *m)
{
fpdb_iterate(&block_entry, m->priv);
}
static ssize_t
learner_ct_update_all(struct fastpath_module *m, const char *buf, size_t count)
{
__learner_ct_update_all((struct fp_learner *)m->priv);
return count;
}
static ssize_t
learner_ct_block_all(struct fastpath_module *m, const char *buf, size_t count)
{
__learner_ct_block_all(m);
return count;
}
static ssize_t learner_policy_show(struct fastpath_module *m, char *buf)
{
struct policy_entry *itr;
struct fp_learner *priv = m->priv;
int len;
len = scnprintf(buf, PAGE_SIZE, "dynamic policy restricted ports:\n");
spin_lock_bh(&priv->lock);
list_for_each_entry(itr, &priv->policy_list, list)
len += scnprintf(buf + len, PAGE_SIZE - len, "%d, ", itr->port);
spin_unlock_bh(&priv->lock);
len += scnprintf(buf + len, PAGE_SIZE - len, "\n");
return len;
}
static ssize_t learner_policy_store(struct fastpath_module *m,
const char *buf, size_t count)
{
char op;
unsigned int port;
struct policy_entry *entry, *tmp;
struct fp_learner *priv = m->priv;
if (sscanf(buf, "%c%u", &op, &port) != 2 || port > 0xFFFF)
return -EINVAL;
pr_err("Enter learner_policy_store:op=%c\n", op);
if (op == '-') {
/* remove port from the restricted list*/
spin_lock_bh(&priv->lock);
list_for_each_entry_safe(entry, tmp, &priv->policy_list, list)
if (entry && entry->port == port) {
list_del(&entry->list);
kfree(entry);
}
spin_unlock_bh(&priv->lock);
} else if (op == '+') {
/* add port to the restricted list*/
entry = kzalloc(sizeof(struct policy_entry), GFP_KERNEL);
if (!entry)
return -ENOMEM;
INIT_LIST_HEAD(&entry->list);
entry->port = port;
spin_lock_bh(&priv->lock);
list_add(&entry->list, &priv->policy_list);
spin_unlock_bh(&priv->lock);
fpdb_del_by_port(port);
} else {
return -EINVAL;
}
return count;
}
static ssize_t learner_lookup_retries_store(struct fastpath_module *m,
const char *buf, size_t count)
{
unsigned int retries;
struct fp_learner *priv = m->priv;
if (sscanf(buf, "%u", &retries) != 1)
return -EINVAL;
priv->lookups_retries = retries;
return count;
}
static ssize_t learner_lookup_retries_show(struct fastpath_module *m, char *buf)
{
struct fp_learner *priv = m->priv;
return scnprintf(buf, PAGE_SIZE, "%u\n", priv->lookups_retries);
}
static ssize_t learner_lookup_delay_store(struct fastpath_module *m,
const char *buf, size_t count)
{
unsigned int delay;
struct fp_learner *priv = m->priv;
if (sscanf(buf, "%u", &delay) != 1)
return -EINVAL;
priv->lookups_delay = delay;
return count;
}
static ssize_t learner_lookup_delay_show(struct fastpath_module *m, char *buf)
{
struct fp_learner *priv = m->priv;
return scnprintf(buf, PAGE_SIZE, "%u[ms]\n", priv->lookups_delay);
}
static ssize_t learner_nfnl_groups_show(struct fastpath_module *m, char *buf)
{
struct fp_learner *priv = m->priv;
struct sockaddr addr;
int rc;
rc = kernel_getsockname(priv->nf_nl_sock, &addr);
if (rc < 0)
return scnprintf(buf, PAGE_SIZE, "ERROR\n");
return scnprintf(buf, PAGE_SIZE, "0x%08x\n", ((struct sockaddr_nl *)&addr)->nl_groups);
}
static ssize_t learner_rtnl_groups_show(struct fastpath_module *m, char *buf)
{
struct fp_learner *priv = m->priv;
struct sockaddr addr;
int rc;
rc = kernel_getsockname(priv->rt_nl_sock, &addr);
if (rc < 0)
return scnprintf(buf, PAGE_SIZE, "ERROR\n");
return scnprintf(buf, PAGE_SIZE, "0x%08x\n", ((struct sockaddr_nl *)&addr)->nl_groups);
}
static FP_ATTR(policy, S_IRUGO|S_IWUSR, learner_policy_show, learner_policy_store);
static FP_ATTR(lookup_retries, S_IRUGO|S_IWUSR, learner_lookup_retries_show, learner_lookup_retries_store);
static FP_ATTR(lookup_delay, S_IRUGO|S_IWUSR, learner_lookup_delay_show, learner_lookup_delay_store);
static FP_ATTR(nfnl_groups, S_IRUGO, learner_nfnl_groups_show, NULL);
static FP_ATTR(rtnl_groups, S_IRUGO, learner_rtnl_groups_show, NULL);
static FP_ATTR(update, S_IWUSR, NULL, learner_ct_update_all);
static FP_ATTR(block, S_IWUSR, NULL, learner_ct_block_all);
static struct attribute *fp_learner_attrs[] = {
&fp_attr_policy.attr,
&fp_attr_lookup_retries.attr,
&fp_attr_lookup_delay.attr,
&fp_attr_nfnl_groups.attr,
&fp_attr_rtnl_groups.attr,
&fp_attr_update.attr,
&fp_attr_block.attr,
NULL, /* need to NULL terminate the list of attributes */
};
static int
fp_learner_ioctl(struct fastpath_module *m, unsigned int cmd, void *data)
{
BUG_ON(!m);
switch (cmd) {
case FASTPATH_NL_C_IPT_NOTIFY:
__learner_ct_block_all(m);
break;
default:
pr_err("unsupported command %u\n", cmd);
return -ENOTSUPP;
}
return 0;
}
static void fp_learner_release(struct kobject *kobj)
{
struct fastpath_module *module = to_fpmod(kobj);
struct fp_learner *priv = module->priv;
priv->fp_rmmoding = 1;
nf_unregister_net_hooks(&init_net, nf_learner_hook_data, ARRAY_SIZE(nf_learner_hook_data));
unregister_inet6addr_notifier(&priv->inet6addr_notifier);
unregister_netevent_notifier(&priv->netevent_notifier);
learner_netdev_cleanup(&priv->netdev_notifier);
unregister_netdevice_notifier(&priv->netdev_notifier);
learner_nl_close(priv->nf_nl_sock);
learner_nl_close(priv->rt_nl_sock);
cancel_work_sync(&priv->update_work);
if (fp_learner_wq) {
flush_workqueue(priv->wq);
destroy_workqueue(priv->wq);
}
kfree(priv);
kfree(module);
pr_debug("fp_learner released\n");
}
static struct kobj_type ktype_learner = {
.sysfs_ops = &fp_sysfs_ops,
.default_attrs = fp_learner_attrs,
.release = fp_learner_release,
};
static int fp_learner_probe(struct fastpath_module *module)
{
struct fp_learner *priv;
int ret;
priv = kzalloc(sizeof(*priv), GFP_KERNEL);
if (!priv) {
pr_err("no memeory\n");
return -ENOMEM;
}
module->priv = priv;
snprintf(module->name, sizeof(module->name),"fp_learner");
spin_lock_init(&priv->lock);
INIT_LIST_HEAD(&priv->policy_list);
priv->lookups_retries = DEFAULT_LOOKUPS_RETRIES;
priv->fp_rmmoding = 0;
if (fp_learner_wq) {
INIT_LIST_HEAD(&priv->work_items_list);
priv->lookups_delay = DEFAULT_LOOKUPS_DELAY_MS;
priv->wq = create_singlethread_workqueue(module->name);
if (!priv->wq) {
pr_err("create workqueue failed\n");
ret = -EBUSY;
goto priv_kfree;
}
}
INIT_WORK(&priv->update_work, (void *)learner_ct_update_work);
rtnl_lock();
ret = learner_nl_open(priv, &priv->rt_nl_sock, NETLINK_ROUTE, RTNETLINK_GRP);
rtnl_unlock();
if (ret < 0) {
pr_err("learner_nl_open(NETLINK_ROUTE) failed (%d)\n", ret);
goto wq_destroy;
}
nfnl_lock(NFNL_SUBSYS_CTNETLINK);
ret = learner_nl_open(priv, &priv->nf_nl_sock, NETLINK_NETFILTER, NFNETLINK_GRP);
nfnl_unlock(NFNL_SUBSYS_CTNETLINK);
if (ret < 0) {
pr_err("learner_nl_open(NETLINK_NETFILTER) failed (%d)\n", ret);
goto nl_close_rt;
}
priv->netdev_notifier.notifier_call = learner_netdev_event;
ret = register_netdevice_notifier(&priv->netdev_notifier);
if (ret < 0) {
pr_err("register_netdevice_notifier failed (%d)\n", ret);
goto nl_close_nf;
}
priv->netevent_notifier.notifier_call = learner_netevent;
ret = register_netevent_notifier(&priv->netevent_notifier);
if (ret < 0) {
pr_err("register_netevent_notifier failed (%d)\n", ret);
goto netdev_notifier_unreg;
}
priv->inet6addr_notifier.notifier_call = fp_inet6addr_event;
ret = register_inet6addr_notifier(&priv->inet6addr_notifier);
if (ret < 0) {
pr_err("register_inet6addr_notifier failed (%d)\n", ret);
goto netdev_netevent_unreg;
}
ret = nf_register_net_hooks(&init_net, nf_learner_hook_data, ARRAY_SIZE(nf_learner_hook_data));
if (ret < 0) {
pr_err("nf_register_hooks failed (%d)\n", ret);
goto in6_notifier_err;
}
kobject_init(&module->kobj, &ktype_learner);
ret = kobject_add(&module->kobj, module->fastpath->kobj, "%s", module->name);
if (ret < 0) {
pr_err("kobject_add failed (%d)\n", ret);
goto nf_hooks_unreg;
}
kobject_uevent(&module->kobj, KOBJ_ADD);
pr_debug("fp_learner probed\n");
return 0;
nf_hooks_unreg:
kobject_put(&module->kobj);
nf_unregister_net_hooks(&init_net, nf_learner_hook_data, ARRAY_SIZE(nf_learner_hook_data));
in6_notifier_err:
unregister_inet6addr_notifier(&priv->inet6addr_notifier);
netdev_netevent_unreg:
unregister_netevent_notifier(&priv->netevent_notifier);
netdev_notifier_unreg:
learner_netdev_cleanup(&priv->netdev_notifier);
unregister_netdevice_notifier(&priv->netdev_notifier);
nl_close_nf:
learner_nl_close(priv->nf_nl_sock);
nl_close_rt:
learner_nl_close(priv->rt_nl_sock);
wq_destroy:
if (fp_learner_wq) {
flush_workqueue(priv->wq);
destroy_workqueue(priv->wq);
}
priv_kfree:
kfree(priv);
return ret;
}
static int fp_learner_remove(struct fastpath_module *module)
{
kobject_put(&module->kobj);
pr_debug("fp_learner removed\n");
return 0;
}
struct fastpath_module_ops fp_learner_ops = {
.probe = fp_learner_probe,
.remove = fp_learner_remove,
.ioctl = fp_learner_ioctl,
};
module_param(fp_learner_wq, bool, S_IRUGO);
MODULE_PARM_DESC(fp_learner_wq, "fastpath learner worqueue mode (default="
__MODULE_STRING(FP_LEARNER_WQ_DEFAULT) ")");