162306a36Sopenharmony_ci// SPDX-License-Identifier: GPL-2.0-or-later 262306a36Sopenharmony_ci/* 362306a36Sopenharmony_ci * Checksum updating actions 462306a36Sopenharmony_ci * 562306a36Sopenharmony_ci * Copyright (c) 2010 Gregoire Baron <baronchon@n7mm.org> 662306a36Sopenharmony_ci */ 762306a36Sopenharmony_ci 862306a36Sopenharmony_ci#include <linux/types.h> 962306a36Sopenharmony_ci#include <linux/init.h> 1062306a36Sopenharmony_ci#include <linux/kernel.h> 1162306a36Sopenharmony_ci#include <linux/module.h> 1262306a36Sopenharmony_ci#include <linux/spinlock.h> 1362306a36Sopenharmony_ci 1462306a36Sopenharmony_ci#include <linux/netlink.h> 1562306a36Sopenharmony_ci#include <net/netlink.h> 1662306a36Sopenharmony_ci#include <linux/rtnetlink.h> 1762306a36Sopenharmony_ci 1862306a36Sopenharmony_ci#include <linux/skbuff.h> 1962306a36Sopenharmony_ci 2062306a36Sopenharmony_ci#include <net/ip.h> 2162306a36Sopenharmony_ci#include <net/ipv6.h> 2262306a36Sopenharmony_ci#include <net/icmp.h> 2362306a36Sopenharmony_ci#include <linux/icmpv6.h> 2462306a36Sopenharmony_ci#include <linux/igmp.h> 2562306a36Sopenharmony_ci#include <net/tcp.h> 2662306a36Sopenharmony_ci#include <net/udp.h> 2762306a36Sopenharmony_ci#include <net/ip6_checksum.h> 2862306a36Sopenharmony_ci#include <net/sctp/checksum.h> 2962306a36Sopenharmony_ci 3062306a36Sopenharmony_ci#include <net/act_api.h> 3162306a36Sopenharmony_ci#include <net/pkt_cls.h> 3262306a36Sopenharmony_ci 3362306a36Sopenharmony_ci#include <linux/tc_act/tc_csum.h> 3462306a36Sopenharmony_ci#include <net/tc_act/tc_csum.h> 3562306a36Sopenharmony_ci#include <net/tc_wrapper.h> 3662306a36Sopenharmony_ci 3762306a36Sopenharmony_cistatic const struct nla_policy csum_policy[TCA_CSUM_MAX + 1] = { 3862306a36Sopenharmony_ci [TCA_CSUM_PARMS] = { .len = sizeof(struct tc_csum), }, 3962306a36Sopenharmony_ci}; 4062306a36Sopenharmony_ci 4162306a36Sopenharmony_cistatic struct tc_action_ops act_csum_ops; 4262306a36Sopenharmony_ci 4362306a36Sopenharmony_cistatic int tcf_csum_init(struct net *net, struct nlattr *nla, 4462306a36Sopenharmony_ci struct nlattr *est, struct tc_action **a, 4562306a36Sopenharmony_ci struct tcf_proto *tp, 4662306a36Sopenharmony_ci u32 flags, struct netlink_ext_ack *extack) 4762306a36Sopenharmony_ci{ 4862306a36Sopenharmony_ci struct tc_action_net *tn = net_generic(net, act_csum_ops.net_id); 4962306a36Sopenharmony_ci bool bind = flags & TCA_ACT_FLAGS_BIND; 5062306a36Sopenharmony_ci struct tcf_csum_params *params_new; 5162306a36Sopenharmony_ci struct nlattr *tb[TCA_CSUM_MAX + 1]; 5262306a36Sopenharmony_ci struct tcf_chain *goto_ch = NULL; 5362306a36Sopenharmony_ci struct tc_csum *parm; 5462306a36Sopenharmony_ci struct tcf_csum *p; 5562306a36Sopenharmony_ci int ret = 0, err; 5662306a36Sopenharmony_ci u32 index; 5762306a36Sopenharmony_ci 5862306a36Sopenharmony_ci if (nla == NULL) 5962306a36Sopenharmony_ci return -EINVAL; 6062306a36Sopenharmony_ci 6162306a36Sopenharmony_ci err = nla_parse_nested_deprecated(tb, TCA_CSUM_MAX, nla, csum_policy, 6262306a36Sopenharmony_ci NULL); 6362306a36Sopenharmony_ci if (err < 0) 6462306a36Sopenharmony_ci return err; 6562306a36Sopenharmony_ci 6662306a36Sopenharmony_ci if (tb[TCA_CSUM_PARMS] == NULL) 6762306a36Sopenharmony_ci return -EINVAL; 6862306a36Sopenharmony_ci parm = nla_data(tb[TCA_CSUM_PARMS]); 6962306a36Sopenharmony_ci index = parm->index; 7062306a36Sopenharmony_ci err = tcf_idr_check_alloc(tn, &index, a, bind); 7162306a36Sopenharmony_ci if (!err) { 7262306a36Sopenharmony_ci ret = tcf_idr_create_from_flags(tn, index, est, a, 7362306a36Sopenharmony_ci &act_csum_ops, bind, flags); 7462306a36Sopenharmony_ci if (ret) { 7562306a36Sopenharmony_ci tcf_idr_cleanup(tn, index); 7662306a36Sopenharmony_ci return ret; 7762306a36Sopenharmony_ci } 7862306a36Sopenharmony_ci ret = ACT_P_CREATED; 7962306a36Sopenharmony_ci } else if (err > 0) { 8062306a36Sopenharmony_ci if (bind)/* dont override defaults */ 8162306a36Sopenharmony_ci return 0; 8262306a36Sopenharmony_ci if (!(flags & TCA_ACT_FLAGS_REPLACE)) { 8362306a36Sopenharmony_ci tcf_idr_release(*a, bind); 8462306a36Sopenharmony_ci return -EEXIST; 8562306a36Sopenharmony_ci } 8662306a36Sopenharmony_ci } else { 8762306a36Sopenharmony_ci return err; 8862306a36Sopenharmony_ci } 8962306a36Sopenharmony_ci 9062306a36Sopenharmony_ci err = tcf_action_check_ctrlact(parm->action, tp, &goto_ch, extack); 9162306a36Sopenharmony_ci if (err < 0) 9262306a36Sopenharmony_ci goto release_idr; 9362306a36Sopenharmony_ci 9462306a36Sopenharmony_ci p = to_tcf_csum(*a); 9562306a36Sopenharmony_ci 9662306a36Sopenharmony_ci params_new = kzalloc(sizeof(*params_new), GFP_KERNEL); 9762306a36Sopenharmony_ci if (unlikely(!params_new)) { 9862306a36Sopenharmony_ci err = -ENOMEM; 9962306a36Sopenharmony_ci goto put_chain; 10062306a36Sopenharmony_ci } 10162306a36Sopenharmony_ci params_new->update_flags = parm->update_flags; 10262306a36Sopenharmony_ci 10362306a36Sopenharmony_ci spin_lock_bh(&p->tcf_lock); 10462306a36Sopenharmony_ci goto_ch = tcf_action_set_ctrlact(*a, parm->action, goto_ch); 10562306a36Sopenharmony_ci params_new = rcu_replace_pointer(p->params, params_new, 10662306a36Sopenharmony_ci lockdep_is_held(&p->tcf_lock)); 10762306a36Sopenharmony_ci spin_unlock_bh(&p->tcf_lock); 10862306a36Sopenharmony_ci 10962306a36Sopenharmony_ci if (goto_ch) 11062306a36Sopenharmony_ci tcf_chain_put_by_act(goto_ch); 11162306a36Sopenharmony_ci if (params_new) 11262306a36Sopenharmony_ci kfree_rcu(params_new, rcu); 11362306a36Sopenharmony_ci 11462306a36Sopenharmony_ci return ret; 11562306a36Sopenharmony_ciput_chain: 11662306a36Sopenharmony_ci if (goto_ch) 11762306a36Sopenharmony_ci tcf_chain_put_by_act(goto_ch); 11862306a36Sopenharmony_cirelease_idr: 11962306a36Sopenharmony_ci tcf_idr_release(*a, bind); 12062306a36Sopenharmony_ci return err; 12162306a36Sopenharmony_ci} 12262306a36Sopenharmony_ci 12362306a36Sopenharmony_ci/** 12462306a36Sopenharmony_ci * tcf_csum_skb_nextlayer - Get next layer pointer 12562306a36Sopenharmony_ci * @skb: sk_buff to use 12662306a36Sopenharmony_ci * @ihl: previous summed headers length 12762306a36Sopenharmony_ci * @ipl: complete packet length 12862306a36Sopenharmony_ci * @jhl: next header length 12962306a36Sopenharmony_ci * 13062306a36Sopenharmony_ci * Check the expected next layer availability in the specified sk_buff. 13162306a36Sopenharmony_ci * Return the next layer pointer if pass, NULL otherwise. 13262306a36Sopenharmony_ci */ 13362306a36Sopenharmony_cistatic void *tcf_csum_skb_nextlayer(struct sk_buff *skb, 13462306a36Sopenharmony_ci unsigned int ihl, unsigned int ipl, 13562306a36Sopenharmony_ci unsigned int jhl) 13662306a36Sopenharmony_ci{ 13762306a36Sopenharmony_ci int ntkoff = skb_network_offset(skb); 13862306a36Sopenharmony_ci int hl = ihl + jhl; 13962306a36Sopenharmony_ci 14062306a36Sopenharmony_ci if (!pskb_may_pull(skb, ipl + ntkoff) || (ipl < hl) || 14162306a36Sopenharmony_ci skb_try_make_writable(skb, hl + ntkoff)) 14262306a36Sopenharmony_ci return NULL; 14362306a36Sopenharmony_ci else 14462306a36Sopenharmony_ci return (void *)(skb_network_header(skb) + ihl); 14562306a36Sopenharmony_ci} 14662306a36Sopenharmony_ci 14762306a36Sopenharmony_cistatic int tcf_csum_ipv4_icmp(struct sk_buff *skb, unsigned int ihl, 14862306a36Sopenharmony_ci unsigned int ipl) 14962306a36Sopenharmony_ci{ 15062306a36Sopenharmony_ci struct icmphdr *icmph; 15162306a36Sopenharmony_ci 15262306a36Sopenharmony_ci icmph = tcf_csum_skb_nextlayer(skb, ihl, ipl, sizeof(*icmph)); 15362306a36Sopenharmony_ci if (icmph == NULL) 15462306a36Sopenharmony_ci return 0; 15562306a36Sopenharmony_ci 15662306a36Sopenharmony_ci icmph->checksum = 0; 15762306a36Sopenharmony_ci skb->csum = csum_partial(icmph, ipl - ihl, 0); 15862306a36Sopenharmony_ci icmph->checksum = csum_fold(skb->csum); 15962306a36Sopenharmony_ci 16062306a36Sopenharmony_ci skb->ip_summed = CHECKSUM_NONE; 16162306a36Sopenharmony_ci 16262306a36Sopenharmony_ci return 1; 16362306a36Sopenharmony_ci} 16462306a36Sopenharmony_ci 16562306a36Sopenharmony_cistatic int tcf_csum_ipv4_igmp(struct sk_buff *skb, 16662306a36Sopenharmony_ci unsigned int ihl, unsigned int ipl) 16762306a36Sopenharmony_ci{ 16862306a36Sopenharmony_ci struct igmphdr *igmph; 16962306a36Sopenharmony_ci 17062306a36Sopenharmony_ci igmph = tcf_csum_skb_nextlayer(skb, ihl, ipl, sizeof(*igmph)); 17162306a36Sopenharmony_ci if (igmph == NULL) 17262306a36Sopenharmony_ci return 0; 17362306a36Sopenharmony_ci 17462306a36Sopenharmony_ci igmph->csum = 0; 17562306a36Sopenharmony_ci skb->csum = csum_partial(igmph, ipl - ihl, 0); 17662306a36Sopenharmony_ci igmph->csum = csum_fold(skb->csum); 17762306a36Sopenharmony_ci 17862306a36Sopenharmony_ci skb->ip_summed = CHECKSUM_NONE; 17962306a36Sopenharmony_ci 18062306a36Sopenharmony_ci return 1; 18162306a36Sopenharmony_ci} 18262306a36Sopenharmony_ci 18362306a36Sopenharmony_cistatic int tcf_csum_ipv6_icmp(struct sk_buff *skb, unsigned int ihl, 18462306a36Sopenharmony_ci unsigned int ipl) 18562306a36Sopenharmony_ci{ 18662306a36Sopenharmony_ci struct icmp6hdr *icmp6h; 18762306a36Sopenharmony_ci const struct ipv6hdr *ip6h; 18862306a36Sopenharmony_ci 18962306a36Sopenharmony_ci icmp6h = tcf_csum_skb_nextlayer(skb, ihl, ipl, sizeof(*icmp6h)); 19062306a36Sopenharmony_ci if (icmp6h == NULL) 19162306a36Sopenharmony_ci return 0; 19262306a36Sopenharmony_ci 19362306a36Sopenharmony_ci ip6h = ipv6_hdr(skb); 19462306a36Sopenharmony_ci icmp6h->icmp6_cksum = 0; 19562306a36Sopenharmony_ci skb->csum = csum_partial(icmp6h, ipl - ihl, 0); 19662306a36Sopenharmony_ci icmp6h->icmp6_cksum = csum_ipv6_magic(&ip6h->saddr, &ip6h->daddr, 19762306a36Sopenharmony_ci ipl - ihl, IPPROTO_ICMPV6, 19862306a36Sopenharmony_ci skb->csum); 19962306a36Sopenharmony_ci 20062306a36Sopenharmony_ci skb->ip_summed = CHECKSUM_NONE; 20162306a36Sopenharmony_ci 20262306a36Sopenharmony_ci return 1; 20362306a36Sopenharmony_ci} 20462306a36Sopenharmony_ci 20562306a36Sopenharmony_cistatic int tcf_csum_ipv4_tcp(struct sk_buff *skb, unsigned int ihl, 20662306a36Sopenharmony_ci unsigned int ipl) 20762306a36Sopenharmony_ci{ 20862306a36Sopenharmony_ci struct tcphdr *tcph; 20962306a36Sopenharmony_ci const struct iphdr *iph; 21062306a36Sopenharmony_ci 21162306a36Sopenharmony_ci if (skb_is_gso(skb) && skb_shinfo(skb)->gso_type & SKB_GSO_TCPV4) 21262306a36Sopenharmony_ci return 1; 21362306a36Sopenharmony_ci 21462306a36Sopenharmony_ci tcph = tcf_csum_skb_nextlayer(skb, ihl, ipl, sizeof(*tcph)); 21562306a36Sopenharmony_ci if (tcph == NULL) 21662306a36Sopenharmony_ci return 0; 21762306a36Sopenharmony_ci 21862306a36Sopenharmony_ci iph = ip_hdr(skb); 21962306a36Sopenharmony_ci tcph->check = 0; 22062306a36Sopenharmony_ci skb->csum = csum_partial(tcph, ipl - ihl, 0); 22162306a36Sopenharmony_ci tcph->check = tcp_v4_check(ipl - ihl, 22262306a36Sopenharmony_ci iph->saddr, iph->daddr, skb->csum); 22362306a36Sopenharmony_ci 22462306a36Sopenharmony_ci skb->ip_summed = CHECKSUM_NONE; 22562306a36Sopenharmony_ci 22662306a36Sopenharmony_ci return 1; 22762306a36Sopenharmony_ci} 22862306a36Sopenharmony_ci 22962306a36Sopenharmony_cistatic int tcf_csum_ipv6_tcp(struct sk_buff *skb, unsigned int ihl, 23062306a36Sopenharmony_ci unsigned int ipl) 23162306a36Sopenharmony_ci{ 23262306a36Sopenharmony_ci struct tcphdr *tcph; 23362306a36Sopenharmony_ci const struct ipv6hdr *ip6h; 23462306a36Sopenharmony_ci 23562306a36Sopenharmony_ci if (skb_is_gso(skb) && skb_shinfo(skb)->gso_type & SKB_GSO_TCPV6) 23662306a36Sopenharmony_ci return 1; 23762306a36Sopenharmony_ci 23862306a36Sopenharmony_ci tcph = tcf_csum_skb_nextlayer(skb, ihl, ipl, sizeof(*tcph)); 23962306a36Sopenharmony_ci if (tcph == NULL) 24062306a36Sopenharmony_ci return 0; 24162306a36Sopenharmony_ci 24262306a36Sopenharmony_ci ip6h = ipv6_hdr(skb); 24362306a36Sopenharmony_ci tcph->check = 0; 24462306a36Sopenharmony_ci skb->csum = csum_partial(tcph, ipl - ihl, 0); 24562306a36Sopenharmony_ci tcph->check = csum_ipv6_magic(&ip6h->saddr, &ip6h->daddr, 24662306a36Sopenharmony_ci ipl - ihl, IPPROTO_TCP, 24762306a36Sopenharmony_ci skb->csum); 24862306a36Sopenharmony_ci 24962306a36Sopenharmony_ci skb->ip_summed = CHECKSUM_NONE; 25062306a36Sopenharmony_ci 25162306a36Sopenharmony_ci return 1; 25262306a36Sopenharmony_ci} 25362306a36Sopenharmony_ci 25462306a36Sopenharmony_cistatic int tcf_csum_ipv4_udp(struct sk_buff *skb, unsigned int ihl, 25562306a36Sopenharmony_ci unsigned int ipl, int udplite) 25662306a36Sopenharmony_ci{ 25762306a36Sopenharmony_ci struct udphdr *udph; 25862306a36Sopenharmony_ci const struct iphdr *iph; 25962306a36Sopenharmony_ci u16 ul; 26062306a36Sopenharmony_ci 26162306a36Sopenharmony_ci if (skb_is_gso(skb) && skb_shinfo(skb)->gso_type & SKB_GSO_UDP) 26262306a36Sopenharmony_ci return 1; 26362306a36Sopenharmony_ci 26462306a36Sopenharmony_ci /* 26562306a36Sopenharmony_ci * Support both UDP and UDPLITE checksum algorithms, Don't use 26662306a36Sopenharmony_ci * udph->len to get the real length without any protocol check, 26762306a36Sopenharmony_ci * UDPLITE uses udph->len for another thing, 26862306a36Sopenharmony_ci * Use iph->tot_len, or just ipl. 26962306a36Sopenharmony_ci */ 27062306a36Sopenharmony_ci 27162306a36Sopenharmony_ci udph = tcf_csum_skb_nextlayer(skb, ihl, ipl, sizeof(*udph)); 27262306a36Sopenharmony_ci if (udph == NULL) 27362306a36Sopenharmony_ci return 0; 27462306a36Sopenharmony_ci 27562306a36Sopenharmony_ci iph = ip_hdr(skb); 27662306a36Sopenharmony_ci ul = ntohs(udph->len); 27762306a36Sopenharmony_ci 27862306a36Sopenharmony_ci if (udplite || udph->check) { 27962306a36Sopenharmony_ci 28062306a36Sopenharmony_ci udph->check = 0; 28162306a36Sopenharmony_ci 28262306a36Sopenharmony_ci if (udplite) { 28362306a36Sopenharmony_ci if (ul == 0) 28462306a36Sopenharmony_ci skb->csum = csum_partial(udph, ipl - ihl, 0); 28562306a36Sopenharmony_ci else if ((ul >= sizeof(*udph)) && (ul <= ipl - ihl)) 28662306a36Sopenharmony_ci skb->csum = csum_partial(udph, ul, 0); 28762306a36Sopenharmony_ci else 28862306a36Sopenharmony_ci goto ignore_obscure_skb; 28962306a36Sopenharmony_ci } else { 29062306a36Sopenharmony_ci if (ul != ipl - ihl) 29162306a36Sopenharmony_ci goto ignore_obscure_skb; 29262306a36Sopenharmony_ci 29362306a36Sopenharmony_ci skb->csum = csum_partial(udph, ul, 0); 29462306a36Sopenharmony_ci } 29562306a36Sopenharmony_ci 29662306a36Sopenharmony_ci udph->check = csum_tcpudp_magic(iph->saddr, iph->daddr, 29762306a36Sopenharmony_ci ul, iph->protocol, 29862306a36Sopenharmony_ci skb->csum); 29962306a36Sopenharmony_ci 30062306a36Sopenharmony_ci if (!udph->check) 30162306a36Sopenharmony_ci udph->check = CSUM_MANGLED_0; 30262306a36Sopenharmony_ci } 30362306a36Sopenharmony_ci 30462306a36Sopenharmony_ci skb->ip_summed = CHECKSUM_NONE; 30562306a36Sopenharmony_ci 30662306a36Sopenharmony_ciignore_obscure_skb: 30762306a36Sopenharmony_ci return 1; 30862306a36Sopenharmony_ci} 30962306a36Sopenharmony_ci 31062306a36Sopenharmony_cistatic int tcf_csum_ipv6_udp(struct sk_buff *skb, unsigned int ihl, 31162306a36Sopenharmony_ci unsigned int ipl, int udplite) 31262306a36Sopenharmony_ci{ 31362306a36Sopenharmony_ci struct udphdr *udph; 31462306a36Sopenharmony_ci const struct ipv6hdr *ip6h; 31562306a36Sopenharmony_ci u16 ul; 31662306a36Sopenharmony_ci 31762306a36Sopenharmony_ci if (skb_is_gso(skb) && skb_shinfo(skb)->gso_type & SKB_GSO_UDP) 31862306a36Sopenharmony_ci return 1; 31962306a36Sopenharmony_ci 32062306a36Sopenharmony_ci /* 32162306a36Sopenharmony_ci * Support both UDP and UDPLITE checksum algorithms, Don't use 32262306a36Sopenharmony_ci * udph->len to get the real length without any protocol check, 32362306a36Sopenharmony_ci * UDPLITE uses udph->len for another thing, 32462306a36Sopenharmony_ci * Use ip6h->payload_len + sizeof(*ip6h) ... , or just ipl. 32562306a36Sopenharmony_ci */ 32662306a36Sopenharmony_ci 32762306a36Sopenharmony_ci udph = tcf_csum_skb_nextlayer(skb, ihl, ipl, sizeof(*udph)); 32862306a36Sopenharmony_ci if (udph == NULL) 32962306a36Sopenharmony_ci return 0; 33062306a36Sopenharmony_ci 33162306a36Sopenharmony_ci ip6h = ipv6_hdr(skb); 33262306a36Sopenharmony_ci ul = ntohs(udph->len); 33362306a36Sopenharmony_ci 33462306a36Sopenharmony_ci udph->check = 0; 33562306a36Sopenharmony_ci 33662306a36Sopenharmony_ci if (udplite) { 33762306a36Sopenharmony_ci if (ul == 0) 33862306a36Sopenharmony_ci skb->csum = csum_partial(udph, ipl - ihl, 0); 33962306a36Sopenharmony_ci 34062306a36Sopenharmony_ci else if ((ul >= sizeof(*udph)) && (ul <= ipl - ihl)) 34162306a36Sopenharmony_ci skb->csum = csum_partial(udph, ul, 0); 34262306a36Sopenharmony_ci 34362306a36Sopenharmony_ci else 34462306a36Sopenharmony_ci goto ignore_obscure_skb; 34562306a36Sopenharmony_ci } else { 34662306a36Sopenharmony_ci if (ul != ipl - ihl) 34762306a36Sopenharmony_ci goto ignore_obscure_skb; 34862306a36Sopenharmony_ci 34962306a36Sopenharmony_ci skb->csum = csum_partial(udph, ul, 0); 35062306a36Sopenharmony_ci } 35162306a36Sopenharmony_ci 35262306a36Sopenharmony_ci udph->check = csum_ipv6_magic(&ip6h->saddr, &ip6h->daddr, ul, 35362306a36Sopenharmony_ci udplite ? IPPROTO_UDPLITE : IPPROTO_UDP, 35462306a36Sopenharmony_ci skb->csum); 35562306a36Sopenharmony_ci 35662306a36Sopenharmony_ci if (!udph->check) 35762306a36Sopenharmony_ci udph->check = CSUM_MANGLED_0; 35862306a36Sopenharmony_ci 35962306a36Sopenharmony_ci skb->ip_summed = CHECKSUM_NONE; 36062306a36Sopenharmony_ci 36162306a36Sopenharmony_ciignore_obscure_skb: 36262306a36Sopenharmony_ci return 1; 36362306a36Sopenharmony_ci} 36462306a36Sopenharmony_ci 36562306a36Sopenharmony_cistatic int tcf_csum_sctp(struct sk_buff *skb, unsigned int ihl, 36662306a36Sopenharmony_ci unsigned int ipl) 36762306a36Sopenharmony_ci{ 36862306a36Sopenharmony_ci struct sctphdr *sctph; 36962306a36Sopenharmony_ci 37062306a36Sopenharmony_ci if (skb_is_gso(skb) && skb_is_gso_sctp(skb)) 37162306a36Sopenharmony_ci return 1; 37262306a36Sopenharmony_ci 37362306a36Sopenharmony_ci sctph = tcf_csum_skb_nextlayer(skb, ihl, ipl, sizeof(*sctph)); 37462306a36Sopenharmony_ci if (!sctph) 37562306a36Sopenharmony_ci return 0; 37662306a36Sopenharmony_ci 37762306a36Sopenharmony_ci sctph->checksum = sctp_compute_cksum(skb, 37862306a36Sopenharmony_ci skb_network_offset(skb) + ihl); 37962306a36Sopenharmony_ci skb_reset_csum_not_inet(skb); 38062306a36Sopenharmony_ci 38162306a36Sopenharmony_ci return 1; 38262306a36Sopenharmony_ci} 38362306a36Sopenharmony_ci 38462306a36Sopenharmony_cistatic int tcf_csum_ipv4(struct sk_buff *skb, u32 update_flags) 38562306a36Sopenharmony_ci{ 38662306a36Sopenharmony_ci const struct iphdr *iph; 38762306a36Sopenharmony_ci int ntkoff; 38862306a36Sopenharmony_ci 38962306a36Sopenharmony_ci ntkoff = skb_network_offset(skb); 39062306a36Sopenharmony_ci 39162306a36Sopenharmony_ci if (!pskb_may_pull(skb, sizeof(*iph) + ntkoff)) 39262306a36Sopenharmony_ci goto fail; 39362306a36Sopenharmony_ci 39462306a36Sopenharmony_ci iph = ip_hdr(skb); 39562306a36Sopenharmony_ci 39662306a36Sopenharmony_ci switch (iph->frag_off & htons(IP_OFFSET) ? 0 : iph->protocol) { 39762306a36Sopenharmony_ci case IPPROTO_ICMP: 39862306a36Sopenharmony_ci if (update_flags & TCA_CSUM_UPDATE_FLAG_ICMP) 39962306a36Sopenharmony_ci if (!tcf_csum_ipv4_icmp(skb, iph->ihl * 4, 40062306a36Sopenharmony_ci ntohs(iph->tot_len))) 40162306a36Sopenharmony_ci goto fail; 40262306a36Sopenharmony_ci break; 40362306a36Sopenharmony_ci case IPPROTO_IGMP: 40462306a36Sopenharmony_ci if (update_flags & TCA_CSUM_UPDATE_FLAG_IGMP) 40562306a36Sopenharmony_ci if (!tcf_csum_ipv4_igmp(skb, iph->ihl * 4, 40662306a36Sopenharmony_ci ntohs(iph->tot_len))) 40762306a36Sopenharmony_ci goto fail; 40862306a36Sopenharmony_ci break; 40962306a36Sopenharmony_ci case IPPROTO_TCP: 41062306a36Sopenharmony_ci if (update_flags & TCA_CSUM_UPDATE_FLAG_TCP) 41162306a36Sopenharmony_ci if (!tcf_csum_ipv4_tcp(skb, iph->ihl * 4, 41262306a36Sopenharmony_ci ntohs(iph->tot_len))) 41362306a36Sopenharmony_ci goto fail; 41462306a36Sopenharmony_ci break; 41562306a36Sopenharmony_ci case IPPROTO_UDP: 41662306a36Sopenharmony_ci if (update_flags & TCA_CSUM_UPDATE_FLAG_UDP) 41762306a36Sopenharmony_ci if (!tcf_csum_ipv4_udp(skb, iph->ihl * 4, 41862306a36Sopenharmony_ci ntohs(iph->tot_len), 0)) 41962306a36Sopenharmony_ci goto fail; 42062306a36Sopenharmony_ci break; 42162306a36Sopenharmony_ci case IPPROTO_UDPLITE: 42262306a36Sopenharmony_ci if (update_flags & TCA_CSUM_UPDATE_FLAG_UDPLITE) 42362306a36Sopenharmony_ci if (!tcf_csum_ipv4_udp(skb, iph->ihl * 4, 42462306a36Sopenharmony_ci ntohs(iph->tot_len), 1)) 42562306a36Sopenharmony_ci goto fail; 42662306a36Sopenharmony_ci break; 42762306a36Sopenharmony_ci case IPPROTO_SCTP: 42862306a36Sopenharmony_ci if ((update_flags & TCA_CSUM_UPDATE_FLAG_SCTP) && 42962306a36Sopenharmony_ci !tcf_csum_sctp(skb, iph->ihl * 4, ntohs(iph->tot_len))) 43062306a36Sopenharmony_ci goto fail; 43162306a36Sopenharmony_ci break; 43262306a36Sopenharmony_ci } 43362306a36Sopenharmony_ci 43462306a36Sopenharmony_ci if (update_flags & TCA_CSUM_UPDATE_FLAG_IPV4HDR) { 43562306a36Sopenharmony_ci if (skb_try_make_writable(skb, sizeof(*iph) + ntkoff)) 43662306a36Sopenharmony_ci goto fail; 43762306a36Sopenharmony_ci 43862306a36Sopenharmony_ci ip_send_check(ip_hdr(skb)); 43962306a36Sopenharmony_ci } 44062306a36Sopenharmony_ci 44162306a36Sopenharmony_ci return 1; 44262306a36Sopenharmony_ci 44362306a36Sopenharmony_cifail: 44462306a36Sopenharmony_ci return 0; 44562306a36Sopenharmony_ci} 44662306a36Sopenharmony_ci 44762306a36Sopenharmony_cistatic int tcf_csum_ipv6_hopopts(struct ipv6_opt_hdr *ip6xh, unsigned int ixhl, 44862306a36Sopenharmony_ci unsigned int *pl) 44962306a36Sopenharmony_ci{ 45062306a36Sopenharmony_ci int off, len, optlen; 45162306a36Sopenharmony_ci unsigned char *xh = (void *)ip6xh; 45262306a36Sopenharmony_ci 45362306a36Sopenharmony_ci off = sizeof(*ip6xh); 45462306a36Sopenharmony_ci len = ixhl - off; 45562306a36Sopenharmony_ci 45662306a36Sopenharmony_ci while (len > 1) { 45762306a36Sopenharmony_ci switch (xh[off]) { 45862306a36Sopenharmony_ci case IPV6_TLV_PAD1: 45962306a36Sopenharmony_ci optlen = 1; 46062306a36Sopenharmony_ci break; 46162306a36Sopenharmony_ci case IPV6_TLV_JUMBO: 46262306a36Sopenharmony_ci optlen = xh[off + 1] + 2; 46362306a36Sopenharmony_ci if (optlen != 6 || len < 6 || (off & 3) != 2) 46462306a36Sopenharmony_ci /* wrong jumbo option length/alignment */ 46562306a36Sopenharmony_ci return 0; 46662306a36Sopenharmony_ci *pl = ntohl(*(__be32 *)(xh + off + 2)); 46762306a36Sopenharmony_ci goto done; 46862306a36Sopenharmony_ci default: 46962306a36Sopenharmony_ci optlen = xh[off + 1] + 2; 47062306a36Sopenharmony_ci if (optlen > len) 47162306a36Sopenharmony_ci /* ignore obscure options */ 47262306a36Sopenharmony_ci goto done; 47362306a36Sopenharmony_ci break; 47462306a36Sopenharmony_ci } 47562306a36Sopenharmony_ci off += optlen; 47662306a36Sopenharmony_ci len -= optlen; 47762306a36Sopenharmony_ci } 47862306a36Sopenharmony_ci 47962306a36Sopenharmony_cidone: 48062306a36Sopenharmony_ci return 1; 48162306a36Sopenharmony_ci} 48262306a36Sopenharmony_ci 48362306a36Sopenharmony_cistatic int tcf_csum_ipv6(struct sk_buff *skb, u32 update_flags) 48462306a36Sopenharmony_ci{ 48562306a36Sopenharmony_ci struct ipv6hdr *ip6h; 48662306a36Sopenharmony_ci struct ipv6_opt_hdr *ip6xh; 48762306a36Sopenharmony_ci unsigned int hl, ixhl; 48862306a36Sopenharmony_ci unsigned int pl; 48962306a36Sopenharmony_ci int ntkoff; 49062306a36Sopenharmony_ci u8 nexthdr; 49162306a36Sopenharmony_ci 49262306a36Sopenharmony_ci ntkoff = skb_network_offset(skb); 49362306a36Sopenharmony_ci 49462306a36Sopenharmony_ci hl = sizeof(*ip6h); 49562306a36Sopenharmony_ci 49662306a36Sopenharmony_ci if (!pskb_may_pull(skb, hl + ntkoff)) 49762306a36Sopenharmony_ci goto fail; 49862306a36Sopenharmony_ci 49962306a36Sopenharmony_ci ip6h = ipv6_hdr(skb); 50062306a36Sopenharmony_ci 50162306a36Sopenharmony_ci pl = ntohs(ip6h->payload_len); 50262306a36Sopenharmony_ci nexthdr = ip6h->nexthdr; 50362306a36Sopenharmony_ci 50462306a36Sopenharmony_ci do { 50562306a36Sopenharmony_ci switch (nexthdr) { 50662306a36Sopenharmony_ci case NEXTHDR_FRAGMENT: 50762306a36Sopenharmony_ci goto ignore_skb; 50862306a36Sopenharmony_ci case NEXTHDR_ROUTING: 50962306a36Sopenharmony_ci case NEXTHDR_HOP: 51062306a36Sopenharmony_ci case NEXTHDR_DEST: 51162306a36Sopenharmony_ci if (!pskb_may_pull(skb, hl + sizeof(*ip6xh) + ntkoff)) 51262306a36Sopenharmony_ci goto fail; 51362306a36Sopenharmony_ci ip6xh = (void *)(skb_network_header(skb) + hl); 51462306a36Sopenharmony_ci ixhl = ipv6_optlen(ip6xh); 51562306a36Sopenharmony_ci if (!pskb_may_pull(skb, hl + ixhl + ntkoff)) 51662306a36Sopenharmony_ci goto fail; 51762306a36Sopenharmony_ci ip6xh = (void *)(skb_network_header(skb) + hl); 51862306a36Sopenharmony_ci if ((nexthdr == NEXTHDR_HOP) && 51962306a36Sopenharmony_ci !(tcf_csum_ipv6_hopopts(ip6xh, ixhl, &pl))) 52062306a36Sopenharmony_ci goto fail; 52162306a36Sopenharmony_ci nexthdr = ip6xh->nexthdr; 52262306a36Sopenharmony_ci hl += ixhl; 52362306a36Sopenharmony_ci break; 52462306a36Sopenharmony_ci case IPPROTO_ICMPV6: 52562306a36Sopenharmony_ci if (update_flags & TCA_CSUM_UPDATE_FLAG_ICMP) 52662306a36Sopenharmony_ci if (!tcf_csum_ipv6_icmp(skb, 52762306a36Sopenharmony_ci hl, pl + sizeof(*ip6h))) 52862306a36Sopenharmony_ci goto fail; 52962306a36Sopenharmony_ci goto done; 53062306a36Sopenharmony_ci case IPPROTO_TCP: 53162306a36Sopenharmony_ci if (update_flags & TCA_CSUM_UPDATE_FLAG_TCP) 53262306a36Sopenharmony_ci if (!tcf_csum_ipv6_tcp(skb, 53362306a36Sopenharmony_ci hl, pl + sizeof(*ip6h))) 53462306a36Sopenharmony_ci goto fail; 53562306a36Sopenharmony_ci goto done; 53662306a36Sopenharmony_ci case IPPROTO_UDP: 53762306a36Sopenharmony_ci if (update_flags & TCA_CSUM_UPDATE_FLAG_UDP) 53862306a36Sopenharmony_ci if (!tcf_csum_ipv6_udp(skb, hl, 53962306a36Sopenharmony_ci pl + sizeof(*ip6h), 0)) 54062306a36Sopenharmony_ci goto fail; 54162306a36Sopenharmony_ci goto done; 54262306a36Sopenharmony_ci case IPPROTO_UDPLITE: 54362306a36Sopenharmony_ci if (update_flags & TCA_CSUM_UPDATE_FLAG_UDPLITE) 54462306a36Sopenharmony_ci if (!tcf_csum_ipv6_udp(skb, hl, 54562306a36Sopenharmony_ci pl + sizeof(*ip6h), 1)) 54662306a36Sopenharmony_ci goto fail; 54762306a36Sopenharmony_ci goto done; 54862306a36Sopenharmony_ci case IPPROTO_SCTP: 54962306a36Sopenharmony_ci if ((update_flags & TCA_CSUM_UPDATE_FLAG_SCTP) && 55062306a36Sopenharmony_ci !tcf_csum_sctp(skb, hl, pl + sizeof(*ip6h))) 55162306a36Sopenharmony_ci goto fail; 55262306a36Sopenharmony_ci goto done; 55362306a36Sopenharmony_ci default: 55462306a36Sopenharmony_ci goto ignore_skb; 55562306a36Sopenharmony_ci } 55662306a36Sopenharmony_ci } while (pskb_may_pull(skb, hl + 1 + ntkoff)); 55762306a36Sopenharmony_ci 55862306a36Sopenharmony_cidone: 55962306a36Sopenharmony_ciignore_skb: 56062306a36Sopenharmony_ci return 1; 56162306a36Sopenharmony_ci 56262306a36Sopenharmony_cifail: 56362306a36Sopenharmony_ci return 0; 56462306a36Sopenharmony_ci} 56562306a36Sopenharmony_ci 56662306a36Sopenharmony_ciTC_INDIRECT_SCOPE int tcf_csum_act(struct sk_buff *skb, 56762306a36Sopenharmony_ci const struct tc_action *a, 56862306a36Sopenharmony_ci struct tcf_result *res) 56962306a36Sopenharmony_ci{ 57062306a36Sopenharmony_ci struct tcf_csum *p = to_tcf_csum(a); 57162306a36Sopenharmony_ci bool orig_vlan_tag_present = false; 57262306a36Sopenharmony_ci unsigned int vlan_hdr_count = 0; 57362306a36Sopenharmony_ci struct tcf_csum_params *params; 57462306a36Sopenharmony_ci u32 update_flags; 57562306a36Sopenharmony_ci __be16 protocol; 57662306a36Sopenharmony_ci int action; 57762306a36Sopenharmony_ci 57862306a36Sopenharmony_ci params = rcu_dereference_bh(p->params); 57962306a36Sopenharmony_ci 58062306a36Sopenharmony_ci tcf_lastuse_update(&p->tcf_tm); 58162306a36Sopenharmony_ci tcf_action_update_bstats(&p->common, skb); 58262306a36Sopenharmony_ci 58362306a36Sopenharmony_ci action = READ_ONCE(p->tcf_action); 58462306a36Sopenharmony_ci if (unlikely(action == TC_ACT_SHOT)) 58562306a36Sopenharmony_ci goto drop; 58662306a36Sopenharmony_ci 58762306a36Sopenharmony_ci update_flags = params->update_flags; 58862306a36Sopenharmony_ci protocol = skb_protocol(skb, false); 58962306a36Sopenharmony_ciagain: 59062306a36Sopenharmony_ci switch (protocol) { 59162306a36Sopenharmony_ci case cpu_to_be16(ETH_P_IP): 59262306a36Sopenharmony_ci if (!tcf_csum_ipv4(skb, update_flags)) 59362306a36Sopenharmony_ci goto drop; 59462306a36Sopenharmony_ci break; 59562306a36Sopenharmony_ci case cpu_to_be16(ETH_P_IPV6): 59662306a36Sopenharmony_ci if (!tcf_csum_ipv6(skb, update_flags)) 59762306a36Sopenharmony_ci goto drop; 59862306a36Sopenharmony_ci break; 59962306a36Sopenharmony_ci case cpu_to_be16(ETH_P_8021AD): 60062306a36Sopenharmony_ci fallthrough; 60162306a36Sopenharmony_ci case cpu_to_be16(ETH_P_8021Q): 60262306a36Sopenharmony_ci if (skb_vlan_tag_present(skb) && !orig_vlan_tag_present) { 60362306a36Sopenharmony_ci protocol = skb->protocol; 60462306a36Sopenharmony_ci orig_vlan_tag_present = true; 60562306a36Sopenharmony_ci } else { 60662306a36Sopenharmony_ci struct vlan_hdr *vlan = (struct vlan_hdr *)skb->data; 60762306a36Sopenharmony_ci 60862306a36Sopenharmony_ci protocol = vlan->h_vlan_encapsulated_proto; 60962306a36Sopenharmony_ci skb_pull(skb, VLAN_HLEN); 61062306a36Sopenharmony_ci skb_reset_network_header(skb); 61162306a36Sopenharmony_ci vlan_hdr_count++; 61262306a36Sopenharmony_ci } 61362306a36Sopenharmony_ci goto again; 61462306a36Sopenharmony_ci } 61562306a36Sopenharmony_ci 61662306a36Sopenharmony_ciout: 61762306a36Sopenharmony_ci /* Restore the skb for the pulled VLAN tags */ 61862306a36Sopenharmony_ci while (vlan_hdr_count--) { 61962306a36Sopenharmony_ci skb_push(skb, VLAN_HLEN); 62062306a36Sopenharmony_ci skb_reset_network_header(skb); 62162306a36Sopenharmony_ci } 62262306a36Sopenharmony_ci 62362306a36Sopenharmony_ci return action; 62462306a36Sopenharmony_ci 62562306a36Sopenharmony_cidrop: 62662306a36Sopenharmony_ci tcf_action_inc_drop_qstats(&p->common); 62762306a36Sopenharmony_ci action = TC_ACT_SHOT; 62862306a36Sopenharmony_ci goto out; 62962306a36Sopenharmony_ci} 63062306a36Sopenharmony_ci 63162306a36Sopenharmony_cistatic int tcf_csum_dump(struct sk_buff *skb, struct tc_action *a, int bind, 63262306a36Sopenharmony_ci int ref) 63362306a36Sopenharmony_ci{ 63462306a36Sopenharmony_ci unsigned char *b = skb_tail_pointer(skb); 63562306a36Sopenharmony_ci struct tcf_csum *p = to_tcf_csum(a); 63662306a36Sopenharmony_ci struct tcf_csum_params *params; 63762306a36Sopenharmony_ci struct tc_csum opt = { 63862306a36Sopenharmony_ci .index = p->tcf_index, 63962306a36Sopenharmony_ci .refcnt = refcount_read(&p->tcf_refcnt) - ref, 64062306a36Sopenharmony_ci .bindcnt = atomic_read(&p->tcf_bindcnt) - bind, 64162306a36Sopenharmony_ci }; 64262306a36Sopenharmony_ci struct tcf_t t; 64362306a36Sopenharmony_ci 64462306a36Sopenharmony_ci spin_lock_bh(&p->tcf_lock); 64562306a36Sopenharmony_ci params = rcu_dereference_protected(p->params, 64662306a36Sopenharmony_ci lockdep_is_held(&p->tcf_lock)); 64762306a36Sopenharmony_ci opt.action = p->tcf_action; 64862306a36Sopenharmony_ci opt.update_flags = params->update_flags; 64962306a36Sopenharmony_ci 65062306a36Sopenharmony_ci if (nla_put(skb, TCA_CSUM_PARMS, sizeof(opt), &opt)) 65162306a36Sopenharmony_ci goto nla_put_failure; 65262306a36Sopenharmony_ci 65362306a36Sopenharmony_ci tcf_tm_dump(&t, &p->tcf_tm); 65462306a36Sopenharmony_ci if (nla_put_64bit(skb, TCA_CSUM_TM, sizeof(t), &t, TCA_CSUM_PAD)) 65562306a36Sopenharmony_ci goto nla_put_failure; 65662306a36Sopenharmony_ci spin_unlock_bh(&p->tcf_lock); 65762306a36Sopenharmony_ci 65862306a36Sopenharmony_ci return skb->len; 65962306a36Sopenharmony_ci 66062306a36Sopenharmony_cinla_put_failure: 66162306a36Sopenharmony_ci spin_unlock_bh(&p->tcf_lock); 66262306a36Sopenharmony_ci nlmsg_trim(skb, b); 66362306a36Sopenharmony_ci return -1; 66462306a36Sopenharmony_ci} 66562306a36Sopenharmony_ci 66662306a36Sopenharmony_cistatic void tcf_csum_cleanup(struct tc_action *a) 66762306a36Sopenharmony_ci{ 66862306a36Sopenharmony_ci struct tcf_csum *p = to_tcf_csum(a); 66962306a36Sopenharmony_ci struct tcf_csum_params *params; 67062306a36Sopenharmony_ci 67162306a36Sopenharmony_ci params = rcu_dereference_protected(p->params, 1); 67262306a36Sopenharmony_ci if (params) 67362306a36Sopenharmony_ci kfree_rcu(params, rcu); 67462306a36Sopenharmony_ci} 67562306a36Sopenharmony_ci 67662306a36Sopenharmony_cistatic size_t tcf_csum_get_fill_size(const struct tc_action *act) 67762306a36Sopenharmony_ci{ 67862306a36Sopenharmony_ci return nla_total_size(sizeof(struct tc_csum)); 67962306a36Sopenharmony_ci} 68062306a36Sopenharmony_ci 68162306a36Sopenharmony_cistatic int tcf_csum_offload_act_setup(struct tc_action *act, void *entry_data, 68262306a36Sopenharmony_ci u32 *index_inc, bool bind, 68362306a36Sopenharmony_ci struct netlink_ext_ack *extack) 68462306a36Sopenharmony_ci{ 68562306a36Sopenharmony_ci if (bind) { 68662306a36Sopenharmony_ci struct flow_action_entry *entry = entry_data; 68762306a36Sopenharmony_ci 68862306a36Sopenharmony_ci entry->id = FLOW_ACTION_CSUM; 68962306a36Sopenharmony_ci entry->csum_flags = tcf_csum_update_flags(act); 69062306a36Sopenharmony_ci *index_inc = 1; 69162306a36Sopenharmony_ci } else { 69262306a36Sopenharmony_ci struct flow_offload_action *fl_action = entry_data; 69362306a36Sopenharmony_ci 69462306a36Sopenharmony_ci fl_action->id = FLOW_ACTION_CSUM; 69562306a36Sopenharmony_ci } 69662306a36Sopenharmony_ci 69762306a36Sopenharmony_ci return 0; 69862306a36Sopenharmony_ci} 69962306a36Sopenharmony_ci 70062306a36Sopenharmony_cistatic struct tc_action_ops act_csum_ops = { 70162306a36Sopenharmony_ci .kind = "csum", 70262306a36Sopenharmony_ci .id = TCA_ID_CSUM, 70362306a36Sopenharmony_ci .owner = THIS_MODULE, 70462306a36Sopenharmony_ci .act = tcf_csum_act, 70562306a36Sopenharmony_ci .dump = tcf_csum_dump, 70662306a36Sopenharmony_ci .init = tcf_csum_init, 70762306a36Sopenharmony_ci .cleanup = tcf_csum_cleanup, 70862306a36Sopenharmony_ci .get_fill_size = tcf_csum_get_fill_size, 70962306a36Sopenharmony_ci .offload_act_setup = tcf_csum_offload_act_setup, 71062306a36Sopenharmony_ci .size = sizeof(struct tcf_csum), 71162306a36Sopenharmony_ci}; 71262306a36Sopenharmony_ci 71362306a36Sopenharmony_cistatic __net_init int csum_init_net(struct net *net) 71462306a36Sopenharmony_ci{ 71562306a36Sopenharmony_ci struct tc_action_net *tn = net_generic(net, act_csum_ops.net_id); 71662306a36Sopenharmony_ci 71762306a36Sopenharmony_ci return tc_action_net_init(net, tn, &act_csum_ops); 71862306a36Sopenharmony_ci} 71962306a36Sopenharmony_ci 72062306a36Sopenharmony_cistatic void __net_exit csum_exit_net(struct list_head *net_list) 72162306a36Sopenharmony_ci{ 72262306a36Sopenharmony_ci tc_action_net_exit(net_list, act_csum_ops.net_id); 72362306a36Sopenharmony_ci} 72462306a36Sopenharmony_ci 72562306a36Sopenharmony_cistatic struct pernet_operations csum_net_ops = { 72662306a36Sopenharmony_ci .init = csum_init_net, 72762306a36Sopenharmony_ci .exit_batch = csum_exit_net, 72862306a36Sopenharmony_ci .id = &act_csum_ops.net_id, 72962306a36Sopenharmony_ci .size = sizeof(struct tc_action_net), 73062306a36Sopenharmony_ci}; 73162306a36Sopenharmony_ci 73262306a36Sopenharmony_ciMODULE_DESCRIPTION("Checksum updating actions"); 73362306a36Sopenharmony_ciMODULE_LICENSE("GPL"); 73462306a36Sopenharmony_ci 73562306a36Sopenharmony_cistatic int __init csum_init_module(void) 73662306a36Sopenharmony_ci{ 73762306a36Sopenharmony_ci return tcf_register_action(&act_csum_ops, &csum_net_ops); 73862306a36Sopenharmony_ci} 73962306a36Sopenharmony_ci 74062306a36Sopenharmony_cistatic void __exit csum_cleanup_module(void) 74162306a36Sopenharmony_ci{ 74262306a36Sopenharmony_ci tcf_unregister_action(&act_csum_ops, &csum_net_ops); 74362306a36Sopenharmony_ci} 74462306a36Sopenharmony_ci 74562306a36Sopenharmony_cimodule_init(csum_init_module); 74662306a36Sopenharmony_cimodule_exit(csum_cleanup_module); 747