linux/net/sched/act_csum.c
Davide Caratti aab378a7ed net/sched: fix NULL dereference in the error path of tcf_csum_init()
when the following command

 # tc action add action csum udp continue index 100

is run for the first time, and tcf_csum_init() fails allocating struct
tcf_csum, tcf_csum_cleanup() calls kfree_rcu(NULL,...). This causes the
following error:

 BUG: unable to handle kernel NULL pointer dereference at 0000000000000010
 IP: __call_rcu+0x23/0x2b0
 PGD 80000000740b4067 P4D 80000000740b4067 PUD 32e7f067 PMD 0
 Oops: 0002 [#1] SMP PTI
 Modules linked in: act_csum(E) act_vlan ip6table_filter ip6_tables iptable_filter binfmt_misc ext4 mbcache jbd2 crct10dif_pclmul crc32_pclmul ghash_clmulni_intel snd_hda_codec_generic pcbc snd_hda_intel snd_hda_codec snd_hda_core snd_hwdep snd_seq snd_seq_device snd_pcm snd_timer aesni_intel crypto_simd glue_helper cryptd snd joydev pcspkr virtio_balloon i2c_piix4 soundcore nfsd auth_rpcgss nfs_acl lockd grace sunrpc ip_tables xfs libcrc32c ata_generic pata_acpi qxl drm_kms_helper syscopyarea sysfillrect sysimgblt fb_sys_fops ttm virtio_blk drm virtio_net virtio_console ata_piix crc32c_intel libata virtio_pci serio_raw i2c_core virtio_ring virtio floppy dm_mirror dm_region_hash dm_log dm_mod [last unloaded: act_vlan]
 CPU: 2 PID: 5763 Comm: tc Tainted: G            E    4.16.0-rc4.act_vlan.orig+ #403
 Hardware name: Red Hat KVM, BIOS 0.5.1 01/01/2011
 RIP: 0010:__call_rcu+0x23/0x2b0
 RSP: 0018:ffffb275803e77c0 EFLAGS: 00010246
 RAX: ffffffffc057b080 RBX: ffff9674bc6f5240 RCX: 00000000ffffffff
 RDX: ffffffff928a5f00 RSI: 0000000000000008 RDI: 0000000000000008
 RBP: 0000000000000008 R08: 0000000000000001 R09: 0000000000000044
 R10: 0000000000000220 R11: ffff9674b9ab4821 R12: 0000000000000000
 R13: ffffffff928a5f00 R14: 0000000000000000 R15: 0000000000000001
 FS:  00007fa6368d8740(0000) GS:ffff9674bfd00000(0000) knlGS:0000000000000000
 CS:  0010 DS: 0000 ES: 0000 CR0: 0000000080050033
 CR2: 0000000000000010 CR3: 0000000073dec001 CR4: 00000000001606e0
 Call Trace:
  __tcf_idr_release+0x79/0xf0
  tcf_csum_init+0xfb/0x180 [act_csum]
  tcf_action_init_1+0x2cc/0x430
  tcf_action_init+0xd3/0x1b0
  tc_ctl_action+0x18b/0x240
  rtnetlink_rcv_msg+0x29c/0x310
  ? _cond_resched+0x15/0x30
  ? __kmalloc_node_track_caller+0x1b9/0x270
  ? rtnl_calcit.isra.28+0x100/0x100
  netlink_rcv_skb+0xd2/0x110
  netlink_unicast+0x17c/0x230
  netlink_sendmsg+0x2cd/0x3c0
  sock_sendmsg+0x30/0x40
  ___sys_sendmsg+0x27a/0x290
  ? filemap_map_pages+0x34a/0x3a0
  ? __handle_mm_fault+0xbfd/0xe20
  __sys_sendmsg+0x51/0x90
  do_syscall_64+0x6e/0x1a0
  entry_SYSCALL_64_after_hwframe+0x3d/0xa2
 RIP: 0033:0x7fa635ce9ba0
 RSP: 002b:00007ffc185b0fc8 EFLAGS: 00000246 ORIG_RAX: 000000000000002e
 RAX: ffffffffffffffda RBX: 00007ffc185b10f0 RCX: 00007fa635ce9ba0
 RDX: 0000000000000000 RSI: 00007ffc185b1040 RDI: 0000000000000003
 RBP: 000000005aaa85e0 R08: 0000000000000002 R09: 0000000000000000
 R10: 00007ffc185b0a20 R11: 0000000000000246 R12: 0000000000000000
 R13: 00007ffc185b1104 R14: 0000000000000001 R15: 0000000000669f60
 Code: 5d e9 42 da ff ff 66 90 0f 1f 44 00 00 41 57 41 56 41 55 49 89 d5 41 54 55 48 89 fd 53 48 83 ec 08 40 f6 c7 07 0f 85 19 02 00 00 <48> 89 75 08 48 c7 45 00 00 00 00 00 9c 58 0f 1f 44 00 00 49 89
 RIP: __call_rcu+0x23/0x2b0 RSP: ffffb275803e77c0
 CR2: 0000000000000010

fix this in tcf_csum_cleanup(), ensuring that kfree_rcu(param, ...) is
called only when param is not NULL.

Fixes: 9c5f69bbd7 ("net/sched: act_csum: don't use spinlock in the fast path")
Signed-off-by: Davide Caratti <dcaratti@redhat.com>
Acked-by: Jiri Pirko <jiri@mellanox.com>
Signed-off-by: David S. Miller <davem@davemloft.net>
2018-03-17 19:53:28 -04:00

696 lines
16 KiB
C

/*
* Checksum updating actions
*
* Copyright (c) 2010 Gregoire Baron <baronchon@n7mm.org>
*
* This program is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License as published by the Free
* Software Foundation; either version 2 of the License, or (at your option)
* any later version.
*
*/
#include <linux/types.h>
#include <linux/init.h>
#include <linux/kernel.h>
#include <linux/module.h>
#include <linux/spinlock.h>
#include <linux/netlink.h>
#include <net/netlink.h>
#include <linux/rtnetlink.h>
#include <linux/skbuff.h>
#include <net/ip.h>
#include <net/ipv6.h>
#include <net/icmp.h>
#include <linux/icmpv6.h>
#include <linux/igmp.h>
#include <net/tcp.h>
#include <net/udp.h>
#include <net/ip6_checksum.h>
#include <net/sctp/checksum.h>
#include <net/act_api.h>
#include <linux/tc_act/tc_csum.h>
#include <net/tc_act/tc_csum.h>
static const struct nla_policy csum_policy[TCA_CSUM_MAX + 1] = {
[TCA_CSUM_PARMS] = { .len = sizeof(struct tc_csum), },
};
static unsigned int csum_net_id;
static struct tc_action_ops act_csum_ops;
static int tcf_csum_init(struct net *net, struct nlattr *nla,
struct nlattr *est, struct tc_action **a, int ovr,
int bind)
{
struct tc_action_net *tn = net_generic(net, csum_net_id);
struct tcf_csum_params *params_old, *params_new;
struct nlattr *tb[TCA_CSUM_MAX + 1];
struct tc_csum *parm;
struct tcf_csum *p;
int ret = 0, err;
if (nla == NULL)
return -EINVAL;
err = nla_parse_nested(tb, TCA_CSUM_MAX, nla, csum_policy, NULL);
if (err < 0)
return err;
if (tb[TCA_CSUM_PARMS] == NULL)
return -EINVAL;
parm = nla_data(tb[TCA_CSUM_PARMS]);
if (!tcf_idr_check(tn, parm->index, a, bind)) {
ret = tcf_idr_create(tn, parm->index, est, a,
&act_csum_ops, bind, true);
if (ret)
return ret;
ret = ACT_P_CREATED;
} else {
if (bind)/* dont override defaults */
return 0;
tcf_idr_release(*a, bind);
if (!ovr)
return -EEXIST;
}
p = to_tcf_csum(*a);
ASSERT_RTNL();
params_new = kzalloc(sizeof(*params_new), GFP_KERNEL);
if (unlikely(!params_new)) {
if (ret == ACT_P_CREATED)
tcf_idr_release(*a, bind);
return -ENOMEM;
}
params_old = rtnl_dereference(p->params);
params_new->action = parm->action;
params_new->update_flags = parm->update_flags;
rcu_assign_pointer(p->params, params_new);
if (params_old)
kfree_rcu(params_old, rcu);
if (ret == ACT_P_CREATED)
tcf_idr_insert(tn, *a);
return ret;
}
/**
* tcf_csum_skb_nextlayer - Get next layer pointer
* @skb: sk_buff to use
* @ihl: previous summed headers length
* @ipl: complete packet length
* @jhl: next header length
*
* Check the expected next layer availability in the specified sk_buff.
* Return the next layer pointer if pass, NULL otherwise.
*/
static void *tcf_csum_skb_nextlayer(struct sk_buff *skb,
unsigned int ihl, unsigned int ipl,
unsigned int jhl)
{
int ntkoff = skb_network_offset(skb);
int hl = ihl + jhl;
if (!pskb_may_pull(skb, ipl + ntkoff) || (ipl < hl) ||
skb_try_make_writable(skb, hl + ntkoff))
return NULL;
else
return (void *)(skb_network_header(skb) + ihl);
}
static int tcf_csum_ipv4_icmp(struct sk_buff *skb, unsigned int ihl,
unsigned int ipl)
{
struct icmphdr *icmph;
icmph = tcf_csum_skb_nextlayer(skb, ihl, ipl, sizeof(*icmph));
if (icmph == NULL)
return 0;
icmph->checksum = 0;
skb->csum = csum_partial(icmph, ipl - ihl, 0);
icmph->checksum = csum_fold(skb->csum);
skb->ip_summed = CHECKSUM_NONE;
return 1;
}
static int tcf_csum_ipv4_igmp(struct sk_buff *skb,
unsigned int ihl, unsigned int ipl)
{
struct igmphdr *igmph;
igmph = tcf_csum_skb_nextlayer(skb, ihl, ipl, sizeof(*igmph));
if (igmph == NULL)
return 0;
igmph->csum = 0;
skb->csum = csum_partial(igmph, ipl - ihl, 0);
igmph->csum = csum_fold(skb->csum);
skb->ip_summed = CHECKSUM_NONE;
return 1;
}
static int tcf_csum_ipv6_icmp(struct sk_buff *skb, unsigned int ihl,
unsigned int ipl)
{
struct icmp6hdr *icmp6h;
const struct ipv6hdr *ip6h;
icmp6h = tcf_csum_skb_nextlayer(skb, ihl, ipl, sizeof(*icmp6h));
if (icmp6h == NULL)
return 0;
ip6h = ipv6_hdr(skb);
icmp6h->icmp6_cksum = 0;
skb->csum = csum_partial(icmp6h, ipl - ihl, 0);
icmp6h->icmp6_cksum = csum_ipv6_magic(&ip6h->saddr, &ip6h->daddr,
ipl - ihl, IPPROTO_ICMPV6,
skb->csum);
skb->ip_summed = CHECKSUM_NONE;
return 1;
}
static int tcf_csum_ipv4_tcp(struct sk_buff *skb, unsigned int ihl,
unsigned int ipl)
{
struct tcphdr *tcph;
const struct iphdr *iph;
if (skb_is_gso(skb) && skb_shinfo(skb)->gso_type & SKB_GSO_TCPV4)
return 1;
tcph = tcf_csum_skb_nextlayer(skb, ihl, ipl, sizeof(*tcph));
if (tcph == NULL)
return 0;
iph = ip_hdr(skb);
tcph->check = 0;
skb->csum = csum_partial(tcph, ipl - ihl, 0);
tcph->check = tcp_v4_check(ipl - ihl,
iph->saddr, iph->daddr, skb->csum);
skb->ip_summed = CHECKSUM_NONE;
return 1;
}
static int tcf_csum_ipv6_tcp(struct sk_buff *skb, unsigned int ihl,
unsigned int ipl)
{
struct tcphdr *tcph;
const struct ipv6hdr *ip6h;
if (skb_is_gso(skb) && skb_shinfo(skb)->gso_type & SKB_GSO_TCPV6)
return 1;
tcph = tcf_csum_skb_nextlayer(skb, ihl, ipl, sizeof(*tcph));
if (tcph == NULL)
return 0;
ip6h = ipv6_hdr(skb);
tcph->check = 0;
skb->csum = csum_partial(tcph, ipl - ihl, 0);
tcph->check = csum_ipv6_magic(&ip6h->saddr, &ip6h->daddr,
ipl - ihl, IPPROTO_TCP,
skb->csum);
skb->ip_summed = CHECKSUM_NONE;
return 1;
}
static int tcf_csum_ipv4_udp(struct sk_buff *skb, unsigned int ihl,
unsigned int ipl, int udplite)
{
struct udphdr *udph;
const struct iphdr *iph;
u16 ul;
if (skb_is_gso(skb) && skb_shinfo(skb)->gso_type & SKB_GSO_UDP)
return 1;
/*
* Support both UDP and UDPLITE checksum algorithms, Don't use
* udph->len to get the real length without any protocol check,
* UDPLITE uses udph->len for another thing,
* Use iph->tot_len, or just ipl.
*/
udph = tcf_csum_skb_nextlayer(skb, ihl, ipl, sizeof(*udph));
if (udph == NULL)
return 0;
iph = ip_hdr(skb);
ul = ntohs(udph->len);
if (udplite || udph->check) {
udph->check = 0;
if (udplite) {
if (ul == 0)
skb->csum = csum_partial(udph, ipl - ihl, 0);
else if ((ul >= sizeof(*udph)) && (ul <= ipl - ihl))
skb->csum = csum_partial(udph, ul, 0);
else
goto ignore_obscure_skb;
} else {
if (ul != ipl - ihl)
goto ignore_obscure_skb;
skb->csum = csum_partial(udph, ul, 0);
}
udph->check = csum_tcpudp_magic(iph->saddr, iph->daddr,
ul, iph->protocol,
skb->csum);
if (!udph->check)
udph->check = CSUM_MANGLED_0;
}
skb->ip_summed = CHECKSUM_NONE;
ignore_obscure_skb:
return 1;
}
static int tcf_csum_ipv6_udp(struct sk_buff *skb, unsigned int ihl,
unsigned int ipl, int udplite)
{
struct udphdr *udph;
const struct ipv6hdr *ip6h;
u16 ul;
if (skb_is_gso(skb) && skb_shinfo(skb)->gso_type & SKB_GSO_UDP)
return 1;
/*
* Support both UDP and UDPLITE checksum algorithms, Don't use
* udph->len to get the real length without any protocol check,
* UDPLITE uses udph->len for another thing,
* Use ip6h->payload_len + sizeof(*ip6h) ... , or just ipl.
*/
udph = tcf_csum_skb_nextlayer(skb, ihl, ipl, sizeof(*udph));
if (udph == NULL)
return 0;
ip6h = ipv6_hdr(skb);
ul = ntohs(udph->len);
udph->check = 0;
if (udplite) {
if (ul == 0)
skb->csum = csum_partial(udph, ipl - ihl, 0);
else if ((ul >= sizeof(*udph)) && (ul <= ipl - ihl))
skb->csum = csum_partial(udph, ul, 0);
else
goto ignore_obscure_skb;
} else {
if (ul != ipl - ihl)
goto ignore_obscure_skb;
skb->csum = csum_partial(udph, ul, 0);
}
udph->check = csum_ipv6_magic(&ip6h->saddr, &ip6h->daddr, ul,
udplite ? IPPROTO_UDPLITE : IPPROTO_UDP,
skb->csum);
if (!udph->check)
udph->check = CSUM_MANGLED_0;
skb->ip_summed = CHECKSUM_NONE;
ignore_obscure_skb:
return 1;
}
static int tcf_csum_sctp(struct sk_buff *skb, unsigned int ihl,
unsigned int ipl)
{
struct sctphdr *sctph;
if (skb_is_gso(skb) && skb_is_gso_sctp(skb))
return 1;
sctph = tcf_csum_skb_nextlayer(skb, ihl, ipl, sizeof(*sctph));
if (!sctph)
return 0;
sctph->checksum = sctp_compute_cksum(skb,
skb_network_offset(skb) + ihl);
skb->ip_summed = CHECKSUM_NONE;
skb->csum_not_inet = 0;
return 1;
}
static int tcf_csum_ipv4(struct sk_buff *skb, u32 update_flags)
{
const struct iphdr *iph;
int ntkoff;
ntkoff = skb_network_offset(skb);
if (!pskb_may_pull(skb, sizeof(*iph) + ntkoff))
goto fail;
iph = ip_hdr(skb);
switch (iph->frag_off & htons(IP_OFFSET) ? 0 : iph->protocol) {
case IPPROTO_ICMP:
if (update_flags & TCA_CSUM_UPDATE_FLAG_ICMP)
if (!tcf_csum_ipv4_icmp(skb, iph->ihl * 4,
ntohs(iph->tot_len)))
goto fail;
break;
case IPPROTO_IGMP:
if (update_flags & TCA_CSUM_UPDATE_FLAG_IGMP)
if (!tcf_csum_ipv4_igmp(skb, iph->ihl * 4,
ntohs(iph->tot_len)))
goto fail;
break;
case IPPROTO_TCP:
if (update_flags & TCA_CSUM_UPDATE_FLAG_TCP)
if (!tcf_csum_ipv4_tcp(skb, iph->ihl * 4,
ntohs(iph->tot_len)))
goto fail;
break;
case IPPROTO_UDP:
if (update_flags & TCA_CSUM_UPDATE_FLAG_UDP)
if (!tcf_csum_ipv4_udp(skb, iph->ihl * 4,
ntohs(iph->tot_len), 0))
goto fail;
break;
case IPPROTO_UDPLITE:
if (update_flags & TCA_CSUM_UPDATE_FLAG_UDPLITE)
if (!tcf_csum_ipv4_udp(skb, iph->ihl * 4,
ntohs(iph->tot_len), 1))
goto fail;
break;
case IPPROTO_SCTP:
if ((update_flags & TCA_CSUM_UPDATE_FLAG_SCTP) &&
!tcf_csum_sctp(skb, iph->ihl * 4, ntohs(iph->tot_len)))
goto fail;
break;
}
if (update_flags & TCA_CSUM_UPDATE_FLAG_IPV4HDR) {
if (skb_try_make_writable(skb, sizeof(*iph) + ntkoff))
goto fail;
ip_send_check(ip_hdr(skb));
}
return 1;
fail:
return 0;
}
static int tcf_csum_ipv6_hopopts(struct ipv6_opt_hdr *ip6xh, unsigned int ixhl,
unsigned int *pl)
{
int off, len, optlen;
unsigned char *xh = (void *)ip6xh;
off = sizeof(*ip6xh);
len = ixhl - off;
while (len > 1) {
switch (xh[off]) {
case IPV6_TLV_PAD1:
optlen = 1;
break;
case IPV6_TLV_JUMBO:
optlen = xh[off + 1] + 2;
if (optlen != 6 || len < 6 || (off & 3) != 2)
/* wrong jumbo option length/alignment */
return 0;
*pl = ntohl(*(__be32 *)(xh + off + 2));
goto done;
default:
optlen = xh[off + 1] + 2;
if (optlen > len)
/* ignore obscure options */
goto done;
break;
}
off += optlen;
len -= optlen;
}
done:
return 1;
}
static int tcf_csum_ipv6(struct sk_buff *skb, u32 update_flags)
{
struct ipv6hdr *ip6h;
struct ipv6_opt_hdr *ip6xh;
unsigned int hl, ixhl;
unsigned int pl;
int ntkoff;
u8 nexthdr;
ntkoff = skb_network_offset(skb);
hl = sizeof(*ip6h);
if (!pskb_may_pull(skb, hl + ntkoff))
goto fail;
ip6h = ipv6_hdr(skb);
pl = ntohs(ip6h->payload_len);
nexthdr = ip6h->nexthdr;
do {
switch (nexthdr) {
case NEXTHDR_FRAGMENT:
goto ignore_skb;
case NEXTHDR_ROUTING:
case NEXTHDR_HOP:
case NEXTHDR_DEST:
if (!pskb_may_pull(skb, hl + sizeof(*ip6xh) + ntkoff))
goto fail;
ip6xh = (void *)(skb_network_header(skb) + hl);
ixhl = ipv6_optlen(ip6xh);
if (!pskb_may_pull(skb, hl + ixhl + ntkoff))
goto fail;
ip6xh = (void *)(skb_network_header(skb) + hl);
if ((nexthdr == NEXTHDR_HOP) &&
!(tcf_csum_ipv6_hopopts(ip6xh, ixhl, &pl)))
goto fail;
nexthdr = ip6xh->nexthdr;
hl += ixhl;
break;
case IPPROTO_ICMPV6:
if (update_flags & TCA_CSUM_UPDATE_FLAG_ICMP)
if (!tcf_csum_ipv6_icmp(skb,
hl, pl + sizeof(*ip6h)))
goto fail;
goto done;
case IPPROTO_TCP:
if (update_flags & TCA_CSUM_UPDATE_FLAG_TCP)
if (!tcf_csum_ipv6_tcp(skb,
hl, pl + sizeof(*ip6h)))
goto fail;
goto done;
case IPPROTO_UDP:
if (update_flags & TCA_CSUM_UPDATE_FLAG_UDP)
if (!tcf_csum_ipv6_udp(skb, hl,
pl + sizeof(*ip6h), 0))
goto fail;
goto done;
case IPPROTO_UDPLITE:
if (update_flags & TCA_CSUM_UPDATE_FLAG_UDPLITE)
if (!tcf_csum_ipv6_udp(skb, hl,
pl + sizeof(*ip6h), 1))
goto fail;
goto done;
case IPPROTO_SCTP:
if ((update_flags & TCA_CSUM_UPDATE_FLAG_SCTP) &&
!tcf_csum_sctp(skb, hl, pl + sizeof(*ip6h)))
goto fail;
goto done;
default:
goto ignore_skb;
}
} while (pskb_may_pull(skb, hl + 1 + ntkoff));
done:
ignore_skb:
return 1;
fail:
return 0;
}
static int tcf_csum(struct sk_buff *skb, const struct tc_action *a,
struct tcf_result *res)
{
struct tcf_csum *p = to_tcf_csum(a);
struct tcf_csum_params *params;
u32 update_flags;
int action;
rcu_read_lock();
params = rcu_dereference(p->params);
tcf_lastuse_update(&p->tcf_tm);
bstats_cpu_update(this_cpu_ptr(p->common.cpu_bstats), skb);
action = params->action;
if (unlikely(action == TC_ACT_SHOT))
goto drop_stats;
update_flags = params->update_flags;
switch (tc_skb_protocol(skb)) {
case cpu_to_be16(ETH_P_IP):
if (!tcf_csum_ipv4(skb, update_flags))
goto drop;
break;
case cpu_to_be16(ETH_P_IPV6):
if (!tcf_csum_ipv6(skb, update_flags))
goto drop;
break;
}
unlock:
rcu_read_unlock();
return action;
drop:
action = TC_ACT_SHOT;
drop_stats:
qstats_drop_inc(this_cpu_ptr(p->common.cpu_qstats));
goto unlock;
}
static int tcf_csum_dump(struct sk_buff *skb, struct tc_action *a, int bind,
int ref)
{
unsigned char *b = skb_tail_pointer(skb);
struct tcf_csum *p = to_tcf_csum(a);
struct tcf_csum_params *params;
struct tc_csum opt = {
.index = p->tcf_index,
.refcnt = p->tcf_refcnt - ref,
.bindcnt = p->tcf_bindcnt - bind,
};
struct tcf_t t;
params = rtnl_dereference(p->params);
opt.action = params->action;
opt.update_flags = params->update_flags;
if (nla_put(skb, TCA_CSUM_PARMS, sizeof(opt), &opt))
goto nla_put_failure;
tcf_tm_dump(&t, &p->tcf_tm);
if (nla_put_64bit(skb, TCA_CSUM_TM, sizeof(t), &t, TCA_CSUM_PAD))
goto nla_put_failure;
return skb->len;
nla_put_failure:
nlmsg_trim(skb, b);
return -1;
}
static void tcf_csum_cleanup(struct tc_action *a)
{
struct tcf_csum *p = to_tcf_csum(a);
struct tcf_csum_params *params;
params = rcu_dereference_protected(p->params, 1);
if (params)
kfree_rcu(params, rcu);
}
static int tcf_csum_walker(struct net *net, struct sk_buff *skb,
struct netlink_callback *cb, int type,
const struct tc_action_ops *ops)
{
struct tc_action_net *tn = net_generic(net, csum_net_id);
return tcf_generic_walker(tn, skb, cb, type, ops);
}
static int tcf_csum_search(struct net *net, struct tc_action **a, u32 index)
{
struct tc_action_net *tn = net_generic(net, csum_net_id);
return tcf_idr_search(tn, a, index);
}
static struct tc_action_ops act_csum_ops = {
.kind = "csum",
.type = TCA_ACT_CSUM,
.owner = THIS_MODULE,
.act = tcf_csum,
.dump = tcf_csum_dump,
.init = tcf_csum_init,
.cleanup = tcf_csum_cleanup,
.walk = tcf_csum_walker,
.lookup = tcf_csum_search,
.size = sizeof(struct tcf_csum),
};
static __net_init int csum_init_net(struct net *net)
{
struct tc_action_net *tn = net_generic(net, csum_net_id);
return tc_action_net_init(tn, &act_csum_ops);
}
static void __net_exit csum_exit_net(struct list_head *net_list)
{
tc_action_net_exit(net_list, csum_net_id);
}
static struct pernet_operations csum_net_ops = {
.init = csum_init_net,
.exit_batch = csum_exit_net,
.id = &csum_net_id,
.size = sizeof(struct tc_action_net),
};
MODULE_DESCRIPTION("Checksum updating actions");
MODULE_LICENSE("GPL");
static int __init csum_init_module(void)
{
return tcf_register_action(&act_csum_ops, &csum_net_ops);
}
static void __exit csum_cleanup_module(void)
{
tcf_unregister_action(&act_csum_ops, &csum_net_ops);
}
module_init(csum_init_module);
module_exit(csum_cleanup_module);