xref: /openbmc/linux/net/sched/act_nat.c (revision 9a87ffc99ec8eb8d35eed7c4f816d75f5cc9662e)
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 
tcf_nat_init(struct net * net,struct nlattr * nla,struct nlattr * est,struct tc_action ** a,struct tcf_proto * tp,u32 flags,struct netlink_ext_ack * extack)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 
tcf_nat_act(struct sk_buff * skb,const struct tc_action * a,struct tcf_result * res)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 
tcf_nat_dump(struct sk_buff * skb,struct tc_action * a,int bind,int ref)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 
tcf_nat_cleanup(struct tc_action * a)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 
nat_init_net(struct net * net)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 
nat_exit_net(struct list_head * net_list)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 
nat_init_module(void)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 
nat_cleanup_module(void)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