1*2874c5fdSThomas Gleixner // SPDX-License-Identifier: GPL-2.0-or-later 2b4219952SHerbert Xu /* 3b4219952SHerbert Xu * Stateless NAT actions 4b4219952SHerbert Xu * 5b4219952SHerbert Xu * Copyright (c) 2007 Herbert Xu <herbert@gondor.apana.org.au> 6b4219952SHerbert Xu */ 7b4219952SHerbert Xu 8b4219952SHerbert Xu #include <linux/errno.h> 9b4219952SHerbert Xu #include <linux/init.h> 10b4219952SHerbert Xu #include <linux/kernel.h> 11b4219952SHerbert Xu #include <linux/module.h> 12b4219952SHerbert Xu #include <linux/netfilter.h> 13b4219952SHerbert Xu #include <linux/rtnetlink.h> 14b4219952SHerbert Xu #include <linux/skbuff.h> 15b4219952SHerbert Xu #include <linux/slab.h> 16b4219952SHerbert Xu #include <linux/spinlock.h> 17b4219952SHerbert Xu #include <linux/string.h> 18b4219952SHerbert Xu #include <linux/tc_act/tc_nat.h> 19b4219952SHerbert Xu #include <net/act_api.h> 201e45d043SDavide Caratti #include <net/pkt_cls.h> 21b4219952SHerbert Xu #include <net/icmp.h> 22b4219952SHerbert Xu #include <net/ip.h> 23b4219952SHerbert Xu #include <net/netlink.h> 24b4219952SHerbert Xu #include <net/tc_act/tc_nat.h> 25b4219952SHerbert Xu #include <net/tcp.h> 26b4219952SHerbert Xu #include <net/udp.h> 27b4219952SHerbert Xu 28b4219952SHerbert Xu 29c7d03a00SAlexey Dobriyan static unsigned int nat_net_id; 30a85a970aSWANG Cong static struct tc_action_ops act_nat_ops; 31ddf97ccdSWANG Cong 3253b2bf3fSPatrick McHardy static const struct nla_policy nat_policy[TCA_NAT_MAX + 1] = { 3353b2bf3fSPatrick McHardy [TCA_NAT_PARMS] = { .len = sizeof(struct tc_nat) }, 3453b2bf3fSPatrick McHardy }; 3553b2bf3fSPatrick McHardy 36c1b52739SBenjamin LaHaise static int tcf_nat_init(struct net *net, struct nlattr *nla, struct nlattr *est, 37589dad6dSAlexander Aring struct tc_action **a, int ovr, int bind, 3885d0966fSDavide Caratti bool rtnl_held, struct tcf_proto *tp, 3985d0966fSDavide Caratti struct netlink_ext_ack *extack) 40b4219952SHerbert Xu { 41ddf97ccdSWANG Cong struct tc_action_net *tn = net_generic(net, nat_net_id); 427ba699c6SPatrick McHardy struct nlattr *tb[TCA_NAT_MAX + 1]; 431e45d043SDavide Caratti struct tcf_chain *goto_ch = NULL; 44b4219952SHerbert Xu struct tc_nat *parm; 45cee63723SPatrick McHardy int ret = 0, err; 46b4219952SHerbert Xu struct tcf_nat *p; 47b4219952SHerbert Xu 48cee63723SPatrick McHardy if (nla == NULL) 49b4219952SHerbert Xu return -EINVAL; 50b4219952SHerbert Xu 518cb08174SJohannes Berg err = nla_parse_nested_deprecated(tb, TCA_NAT_MAX, nla, nat_policy, 528cb08174SJohannes Berg NULL); 53cee63723SPatrick McHardy if (err < 0) 54cee63723SPatrick McHardy return err; 55cee63723SPatrick McHardy 5653b2bf3fSPatrick McHardy if (tb[TCA_NAT_PARMS] == NULL) 57b4219952SHerbert Xu return -EINVAL; 587ba699c6SPatrick McHardy parm = nla_data(tb[TCA_NAT_PARMS]); 59b4219952SHerbert Xu 600190c1d4SVlad Buslov err = tcf_idr_check_alloc(tn, &parm->index, a, bind); 610190c1d4SVlad Buslov if (!err) { 6265a206c0SChris Mi ret = tcf_idr_create(tn, parm->index, est, a, 63a85a970aSWANG Cong &act_nat_ops, bind, false); 640190c1d4SVlad Buslov if (ret) { 650190c1d4SVlad Buslov tcf_idr_cleanup(tn, parm->index); 6686062033SWANG Cong return ret; 670190c1d4SVlad Buslov } 68b4219952SHerbert Xu ret = ACT_P_CREATED; 690190c1d4SVlad Buslov } else if (err > 0) { 701a29321eSJamal Hadi Salim if (bind) 711a29321eSJamal Hadi Salim return 0; 724e8ddd7fSVlad Buslov if (!ovr) { 7365a206c0SChris Mi tcf_idr_release(*a, bind); 74b4219952SHerbert Xu return -EEXIST; 75b4219952SHerbert Xu } 760190c1d4SVlad Buslov } else { 770190c1d4SVlad Buslov return err; 784e8ddd7fSVlad Buslov } 791e45d043SDavide Caratti err = tcf_action_check_ctrlact(parm->action, tp, &goto_ch, extack); 801e45d043SDavide Caratti if (err < 0) 811e45d043SDavide Caratti goto release_idr; 82a85a970aSWANG Cong p = to_tcf_nat(*a); 83b4219952SHerbert Xu 84b4219952SHerbert Xu spin_lock_bh(&p->tcf_lock); 85b4219952SHerbert Xu p->old_addr = parm->old_addr; 86b4219952SHerbert Xu p->new_addr = parm->new_addr; 87b4219952SHerbert Xu p->mask = parm->mask; 88b4219952SHerbert Xu p->flags = parm->flags; 89b4219952SHerbert Xu 901e45d043SDavide Caratti goto_ch = tcf_action_set_ctrlact(*a, parm->action, goto_ch); 91b4219952SHerbert Xu spin_unlock_bh(&p->tcf_lock); 921e45d043SDavide Caratti if (goto_ch) 931e45d043SDavide Caratti tcf_chain_put_by_act(goto_ch); 94b4219952SHerbert Xu 95b4219952SHerbert Xu if (ret == ACT_P_CREATED) 9665a206c0SChris Mi tcf_idr_insert(tn, *a); 97b4219952SHerbert Xu 98b4219952SHerbert Xu return ret; 991e45d043SDavide Caratti release_idr: 1001e45d043SDavide Caratti tcf_idr_release(*a, bind); 1011e45d043SDavide Caratti return err; 102b4219952SHerbert Xu } 103b4219952SHerbert Xu 1040390514fSJamal Hadi Salim static int tcf_nat_act(struct sk_buff *skb, const struct tc_action *a, 105b4219952SHerbert Xu struct tcf_result *res) 106b4219952SHerbert Xu { 107a85a970aSWANG Cong struct tcf_nat *p = to_tcf_nat(a); 108b4219952SHerbert Xu struct iphdr *iph; 109b4219952SHerbert Xu __be32 old_addr; 110b4219952SHerbert Xu __be32 new_addr; 111b4219952SHerbert Xu __be32 mask; 112b4219952SHerbert Xu __be32 addr; 113b4219952SHerbert Xu int egress; 114b4219952SHerbert Xu int action; 115b4219952SHerbert Xu int ihl; 11636d12690SChangli Gao int noff; 117b4219952SHerbert Xu 118b4219952SHerbert Xu spin_lock(&p->tcf_lock); 119b4219952SHerbert Xu 1209c4a4e48SJamal Hadi Salim tcf_lastuse_update(&p->tcf_tm); 121b4219952SHerbert Xu old_addr = p->old_addr; 122b4219952SHerbert Xu new_addr = p->new_addr; 123b4219952SHerbert Xu mask = p->mask; 124b4219952SHerbert Xu egress = p->flags & TCA_NAT_FLAG_EGRESS; 125b4219952SHerbert Xu action = p->tcf_action; 126b4219952SHerbert Xu 127bfe0d029SEric Dumazet bstats_update(&p->tcf_bstats, skb); 128b4219952SHerbert Xu 129b4219952SHerbert Xu spin_unlock(&p->tcf_lock); 130b4219952SHerbert Xu 131b4219952SHerbert Xu if (unlikely(action == TC_ACT_SHOT)) 132b4219952SHerbert Xu goto drop; 133b4219952SHerbert Xu 13436d12690SChangli Gao noff = skb_network_offset(skb); 13536d12690SChangli Gao if (!pskb_may_pull(skb, sizeof(*iph) + noff)) 136b4219952SHerbert Xu goto drop; 137b4219952SHerbert Xu 138b4219952SHerbert Xu iph = ip_hdr(skb); 139b4219952SHerbert Xu 140b4219952SHerbert Xu if (egress) 141b4219952SHerbert Xu addr = iph->saddr; 142b4219952SHerbert Xu else 143b4219952SHerbert Xu addr = iph->daddr; 144b4219952SHerbert Xu 145b4219952SHerbert Xu if (!((old_addr ^ addr) & mask)) { 1463697649fSDaniel Borkmann if (skb_try_make_writable(skb, sizeof(*iph) + noff)) 147b4219952SHerbert Xu goto drop; 148b4219952SHerbert Xu 149b4219952SHerbert Xu new_addr &= mask; 150b4219952SHerbert Xu new_addr |= addr & ~mask; 151b4219952SHerbert Xu 152b4219952SHerbert Xu /* Rewrite IP header */ 153b4219952SHerbert Xu iph = ip_hdr(skb); 154b4219952SHerbert Xu if (egress) 155b4219952SHerbert Xu iph->saddr = new_addr; 156b4219952SHerbert Xu else 157b4219952SHerbert Xu iph->daddr = new_addr; 158b4219952SHerbert Xu 159be0ea7d5SPatrick McHardy csum_replace4(&iph->check, addr, new_addr); 16033c29ddeSChangli Gao } else if ((iph->frag_off & htons(IP_OFFSET)) || 16133c29ddeSChangli Gao iph->protocol != IPPROTO_ICMP) { 16233c29ddeSChangli Gao goto out; 163b4219952SHerbert Xu } 164b4219952SHerbert Xu 165b4219952SHerbert Xu ihl = iph->ihl * 4; 166b4219952SHerbert Xu 167b4219952SHerbert Xu /* It would be nice to share code with stateful NAT. */ 168b4219952SHerbert Xu switch (iph->frag_off & htons(IP_OFFSET) ? 0 : iph->protocol) { 169b4219952SHerbert Xu case IPPROTO_TCP: 170b4219952SHerbert Xu { 171b4219952SHerbert Xu struct tcphdr *tcph; 172b4219952SHerbert Xu 17336d12690SChangli Gao if (!pskb_may_pull(skb, ihl + sizeof(*tcph) + noff) || 1743697649fSDaniel Borkmann skb_try_make_writable(skb, ihl + sizeof(*tcph) + noff)) 175b4219952SHerbert Xu goto drop; 176b4219952SHerbert Xu 177b4219952SHerbert Xu tcph = (void *)(skb_network_header(skb) + ihl); 1784b048d6dSTom Herbert inet_proto_csum_replace4(&tcph->check, skb, addr, new_addr, 1794b048d6dSTom Herbert true); 180b4219952SHerbert Xu break; 181b4219952SHerbert Xu } 182b4219952SHerbert Xu case IPPROTO_UDP: 183b4219952SHerbert Xu { 184b4219952SHerbert Xu struct udphdr *udph; 185b4219952SHerbert Xu 18636d12690SChangli Gao if (!pskb_may_pull(skb, ihl + sizeof(*udph) + noff) || 1873697649fSDaniel Borkmann skb_try_make_writable(skb, ihl + sizeof(*udph) + noff)) 188b4219952SHerbert Xu goto drop; 189b4219952SHerbert Xu 190b4219952SHerbert Xu udph = (void *)(skb_network_header(skb) + ihl); 191b4219952SHerbert Xu if (udph->check || skb->ip_summed == CHECKSUM_PARTIAL) { 192be0ea7d5SPatrick McHardy inet_proto_csum_replace4(&udph->check, skb, addr, 1934b048d6dSTom Herbert new_addr, true); 194b4219952SHerbert Xu if (!udph->check) 195b4219952SHerbert Xu udph->check = CSUM_MANGLED_0; 196b4219952SHerbert Xu } 197b4219952SHerbert Xu break; 198b4219952SHerbert Xu } 199b4219952SHerbert Xu case IPPROTO_ICMP: 200b4219952SHerbert Xu { 201b4219952SHerbert Xu struct icmphdr *icmph; 202b4219952SHerbert Xu 20336d12690SChangli Gao if (!pskb_may_pull(skb, ihl + sizeof(*icmph) + noff)) 204b4219952SHerbert Xu goto drop; 205b4219952SHerbert Xu 206b4219952SHerbert Xu icmph = (void *)(skb_network_header(skb) + ihl); 207b4219952SHerbert Xu 208b4219952SHerbert Xu if ((icmph->type != ICMP_DEST_UNREACH) && 209b4219952SHerbert Xu (icmph->type != ICMP_TIME_EXCEEDED) && 210b4219952SHerbert Xu (icmph->type != ICMP_PARAMETERPROB)) 211b4219952SHerbert Xu break; 212b4219952SHerbert Xu 21336d12690SChangli Gao if (!pskb_may_pull(skb, ihl + sizeof(*icmph) + sizeof(*iph) + 21436d12690SChangli Gao noff)) 21570c2efa5SChangli Gao goto drop; 21670c2efa5SChangli Gao 217072d79a3SChangli Gao icmph = (void *)(skb_network_header(skb) + ihl); 218b4219952SHerbert Xu iph = (void *)(icmph + 1); 219b4219952SHerbert Xu if (egress) 220b4219952SHerbert Xu addr = iph->daddr; 221b4219952SHerbert Xu else 222b4219952SHerbert Xu addr = iph->saddr; 223b4219952SHerbert Xu 224b4219952SHerbert Xu if ((old_addr ^ addr) & mask) 225b4219952SHerbert Xu break; 226b4219952SHerbert Xu 2273697649fSDaniel Borkmann if (skb_try_make_writable(skb, ihl + sizeof(*icmph) + 2283697649fSDaniel Borkmann sizeof(*iph) + noff)) 229b4219952SHerbert Xu goto drop; 230b4219952SHerbert Xu 231b4219952SHerbert Xu icmph = (void *)(skb_network_header(skb) + ihl); 232b4219952SHerbert Xu iph = (void *)(icmph + 1); 233b4219952SHerbert Xu 234b4219952SHerbert Xu new_addr &= mask; 235b4219952SHerbert Xu new_addr |= addr & ~mask; 236b4219952SHerbert Xu 237b4219952SHerbert Xu /* XXX Fix up the inner checksums. */ 238b4219952SHerbert Xu if (egress) 239b4219952SHerbert Xu iph->daddr = new_addr; 240b4219952SHerbert Xu else 241b4219952SHerbert Xu iph->saddr = new_addr; 242b4219952SHerbert Xu 243be0ea7d5SPatrick McHardy inet_proto_csum_replace4(&icmph->checksum, skb, addr, new_addr, 2444b048d6dSTom Herbert false); 245b4219952SHerbert Xu break; 246b4219952SHerbert Xu } 247b4219952SHerbert Xu default: 248b4219952SHerbert Xu break; 249b4219952SHerbert Xu } 250b4219952SHerbert Xu 25133c29ddeSChangli Gao out: 252b4219952SHerbert Xu return action; 253b4219952SHerbert Xu 254b4219952SHerbert Xu drop: 255b4219952SHerbert Xu spin_lock(&p->tcf_lock); 256b4219952SHerbert Xu p->tcf_qstats.drops++; 257b4219952SHerbert Xu spin_unlock(&p->tcf_lock); 258b4219952SHerbert Xu return TC_ACT_SHOT; 259b4219952SHerbert Xu } 260b4219952SHerbert Xu 261b4219952SHerbert Xu static int tcf_nat_dump(struct sk_buff *skb, struct tc_action *a, 262b4219952SHerbert Xu int bind, int ref) 263b4219952SHerbert Xu { 264b4219952SHerbert Xu unsigned char *b = skb_tail_pointer(skb); 265a85a970aSWANG Cong struct tcf_nat *p = to_tcf_nat(a); 2661c40be12SEric Dumazet struct tc_nat opt = { 2671c40be12SEric Dumazet .index = p->tcf_index, 268036bb443SVlad Buslov .refcnt = refcount_read(&p->tcf_refcnt) - ref, 269036bb443SVlad Buslov .bindcnt = atomic_read(&p->tcf_bindcnt) - bind, 2701c40be12SEric Dumazet }; 271b4219952SHerbert Xu struct tcf_t t; 272b4219952SHerbert Xu 273f20a4d01SVlad Buslov spin_lock_bh(&p->tcf_lock); 274f20a4d01SVlad Buslov opt.old_addr = p->old_addr; 275f20a4d01SVlad Buslov opt.new_addr = p->new_addr; 276f20a4d01SVlad Buslov opt.mask = p->mask; 277f20a4d01SVlad Buslov opt.flags = p->flags; 278f20a4d01SVlad Buslov opt.action = p->tcf_action; 279f20a4d01SVlad Buslov 2801b34ec43SDavid S. Miller if (nla_put(skb, TCA_NAT_PARMS, sizeof(opt), &opt)) 2811b34ec43SDavid S. Miller goto nla_put_failure; 28248d8ee16SJamal Hadi Salim 28348d8ee16SJamal Hadi Salim tcf_tm_dump(&t, &p->tcf_tm); 2849854518eSNicolas Dichtel if (nla_put_64bit(skb, TCA_NAT_TM, sizeof(t), &t, TCA_NAT_PAD)) 2851b34ec43SDavid S. Miller goto nla_put_failure; 286f20a4d01SVlad Buslov spin_unlock_bh(&p->tcf_lock); 287b4219952SHerbert Xu 288b4219952SHerbert Xu return skb->len; 289b4219952SHerbert Xu 2907ba699c6SPatrick McHardy nla_put_failure: 291f20a4d01SVlad Buslov spin_unlock_bh(&p->tcf_lock); 292b4219952SHerbert Xu nlmsg_trim(skb, b); 293b4219952SHerbert Xu return -1; 294b4219952SHerbert Xu } 295b4219952SHerbert Xu 296ddf97ccdSWANG Cong static int tcf_nat_walker(struct net *net, struct sk_buff *skb, 297ddf97ccdSWANG Cong struct netlink_callback *cb, int type, 29841780105SAlexander Aring const struct tc_action_ops *ops, 29941780105SAlexander Aring struct netlink_ext_ack *extack) 300ddf97ccdSWANG Cong { 301ddf97ccdSWANG Cong struct tc_action_net *tn = net_generic(net, nat_net_id); 302ddf97ccdSWANG Cong 303b3620145SAlexander Aring return tcf_generic_walker(tn, skb, cb, type, ops, extack); 304ddf97ccdSWANG Cong } 305ddf97ccdSWANG Cong 306f061b48cSCong Wang static int tcf_nat_search(struct net *net, struct tc_action **a, u32 index) 307ddf97ccdSWANG Cong { 308ddf97ccdSWANG Cong struct tc_action_net *tn = net_generic(net, nat_net_id); 309ddf97ccdSWANG Cong 31065a206c0SChris Mi return tcf_idr_search(tn, a, index); 311ddf97ccdSWANG Cong } 312ddf97ccdSWANG Cong 313b4219952SHerbert Xu static struct tc_action_ops act_nat_ops = { 314b4219952SHerbert Xu .kind = "nat", 315eddd2cf1SEli Cohen .id = TCA_ID_NAT, 316b4219952SHerbert Xu .owner = THIS_MODULE, 3170390514fSJamal Hadi Salim .act = tcf_nat_act, 318b4219952SHerbert Xu .dump = tcf_nat_dump, 319b4219952SHerbert Xu .init = tcf_nat_init, 320ddf97ccdSWANG Cong .walk = tcf_nat_walker, 321ddf97ccdSWANG Cong .lookup = tcf_nat_search, 322a85a970aSWANG Cong .size = sizeof(struct tcf_nat), 323ddf97ccdSWANG Cong }; 324ddf97ccdSWANG Cong 325ddf97ccdSWANG Cong static __net_init int nat_init_net(struct net *net) 326ddf97ccdSWANG Cong { 327ddf97ccdSWANG Cong struct tc_action_net *tn = net_generic(net, nat_net_id); 328ddf97ccdSWANG Cong 329c7e460ceSCong Wang return tc_action_net_init(tn, &act_nat_ops); 330ddf97ccdSWANG Cong } 331ddf97ccdSWANG Cong 332039af9c6SCong Wang static void __net_exit nat_exit_net(struct list_head *net_list) 333ddf97ccdSWANG Cong { 334039af9c6SCong Wang tc_action_net_exit(net_list, nat_net_id); 335ddf97ccdSWANG Cong } 336ddf97ccdSWANG Cong 337ddf97ccdSWANG Cong static struct pernet_operations nat_net_ops = { 338ddf97ccdSWANG Cong .init = nat_init_net, 339039af9c6SCong Wang .exit_batch = nat_exit_net, 340ddf97ccdSWANG Cong .id = &nat_net_id, 341ddf97ccdSWANG Cong .size = sizeof(struct tc_action_net), 342b4219952SHerbert Xu }; 343b4219952SHerbert Xu 344b4219952SHerbert Xu MODULE_DESCRIPTION("Stateless NAT actions"); 345b4219952SHerbert Xu MODULE_LICENSE("GPL"); 346b4219952SHerbert Xu 347b4219952SHerbert Xu static int __init nat_init_module(void) 348b4219952SHerbert Xu { 349ddf97ccdSWANG Cong return tcf_register_action(&act_nat_ops, &nat_net_ops); 350b4219952SHerbert Xu } 351b4219952SHerbert Xu 352b4219952SHerbert Xu static void __exit nat_cleanup_module(void) 353b4219952SHerbert Xu { 354ddf97ccdSWANG Cong tcf_unregister_action(&act_nat_ops, &nat_net_ops); 355b4219952SHerbert Xu } 356b4219952SHerbert Xu 357b4219952SHerbert Xu module_init(nat_init_module); 358b4219952SHerbert Xu module_exit(nat_cleanup_module); 359