12874c5fdSThomas 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> 27871cf386SPedro Tammela #include <net/tc_wrapper.h> 28b4219952SHerbert Xu 29a85a970aSWANG Cong static struct tc_action_ops act_nat_ops; 30ddf97ccdSWANG Cong 3153b2bf3fSPatrick McHardy static const struct nla_policy nat_policy[TCA_NAT_MAX + 1] = { 3253b2bf3fSPatrick McHardy [TCA_NAT_PARMS] = { .len = sizeof(struct tc_nat) }, 3353b2bf3fSPatrick McHardy }; 3453b2bf3fSPatrick McHardy 35c1b52739SBenjamin LaHaise static int tcf_nat_init(struct net *net, struct nlattr *nla, struct nlattr *est, 36695176bfSCong Wang struct tc_action **a, struct tcf_proto *tp, 37abbb0d33SVlad Buslov u32 flags, struct netlink_ext_ack *extack) 38b4219952SHerbert Xu { 39acd0a7abSZhengchao Shao struct tc_action_net *tn = net_generic(net, act_nat_ops.net_id); 40695176bfSCong Wang bool bind = flags & TCA_ACT_FLAGS_BIND; 41*7d12057bSPedro Tammela struct tcf_nat_parms *nparm, *oparm; 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; 477be8ef2cSDmytro Linkin u32 index; 48b4219952SHerbert Xu 49cee63723SPatrick McHardy if (nla == NULL) 50b4219952SHerbert Xu return -EINVAL; 51b4219952SHerbert Xu 528cb08174SJohannes Berg err = nla_parse_nested_deprecated(tb, TCA_NAT_MAX, nla, nat_policy, 538cb08174SJohannes Berg NULL); 54cee63723SPatrick McHardy if (err < 0) 55cee63723SPatrick McHardy return err; 56cee63723SPatrick McHardy 5753b2bf3fSPatrick McHardy if (tb[TCA_NAT_PARMS] == NULL) 58b4219952SHerbert Xu return -EINVAL; 597ba699c6SPatrick McHardy parm = nla_data(tb[TCA_NAT_PARMS]); 607be8ef2cSDmytro Linkin index = parm->index; 617be8ef2cSDmytro Linkin err = tcf_idr_check_alloc(tn, &index, a, bind); 620190c1d4SVlad Buslov if (!err) { 63*7d12057bSPedro Tammela ret = tcf_idr_create_from_flags(tn, index, est, a, &act_nat_ops, 64*7d12057bSPedro Tammela bind, flags); 650190c1d4SVlad Buslov if (ret) { 667be8ef2cSDmytro Linkin tcf_idr_cleanup(tn, index); 6786062033SWANG Cong return ret; 680190c1d4SVlad Buslov } 69b4219952SHerbert Xu ret = ACT_P_CREATED; 700190c1d4SVlad Buslov } else if (err > 0) { 711a29321eSJamal Hadi Salim if (bind) 721a29321eSJamal Hadi Salim return 0; 73695176bfSCong Wang if (!(flags & TCA_ACT_FLAGS_REPLACE)) { 7465a206c0SChris Mi tcf_idr_release(*a, bind); 75b4219952SHerbert Xu return -EEXIST; 76b4219952SHerbert Xu } 770190c1d4SVlad Buslov } else { 780190c1d4SVlad Buslov return err; 794e8ddd7fSVlad Buslov } 801e45d043SDavide Caratti err = tcf_action_check_ctrlact(parm->action, tp, &goto_ch, extack); 811e45d043SDavide Caratti if (err < 0) 821e45d043SDavide Caratti goto release_idr; 83*7d12057bSPedro Tammela 84*7d12057bSPedro Tammela nparm = kzalloc(sizeof(*nparm), GFP_KERNEL); 85*7d12057bSPedro Tammela if (!nparm) { 86*7d12057bSPedro Tammela err = -ENOMEM; 87*7d12057bSPedro Tammela goto release_idr; 88*7d12057bSPedro Tammela } 89*7d12057bSPedro Tammela 90*7d12057bSPedro Tammela nparm->old_addr = parm->old_addr; 91*7d12057bSPedro Tammela nparm->new_addr = parm->new_addr; 92*7d12057bSPedro Tammela nparm->mask = parm->mask; 93*7d12057bSPedro Tammela nparm->flags = parm->flags; 94*7d12057bSPedro Tammela 95a85a970aSWANG Cong p = to_tcf_nat(*a); 96b4219952SHerbert Xu 97b4219952SHerbert Xu spin_lock_bh(&p->tcf_lock); 981e45d043SDavide Caratti goto_ch = tcf_action_set_ctrlact(*a, parm->action, goto_ch); 99*7d12057bSPedro Tammela oparm = rcu_replace_pointer(p->parms, nparm, lockdep_is_held(&p->tcf_lock)); 100b4219952SHerbert Xu spin_unlock_bh(&p->tcf_lock); 101*7d12057bSPedro Tammela 1021e45d043SDavide Caratti if (goto_ch) 1031e45d043SDavide Caratti tcf_chain_put_by_act(goto_ch); 104b4219952SHerbert Xu 105*7d12057bSPedro Tammela if (oparm) 106*7d12057bSPedro Tammela kfree_rcu(oparm, rcu); 107*7d12057bSPedro Tammela 108b4219952SHerbert Xu return ret; 1091e45d043SDavide Caratti release_idr: 1101e45d043SDavide Caratti tcf_idr_release(*a, bind); 1111e45d043SDavide Caratti return err; 112b4219952SHerbert Xu } 113b4219952SHerbert Xu 114871cf386SPedro Tammela TC_INDIRECT_SCOPE int tcf_nat_act(struct sk_buff *skb, 115871cf386SPedro Tammela const struct tc_action *a, 116b4219952SHerbert Xu struct tcf_result *res) 117b4219952SHerbert Xu { 118a85a970aSWANG Cong struct tcf_nat *p = to_tcf_nat(a); 119*7d12057bSPedro Tammela struct tcf_nat_parms *parms; 120b4219952SHerbert Xu struct iphdr *iph; 121b4219952SHerbert Xu __be32 old_addr; 122b4219952SHerbert Xu __be32 new_addr; 123b4219952SHerbert Xu __be32 mask; 124b4219952SHerbert Xu __be32 addr; 125b4219952SHerbert Xu int egress; 126b4219952SHerbert Xu int action; 127b4219952SHerbert Xu int ihl; 12836d12690SChangli Gao int noff; 129b4219952SHerbert Xu 1309c4a4e48SJamal Hadi Salim tcf_lastuse_update(&p->tcf_tm); 131*7d12057bSPedro Tammela tcf_action_update_bstats(&p->common, skb); 132b4219952SHerbert Xu 133*7d12057bSPedro Tammela action = READ_ONCE(p->tcf_action); 134b4219952SHerbert Xu 135*7d12057bSPedro Tammela parms = rcu_dereference_bh(p->parms); 136*7d12057bSPedro Tammela old_addr = parms->old_addr; 137*7d12057bSPedro Tammela new_addr = parms->new_addr; 138*7d12057bSPedro Tammela mask = parms->mask; 139*7d12057bSPedro Tammela egress = parms->flags & TCA_NAT_FLAG_EGRESS; 140b4219952SHerbert Xu 141b4219952SHerbert Xu if (unlikely(action == TC_ACT_SHOT)) 142b4219952SHerbert Xu goto drop; 143b4219952SHerbert Xu 14436d12690SChangli Gao noff = skb_network_offset(skb); 14536d12690SChangli Gao if (!pskb_may_pull(skb, sizeof(*iph) + noff)) 146b4219952SHerbert Xu goto drop; 147b4219952SHerbert Xu 148b4219952SHerbert Xu iph = ip_hdr(skb); 149b4219952SHerbert Xu 150b4219952SHerbert Xu if (egress) 151b4219952SHerbert Xu addr = iph->saddr; 152b4219952SHerbert Xu else 153b4219952SHerbert Xu addr = iph->daddr; 154b4219952SHerbert Xu 155b4219952SHerbert Xu if (!((old_addr ^ addr) & mask)) { 1563697649fSDaniel Borkmann if (skb_try_make_writable(skb, sizeof(*iph) + noff)) 157b4219952SHerbert Xu goto drop; 158b4219952SHerbert Xu 159b4219952SHerbert Xu new_addr &= mask; 160b4219952SHerbert Xu new_addr |= addr & ~mask; 161b4219952SHerbert Xu 162b4219952SHerbert Xu /* Rewrite IP header */ 163b4219952SHerbert Xu iph = ip_hdr(skb); 164b4219952SHerbert Xu if (egress) 165b4219952SHerbert Xu iph->saddr = new_addr; 166b4219952SHerbert Xu else 167b4219952SHerbert Xu iph->daddr = new_addr; 168b4219952SHerbert Xu 169be0ea7d5SPatrick McHardy csum_replace4(&iph->check, addr, new_addr); 17033c29ddeSChangli Gao } else if ((iph->frag_off & htons(IP_OFFSET)) || 17133c29ddeSChangli Gao iph->protocol != IPPROTO_ICMP) { 17233c29ddeSChangli Gao goto out; 173b4219952SHerbert Xu } 174b4219952SHerbert Xu 175b4219952SHerbert Xu ihl = iph->ihl * 4; 176b4219952SHerbert Xu 177b4219952SHerbert Xu /* It would be nice to share code with stateful NAT. */ 178b4219952SHerbert Xu switch (iph->frag_off & htons(IP_OFFSET) ? 0 : iph->protocol) { 179b4219952SHerbert Xu case IPPROTO_TCP: 180b4219952SHerbert Xu { 181b4219952SHerbert Xu struct tcphdr *tcph; 182b4219952SHerbert Xu 18336d12690SChangli Gao if (!pskb_may_pull(skb, ihl + sizeof(*tcph) + noff) || 1843697649fSDaniel Borkmann skb_try_make_writable(skb, ihl + sizeof(*tcph) + noff)) 185b4219952SHerbert Xu goto drop; 186b4219952SHerbert Xu 187b4219952SHerbert Xu tcph = (void *)(skb_network_header(skb) + ihl); 1884b048d6dSTom Herbert inet_proto_csum_replace4(&tcph->check, skb, addr, new_addr, 1894b048d6dSTom Herbert true); 190b4219952SHerbert Xu break; 191b4219952SHerbert Xu } 192b4219952SHerbert Xu case IPPROTO_UDP: 193b4219952SHerbert Xu { 194b4219952SHerbert Xu struct udphdr *udph; 195b4219952SHerbert Xu 19636d12690SChangli Gao if (!pskb_may_pull(skb, ihl + sizeof(*udph) + noff) || 1973697649fSDaniel Borkmann skb_try_make_writable(skb, ihl + sizeof(*udph) + noff)) 198b4219952SHerbert Xu goto drop; 199b4219952SHerbert Xu 200b4219952SHerbert Xu udph = (void *)(skb_network_header(skb) + ihl); 201b4219952SHerbert Xu if (udph->check || skb->ip_summed == CHECKSUM_PARTIAL) { 202be0ea7d5SPatrick McHardy inet_proto_csum_replace4(&udph->check, skb, addr, 2034b048d6dSTom Herbert new_addr, true); 204b4219952SHerbert Xu if (!udph->check) 205b4219952SHerbert Xu udph->check = CSUM_MANGLED_0; 206b4219952SHerbert Xu } 207b4219952SHerbert Xu break; 208b4219952SHerbert Xu } 209b4219952SHerbert Xu case IPPROTO_ICMP: 210b4219952SHerbert Xu { 211b4219952SHerbert Xu struct icmphdr *icmph; 212b4219952SHerbert Xu 21336d12690SChangli Gao if (!pskb_may_pull(skb, ihl + sizeof(*icmph) + noff)) 214b4219952SHerbert Xu goto drop; 215b4219952SHerbert Xu 216b4219952SHerbert Xu icmph = (void *)(skb_network_header(skb) + ihl); 217b4219952SHerbert Xu 21854074f1dSMatteo Croce if (!icmp_is_err(icmph->type)) 219b4219952SHerbert Xu break; 220b4219952SHerbert Xu 22136d12690SChangli Gao if (!pskb_may_pull(skb, ihl + sizeof(*icmph) + sizeof(*iph) + 22236d12690SChangli Gao noff)) 22370c2efa5SChangli Gao goto drop; 22470c2efa5SChangli Gao 225072d79a3SChangli Gao icmph = (void *)(skb_network_header(skb) + ihl); 226b4219952SHerbert Xu iph = (void *)(icmph + 1); 227b4219952SHerbert Xu if (egress) 228b4219952SHerbert Xu addr = iph->daddr; 229b4219952SHerbert Xu else 230b4219952SHerbert Xu addr = iph->saddr; 231b4219952SHerbert Xu 232b4219952SHerbert Xu if ((old_addr ^ addr) & mask) 233b4219952SHerbert Xu break; 234b4219952SHerbert Xu 2353697649fSDaniel Borkmann if (skb_try_make_writable(skb, ihl + sizeof(*icmph) + 2363697649fSDaniel Borkmann sizeof(*iph) + noff)) 237b4219952SHerbert Xu goto drop; 238b4219952SHerbert Xu 239b4219952SHerbert Xu icmph = (void *)(skb_network_header(skb) + ihl); 240b4219952SHerbert Xu iph = (void *)(icmph + 1); 241b4219952SHerbert Xu 242b4219952SHerbert Xu new_addr &= mask; 243b4219952SHerbert Xu new_addr |= addr & ~mask; 244b4219952SHerbert Xu 245b4219952SHerbert Xu /* XXX Fix up the inner checksums. */ 246b4219952SHerbert Xu if (egress) 247b4219952SHerbert Xu iph->daddr = new_addr; 248b4219952SHerbert Xu else 249b4219952SHerbert Xu iph->saddr = new_addr; 250b4219952SHerbert Xu 251be0ea7d5SPatrick McHardy inet_proto_csum_replace4(&icmph->checksum, skb, addr, new_addr, 2524b048d6dSTom Herbert false); 253b4219952SHerbert Xu break; 254b4219952SHerbert Xu } 255b4219952SHerbert Xu default: 256b4219952SHerbert Xu break; 257b4219952SHerbert Xu } 258b4219952SHerbert Xu 25933c29ddeSChangli Gao out: 260b4219952SHerbert Xu return action; 261b4219952SHerbert Xu 262b4219952SHerbert Xu drop: 263*7d12057bSPedro Tammela tcf_action_inc_drop_qstats(&p->common); 264b4219952SHerbert Xu return TC_ACT_SHOT; 265b4219952SHerbert Xu } 266b4219952SHerbert Xu 267b4219952SHerbert Xu static int tcf_nat_dump(struct sk_buff *skb, struct tc_action *a, 268b4219952SHerbert Xu int bind, int ref) 269b4219952SHerbert Xu { 270b4219952SHerbert Xu unsigned char *b = skb_tail_pointer(skb); 271a85a970aSWANG Cong struct tcf_nat *p = to_tcf_nat(a); 2721c40be12SEric Dumazet struct tc_nat opt = { 2731c40be12SEric Dumazet .index = p->tcf_index, 274036bb443SVlad Buslov .refcnt = refcount_read(&p->tcf_refcnt) - ref, 275036bb443SVlad Buslov .bindcnt = atomic_read(&p->tcf_bindcnt) - bind, 2761c40be12SEric Dumazet }; 277*7d12057bSPedro Tammela struct tcf_nat_parms *parms; 278b4219952SHerbert Xu struct tcf_t t; 279b4219952SHerbert Xu 280f20a4d01SVlad Buslov spin_lock_bh(&p->tcf_lock); 281*7d12057bSPedro Tammela 282f20a4d01SVlad Buslov opt.action = p->tcf_action; 283f20a4d01SVlad Buslov 284*7d12057bSPedro Tammela parms = rcu_dereference_protected(p->parms, lockdep_is_held(&p->tcf_lock)); 285*7d12057bSPedro Tammela 286*7d12057bSPedro Tammela opt.old_addr = parms->old_addr; 287*7d12057bSPedro Tammela opt.new_addr = parms->new_addr; 288*7d12057bSPedro Tammela opt.mask = parms->mask; 289*7d12057bSPedro Tammela opt.flags = parms->flags; 290*7d12057bSPedro Tammela 2911b34ec43SDavid S. Miller if (nla_put(skb, TCA_NAT_PARMS, sizeof(opt), &opt)) 2921b34ec43SDavid S. Miller goto nla_put_failure; 29348d8ee16SJamal Hadi Salim 29448d8ee16SJamal Hadi Salim tcf_tm_dump(&t, &p->tcf_tm); 2959854518eSNicolas Dichtel if (nla_put_64bit(skb, TCA_NAT_TM, sizeof(t), &t, TCA_NAT_PAD)) 2961b34ec43SDavid S. Miller goto nla_put_failure; 297f20a4d01SVlad Buslov spin_unlock_bh(&p->tcf_lock); 298b4219952SHerbert Xu 299b4219952SHerbert Xu return skb->len; 300b4219952SHerbert Xu 3017ba699c6SPatrick McHardy nla_put_failure: 302f20a4d01SVlad Buslov spin_unlock_bh(&p->tcf_lock); 303b4219952SHerbert Xu nlmsg_trim(skb, b); 304b4219952SHerbert Xu return -1; 305b4219952SHerbert Xu } 306b4219952SHerbert Xu 307*7d12057bSPedro Tammela static void tcf_nat_cleanup(struct tc_action *a) 308*7d12057bSPedro Tammela { 309*7d12057bSPedro Tammela struct tcf_nat *p = to_tcf_nat(a); 310*7d12057bSPedro Tammela struct tcf_nat_parms *parms; 311*7d12057bSPedro Tammela 312*7d12057bSPedro Tammela parms = rcu_dereference_protected(p->parms, 1); 313*7d12057bSPedro Tammela if (parms) 314*7d12057bSPedro Tammela kfree_rcu(parms, rcu); 315*7d12057bSPedro Tammela } 316*7d12057bSPedro Tammela 317b4219952SHerbert Xu static struct tc_action_ops act_nat_ops = { 318b4219952SHerbert Xu .kind = "nat", 319eddd2cf1SEli Cohen .id = TCA_ID_NAT, 320b4219952SHerbert Xu .owner = THIS_MODULE, 3210390514fSJamal Hadi Salim .act = tcf_nat_act, 322b4219952SHerbert Xu .dump = tcf_nat_dump, 323b4219952SHerbert Xu .init = tcf_nat_init, 324*7d12057bSPedro Tammela .cleanup = tcf_nat_cleanup, 325a85a970aSWANG Cong .size = sizeof(struct tcf_nat), 326ddf97ccdSWANG Cong }; 327ddf97ccdSWANG Cong 328ddf97ccdSWANG Cong static __net_init int nat_init_net(struct net *net) 329ddf97ccdSWANG Cong { 330acd0a7abSZhengchao Shao struct tc_action_net *tn = net_generic(net, act_nat_ops.net_id); 331ddf97ccdSWANG Cong 332981471bdSCong Wang return tc_action_net_init(net, tn, &act_nat_ops); 333ddf97ccdSWANG Cong } 334ddf97ccdSWANG Cong 335039af9c6SCong Wang static void __net_exit nat_exit_net(struct list_head *net_list) 336ddf97ccdSWANG Cong { 337acd0a7abSZhengchao Shao tc_action_net_exit(net_list, act_nat_ops.net_id); 338ddf97ccdSWANG Cong } 339ddf97ccdSWANG Cong 340ddf97ccdSWANG Cong static struct pernet_operations nat_net_ops = { 341ddf97ccdSWANG Cong .init = nat_init_net, 342039af9c6SCong Wang .exit_batch = nat_exit_net, 343acd0a7abSZhengchao Shao .id = &act_nat_ops.net_id, 344ddf97ccdSWANG Cong .size = sizeof(struct tc_action_net), 345b4219952SHerbert Xu }; 346b4219952SHerbert Xu 347b4219952SHerbert Xu MODULE_DESCRIPTION("Stateless NAT actions"); 348b4219952SHerbert Xu MODULE_LICENSE("GPL"); 349b4219952SHerbert Xu 350b4219952SHerbert Xu static int __init nat_init_module(void) 351b4219952SHerbert Xu { 352ddf97ccdSWANG Cong return tcf_register_action(&act_nat_ops, &nat_net_ops); 353b4219952SHerbert Xu } 354b4219952SHerbert Xu 355b4219952SHerbert Xu static void __exit nat_cleanup_module(void) 356b4219952SHerbert Xu { 357ddf97ccdSWANG Cong tcf_unregister_action(&act_nat_ops, &nat_net_ops); 358b4219952SHerbert Xu } 359b4219952SHerbert Xu 360b4219952SHerbert Xu module_init(nat_init_module); 361b4219952SHerbert Xu module_exit(nat_cleanup_module); 362