Commit 7f3579e1 authored by David S. Miller's avatar David S. Miller

Merge git://git.kernel.org/pub/scm/linux/kernel/git/pablo/nf-next

Pablo Neira Ayuso says:

====================
Netfilter updates for net-next

The following patchset contains Netfilter updates for net-next:

1) Add nfgenmsg field to nfnetlink's struct nfnl_info and use it.

2) Remove nft_ctx_init_from_elemattr() and nft_ctx_init_from_setattr()
   helper functions.

3) Add the nf_ct_pernet() helper function to fetch the conntrack
   pernetns data area.

4) Expose TCP and UDP flowtable offload timeouts through sysctl,
   from Oz Shlomo.

5) Add nfnetlink_hook subsystem to fetch the netfilter hook
   pipeline configuration, from Florian Westphal. This also includes
   a new field to annotate the hook type as metadata.

6) Fix unsafe memory access to non-linear skbuff in the new SCTP
   chunk support for nft_exthdr, from Phil Sutter.
====================
Signed-off-by: default avatarDavid S. Miller <davem@davemloft.net>
parents 4e744cb8 c5c6accd
...@@ -77,12 +77,18 @@ struct nf_hook_state { ...@@ -77,12 +77,18 @@ struct nf_hook_state {
typedef unsigned int nf_hookfn(void *priv, typedef unsigned int nf_hookfn(void *priv,
struct sk_buff *skb, struct sk_buff *skb,
const struct nf_hook_state *state); const struct nf_hook_state *state);
enum nf_hook_ops_type {
NF_HOOK_OP_UNDEFINED,
NF_HOOK_OP_NF_TABLES,
};
struct nf_hook_ops { struct nf_hook_ops {
/* User fills in from here down. */ /* User fills in from here down. */
nf_hookfn *hook; nf_hookfn *hook;
struct net_device *dev; struct net_device *dev;
void *priv; void *priv;
u_int8_t pf; u8 pf;
enum nf_hook_ops_type hook_ops_type:8;
unsigned int hooknum; unsigned int hooknum;
/* Hooks are ordered in ascending priority. */ /* Hooks are ordered in ascending priority. */
int priority; int priority;
......
...@@ -11,6 +11,7 @@ struct nfnl_info { ...@@ -11,6 +11,7 @@ struct nfnl_info {
struct net *net; struct net *net;
struct sock *sk; struct sock *sk;
const struct nlmsghdr *nlh; const struct nlmsghdr *nlh;
const struct nfgenmsg *nfmsg;
struct netlink_ext_ack *extack; struct netlink_ext_ack *extack;
}; };
......
...@@ -346,6 +346,13 @@ nf_ct_set(struct sk_buff *skb, struct nf_conn *ct, enum ip_conntrack_info info) ...@@ -346,6 +346,13 @@ nf_ct_set(struct sk_buff *skb, struct nf_conn *ct, enum ip_conntrack_info info)
skb_set_nfct(skb, (unsigned long)ct | info); skb_set_nfct(skb, (unsigned long)ct | info);
} }
extern unsigned int nf_conntrack_net_id;
static inline struct nf_conntrack_net *nf_ct_pernet(const struct net *net)
{
return net_generic(net, nf_conntrack_net_id);
}
#define NF_CT_STAT_INC(net, count) __this_cpu_inc((net)->ct.stat->count) #define NF_CT_STAT_INC(net, count) __this_cpu_inc((net)->ct.stat->count)
#define NF_CT_STAT_INC_ATOMIC(net, count) this_cpu_inc((net)->ct.stat->count) #define NF_CT_STAT_INC_ATOMIC(net, count) this_cpu_inc((net)->ct.stat->count)
#define NF_CT_STAT_ADD_ATOMIC(net, count, v) this_cpu_add((net)->ct.stat->count, (v)) #define NF_CT_STAT_ADD_ATOMIC(net, count, v) this_cpu_add((net)->ct.stat->count, (v))
......
...@@ -177,6 +177,8 @@ struct flow_offload { ...@@ -177,6 +177,8 @@ struct flow_offload {
#define NF_FLOW_TIMEOUT (30 * HZ) #define NF_FLOW_TIMEOUT (30 * HZ)
#define nf_flowtable_time_stamp (u32)jiffies #define nf_flowtable_time_stamp (u32)jiffies
unsigned long flow_offload_get_timeout(struct flow_offload *flow);
static inline __s32 nf_flow_timeout_delta(unsigned int timeout) static inline __s32 nf_flow_timeout_delta(unsigned int timeout)
{ {
return (__s32)(timeout - nf_flowtable_time_stamp); return (__s32)(timeout - nf_flowtable_time_stamp);
......
...@@ -27,6 +27,10 @@ struct nf_tcp_net { ...@@ -27,6 +27,10 @@ struct nf_tcp_net {
u8 tcp_loose; u8 tcp_loose;
u8 tcp_be_liberal; u8 tcp_be_liberal;
u8 tcp_max_retrans; u8 tcp_max_retrans;
#if IS_ENABLED(CONFIG_NF_FLOW_TABLE)
unsigned int offload_timeout;
unsigned int offload_pickup;
#endif
}; };
enum udp_conntrack { enum udp_conntrack {
...@@ -37,6 +41,10 @@ enum udp_conntrack { ...@@ -37,6 +41,10 @@ enum udp_conntrack {
struct nf_udp_net { struct nf_udp_net {
unsigned int timeouts[UDP_CT_MAX]; unsigned int timeouts[UDP_CT_MAX];
#if IS_ENABLED(CONFIG_NF_FLOW_TABLE)
unsigned int offload_timeout;
unsigned int offload_pickup;
#endif
}; };
struct nf_icmp_net { struct nf_icmp_net {
......
...@@ -60,7 +60,8 @@ struct nfgenmsg { ...@@ -60,7 +60,8 @@ struct nfgenmsg {
#define NFNL_SUBSYS_CTHELPER 9 #define NFNL_SUBSYS_CTHELPER 9
#define NFNL_SUBSYS_NFTABLES 10 #define NFNL_SUBSYS_NFTABLES 10
#define NFNL_SUBSYS_NFT_COMPAT 11 #define NFNL_SUBSYS_NFT_COMPAT 11
#define NFNL_SUBSYS_COUNT 12 #define NFNL_SUBSYS_HOOK 12
#define NFNL_SUBSYS_COUNT 13
/* Reserved control nfnetlink messages */ /* Reserved control nfnetlink messages */
#define NFNL_MSG_BATCH_BEGIN NLMSG_MIN_TYPE #define NFNL_MSG_BATCH_BEGIN NLMSG_MIN_TYPE
......
/* SPDX-License-Identifier: GPL-2.0 WITH Linux-syscall-note */
#ifndef _NFNL_HOOK_H_
#define _NFNL_HOOK_H_
enum nfnl_hook_msg_types {
NFNL_MSG_HOOK_GET,
NFNL_MSG_HOOK_MAX,
};
/**
* enum nfnl_hook_attributes - netfilter hook netlink attributes
*
* @NFNLA_HOOK_HOOKNUM: netfilter hook number (NLA_U32)
* @NFNLA_HOOK_PRIORITY: netfilter hook priority (NLA_U32)
* @NFNLA_HOOK_DEV: netdevice name (NLA_STRING)
* @NFNLA_HOOK_FUNCTION_NAME: hook function name (NLA_STRING)
* @NFNLA_HOOK_MODULE_NAME: kernel module that registered this hook (NLA_STRING)
* @NFNLA_HOOK_CHAIN_INFO: basechain hook metadata (NLA_NESTED)
*/
enum nfnl_hook_attributes {
NFNLA_HOOK_UNSPEC,
NFNLA_HOOK_HOOKNUM,
NFNLA_HOOK_PRIORITY,
NFNLA_HOOK_DEV,
NFNLA_HOOK_FUNCTION_NAME,
NFNLA_HOOK_MODULE_NAME,
NFNLA_HOOK_CHAIN_INFO,
__NFNLA_HOOK_MAX
};
#define NFNLA_HOOK_MAX (__NFNLA_HOOK_MAX - 1)
/**
* enum nfnl_hook_chain_info_attributes - chain description
*
* NFNLA_HOOK_INFO_DESC: nft chain and table name (enum nft_table_attributes) (NLA_NESTED)
* NFNLA_HOOK_INFO_TYPE: chain type (enum nfnl_hook_chaintype) (NLA_U32)
*/
enum nfnl_hook_chain_info_attributes {
NFNLA_HOOK_INFO_UNSPEC,
NFNLA_HOOK_INFO_DESC,
NFNLA_HOOK_INFO_TYPE,
__NFNLA_HOOK_INFO_MAX,
};
#define NFNLA_HOOK_INFO_MAX (__NFNLA_HOOK_INFO_MAX - 1)
/**
* enum nfnl_hook_chaintype - chain type
*
* @NFNL_HOOK_TYPE_NFTABLES nf_tables base chain
*/
enum nfnl_hook_chaintype {
NFNL_HOOK_TYPE_NFTABLES = 0x1,
};
#endif /* _NFNL_HOOK_H */
...@@ -19,6 +19,16 @@ config NETFILTER_FAMILY_BRIDGE ...@@ -19,6 +19,16 @@ config NETFILTER_FAMILY_BRIDGE
config NETFILTER_FAMILY_ARP config NETFILTER_FAMILY_ARP
bool bool
config NETFILTER_NETLINK_HOOK
tristate "Netfilter base hook dump support"
depends on NETFILTER_ADVANCED
depends on NF_TABLES
select NETFILTER_NETLINK
help
If this option is enabled, the kernel will include support
to list the base netfilter hooks via NFNETLINK.
This is helpful for debugging.
config NETFILTER_NETLINK_ACCT config NETFILTER_NETLINK_ACCT
tristate "Netfilter NFACCT over NFNETLINK interface" tristate "Netfilter NFACCT over NFNETLINK interface"
depends on NETFILTER_ADVANCED depends on NETFILTER_ADVANCED
......
...@@ -22,6 +22,7 @@ obj-$(CONFIG_NETFILTER_NETLINK_ACCT) += nfnetlink_acct.o ...@@ -22,6 +22,7 @@ obj-$(CONFIG_NETFILTER_NETLINK_ACCT) += nfnetlink_acct.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
obj-$(CONFIG_NETFILTER_NETLINK_OSF) += nfnetlink_osf.o obj-$(CONFIG_NETFILTER_NETLINK_OSF) += nfnetlink_osf.o
obj-$(CONFIG_NETFILTER_NETLINK_HOOK) += nfnetlink_hook.o
# connection tracking # connection tracking
obj-$(CONFIG_NF_CONNTRACK) += nf_conntrack.o obj-$(CONFIG_NF_CONNTRACK) += nf_conntrack.o
......
...@@ -55,8 +55,6 @@ ...@@ -55,8 +55,6 @@
#include "nf_internals.h" #include "nf_internals.h"
extern unsigned int nf_conntrack_net_id;
__cacheline_aligned_in_smp spinlock_t nf_conntrack_locks[CONNTRACK_LOCKS]; __cacheline_aligned_in_smp spinlock_t nf_conntrack_locks[CONNTRACK_LOCKS];
EXPORT_SYMBOL_GPL(nf_conntrack_locks); EXPORT_SYMBOL_GPL(nf_conntrack_locks);
...@@ -87,8 +85,6 @@ static __read_mostly bool nf_conntrack_locks_all; ...@@ -87,8 +85,6 @@ static __read_mostly bool nf_conntrack_locks_all;
static struct conntrack_gc_work conntrack_gc_work; static struct conntrack_gc_work conntrack_gc_work;
extern unsigned int nf_conntrack_net_id;
void nf_conntrack_lock(spinlock_t *lock) __acquires(lock) void nf_conntrack_lock(spinlock_t *lock) __acquires(lock)
{ {
/* 1) Acquire the lock */ /* 1) Acquire the lock */
...@@ -1404,7 +1400,7 @@ static void gc_worker(struct work_struct *work) ...@@ -1404,7 +1400,7 @@ static void gc_worker(struct work_struct *work)
continue; continue;
net = nf_ct_net(tmp); net = nf_ct_net(tmp);
cnet = net_generic(net, nf_conntrack_net_id); cnet = nf_ct_pernet(net);
if (atomic_read(&cnet->count) < nf_conntrack_max95) if (atomic_read(&cnet->count) < nf_conntrack_max95)
continue; continue;
...@@ -1484,7 +1480,7 @@ __nf_conntrack_alloc(struct net *net, ...@@ -1484,7 +1480,7 @@ __nf_conntrack_alloc(struct net *net,
const struct nf_conntrack_tuple *repl, const struct nf_conntrack_tuple *repl,
gfp_t gfp, u32 hash) gfp_t gfp, u32 hash)
{ {
struct nf_conntrack_net *cnet = net_generic(net, nf_conntrack_net_id); struct nf_conntrack_net *cnet = nf_ct_pernet(net);
unsigned int ct_count; unsigned int ct_count;
struct nf_conn *ct; struct nf_conn *ct;
...@@ -1556,7 +1552,7 @@ void nf_conntrack_free(struct nf_conn *ct) ...@@ -1556,7 +1552,7 @@ void nf_conntrack_free(struct nf_conn *ct)
nf_ct_ext_destroy(ct); nf_ct_ext_destroy(ct);
kmem_cache_free(nf_conntrack_cachep, ct); kmem_cache_free(nf_conntrack_cachep, ct);
cnet = net_generic(net, nf_conntrack_net_id); cnet = nf_ct_pernet(net);
smp_mb__before_atomic(); smp_mb__before_atomic();
atomic_dec(&cnet->count); atomic_dec(&cnet->count);
...@@ -1614,7 +1610,7 @@ init_conntrack(struct net *net, struct nf_conn *tmpl, ...@@ -1614,7 +1610,7 @@ init_conntrack(struct net *net, struct nf_conn *tmpl,
GFP_ATOMIC); GFP_ATOMIC);
local_bh_disable(); local_bh_disable();
cnet = net_generic(net, nf_conntrack_net_id); cnet = nf_ct_pernet(net);
if (cnet->expect_count) { if (cnet->expect_count) {
spin_lock(&nf_conntrack_expect_lock); spin_lock(&nf_conntrack_expect_lock);
exp = nf_ct_find_expectation(net, zone, tuple); exp = nf_ct_find_expectation(net, zone, tuple);
...@@ -2317,7 +2313,7 @@ __nf_ct_unconfirmed_destroy(struct net *net) ...@@ -2317,7 +2313,7 @@ __nf_ct_unconfirmed_destroy(struct net *net)
void nf_ct_unconfirmed_destroy(struct net *net) void nf_ct_unconfirmed_destroy(struct net *net)
{ {
struct nf_conntrack_net *cnet = net_generic(net, nf_conntrack_net_id); struct nf_conntrack_net *cnet = nf_ct_pernet(net);
might_sleep(); might_sleep();
...@@ -2333,7 +2329,7 @@ void nf_ct_iterate_cleanup_net(struct net *net, ...@@ -2333,7 +2329,7 @@ void nf_ct_iterate_cleanup_net(struct net *net,
int (*iter)(struct nf_conn *i, void *data), int (*iter)(struct nf_conn *i, void *data),
void *data, u32 portid, int report) void *data, u32 portid, int report)
{ {
struct nf_conntrack_net *cnet = net_generic(net, nf_conntrack_net_id); struct nf_conntrack_net *cnet = nf_ct_pernet(net);
struct iter_data d; struct iter_data d;
might_sleep(); might_sleep();
...@@ -2367,7 +2363,7 @@ nf_ct_iterate_destroy(int (*iter)(struct nf_conn *i, void *data), void *data) ...@@ -2367,7 +2363,7 @@ nf_ct_iterate_destroy(int (*iter)(struct nf_conn *i, void *data), void *data)
down_read(&net_rwsem); down_read(&net_rwsem);
for_each_net(net) { for_each_net(net) {
struct nf_conntrack_net *cnet = net_generic(net, nf_conntrack_net_id); struct nf_conntrack_net *cnet = nf_ct_pernet(net);
if (atomic_read(&cnet->count) == 0) if (atomic_read(&cnet->count) == 0)
continue; continue;
...@@ -2449,7 +2445,7 @@ void nf_conntrack_cleanup_net_list(struct list_head *net_exit_list) ...@@ -2449,7 +2445,7 @@ void nf_conntrack_cleanup_net_list(struct list_head *net_exit_list)
i_see_dead_people: i_see_dead_people:
busy = 0; busy = 0;
list_for_each_entry(net, net_exit_list, exit_list) { list_for_each_entry(net, net_exit_list, exit_list) {
struct nf_conntrack_net *cnet = net_generic(net, nf_conntrack_net_id); struct nf_conntrack_net *cnet = nf_ct_pernet(net);
nf_ct_iterate_cleanup(kill_all, net, 0, 0); nf_ct_iterate_cleanup(kill_all, net, 0, 0);
if (atomic_read(&cnet->count) != 0) if (atomic_read(&cnet->count) != 0)
...@@ -2733,7 +2729,7 @@ void nf_conntrack_init_end(void) ...@@ -2733,7 +2729,7 @@ void nf_conntrack_init_end(void)
int nf_conntrack_init_net(struct net *net) int nf_conntrack_init_net(struct net *net)
{ {
struct nf_conntrack_net *cnet = net_generic(net, nf_conntrack_net_id); struct nf_conntrack_net *cnet = nf_ct_pernet(net);
int ret = -ENOMEM; int ret = -ENOMEM;
int cpu; int cpu;
......
...@@ -27,8 +27,6 @@ ...@@ -27,8 +27,6 @@
#include <net/netfilter/nf_conntrack_ecache.h> #include <net/netfilter/nf_conntrack_ecache.h>
#include <net/netfilter/nf_conntrack_extend.h> #include <net/netfilter/nf_conntrack_extend.h>
extern unsigned int nf_conntrack_net_id;
static DEFINE_MUTEX(nf_ct_ecache_mutex); static DEFINE_MUTEX(nf_ct_ecache_mutex);
#define ECACHE_RETRY_WAIT (HZ/10) #define ECACHE_RETRY_WAIT (HZ/10)
...@@ -348,7 +346,7 @@ EXPORT_SYMBOL_GPL(nf_ct_expect_unregister_notifier); ...@@ -348,7 +346,7 @@ EXPORT_SYMBOL_GPL(nf_ct_expect_unregister_notifier);
void nf_conntrack_ecache_work(struct net *net, enum nf_ct_ecache_state state) void nf_conntrack_ecache_work(struct net *net, enum nf_ct_ecache_state state)
{ {
struct nf_conntrack_net *cnet = net_generic(net, nf_conntrack_net_id); struct nf_conntrack_net *cnet = nf_ct_pernet(net);
if (state == NFCT_ECACHE_DESTROY_FAIL && if (state == NFCT_ECACHE_DESTROY_FAIL &&
!delayed_work_pending(&cnet->ecache_dwork)) { !delayed_work_pending(&cnet->ecache_dwork)) {
...@@ -371,7 +369,7 @@ static const struct nf_ct_ext_type event_extend = { ...@@ -371,7 +369,7 @@ static const struct nf_ct_ext_type event_extend = {
void nf_conntrack_ecache_pernet_init(struct net *net) void nf_conntrack_ecache_pernet_init(struct net *net)
{ {
struct nf_conntrack_net *cnet = net_generic(net, nf_conntrack_net_id); struct nf_conntrack_net *cnet = nf_ct_pernet(net);
net->ct.sysctl_events = nf_ct_events; net->ct.sysctl_events = nf_ct_events;
cnet->ct_net = &net->ct; cnet->ct_net = &net->ct;
...@@ -380,7 +378,7 @@ void nf_conntrack_ecache_pernet_init(struct net *net) ...@@ -380,7 +378,7 @@ void nf_conntrack_ecache_pernet_init(struct net *net)
void nf_conntrack_ecache_pernet_fini(struct net *net) void nf_conntrack_ecache_pernet_fini(struct net *net)
{ {
struct nf_conntrack_net *cnet = net_generic(net, nf_conntrack_net_id); struct nf_conntrack_net *cnet = nf_ct_pernet(net);
cancel_delayed_work_sync(&cnet->ecache_dwork); cancel_delayed_work_sync(&cnet->ecache_dwork);
} }
......
...@@ -43,8 +43,6 @@ unsigned int nf_ct_expect_max __read_mostly; ...@@ -43,8 +43,6 @@ unsigned int nf_ct_expect_max __read_mostly;
static struct kmem_cache *nf_ct_expect_cachep __read_mostly; static struct kmem_cache *nf_ct_expect_cachep __read_mostly;
static unsigned int nf_ct_expect_hashrnd __read_mostly; static unsigned int nf_ct_expect_hashrnd __read_mostly;
extern unsigned int nf_conntrack_net_id;
/* nf_conntrack_expect helper functions */ /* nf_conntrack_expect helper functions */
void nf_ct_unlink_expect_report(struct nf_conntrack_expect *exp, void nf_ct_unlink_expect_report(struct nf_conntrack_expect *exp,
u32 portid, int report) u32 portid, int report)
...@@ -58,7 +56,7 @@ void nf_ct_unlink_expect_report(struct nf_conntrack_expect *exp, ...@@ -58,7 +56,7 @@ void nf_ct_unlink_expect_report(struct nf_conntrack_expect *exp,
hlist_del_rcu(&exp->hnode); hlist_del_rcu(&exp->hnode);
cnet = net_generic(net, nf_conntrack_net_id); cnet = nf_ct_pernet(net);
cnet->expect_count--; cnet->expect_count--;
hlist_del_rcu(&exp->lnode); hlist_del_rcu(&exp->lnode);
...@@ -123,7 +121,7 @@ __nf_ct_expect_find(struct net *net, ...@@ -123,7 +121,7 @@ __nf_ct_expect_find(struct net *net,
const struct nf_conntrack_zone *zone, const struct nf_conntrack_zone *zone,
const struct nf_conntrack_tuple *tuple) const struct nf_conntrack_tuple *tuple)
{ {
struct nf_conntrack_net *cnet = net_generic(net, nf_conntrack_net_id); struct nf_conntrack_net *cnet = nf_ct_pernet(net);
struct nf_conntrack_expect *i; struct nf_conntrack_expect *i;
unsigned int h; unsigned int h;
...@@ -164,7 +162,7 @@ nf_ct_find_expectation(struct net *net, ...@@ -164,7 +162,7 @@ nf_ct_find_expectation(struct net *net,
const struct nf_conntrack_zone *zone, const struct nf_conntrack_zone *zone,
const struct nf_conntrack_tuple *tuple) const struct nf_conntrack_tuple *tuple)
{ {
struct nf_conntrack_net *cnet = net_generic(net, nf_conntrack_net_id); struct nf_conntrack_net *cnet = nf_ct_pernet(net);
struct nf_conntrack_expect *i, *exp = NULL; struct nf_conntrack_expect *i, *exp = NULL;
unsigned int h; unsigned int h;
...@@ -397,7 +395,7 @@ static void nf_ct_expect_insert(struct nf_conntrack_expect *exp) ...@@ -397,7 +395,7 @@ static void nf_ct_expect_insert(struct nf_conntrack_expect *exp)
master_help->expecting[exp->class]++; master_help->expecting[exp->class]++;
hlist_add_head_rcu(&exp->hnode, &nf_ct_expect_hash[h]); hlist_add_head_rcu(&exp->hnode, &nf_ct_expect_hash[h]);
cnet = net_generic(net, nf_conntrack_net_id); cnet = nf_ct_pernet(net);
cnet->expect_count++; cnet->expect_count++;
NF_CT_STAT_INC(net, expect_create); NF_CT_STAT_INC(net, expect_create);
...@@ -468,7 +466,7 @@ static inline int __nf_ct_expect_check(struct nf_conntrack_expect *expect, ...@@ -468,7 +466,7 @@ static inline int __nf_ct_expect_check(struct nf_conntrack_expect *expect,
} }
} }
cnet = net_generic(net, nf_conntrack_net_id); cnet = nf_ct_pernet(net);
if (cnet->expect_count >= nf_ct_expect_max) { if (cnet->expect_count >= nf_ct_expect_max) {
net_warn_ratelimited("nf_conntrack: expectation table full\n"); net_warn_ratelimited("nf_conntrack: expectation table full\n");
ret = -EMFILE; ret = -EMFILE;
......
...@@ -43,8 +43,6 @@ MODULE_PARM_DESC(nf_conntrack_helper, ...@@ -43,8 +43,6 @@ MODULE_PARM_DESC(nf_conntrack_helper,
static DEFINE_MUTEX(nf_ct_nat_helpers_mutex); static DEFINE_MUTEX(nf_ct_nat_helpers_mutex);
static struct list_head nf_ct_nat_helpers __read_mostly; static struct list_head nf_ct_nat_helpers __read_mostly;
extern unsigned int nf_conntrack_net_id;
/* Stupid hash, but collision free for the default registrations of the /* Stupid hash, but collision free for the default registrations of the
* helpers currently in the kernel. */ * helpers currently in the kernel. */
static unsigned int helper_hash(const struct nf_conntrack_tuple *tuple) static unsigned int helper_hash(const struct nf_conntrack_tuple *tuple)
...@@ -214,7 +212,7 @@ EXPORT_SYMBOL_GPL(nf_ct_helper_ext_add); ...@@ -214,7 +212,7 @@ EXPORT_SYMBOL_GPL(nf_ct_helper_ext_add);
static struct nf_conntrack_helper * static struct nf_conntrack_helper *
nf_ct_lookup_helper(struct nf_conn *ct, struct net *net) nf_ct_lookup_helper(struct nf_conn *ct, struct net *net)
{ {
struct nf_conntrack_net *cnet = net_generic(net, nf_conntrack_net_id); struct nf_conntrack_net *cnet = nf_ct_pernet(net);
if (!cnet->sysctl_auto_assign_helper) { if (!cnet->sysctl_auto_assign_helper) {
if (cnet->auto_assign_helper_warned) if (cnet->auto_assign_helper_warned)
...@@ -560,7 +558,7 @@ static const struct nf_ct_ext_type helper_extend = { ...@@ -560,7 +558,7 @@ static const struct nf_ct_ext_type helper_extend = {
void nf_conntrack_helper_pernet_init(struct net *net) void nf_conntrack_helper_pernet_init(struct net *net)
{ {
struct nf_conntrack_net *cnet = net_generic(net, nf_conntrack_net_id); struct nf_conntrack_net *cnet = nf_ct_pernet(net);
cnet->sysctl_auto_assign_helper = nf_ct_auto_assign_helper; cnet->sysctl_auto_assign_helper = nf_ct_auto_assign_helper;
} }
......
...@@ -1528,7 +1528,7 @@ static int ctnetlink_del_conntrack(struct sk_buff *skb, ...@@ -1528,7 +1528,7 @@ static int ctnetlink_del_conntrack(struct sk_buff *skb,
const struct nfnl_info *info, const struct nfnl_info *info,
const struct nlattr * const cda[]) const struct nlattr * const cda[])
{ {
struct nfgenmsg *nfmsg = nlmsg_data(info->nlh); u8 family = info->nfmsg->nfgen_family;
struct nf_conntrack_tuple_hash *h; struct nf_conntrack_tuple_hash *h;
struct nf_conntrack_tuple tuple; struct nf_conntrack_tuple tuple;
struct nf_conntrack_zone zone; struct nf_conntrack_zone zone;
...@@ -1541,12 +1541,12 @@ static int ctnetlink_del_conntrack(struct sk_buff *skb, ...@@ -1541,12 +1541,12 @@ static int ctnetlink_del_conntrack(struct sk_buff *skb,
if (cda[CTA_TUPLE_ORIG]) if (cda[CTA_TUPLE_ORIG])
err = ctnetlink_parse_tuple(cda, &tuple, CTA_TUPLE_ORIG, err = ctnetlink_parse_tuple(cda, &tuple, CTA_TUPLE_ORIG,
nfmsg->nfgen_family, &zone); family, &zone);
else if (cda[CTA_TUPLE_REPLY]) else if (cda[CTA_TUPLE_REPLY])
err = ctnetlink_parse_tuple(cda, &tuple, CTA_TUPLE_REPLY, err = ctnetlink_parse_tuple(cda, &tuple, CTA_TUPLE_REPLY,
nfmsg->nfgen_family, &zone); family, &zone);
else { else {
u_int8_t u3 = nfmsg->version ? nfmsg->nfgen_family : AF_UNSPEC; u_int8_t u3 = info->nfmsg->version ? family : AF_UNSPEC;
return ctnetlink_flush_conntrack(info->net, cda, return ctnetlink_flush_conntrack(info->net, cda,
NETLINK_CB(skb).portid, NETLINK_CB(skb).portid,
...@@ -1586,8 +1586,7 @@ static int ctnetlink_get_conntrack(struct sk_buff *skb, ...@@ -1586,8 +1586,7 @@ static int ctnetlink_get_conntrack(struct sk_buff *skb,
const struct nfnl_info *info, const struct nfnl_info *info,
const struct nlattr * const cda[]) const struct nlattr * const cda[])
{ {
struct nfgenmsg *nfmsg = nlmsg_data(info->nlh); u_int8_t u3 = info->nfmsg->nfgen_family;
u_int8_t u3 = nfmsg->nfgen_family;
struct nf_conntrack_tuple_hash *h; struct nf_conntrack_tuple_hash *h;
struct nf_conntrack_tuple tuple; struct nf_conntrack_tuple tuple;
struct nf_conntrack_zone zone; struct nf_conntrack_zone zone;
...@@ -2363,10 +2362,9 @@ static int ctnetlink_new_conntrack(struct sk_buff *skb, ...@@ -2363,10 +2362,9 @@ static int ctnetlink_new_conntrack(struct sk_buff *skb,
const struct nfnl_info *info, const struct nfnl_info *info,
const struct nlattr * const cda[]) const struct nlattr * const cda[])
{ {
struct nfgenmsg *nfmsg = nlmsg_data(info->nlh);
struct nf_conntrack_tuple otuple, rtuple; struct nf_conntrack_tuple otuple, rtuple;
struct nf_conntrack_tuple_hash *h = NULL; struct nf_conntrack_tuple_hash *h = NULL;
u_int8_t u3 = nfmsg->nfgen_family; u_int8_t u3 = info->nfmsg->nfgen_family;
struct nf_conntrack_zone zone; struct nf_conntrack_zone zone;
struct nf_conn *ct; struct nf_conn *ct;
int err; int err;
...@@ -3259,8 +3257,7 @@ static int ctnetlink_get_expect(struct sk_buff *skb, ...@@ -3259,8 +3257,7 @@ static int ctnetlink_get_expect(struct sk_buff *skb,
const struct nfnl_info *info, const struct nfnl_info *info,
const struct nlattr * const cda[]) const struct nlattr * const cda[])
{ {
struct nfgenmsg *nfmsg = nlmsg_data(info->nlh); u_int8_t u3 = info->nfmsg->nfgen_family;
u_int8_t u3 = nfmsg->nfgen_family;
struct nf_conntrack_tuple tuple; struct nf_conntrack_tuple tuple;
struct nf_conntrack_expect *exp; struct nf_conntrack_expect *exp;
struct nf_conntrack_zone zone; struct nf_conntrack_zone zone;
...@@ -3349,8 +3346,7 @@ static int ctnetlink_del_expect(struct sk_buff *skb, ...@@ -3349,8 +3346,7 @@ static int ctnetlink_del_expect(struct sk_buff *skb,
const struct nfnl_info *info, const struct nfnl_info *info,
const struct nlattr * const cda[]) const struct nlattr * const cda[])
{ {
struct nfgenmsg *nfmsg = nlmsg_data(info->nlh); u_int8_t u3 = info->nfmsg->nfgen_family;
u_int8_t u3 = nfmsg->nfgen_family;
struct nf_conntrack_expect *exp; struct nf_conntrack_expect *exp;
struct nf_conntrack_tuple tuple; struct nf_conntrack_tuple tuple;
struct nf_conntrack_zone zone; struct nf_conntrack_zone zone;
...@@ -3601,8 +3597,7 @@ static int ctnetlink_new_expect(struct sk_buff *skb, ...@@ -3601,8 +3597,7 @@ static int ctnetlink_new_expect(struct sk_buff *skb,
const struct nfnl_info *info, const struct nfnl_info *info,
const struct nlattr * const cda[]) const struct nlattr * const cda[])
{ {
struct nfgenmsg *nfmsg = nlmsg_data(info->nlh); u_int8_t u3 = info->nfmsg->nfgen_family;
u_int8_t u3 = nfmsg->nfgen_family;
struct nf_conntrack_tuple tuple; struct nf_conntrack_tuple tuple;
struct nf_conntrack_expect *exp; struct nf_conntrack_expect *exp;
struct nf_conntrack_zone zone; struct nf_conntrack_zone zone;
......
...@@ -42,8 +42,6 @@ ...@@ -42,8 +42,6 @@
#include <net/ipv6.h> #include <net/ipv6.h>
#include <net/inet_frag.h> #include <net/inet_frag.h>
extern unsigned int nf_conntrack_net_id;
static DEFINE_MUTEX(nf_ct_proto_mutex); static DEFINE_MUTEX(nf_ct_proto_mutex);
#ifdef CONFIG_SYSCTL #ifdef CONFIG_SYSCTL
...@@ -446,7 +444,7 @@ static struct nf_ct_bridge_info *nf_ct_bridge_info; ...@@ -446,7 +444,7 @@ static struct nf_ct_bridge_info *nf_ct_bridge_info;
static int nf_ct_netns_do_get(struct net *net, u8 nfproto) static int nf_ct_netns_do_get(struct net *net, u8 nfproto)
{ {
struct nf_conntrack_net *cnet = net_generic(net, nf_conntrack_net_id); struct nf_conntrack_net *cnet = nf_ct_pernet(net);
bool fixup_needed = false, retry = true; bool fixup_needed = false, retry = true;
int err = 0; int err = 0;
retry: retry:
...@@ -531,7 +529,7 @@ static int nf_ct_netns_do_get(struct net *net, u8 nfproto) ...@@ -531,7 +529,7 @@ static int nf_ct_netns_do_get(struct net *net, u8 nfproto)
static void nf_ct_netns_do_put(struct net *net, u8 nfproto) static void nf_ct_netns_do_put(struct net *net, u8 nfproto)
{ {
struct nf_conntrack_net *cnet = net_generic(net, nf_conntrack_net_id); struct nf_conntrack_net *cnet = nf_ct_pernet(net);
mutex_lock(&nf_ct_proto_mutex); mutex_lock(&nf_ct_proto_mutex);
switch (nfproto) { switch (nfproto) {
......
...@@ -1441,6 +1441,11 @@ void nf_conntrack_tcp_init_net(struct net *net) ...@@ -1441,6 +1441,11 @@ void nf_conntrack_tcp_init_net(struct net *net)
* will be started. * will be started.
*/ */
tn->tcp_max_retrans = 3; tn->tcp_max_retrans = 3;
#if IS_ENABLED(CONFIG_NF_FLOW_TABLE)
tn->offload_timeout = 30 * HZ;
tn->offload_pickup = 120 * HZ;
#endif
} }
const struct nf_conntrack_l4proto nf_conntrack_l4proto_tcp = const struct nf_conntrack_l4proto nf_conntrack_l4proto_tcp =
......
...@@ -270,6 +270,11 @@ void nf_conntrack_udp_init_net(struct net *net) ...@@ -270,6 +270,11 @@ void nf_conntrack_udp_init_net(struct net *net)
for (i = 0; i < UDP_CT_MAX; i++) for (i = 0; i < UDP_CT_MAX; i++)
un->timeouts[i] = udp_timeouts[i]; un->timeouts[i] = udp_timeouts[i];
#if IS_ENABLED(CONFIG_NF_FLOW_TABLE)
un->offload_timeout = 30 * HZ;
un->offload_pickup = 30 * HZ;
#endif
} }
const struct nf_conntrack_l4proto nf_conntrack_l4proto_udp = const struct nf_conntrack_l4proto nf_conntrack_l4proto_udp =
......
...@@ -512,9 +512,7 @@ static void nf_conntrack_standalone_fini_proc(struct net *net) ...@@ -512,9 +512,7 @@ static void nf_conntrack_standalone_fini_proc(struct net *net)
u32 nf_conntrack_count(const struct net *net) u32 nf_conntrack_count(const struct net *net)
{ {
const struct nf_conntrack_net *cnet; const struct nf_conntrack_net *cnet = nf_ct_pernet(net);
cnet = net_generic(net, nf_conntrack_net_id);
return atomic_read(&cnet->count); return atomic_read(&cnet->count);
} }
...@@ -575,11 +573,19 @@ enum nf_ct_sysctl_index { ...@@ -575,11 +573,19 @@ enum nf_ct_sysctl_index {
NF_SYSCTL_CT_PROTO_TIMEOUT_TCP_CLOSE, NF_SYSCTL_CT_PROTO_TIMEOUT_TCP_CLOSE,
NF_SYSCTL_CT_PROTO_TIMEOUT_TCP_RETRANS, NF_SYSCTL_CT_PROTO_TIMEOUT_TCP_RETRANS,
NF_SYSCTL_CT_PROTO_TIMEOUT_TCP_UNACK, NF_SYSCTL_CT_PROTO_TIMEOUT_TCP_UNACK,
#if IS_ENABLED(CONFIG_NF_FLOW_TABLE)
NF_SYSCTL_CT_PROTO_TIMEOUT_TCP_OFFLOAD,
NF_SYSCTL_CT_PROTO_TIMEOUT_TCP_OFFLOAD_PICKUP,
#endif
NF_SYSCTL_CT_PROTO_TCP_LOOSE, NF_SYSCTL_CT_PROTO_TCP_LOOSE,
NF_SYSCTL_CT_PROTO_TCP_LIBERAL, NF_SYSCTL_CT_PROTO_TCP_LIBERAL,
NF_SYSCTL_CT_PROTO_TCP_MAX_RETRANS, NF_SYSCTL_CT_PROTO_TCP_MAX_RETRANS,
NF_SYSCTL_CT_PROTO_TIMEOUT_UDP, NF_SYSCTL_CT_PROTO_TIMEOUT_UDP,
NF_SYSCTL_CT_PROTO_TIMEOUT_UDP_STREAM, NF_SYSCTL_CT_PROTO_TIMEOUT_UDP_STREAM,
#if IS_ENABLED(CONFIG_NF_FLOW_TABLE)
NF_SYSCTL_CT_PROTO_TIMEOUT_UDP_OFFLOAD,
NF_SYSCTL_CT_PROTO_TIMEOUT_UDP_OFFLOAD_PICKUP,
#endif
NF_SYSCTL_CT_PROTO_TIMEOUT_ICMP, NF_SYSCTL_CT_PROTO_TIMEOUT_ICMP,
NF_SYSCTL_CT_PROTO_TIMEOUT_ICMPV6, NF_SYSCTL_CT_PROTO_TIMEOUT_ICMPV6,
#ifdef CONFIG_NF_CT_PROTO_SCTP #ifdef CONFIG_NF_CT_PROTO_SCTP
...@@ -762,6 +768,20 @@ static struct ctl_table nf_ct_sysctl_table[] = { ...@@ -762,6 +768,20 @@ static struct ctl_table nf_ct_sysctl_table[] = {
.mode = 0644, .mode = 0644,
.proc_handler = proc_dointvec_jiffies, .proc_handler = proc_dointvec_jiffies,
}, },
#if IS_ENABLED(CONFIG_NF_FLOW_TABLE)
[NF_SYSCTL_CT_PROTO_TIMEOUT_TCP_OFFLOAD] = {
.procname = "nf_flowtable_tcp_timeout",
.maxlen = sizeof(unsigned int),
.mode = 0644,
.proc_handler = proc_dointvec_jiffies,
},
[NF_SYSCTL_CT_PROTO_TIMEOUT_TCP_OFFLOAD_PICKUP] = {
.procname = "nf_flowtable_tcp_pickup",
.maxlen = sizeof(unsigned int),
.mode = 0644,
.proc_handler = proc_dointvec_jiffies,
},
#endif
[NF_SYSCTL_CT_PROTO_TCP_LOOSE] = { [NF_SYSCTL_CT_PROTO_TCP_LOOSE] = {
.procname = "nf_conntrack_tcp_loose", .procname = "nf_conntrack_tcp_loose",
.maxlen = sizeof(u8), .maxlen = sizeof(u8),
...@@ -796,6 +816,20 @@ static struct ctl_table nf_ct_sysctl_table[] = { ...@@ -796,6 +816,20 @@ static struct ctl_table nf_ct_sysctl_table[] = {
.mode = 0644, .mode = 0644,
.proc_handler = proc_dointvec_jiffies, .proc_handler = proc_dointvec_jiffies,
}, },
#if IS_ENABLED(CONFIG_NFT_FLOW_OFFLOAD)
[NF_SYSCTL_CT_PROTO_TIMEOUT_UDP_OFFLOAD] = {
.procname = "nf_flowtable_udp_timeout",
.maxlen = sizeof(unsigned int),
.mode = 0644,
.proc_handler = proc_dointvec_jiffies,
},
[NF_SYSCTL_CT_PROTO_TIMEOUT_UDP_OFFLOAD_PICKUP] = {
.procname = "nf_flowtable_udp_pickup",
.maxlen = sizeof(unsigned int),
.mode = 0644,
.proc_handler = proc_dointvec_jiffies,
},
#endif
[NF_SYSCTL_CT_PROTO_TIMEOUT_ICMP] = { [NF_SYSCTL_CT_PROTO_TIMEOUT_ICMP] = {
.procname = "nf_conntrack_icmp_timeout", .procname = "nf_conntrack_icmp_timeout",
.maxlen = sizeof(unsigned int), .maxlen = sizeof(unsigned int),
...@@ -971,6 +1005,12 @@ static void nf_conntrack_standalone_init_tcp_sysctl(struct net *net, ...@@ -971,6 +1005,12 @@ static void nf_conntrack_standalone_init_tcp_sysctl(struct net *net,
XASSIGN(LIBERAL, &tn->tcp_be_liberal); XASSIGN(LIBERAL, &tn->tcp_be_liberal);
XASSIGN(MAX_RETRANS, &tn->tcp_max_retrans); XASSIGN(MAX_RETRANS, &tn->tcp_max_retrans);
#undef XASSIGN #undef XASSIGN
#if IS_ENABLED(CONFIG_NF_FLOW_TABLE)
table[NF_SYSCTL_CT_PROTO_TIMEOUT_TCP_OFFLOAD].data = &tn->offload_timeout;
table[NF_SYSCTL_CT_PROTO_TIMEOUT_TCP_OFFLOAD_PICKUP].data = &tn->offload_pickup;
#endif
} }
static void nf_conntrack_standalone_init_sctp_sysctl(struct net *net, static void nf_conntrack_standalone_init_sctp_sysctl(struct net *net,
...@@ -1032,7 +1072,7 @@ static void nf_conntrack_standalone_init_gre_sysctl(struct net *net, ...@@ -1032,7 +1072,7 @@ static void nf_conntrack_standalone_init_gre_sysctl(struct net *net,
static int nf_conntrack_standalone_init_sysctl(struct net *net) static int nf_conntrack_standalone_init_sysctl(struct net *net)
{ {
struct nf_conntrack_net *cnet = net_generic(net, nf_conntrack_net_id); struct nf_conntrack_net *cnet = nf_ct_pernet(net);
struct nf_udp_net *un = nf_udp_pernet(net); struct nf_udp_net *un = nf_udp_pernet(net);
struct ctl_table *table; struct ctl_table *table;
...@@ -1059,6 +1099,10 @@ static int nf_conntrack_standalone_init_sysctl(struct net *net) ...@@ -1059,6 +1099,10 @@ static int nf_conntrack_standalone_init_sysctl(struct net *net)
table[NF_SYSCTL_CT_PROTO_TIMEOUT_ICMPV6].data = &nf_icmpv6_pernet(net)->timeout; table[NF_SYSCTL_CT_PROTO_TIMEOUT_ICMPV6].data = &nf_icmpv6_pernet(net)->timeout;
table[NF_SYSCTL_CT_PROTO_TIMEOUT_UDP].data = &un->timeouts[UDP_CT_UNREPLIED]; table[NF_SYSCTL_CT_PROTO_TIMEOUT_UDP].data = &un->timeouts[UDP_CT_UNREPLIED];
table[NF_SYSCTL_CT_PROTO_TIMEOUT_UDP_STREAM].data = &un->timeouts[UDP_CT_REPLIED]; table[NF_SYSCTL_CT_PROTO_TIMEOUT_UDP_STREAM].data = &un->timeouts[UDP_CT_REPLIED];
#if IS_ENABLED(CONFIG_NF_FLOW_TABLE)
table[NF_SYSCTL_CT_PROTO_TIMEOUT_UDP_OFFLOAD].data = &un->offload_timeout;
table[NF_SYSCTL_CT_PROTO_TIMEOUT_UDP_OFFLOAD_PICKUP].data = &un->offload_pickup;
#endif
nf_conntrack_standalone_init_tcp_sysctl(net, table); nf_conntrack_standalone_init_tcp_sysctl(net, table);
nf_conntrack_standalone_init_sctp_sysctl(net, table); nf_conntrack_standalone_init_sctp_sysctl(net, table);
...@@ -1085,7 +1129,7 @@ static int nf_conntrack_standalone_init_sysctl(struct net *net) ...@@ -1085,7 +1129,7 @@ static int nf_conntrack_standalone_init_sysctl(struct net *net)
static void nf_conntrack_standalone_fini_sysctl(struct net *net) static void nf_conntrack_standalone_fini_sysctl(struct net *net)
{ {
struct nf_conntrack_net *cnet = net_generic(net, nf_conntrack_net_id); struct nf_conntrack_net *cnet = nf_ct_pernet(net);
struct ctl_table *table; struct ctl_table *table;
table = cnet->sysctl_header->ctl_table_arg; table = cnet->sysctl_header->ctl_table_arg;
......
...@@ -178,12 +178,10 @@ static void flow_offload_fixup_tcp(struct ip_ct_tcp *tcp) ...@@ -178,12 +178,10 @@ static void flow_offload_fixup_tcp(struct ip_ct_tcp *tcp)
tcp->seen[1].td_maxwin = 0; tcp->seen[1].td_maxwin = 0;
} }
#define NF_FLOWTABLE_TCP_PICKUP_TIMEOUT (120 * HZ)
#define NF_FLOWTABLE_UDP_PICKUP_TIMEOUT (30 * HZ)
static void flow_offload_fixup_ct_timeout(struct nf_conn *ct) static void flow_offload_fixup_ct_timeout(struct nf_conn *ct)
{ {
const struct nf_conntrack_l4proto *l4proto; const struct nf_conntrack_l4proto *l4proto;
struct net *net = nf_ct_net(ct);
int l4num = nf_ct_protonum(ct); int l4num = nf_ct_protonum(ct);
unsigned int timeout; unsigned int timeout;
...@@ -191,12 +189,17 @@ static void flow_offload_fixup_ct_timeout(struct nf_conn *ct) ...@@ -191,12 +189,17 @@ static void flow_offload_fixup_ct_timeout(struct nf_conn *ct)
if (!l4proto) if (!l4proto)
return; return;
if (l4num == IPPROTO_TCP) if (l4num == IPPROTO_TCP) {
timeout = NF_FLOWTABLE_TCP_PICKUP_TIMEOUT; struct nf_tcp_net *tn = nf_tcp_pernet(net);
else if (l4num == IPPROTO_UDP)
timeout = NF_FLOWTABLE_UDP_PICKUP_TIMEOUT; timeout = tn->offload_pickup;
else } else if (l4num == IPPROTO_UDP) {
struct nf_udp_net *tn = nf_udp_pernet(net);
timeout = tn->offload_pickup;
} else {
return; return;
}
if (nf_flow_timeout_delta(ct->timeout) > (__s32)timeout) if (nf_flow_timeout_delta(ct->timeout) > (__s32)timeout)
ct->timeout = nfct_time_stamp + timeout; ct->timeout = nfct_time_stamp + timeout;
...@@ -268,11 +271,35 @@ static const struct rhashtable_params nf_flow_offload_rhash_params = { ...@@ -268,11 +271,35 @@ static const struct rhashtable_params nf_flow_offload_rhash_params = {
.automatic_shrinking = true, .automatic_shrinking = true,
}; };
unsigned long flow_offload_get_timeout(struct flow_offload *flow)
{
const struct nf_conntrack_l4proto *l4proto;
unsigned long timeout = NF_FLOW_TIMEOUT;
struct net *net = nf_ct_net(flow->ct);
int l4num = nf_ct_protonum(flow->ct);
l4proto = nf_ct_l4proto_find(l4num);
if (!l4proto)
return timeout;
if (l4num == IPPROTO_TCP) {
struct nf_tcp_net *tn = nf_tcp_pernet(net);
timeout = tn->offload_timeout;
} else if (l4num == IPPROTO_UDP) {
struct nf_udp_net *tn = nf_udp_pernet(net);
timeout = tn->offload_timeout;
}
return timeout;
}
int flow_offload_add(struct nf_flowtable *flow_table, struct flow_offload *flow) int flow_offload_add(struct nf_flowtable *flow_table, struct flow_offload *flow)
{ {
int err; int err;
flow->timeout = nf_flowtable_time_stamp + NF_FLOW_TIMEOUT; flow->timeout = nf_flowtable_time_stamp + flow_offload_get_timeout(flow);
err = rhashtable_insert_fast(&flow_table->rhashtable, err = rhashtable_insert_fast(&flow_table->rhashtable,
&flow->tuplehash[0].node, &flow->tuplehash[0].node,
...@@ -304,7 +331,7 @@ EXPORT_SYMBOL_GPL(flow_offload_add); ...@@ -304,7 +331,7 @@ EXPORT_SYMBOL_GPL(flow_offload_add);
void flow_offload_refresh(struct nf_flowtable *flow_table, void flow_offload_refresh(struct nf_flowtable *flow_table,
struct flow_offload *flow) struct flow_offload *flow)
{ {
flow->timeout = nf_flowtable_time_stamp + NF_FLOW_TIMEOUT; flow->timeout = nf_flowtable_time_stamp + flow_offload_get_timeout(flow);
if (likely(!nf_flowtable_hw_offload(flow_table))) if (likely(!nf_flowtable_hw_offload(flow_table)))
return; return;
......
...@@ -937,7 +937,7 @@ static void flow_offload_work_stats(struct flow_offload_work *offload) ...@@ -937,7 +937,7 @@ static void flow_offload_work_stats(struct flow_offload_work *offload)
lastused = max_t(u64, stats[0].lastused, stats[1].lastused); lastused = max_t(u64, stats[0].lastused, stats[1].lastused);
offload->flow->timeout = max_t(u64, offload->flow->timeout, offload->flow->timeout = max_t(u64, offload->flow->timeout,
lastused + NF_FLOW_TIMEOUT); lastused + flow_offload_get_timeout(offload->flow));
if (offload->flowtable->flags & NF_FLOWTABLE_COUNTER) { if (offload->flowtable->flags & NF_FLOWTABLE_COUNTER) {
if (stats[0].pkts) if (stats[0].pkts)
...@@ -1041,7 +1041,7 @@ void nf_flow_offload_stats(struct nf_flowtable *flowtable, ...@@ -1041,7 +1041,7 @@ void nf_flow_offload_stats(struct nf_flowtable *flowtable,
__s32 delta; __s32 delta;
delta = nf_flow_timeout_delta(flow->timeout); delta = nf_flow_timeout_delta(flow->timeout);
if ((delta >= (9 * NF_FLOW_TIMEOUT) / 10)) if ((delta >= (9 * flow_offload_get_timeout(flow)) / 10))
return; return;
offload = nf_flow_offload_work_alloc(flowtable, flow, FLOW_CLS_STATS); offload = nf_flow_offload_work_alloc(flowtable, flow, FLOW_CLS_STATS);
......
This diff is collapsed.
...@@ -68,6 +68,7 @@ static const char *const nfnl_lockdep_names[NFNL_SUBSYS_COUNT] = { ...@@ -68,6 +68,7 @@ static const char *const nfnl_lockdep_names[NFNL_SUBSYS_COUNT] = {
[NFNL_SUBSYS_CTHELPER] = "nfnl_subsys_cthelper", [NFNL_SUBSYS_CTHELPER] = "nfnl_subsys_cthelper",
[NFNL_SUBSYS_NFTABLES] = "nfnl_subsys_nftables", [NFNL_SUBSYS_NFTABLES] = "nfnl_subsys_nftables",
[NFNL_SUBSYS_NFT_COMPAT] = "nfnl_subsys_nftcompat", [NFNL_SUBSYS_NFT_COMPAT] = "nfnl_subsys_nftcompat",
[NFNL_SUBSYS_HOOK] = "nfnl_subsys_hook",
}; };
static const int nfnl_group2type[NFNLGRP_MAX+1] = { static const int nfnl_group2type[NFNLGRP_MAX+1] = {
...@@ -256,6 +257,7 @@ static int nfnetlink_rcv_msg(struct sk_buff *skb, struct nlmsghdr *nlh, ...@@ -256,6 +257,7 @@ static int nfnetlink_rcv_msg(struct sk_buff *skb, struct nlmsghdr *nlh,
.net = net, .net = net,
.sk = nfnlnet->nfnl, .sk = nfnlnet->nfnl,
.nlh = nlh, .nlh = nlh,
.nfmsg = nlmsg_data(nlh),
.extack = extack, .extack = extack,
}; };
...@@ -491,6 +493,7 @@ static void nfnetlink_rcv_batch(struct sk_buff *skb, struct nlmsghdr *nlh, ...@@ -491,6 +493,7 @@ static void nfnetlink_rcv_batch(struct sk_buff *skb, struct nlmsghdr *nlh,
.net = net, .net = net,
.sk = nfnlnet->nfnl, .sk = nfnlnet->nfnl,
.nlh = nlh, .nlh = nlh,
.nfmsg = nlmsg_data(nlh),
.extack = &extack, .extack = &extack,
}; };
......
// SPDX-License-Identifier: GPL-2.0-or-later
/*
* Copyright (c) 2021 Red Hat GmbH
*
* Author: Florian Westphal <fw@strlen.de>
*/
#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/types.h>
#include <linux/skbuff.h>
#include <linux/errno.h>
#include <linux/netlink.h>
#include <linux/slab.h>
#include <linux/netfilter.h>
#include <linux/netfilter/nfnetlink.h>
#include <linux/netfilter/nfnetlink_hook.h>
#include <net/netfilter/nf_tables.h>
#include <net/sock.h>
static const struct nla_policy nfnl_hook_nla_policy[NFNLA_HOOK_MAX + 1] = {
[NFNLA_HOOK_HOOKNUM] = { .type = NLA_U32 },
[NFNLA_HOOK_PRIORITY] = { .type = NLA_U32 },
[NFNLA_HOOK_DEV] = { .type = NLA_STRING,
.len = IFNAMSIZ - 1 },
[NFNLA_HOOK_FUNCTION_NAME] = { .type = NLA_NUL_STRING,
.len = KSYM_NAME_LEN, },
[NFNLA_HOOK_MODULE_NAME] = { .type = NLA_NUL_STRING,
.len = MODULE_NAME_LEN, },
[NFNLA_HOOK_CHAIN_INFO] = { .type = NLA_NESTED, },
};
static int nf_netlink_dump_start_rcu(struct sock *nlsk, struct sk_buff *skb,
const struct nlmsghdr *nlh,
struct netlink_dump_control *c)
{
int err;
if (!try_module_get(THIS_MODULE))
return -EINVAL;
rcu_read_unlock();
err = netlink_dump_start(nlsk, skb, nlh, c);
rcu_read_lock();
module_put(THIS_MODULE);
return err;
}
struct nfnl_dump_hook_data {
char devname[IFNAMSIZ];
unsigned long headv;
u8 hook;
};
static int nfnl_hook_put_nft_chain_info(struct sk_buff *nlskb,
const struct nfnl_dump_hook_data *ctx,
unsigned int seq,
const struct nf_hook_ops *ops)
{
struct net *net = sock_net(nlskb->sk);
struct nlattr *nest, *nest2;
struct nft_chain *chain;
int ret = 0;
if (ops->hook_ops_type != NF_HOOK_OP_NF_TABLES)
return 0;
chain = ops->priv;
if (WARN_ON_ONCE(!chain))
return 0;
if (!nft_is_active(net, chain))
return 0;
nest = nla_nest_start(nlskb, NFNLA_HOOK_CHAIN_INFO);
if (!nest)
return -EMSGSIZE;
ret = nla_put_be32(nlskb, NFNLA_HOOK_INFO_TYPE,
htonl(NFNL_HOOK_TYPE_NFTABLES));
if (ret)
goto cancel_nest;
nest2 = nla_nest_start(nlskb, NFNLA_HOOK_INFO_DESC);
if (!nest2)
goto cancel_nest;
ret = nla_put_string(nlskb, NFTA_CHAIN_TABLE, chain->table->name);
if (ret)
goto cancel_nest;
ret = nla_put_string(nlskb, NFTA_CHAIN_NAME, chain->name);
if (ret)
goto cancel_nest;
nla_nest_end(nlskb, nest2);
nla_nest_end(nlskb, nest);
return ret;
cancel_nest:
nla_nest_cancel(nlskb, nest);
return -EMSGSIZE;
}
static int nfnl_hook_dump_one(struct sk_buff *nlskb,
const struct nfnl_dump_hook_data *ctx,
const struct nf_hook_ops *ops,
unsigned int seq)
{
u16 event = nfnl_msg_type(NFNL_SUBSYS_HOOK, NFNL_MSG_HOOK_GET);
unsigned int portid = NETLINK_CB(nlskb).portid;
struct nlmsghdr *nlh;
int ret = -EMSGSIZE;
#ifdef CONFIG_KALLSYMS
char sym[KSYM_SYMBOL_LEN];
char *module_name;
#endif
nlh = nfnl_msg_put(nlskb, portid, seq, event,
NLM_F_MULTI, ops->pf, NFNETLINK_V0, 0);
if (!nlh)
goto nla_put_failure;
#ifdef CONFIG_KALLSYMS
ret = snprintf(sym, sizeof(sym), "%ps", ops->hook);
if (ret < 0 || ret > (int)sizeof(sym))
goto nla_put_failure;
module_name = strstr(sym, " [");
if (module_name) {
char *end;
module_name += 2;
end = strchr(module_name, ']');
if (end) {
*end = 0;
ret = nla_put_string(nlskb, NFNLA_HOOK_MODULE_NAME, module_name);
if (ret)
goto nla_put_failure;
}
}
ret = nla_put_string(nlskb, NFNLA_HOOK_FUNCTION_NAME, sym);
if (ret)
goto nla_put_failure;
#endif
ret = nla_put_be32(nlskb, NFNLA_HOOK_HOOKNUM, htonl(ops->hooknum));
if (ret)
goto nla_put_failure;
ret = nla_put_be32(nlskb, NFNLA_HOOK_PRIORITY, htonl(ops->priority));
if (ret)
goto nla_put_failure;
ret = nfnl_hook_put_nft_chain_info(nlskb, ctx, seq, ops);
if (ret)
goto nla_put_failure;
nlmsg_end(nlskb, nlh);
return 0;
nla_put_failure:
nlmsg_trim(nlskb, nlh);
return ret;
}
static const struct nf_hook_entries *
nfnl_hook_entries_head(u8 pf, unsigned int hook, struct net *net, const char *dev)
{
const struct nf_hook_entries *hook_head = NULL;
struct net_device *netdev;
switch (pf) {
case NFPROTO_IPV4:
if (hook >= ARRAY_SIZE(net->nf.hooks_ipv4))
return ERR_PTR(-EINVAL);
hook_head = rcu_dereference(net->nf.hooks_ipv4[hook]);
break;
case NFPROTO_IPV6:
if (hook >= ARRAY_SIZE(net->nf.hooks_ipv6))
return ERR_PTR(-EINVAL);
hook_head = rcu_dereference(net->nf.hooks_ipv6[hook]);
break;
case NFPROTO_ARP:
#ifdef CONFIG_NETFILTER_FAMILY_ARP
if (hook >= ARRAY_SIZE(net->nf.hooks_arp))
return ERR_PTR(-EINVAL);
hook_head = rcu_dereference(net->nf.hooks_arp[hook]);
#endif
break;
case NFPROTO_BRIDGE:
#ifdef CONFIG_NETFILTER_FAMILY_BRIDGE
if (hook >= ARRAY_SIZE(net->nf.hooks_bridge))
return ERR_PTR(-EINVAL);
hook_head = rcu_dereference(net->nf.hooks_bridge[hook]);
#endif
break;
#if IS_ENABLED(CONFIG_DECNET)
case NFPROTO_DECNET:
if (hook >= ARRAY_SIZE(net->nf.hooks_decnet))
return ERR_PTR(-EINVAL);
hook_head = rcu_dereference(net->nf.hooks_decnet[hook]);
break;
#endif
#ifdef CONFIG_NETFILTER_INGRESS
case NFPROTO_NETDEV:
if (hook != NF_NETDEV_INGRESS)
return ERR_PTR(-EOPNOTSUPP);
if (!dev)
return ERR_PTR(-ENODEV);
netdev = dev_get_by_name_rcu(net, dev);
if (!netdev)
return ERR_PTR(-ENODEV);
return rcu_dereference(netdev->nf_hooks_ingress);
#endif
default:
return ERR_PTR(-EPROTONOSUPPORT);
}
return hook_head;
}
static int nfnl_hook_dump(struct sk_buff *nlskb,
struct netlink_callback *cb)
{
struct nfgenmsg *nfmsg = nlmsg_data(cb->nlh);
struct nfnl_dump_hook_data *ctx = cb->data;
int err, family = nfmsg->nfgen_family;
struct net *net = sock_net(nlskb->sk);
struct nf_hook_ops * const *ops;
const struct nf_hook_entries *e;
unsigned int i = cb->args[0];
rcu_read_lock();
e = nfnl_hook_entries_head(family, ctx->hook, net, ctx->devname);
if (!e)
goto done;
if (IS_ERR(e)) {
cb->seq++;
goto done;
}
if ((unsigned long)e != ctx->headv || i >= e->num_hook_entries)
cb->seq++;
ops = nf_hook_entries_get_hook_ops(e);
for (; i < e->num_hook_entries; i++) {
err = nfnl_hook_dump_one(nlskb, ctx, ops[i], cb->seq);
if (err)
break;
}
done:
nl_dump_check_consistent(cb, nlmsg_hdr(nlskb));
rcu_read_unlock();
cb->args[0] = i;
return nlskb->len;
}
static int nfnl_hook_dump_start(struct netlink_callback *cb)
{
const struct nfgenmsg *nfmsg = nlmsg_data(cb->nlh);
const struct nlattr * const *nla = cb->data;
struct nfnl_dump_hook_data *ctx = NULL;
struct net *net = sock_net(cb->skb->sk);
u8 family = nfmsg->nfgen_family;
char name[IFNAMSIZ] = "";
const void *head;
u32 hooknum;
hooknum = ntohl(nla_get_be32(nla[NFNLA_HOOK_HOOKNUM]));
if (hooknum > 255)
return -EINVAL;
if (family == NFPROTO_NETDEV) {
if (!nla[NFNLA_HOOK_DEV])
return -EINVAL;
nla_strscpy(name, nla[NFNLA_HOOK_DEV], sizeof(name));
}
rcu_read_lock();
/* Not dereferenced; for consistency check only */
head = nfnl_hook_entries_head(family, hooknum, net, name);
rcu_read_unlock();
if (head && IS_ERR(head))
return PTR_ERR(head);
ctx = kzalloc(sizeof(*ctx), GFP_KERNEL);
if (!ctx)
return -ENOMEM;
strscpy(ctx->devname, name, sizeof(ctx->devname));
ctx->headv = (unsigned long)head;
ctx->hook = hooknum;
cb->seq = 1;
cb->data = ctx;
return 0;
}
static int nfnl_hook_dump_stop(struct netlink_callback *cb)
{
kfree(cb->data);
return 0;
}
static int nfnl_hook_get(struct sk_buff *skb,
const struct nfnl_info *info,
const struct nlattr * const nla[])
{
if (!nla[NFNLA_HOOK_HOOKNUM])
return -EINVAL;
if (info->nlh->nlmsg_flags & NLM_F_DUMP) {
struct netlink_dump_control c = {
.start = nfnl_hook_dump_start,
.done = nfnl_hook_dump_stop,
.dump = nfnl_hook_dump,
.module = THIS_MODULE,
.data = (void *)nla,
};
return nf_netlink_dump_start_rcu(info->sk, skb, info->nlh, &c);
}
return -EOPNOTSUPP;
}
static const struct nfnl_callback nfnl_hook_cb[NFNL_MSG_HOOK_MAX] = {
[NFNL_MSG_HOOK_GET] = {
.call = nfnl_hook_get,
.type = NFNL_CB_RCU,
.attr_count = NFNLA_HOOK_MAX,
.policy = nfnl_hook_nla_policy
},
};
static const struct nfnetlink_subsystem nfhook_subsys = {
.name = "nfhook",
.subsys_id = NFNL_SUBSYS_HOOK,
.cb_count = NFNL_MSG_HOOK_MAX,
.cb = nfnl_hook_cb,
};
MODULE_ALIAS_NFNL_SUBSYS(NFNL_SUBSYS_HOOK);
static int __init nfnetlink_hook_init(void)
{
return nfnetlink_subsys_register(&nfhook_subsys);
}
static void __exit nfnetlink_hook_exit(void)
{
nfnetlink_subsys_unregister(&nfhook_subsys);
}
module_init(nfnetlink_hook_init);
module_exit(nfnetlink_hook_exit);
MODULE_LICENSE("GPL");
MODULE_AUTHOR("Florian Westphal <fw@strlen.de>");
MODULE_DESCRIPTION("nfnetlink_hook: list registered netfilter hooks");
...@@ -871,15 +871,14 @@ static int nfulnl_recv_config(struct sk_buff *skb, const struct nfnl_info *info, ...@@ -871,15 +871,14 @@ static int nfulnl_recv_config(struct sk_buff *skb, const struct nfnl_info *info,
const struct nlattr * const nfula[]) const struct nlattr * const nfula[])
{ {
struct nfnl_log_net *log = nfnl_log_pernet(info->net); struct nfnl_log_net *log = nfnl_log_pernet(info->net);
struct nfgenmsg *nfmsg = nlmsg_data(info->nlh); u_int16_t group_num = ntohs(info->nfmsg->res_id);
u_int16_t group_num = ntohs(nfmsg->res_id);
struct nfulnl_msg_config_cmd *cmd = NULL; struct nfulnl_msg_config_cmd *cmd = NULL;
struct nfulnl_instance *inst; struct nfulnl_instance *inst;
u16 flags = 0; u16 flags = 0;
int ret = 0; int ret = 0;
if (nfula[NFULA_CFG_CMD]) { if (nfula[NFULA_CFG_CMD]) {
u_int8_t pf = nfmsg->nfgen_family; u_int8_t pf = info->nfmsg->nfgen_family;
cmd = nla_data(nfula[NFULA_CFG_CMD]); cmd = nla_data(nfula[NFULA_CFG_CMD]);
/* Commands without queue context */ /* Commands without queue context */
......
...@@ -1051,8 +1051,7 @@ static int nfqnl_recv_verdict_batch(struct sk_buff *skb, ...@@ -1051,8 +1051,7 @@ static int nfqnl_recv_verdict_batch(struct sk_buff *skb,
const struct nlattr * const nfqa[]) const struct nlattr * const nfqa[])
{ {
struct nfnl_queue_net *q = nfnl_queue_pernet(info->net); struct nfnl_queue_net *q = nfnl_queue_pernet(info->net);
struct nfgenmsg *nfmsg = nlmsg_data(info->nlh); u16 queue_num = ntohs(info->nfmsg->res_id);
u16 queue_num = ntohs(nfmsg->res_id);
struct nf_queue_entry *entry, *tmp; struct nf_queue_entry *entry, *tmp;
struct nfqnl_msg_verdict_hdr *vhdr; struct nfqnl_msg_verdict_hdr *vhdr;
struct nfqnl_instance *queue; struct nfqnl_instance *queue;
...@@ -1160,8 +1159,7 @@ static int nfqnl_recv_verdict(struct sk_buff *skb, const struct nfnl_info *info, ...@@ -1160,8 +1159,7 @@ static int nfqnl_recv_verdict(struct sk_buff *skb, const struct nfnl_info *info,
const struct nlattr * const nfqa[]) const struct nlattr * const nfqa[])
{ {
struct nfnl_queue_net *q = nfnl_queue_pernet(info->net); struct nfnl_queue_net *q = nfnl_queue_pernet(info->net);
struct nfgenmsg *nfmsg = nlmsg_data(info->nlh); u_int16_t queue_num = ntohs(info->nfmsg->res_id);
u_int16_t queue_num = ntohs(nfmsg->res_id);
struct nfqnl_msg_verdict_hdr *vhdr; struct nfqnl_msg_verdict_hdr *vhdr;
enum ip_conntrack_info ctinfo; enum ip_conntrack_info ctinfo;
struct nfqnl_instance *queue; struct nfqnl_instance *queue;
...@@ -1243,8 +1241,7 @@ static int nfqnl_recv_config(struct sk_buff *skb, const struct nfnl_info *info, ...@@ -1243,8 +1241,7 @@ static int nfqnl_recv_config(struct sk_buff *skb, const struct nfnl_info *info,
const struct nlattr * const nfqa[]) const struct nlattr * const nfqa[])
{ {
struct nfnl_queue_net *q = nfnl_queue_pernet(info->net); struct nfnl_queue_net *q = nfnl_queue_pernet(info->net);
struct nfgenmsg *nfmsg = nlmsg_data(info->nlh); u_int16_t queue_num = ntohs(info->nfmsg->res_id);
u_int16_t queue_num = ntohs(nfmsg->res_id);
struct nfqnl_msg_config_cmd *cmd = NULL; struct nfqnl_msg_config_cmd *cmd = NULL;
struct nfqnl_instance *queue; struct nfqnl_instance *queue;
__u32 flags = 0, mask = 0; __u32 flags = 0, mask = 0;
......
...@@ -625,7 +625,7 @@ static int nfnl_compat_get_rcu(struct sk_buff *skb, ...@@ -625,7 +625,7 @@ static int nfnl_compat_get_rcu(struct sk_buff *skb,
const struct nfnl_info *info, const struct nfnl_info *info,
const struct nlattr * const tb[]) const struct nlattr * const tb[])
{ {
struct nfgenmsg *nfmsg; u8 family = info->nfmsg->nfgen_family;
const char *name, *fmt; const char *name, *fmt;
struct sk_buff *skb2; struct sk_buff *skb2;
int ret = 0, target; int ret = 0, target;
...@@ -640,9 +640,7 @@ static int nfnl_compat_get_rcu(struct sk_buff *skb, ...@@ -640,9 +640,7 @@ static int nfnl_compat_get_rcu(struct sk_buff *skb,
rev = ntohl(nla_get_be32(tb[NFTA_COMPAT_REV])); rev = ntohl(nla_get_be32(tb[NFTA_COMPAT_REV]));
target = ntohl(nla_get_be32(tb[NFTA_COMPAT_TYPE])); target = ntohl(nla_get_be32(tb[NFTA_COMPAT_TYPE]));
nfmsg = nlmsg_data(info->nlh); switch(family) {
switch(nfmsg->nfgen_family) {
case AF_INET: case AF_INET:
fmt = "ipt_%s"; fmt = "ipt_%s";
break; break;
...@@ -656,8 +654,7 @@ static int nfnl_compat_get_rcu(struct sk_buff *skb, ...@@ -656,8 +654,7 @@ static int nfnl_compat_get_rcu(struct sk_buff *skb,
fmt = "arpt_%s"; fmt = "arpt_%s";
break; break;
default: default:
pr_err("nft_compat: unsupported protocol %d\n", pr_err("nft_compat: unsupported protocol %d\n", family);
nfmsg->nfgen_family);
return -EINVAL; return -EINVAL;
} }
...@@ -665,8 +662,7 @@ static int nfnl_compat_get_rcu(struct sk_buff *skb, ...@@ -665,8 +662,7 @@ static int nfnl_compat_get_rcu(struct sk_buff *skb,
return -EINVAL; return -EINVAL;
rcu_read_unlock(); rcu_read_unlock();
try_then_request_module(xt_find_revision(nfmsg->nfgen_family, name, try_then_request_module(xt_find_revision(family, name, rev, target, &ret),
rev, target, &ret),
fmt, name); fmt, name);
if (ret < 0) if (ret < 0)
goto out_put; goto out_put;
...@@ -682,8 +678,7 @@ static int nfnl_compat_get_rcu(struct sk_buff *skb, ...@@ -682,8 +678,7 @@ static int nfnl_compat_get_rcu(struct sk_buff *skb,
info->nlh->nlmsg_seq, info->nlh->nlmsg_seq,
NFNL_MSG_TYPE(info->nlh->nlmsg_type), NFNL_MSG_TYPE(info->nlh->nlmsg_type),
NFNL_MSG_COMPAT_GET, NFNL_MSG_COMPAT_GET,
nfmsg->nfgen_family, family, name, ret, target) <= 0) {
name, ret, target) <= 0) {
kfree_skb(skb2); kfree_skb(skb2);
goto out_put; goto out_put;
} }
......
...@@ -327,7 +327,9 @@ static void nft_exthdr_sctp_eval(const struct nft_expr *expr, ...@@ -327,7 +327,9 @@ static void nft_exthdr_sctp_eval(const struct nft_expr *expr,
break; break;
dest[priv->len / NFT_REG32_SIZE] = 0; dest[priv->len / NFT_REG32_SIZE] = 0;
memcpy(dest, (char *)sch + priv->offset, priv->len); if (skb_copy_bits(pkt->skb, offset + priv->offset,
dest, priv->len) < 0)
break;
return; return;
} }
offset += SCTP_PAD4(ntohs(sch->length)); offset += SCTP_PAD4(ntohs(sch->length));
......
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