Commit 12f7a505 authored by Pablo Neira Ayuso's avatar Pablo Neira Ayuso

netfilter: add user-space connection tracking helper infrastructure

There are good reasons to supports helpers in user-space instead:

* Rapid connection tracking helper development, as developing code
  in user-space is usually faster.

* Reliability: A buggy helper does not crash the kernel. Moreover,
  we can monitor the helper process and restart it in case of problems.

* Security: Avoid complex string matching and mangling in kernel-space
  running in privileged mode. Going further, we can even think about
  running user-space helpers as a non-root process.

* Extensibility: It allows the development of very specific helpers (most
  likely non-standard proprietary protocols) that are very likely not to be
  accepted for mainline inclusion in the form of kernel-space connection
  tracking helpers.

This patch adds the infrastructure to allow the implementation of
user-space conntrack helpers by means of the new nfnetlink subsystem
`nfnetlink_cthelper' and the existing queueing infrastructure
(nfnetlink_queue).

I had to add the new hook NF_IP6_PRI_CONNTRACK_HELPER to register
ipv[4|6]_helper which results from splitting ipv[4|6]_confirm into
two pieces. This change is required not to break NAT sequence
adjustment and conntrack confirmation for traffic that is enqueued
to our user-space conntrack helpers.

Basic operation, in a few steps:

1) Register user-space helper by means of `nfct':

 nfct helper add ftp inet tcp

 [ It must be a valid existing helper supported by conntrack-tools ]

2) Add rules to enable the FTP user-space helper which is
   used to track traffic going to TCP port 21.

For locally generated packets:

 iptables -I OUTPUT -t raw -p tcp --dport 21 -j CT --helper ftp

For non-locally generated packets:

 iptables -I PREROUTING -t raw -p tcp --dport 21 -j CT --helper ftp

3) Run the test conntrackd in helper mode (see example files under
   doc/helper/conntrackd.conf

 conntrackd

4) Generate FTP traffic going, if everything is OK, then conntrackd
   should create expectations (you can check that with `conntrack':

 conntrack -E expect

    [NEW] 301 proto=6 src=192.168.1.136 dst=130.89.148.12 sport=0 dport=54037 mask-src=255.255.255.255 mask-dst=255.255.255.255 sport=0 dport=65535 master-src=192.168.1.136 master-dst=130.89.148.12 sport=57127 dport=21 class=0 helper=ftp
[DESTROY] 301 proto=6 src=192.168.1.136 dst=130.89.148.12 sport=0 dport=54037 mask-src=255.255.255.255 mask-dst=255.255.255.255 sport=0 dport=65535 master-src=192.168.1.136 master-dst=130.89.148.12 sport=57127 dport=21 class=0 helper=ftp

This confirms that our test helper is receiving packets including the
conntrack information, and adding expectations in kernel-space.

The user-space helper can also store its private tracking information
in the conntrack structure in the kernel via the CTA_HELP_INFO. The
kernel will consider this a binary blob whose layout is unknown. This
information will be included in the information that is transfered
to user-space via glue code that integrates nfnetlink_queue and
ctnetlink.
Signed-off-by: default avatarPablo Neira Ayuso <pablo@netfilter.org>
parent ae243bee
...@@ -10,6 +10,7 @@ header-y += nfnetlink.h ...@@ -10,6 +10,7 @@ header-y += nfnetlink.h
header-y += nfnetlink_acct.h header-y += nfnetlink_acct.h
header-y += nfnetlink_compat.h header-y += nfnetlink_compat.h
header-y += nfnetlink_conntrack.h header-y += nfnetlink_conntrack.h
header-y += nfnetlink_cthelper.h
header-y += nfnetlink_cttimeout.h header-y += nfnetlink_cttimeout.h
header-y += nfnetlink_log.h header-y += nfnetlink_log.h
header-y += nfnetlink_queue.h header-y += nfnetlink_queue.h
......
...@@ -50,7 +50,8 @@ struct nfgenmsg { ...@@ -50,7 +50,8 @@ struct nfgenmsg {
#define NFNL_SUBSYS_IPSET 6 #define NFNL_SUBSYS_IPSET 6
#define NFNL_SUBSYS_ACCT 7 #define NFNL_SUBSYS_ACCT 7
#define NFNL_SUBSYS_CTNETLINK_TIMEOUT 8 #define NFNL_SUBSYS_CTNETLINK_TIMEOUT 8
#define NFNL_SUBSYS_COUNT 9 #define NFNL_SUBSYS_CTHELPER 9
#define NFNL_SUBSYS_COUNT 10
#ifdef __KERNEL__ #ifdef __KERNEL__
......
#ifndef _NFNL_CTHELPER_H_
#define _NFNL_CTHELPER_H_
#define NFCT_HELPER_STATUS_DISABLED 0
#define NFCT_HELPER_STATUS_ENABLED 1
enum nfnl_acct_msg_types {
NFNL_MSG_CTHELPER_NEW,
NFNL_MSG_CTHELPER_GET,
NFNL_MSG_CTHELPER_DEL,
NFNL_MSG_CTHELPER_MAX
};
enum nfnl_cthelper_type {
NFCTH_UNSPEC,
NFCTH_NAME,
NFCTH_TUPLE,
NFCTH_QUEUE_NUM,
NFCTH_POLICY,
NFCTH_PRIV_DATA_LEN,
NFCTH_STATUS,
__NFCTH_MAX
};
#define NFCTH_MAX (__NFCTH_MAX - 1)
enum nfnl_cthelper_policy_type {
NFCTH_POLICY_SET_UNSPEC,
NFCTH_POLICY_SET_NUM,
NFCTH_POLICY_SET,
NFCTH_POLICY_SET1 = NFCTH_POLICY_SET,
NFCTH_POLICY_SET2,
NFCTH_POLICY_SET3,
NFCTH_POLICY_SET4,
__NFCTH_POLICY_SET_MAX
};
#define NFCTH_POLICY_SET_MAX (__NFCTH_POLICY_SET_MAX - 1)
enum nfnl_cthelper_pol_type {
NFCTH_POLICY_UNSPEC,
NFCTH_POLICY_NAME,
NFCTH_POLICY_EXPECT_MAX,
NFCTH_POLICY_EXPECT_TIMEOUT,
__NFCTH_POLICY_MAX
};
#define NFCTH_POLICY_MAX (__NFCTH_POLICY_MAX - 1)
enum nfnl_cthelper_tuple_type {
NFCTH_TUPLE_UNSPEC,
NFCTH_TUPLE_L3PROTONUM,
NFCTH_TUPLE_L4PROTONUM,
__NFCTH_TUPLE_MAX,
};
#define NFCTH_TUPLE_MAX (__NFCTH_TUPLE_MAX - 1)
#endif /* _NFNL_CTHELPER_H */
...@@ -66,6 +66,7 @@ enum nf_ip_hook_priorities { ...@@ -66,6 +66,7 @@ enum nf_ip_hook_priorities {
NF_IP_PRI_SECURITY = 50, NF_IP_PRI_SECURITY = 50,
NF_IP_PRI_NAT_SRC = 100, NF_IP_PRI_NAT_SRC = 100,
NF_IP_PRI_SELINUX_LAST = 225, NF_IP_PRI_SELINUX_LAST = 225,
NF_IP_PRI_CONNTRACK_HELPER = 300,
NF_IP_PRI_CONNTRACK_CONFIRM = INT_MAX, NF_IP_PRI_CONNTRACK_CONFIRM = INT_MAX,
NF_IP_PRI_LAST = INT_MAX, NF_IP_PRI_LAST = INT_MAX,
}; };
......
...@@ -71,6 +71,7 @@ enum nf_ip6_hook_priorities { ...@@ -71,6 +71,7 @@ enum nf_ip6_hook_priorities {
NF_IP6_PRI_SECURITY = 50, NF_IP6_PRI_SECURITY = 50,
NF_IP6_PRI_NAT_SRC = 100, NF_IP6_PRI_NAT_SRC = 100,
NF_IP6_PRI_SELINUX_LAST = 225, NF_IP6_PRI_SELINUX_LAST = 225,
NF_IP6_PRI_CONNTRACK_HELPER = 300,
NF_IP6_PRI_LAST = INT_MAX, NF_IP6_PRI_LAST = INT_MAX,
}; };
......
...@@ -15,6 +15,11 @@ ...@@ -15,6 +15,11 @@
struct module; struct module;
enum nf_ct_helper_flags {
NF_CT_HELPER_F_USERSPACE = (1 << 0),
NF_CT_HELPER_F_CONFIGURED = (1 << 1),
};
#define NF_CT_HELPER_NAME_LEN 16 #define NF_CT_HELPER_NAME_LEN 16
struct nf_conntrack_helper { struct nf_conntrack_helper {
...@@ -42,6 +47,9 @@ struct nf_conntrack_helper { ...@@ -42,6 +47,9 @@ struct nf_conntrack_helper {
int (*from_nlattr)(struct nlattr *attr, struct nf_conn *ct); int (*from_nlattr)(struct nlattr *attr, struct nf_conn *ct);
int (*to_nlattr)(struct sk_buff *skb, const struct nf_conn *ct); int (*to_nlattr)(struct sk_buff *skb, const struct nf_conn *ct);
unsigned int expect_class_max; unsigned int expect_class_max;
unsigned int flags;
unsigned int queue_num; /* For user-space helpers. */
}; };
extern struct nf_conntrack_helper * extern struct nf_conntrack_helper *
...@@ -96,4 +104,7 @@ nf_ct_helper_expectfn_find_by_name(const char *name); ...@@ -96,4 +104,7 @@ nf_ct_helper_expectfn_find_by_name(const char *name);
struct nf_ct_helper_expectfn * struct nf_ct_helper_expectfn *
nf_ct_helper_expectfn_find_by_symbol(const void *symbol); nf_ct_helper_expectfn_find_by_symbol(const void *symbol);
extern struct hlist_head *nf_ct_helper_hash;
extern unsigned int nf_ct_helper_hsize;
#endif /*_NF_CONNTRACK_HELPER_H*/ #endif /*_NF_CONNTRACK_HELPER_H*/
...@@ -95,11 +95,11 @@ static int ipv4_get_l4proto(const struct sk_buff *skb, unsigned int nhoff, ...@@ -95,11 +95,11 @@ static int ipv4_get_l4proto(const struct sk_buff *skb, unsigned int nhoff,
return NF_ACCEPT; return NF_ACCEPT;
} }
static unsigned int ipv4_confirm(unsigned int hooknum, static unsigned int ipv4_helper(unsigned int hooknum,
struct sk_buff *skb, struct sk_buff *skb,
const struct net_device *in, const struct net_device *in,
const struct net_device *out, const struct net_device *out,
int (*okfn)(struct sk_buff *)) int (*okfn)(struct sk_buff *))
{ {
struct nf_conn *ct; struct nf_conn *ct;
enum ip_conntrack_info ctinfo; enum ip_conntrack_info ctinfo;
...@@ -110,24 +110,38 @@ static unsigned int ipv4_confirm(unsigned int hooknum, ...@@ -110,24 +110,38 @@ static unsigned int ipv4_confirm(unsigned int hooknum,
/* This is where we call the helper: as the packet goes out. */ /* This is where we call the helper: as the packet goes out. */
ct = nf_ct_get(skb, &ctinfo); ct = nf_ct_get(skb, &ctinfo);
if (!ct || ctinfo == IP_CT_RELATED_REPLY) if (!ct || ctinfo == IP_CT_RELATED_REPLY)
goto out; return NF_ACCEPT;
help = nfct_help(ct); help = nfct_help(ct);
if (!help) if (!help)
goto out; return NF_ACCEPT;
/* rcu_read_lock()ed by nf_hook_slow */ /* rcu_read_lock()ed by nf_hook_slow */
helper = rcu_dereference(help->helper); helper = rcu_dereference(help->helper);
if (!helper) if (!helper)
goto out; return NF_ACCEPT;
ret = helper->help(skb, skb_network_offset(skb) + ip_hdrlen(skb), ret = helper->help(skb, skb_network_offset(skb) + ip_hdrlen(skb),
ct, ctinfo); ct, ctinfo);
if (ret != NF_ACCEPT) { if (ret != NF_ACCEPT && (ret & NF_VERDICT_MASK) != NF_QUEUE) {
nf_log_packet(NFPROTO_IPV4, hooknum, skb, in, out, NULL, nf_log_packet(NFPROTO_IPV4, hooknum, skb, in, out, NULL,
"nf_ct_%s: dropping packet", helper->name); "nf_ct_%s: dropping packet", helper->name);
return ret;
} }
return ret;
}
static unsigned int ipv4_confirm(unsigned int hooknum,
struct sk_buff *skb,
const struct net_device *in,
const struct net_device *out,
int (*okfn)(struct sk_buff *))
{
struct nf_conn *ct;
enum ip_conntrack_info ctinfo;
ct = nf_ct_get(skb, &ctinfo);
if (!ct || ctinfo == IP_CT_RELATED_REPLY)
goto out;
/* adjust seqs for loopback traffic only in outgoing direction */ /* adjust seqs for loopback traffic only in outgoing direction */
if (test_bit(IPS_SEQ_ADJUST_BIT, &ct->status) && if (test_bit(IPS_SEQ_ADJUST_BIT, &ct->status) &&
...@@ -184,6 +198,13 @@ static struct nf_hook_ops ipv4_conntrack_ops[] __read_mostly = { ...@@ -184,6 +198,13 @@ static struct nf_hook_ops ipv4_conntrack_ops[] __read_mostly = {
.hooknum = NF_INET_LOCAL_OUT, .hooknum = NF_INET_LOCAL_OUT,
.priority = NF_IP_PRI_CONNTRACK, .priority = NF_IP_PRI_CONNTRACK,
}, },
{
.hook = ipv4_helper,
.owner = THIS_MODULE,
.pf = NFPROTO_IPV4,
.hooknum = NF_INET_POST_ROUTING,
.priority = NF_IP_PRI_CONNTRACK_HELPER,
},
{ {
.hook = ipv4_confirm, .hook = ipv4_confirm,
.owner = THIS_MODULE, .owner = THIS_MODULE,
...@@ -191,6 +212,13 @@ static struct nf_hook_ops ipv4_conntrack_ops[] __read_mostly = { ...@@ -191,6 +212,13 @@ static struct nf_hook_ops ipv4_conntrack_ops[] __read_mostly = {
.hooknum = NF_INET_POST_ROUTING, .hooknum = NF_INET_POST_ROUTING,
.priority = NF_IP_PRI_CONNTRACK_CONFIRM, .priority = NF_IP_PRI_CONNTRACK_CONFIRM,
}, },
{
.hook = ipv4_helper,
.owner = THIS_MODULE,
.pf = NFPROTO_IPV4,
.hooknum = NF_INET_LOCAL_IN,
.priority = NF_IP_PRI_CONNTRACK_HELPER,
},
{ {
.hook = ipv4_confirm, .hook = ipv4_confirm,
.owner = THIS_MODULE, .owner = THIS_MODULE,
......
...@@ -143,11 +143,11 @@ static int ipv6_get_l4proto(const struct sk_buff *skb, unsigned int nhoff, ...@@ -143,11 +143,11 @@ static int ipv6_get_l4proto(const struct sk_buff *skb, unsigned int nhoff,
return NF_ACCEPT; return NF_ACCEPT;
} }
static unsigned int ipv6_confirm(unsigned int hooknum, static unsigned int ipv6_helper(unsigned int hooknum,
struct sk_buff *skb, struct sk_buff *skb,
const struct net_device *in, const struct net_device *in,
const struct net_device *out, const struct net_device *out,
int (*okfn)(struct sk_buff *)) int (*okfn)(struct sk_buff *))
{ {
struct nf_conn *ct; struct nf_conn *ct;
const struct nf_conn_help *help; const struct nf_conn_help *help;
...@@ -161,15 +161,15 @@ static unsigned int ipv6_confirm(unsigned int hooknum, ...@@ -161,15 +161,15 @@ static unsigned int ipv6_confirm(unsigned int hooknum,
/* This is where we call the helper: as the packet goes out. */ /* This is where we call the helper: as the packet goes out. */
ct = nf_ct_get(skb, &ctinfo); ct = nf_ct_get(skb, &ctinfo);
if (!ct || ctinfo == IP_CT_RELATED_REPLY) if (!ct || ctinfo == IP_CT_RELATED_REPLY)
goto out; return NF_ACCEPT;
help = nfct_help(ct); help = nfct_help(ct);
if (!help) if (!help)
goto out; return NF_ACCEPT;
/* rcu_read_lock()ed by nf_hook_slow */ /* rcu_read_lock()ed by nf_hook_slow */
helper = rcu_dereference(help->helper); helper = rcu_dereference(help->helper);
if (!helper) if (!helper)
goto out; return NF_ACCEPT;
protoff = nf_ct_ipv6_skip_exthdr(skb, extoff, &pnum, protoff = nf_ct_ipv6_skip_exthdr(skb, extoff, &pnum,
skb->len - extoff); skb->len - extoff);
...@@ -179,12 +179,19 @@ static unsigned int ipv6_confirm(unsigned int hooknum, ...@@ -179,12 +179,19 @@ static unsigned int ipv6_confirm(unsigned int hooknum,
} }
ret = helper->help(skb, protoff, ct, ctinfo); ret = helper->help(skb, protoff, ct, ctinfo);
if (ret != NF_ACCEPT) { if (ret != NF_ACCEPT && (ret & NF_VERDICT_MASK) != NF_QUEUE) {
nf_log_packet(NFPROTO_IPV6, hooknum, skb, in, out, NULL, nf_log_packet(NFPROTO_IPV6, hooknum, skb, in, out, NULL,
"nf_ct_%s: dropping packet", helper->name); "nf_ct_%s: dropping packet", helper->name);
return ret;
} }
out: return ret;
}
static unsigned int ipv6_confirm(unsigned int hooknum,
struct sk_buff *skb,
const struct net_device *in,
const struct net_device *out,
int (*okfn)(struct sk_buff *))
{
/* We've seen it coming out the other side: confirm it */ /* We've seen it coming out the other side: confirm it */
return nf_conntrack_confirm(skb); return nf_conntrack_confirm(skb);
} }
...@@ -253,6 +260,13 @@ static struct nf_hook_ops ipv6_conntrack_ops[] __read_mostly = { ...@@ -253,6 +260,13 @@ static struct nf_hook_ops ipv6_conntrack_ops[] __read_mostly = {
.hooknum = NF_INET_LOCAL_OUT, .hooknum = NF_INET_LOCAL_OUT,
.priority = NF_IP6_PRI_CONNTRACK, .priority = NF_IP6_PRI_CONNTRACK,
}, },
{
.hook = ipv6_helper,
.owner = THIS_MODULE,
.pf = NFPROTO_IPV6,
.hooknum = NF_INET_POST_ROUTING,
.priority = NF_IP6_PRI_CONNTRACK_HELPER,
},
{ {
.hook = ipv6_confirm, .hook = ipv6_confirm,
.owner = THIS_MODULE, .owner = THIS_MODULE,
...@@ -260,6 +274,13 @@ static struct nf_hook_ops ipv6_conntrack_ops[] __read_mostly = { ...@@ -260,6 +274,13 @@ static struct nf_hook_ops ipv6_conntrack_ops[] __read_mostly = {
.hooknum = NF_INET_POST_ROUTING, .hooknum = NF_INET_POST_ROUTING,
.priority = NF_IP6_PRI_LAST, .priority = NF_IP6_PRI_LAST,
}, },
{
.hook = ipv6_helper,
.owner = THIS_MODULE,
.pf = NFPROTO_IPV6,
.hooknum = NF_INET_LOCAL_IN,
.priority = NF_IP6_PRI_CONNTRACK_HELPER,
},
{ {
.hook = ipv6_confirm, .hook = ipv6_confirm,
.owner = THIS_MODULE, .owner = THIS_MODULE,
......
...@@ -12,6 +12,14 @@ tristate "Netfilter NFACCT over NFNETLINK interface" ...@@ -12,6 +12,14 @@ tristate "Netfilter NFACCT over NFNETLINK interface"
If this option is enabled, the kernel will include support If this option is enabled, the kernel will include support
for extended accounting via NFNETLINK. for extended accounting via NFNETLINK.
config NETFILTER_NETLINK_CTHELPER
tristate "Netfilter CTHELPER over NFNETLINK interface"
depends on NETFILTER_ADVANCED
select NETFILTER_NETLINK
help
If this option is enabled, the kernel will include support
for user-space connection tracking helpers via NFNETLINK.
config NETFILTER_NETLINK_QUEUE config NETFILTER_NETLINK_QUEUE
tristate "Netfilter NFQUEUE over NFNETLINK interface" tristate "Netfilter NFQUEUE over NFNETLINK interface"
depends on NETFILTER_ADVANCED depends on NETFILTER_ADVANCED
......
...@@ -9,6 +9,7 @@ obj-$(CONFIG_NETFILTER) = netfilter.o ...@@ -9,6 +9,7 @@ obj-$(CONFIG_NETFILTER) = netfilter.o
obj-$(CONFIG_NETFILTER_NETLINK) += nfnetlink.o obj-$(CONFIG_NETFILTER_NETLINK) += nfnetlink.o
obj-$(CONFIG_NETFILTER_NETLINK_ACCT) += nfnetlink_acct.o obj-$(CONFIG_NETFILTER_NETLINK_ACCT) += nfnetlink_acct.o
obj-$(CONFIG_NETFILTER_NETLINK_CTHELPER) += nfnetlink_cthelper.o
obj-$(CONFIG_NETFILTER_NETLINK_QUEUE) += nfnetlink_queue.o obj-$(CONFIG_NETFILTER_NETLINK_QUEUE) += nfnetlink_queue.o
obj-$(CONFIG_NETFILTER_NETLINK_LOG) += nfnetlink_log.o obj-$(CONFIG_NETFILTER_NETLINK_LOG) += nfnetlink_log.o
......
...@@ -30,8 +30,10 @@ ...@@ -30,8 +30,10 @@
#include <net/netfilter/nf_conntrack_extend.h> #include <net/netfilter/nf_conntrack_extend.h>
static DEFINE_MUTEX(nf_ct_helper_mutex); static DEFINE_MUTEX(nf_ct_helper_mutex);
static struct hlist_head *nf_ct_helper_hash __read_mostly; struct hlist_head *nf_ct_helper_hash __read_mostly;
static unsigned int nf_ct_helper_hsize __read_mostly; EXPORT_SYMBOL_GPL(nf_ct_helper_hash);
unsigned int nf_ct_helper_hsize __read_mostly;
EXPORT_SYMBOL_GPL(nf_ct_helper_hsize);
static unsigned int nf_ct_helper_count __read_mostly; static unsigned int nf_ct_helper_count __read_mostly;
static bool nf_ct_auto_assign_helper __read_mostly = true; static bool nf_ct_auto_assign_helper __read_mostly = true;
...@@ -322,6 +324,9 @@ EXPORT_SYMBOL_GPL(nf_ct_helper_expectfn_find_by_symbol); ...@@ -322,6 +324,9 @@ EXPORT_SYMBOL_GPL(nf_ct_helper_expectfn_find_by_symbol);
int nf_conntrack_helper_register(struct nf_conntrack_helper *me) int nf_conntrack_helper_register(struct nf_conntrack_helper *me)
{ {
int ret = 0;
struct nf_conntrack_helper *cur;
struct hlist_node *n;
unsigned int h = helper_hash(&me->tuple); unsigned int h = helper_hash(&me->tuple);
BUG_ON(me->expect_policy == NULL); BUG_ON(me->expect_policy == NULL);
...@@ -329,11 +334,19 @@ int nf_conntrack_helper_register(struct nf_conntrack_helper *me) ...@@ -329,11 +334,19 @@ int nf_conntrack_helper_register(struct nf_conntrack_helper *me)
BUG_ON(strlen(me->name) > NF_CT_HELPER_NAME_LEN - 1); BUG_ON(strlen(me->name) > NF_CT_HELPER_NAME_LEN - 1);
mutex_lock(&nf_ct_helper_mutex); mutex_lock(&nf_ct_helper_mutex);
hlist_for_each_entry(cur, n, &nf_ct_helper_hash[h], hnode) {
if (strncmp(cur->name, me->name, NF_CT_HELPER_NAME_LEN) == 0 &&
cur->tuple.src.l3num == me->tuple.src.l3num &&
cur->tuple.dst.protonum == me->tuple.dst.protonum) {
ret = -EEXIST;
goto out;
}
}
hlist_add_head_rcu(&me->hnode, &nf_ct_helper_hash[h]); hlist_add_head_rcu(&me->hnode, &nf_ct_helper_hash[h]);
nf_ct_helper_count++; nf_ct_helper_count++;
out:
mutex_unlock(&nf_ct_helper_mutex); mutex_unlock(&nf_ct_helper_mutex);
return ret;
return 0;
} }
EXPORT_SYMBOL_GPL(nf_conntrack_helper_register); EXPORT_SYMBOL_GPL(nf_conntrack_helper_register);
......
This diff is collapsed.
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment