1// SPDX-License-Identifier: GPL-2.0
2
3#include <linux/skbuff.h>
4#include <linux/netfilter.h>
5#include <linux/netfilter_ipv4.h>
6#include <linux/netfilter_ipv6.h>
7#include <linux/netfilter/nfnetlink.h>
8#include <linux/netfilter/nf_tables.h>
9#include <net/netfilter/nf_tables.h>
10#include <net/netfilter/nf_tables_ipv4.h>
11#include <net/netfilter/nf_tables_ipv6.h>
12#include <net/route.h>
13#include <net/ip.h>
14
15#ifdef CONFIG_NF_TABLES_IPV4
16static unsigned int nf_route_table_hook4(void *priv,
17					 struct sk_buff *skb,
18					 const struct nf_hook_state *state)
19{
20	const struct iphdr *iph;
21	struct nft_pktinfo pkt;
22	__be32 saddr, daddr;
23	unsigned int ret;
24	u32 mark;
25	int err;
26	u8 tos;
27
28	nft_set_pktinfo(&pkt, skb, state);
29	nft_set_pktinfo_ipv4(&pkt);
30
31	mark = skb->mark;
32	iph = ip_hdr(skb);
33	saddr = iph->saddr;
34	daddr = iph->daddr;
35	tos = iph->tos;
36
37	ret = nft_do_chain(&pkt, priv);
38	if (ret == NF_ACCEPT) {
39		iph = ip_hdr(skb);
40
41		if (iph->saddr != saddr ||
42		    iph->daddr != daddr ||
43		    skb->mark != mark ||
44		    iph->tos != tos) {
45			err = ip_route_me_harder(state->net, state->sk, skb, RTN_UNSPEC);
46			if (err < 0)
47				ret = NF_DROP_ERR(err);
48		}
49	}
50	return ret;
51}
52
53static const struct nft_chain_type nft_chain_route_ipv4 = {
54	.name		= "route",
55	.type		= NFT_CHAIN_T_ROUTE,
56	.family		= NFPROTO_IPV4,
57	.hook_mask	= (1 << NF_INET_LOCAL_OUT),
58	.hooks		= {
59		[NF_INET_LOCAL_OUT]	= nf_route_table_hook4,
60	},
61};
62#endif
63
64#ifdef CONFIG_NF_TABLES_IPV6
65static unsigned int nf_route_table_hook6(void *priv,
66					 struct sk_buff *skb,
67					 const struct nf_hook_state *state)
68{
69	struct in6_addr saddr, daddr;
70	struct nft_pktinfo pkt;
71	u32 mark, flowlabel;
72	unsigned int ret;
73	u8 hop_limit;
74	int err;
75
76	nft_set_pktinfo(&pkt, skb, state);
77	nft_set_pktinfo_ipv6(&pkt);
78
79	/* save source/dest address, mark, hoplimit, flowlabel, priority */
80	memcpy(&saddr, &ipv6_hdr(skb)->saddr, sizeof(saddr));
81	memcpy(&daddr, &ipv6_hdr(skb)->daddr, sizeof(daddr));
82	mark = skb->mark;
83	hop_limit = ipv6_hdr(skb)->hop_limit;
84
85	/* flowlabel and prio (includes version, which shouldn't change either)*/
86	flowlabel = *((u32 *)ipv6_hdr(skb));
87
88	ret = nft_do_chain(&pkt, priv);
89	if (ret == NF_ACCEPT &&
90	    (memcmp(&ipv6_hdr(skb)->saddr, &saddr, sizeof(saddr)) ||
91	     memcmp(&ipv6_hdr(skb)->daddr, &daddr, sizeof(daddr)) ||
92	     skb->mark != mark ||
93	     ipv6_hdr(skb)->hop_limit != hop_limit ||
94	     flowlabel != *((u32 *)ipv6_hdr(skb)))) {
95		err = nf_ip6_route_me_harder(state->net, state->sk, skb);
96		if (err < 0)
97			ret = NF_DROP_ERR(err);
98	}
99
100	return ret;
101}
102
103static const struct nft_chain_type nft_chain_route_ipv6 = {
104	.name		= "route",
105	.type		= NFT_CHAIN_T_ROUTE,
106	.family		= NFPROTO_IPV6,
107	.hook_mask	= (1 << NF_INET_LOCAL_OUT),
108	.hooks		= {
109		[NF_INET_LOCAL_OUT]	= nf_route_table_hook6,
110	},
111};
112#endif
113
114#ifdef CONFIG_NF_TABLES_INET
115static unsigned int nf_route_table_inet(void *priv,
116					struct sk_buff *skb,
117					const struct nf_hook_state *state)
118{
119	struct nft_pktinfo pkt;
120
121	switch (state->pf) {
122	case NFPROTO_IPV4:
123		return nf_route_table_hook4(priv, skb, state);
124	case NFPROTO_IPV6:
125		return nf_route_table_hook6(priv, skb, state);
126	default:
127		nft_set_pktinfo(&pkt, skb, state);
128		break;
129	}
130
131	return nft_do_chain(&pkt, priv);
132}
133
134static const struct nft_chain_type nft_chain_route_inet = {
135	.name		= "route",
136	.type		= NFT_CHAIN_T_ROUTE,
137	.family		= NFPROTO_INET,
138	.hook_mask	= (1 << NF_INET_LOCAL_OUT),
139	.hooks		= {
140		[NF_INET_LOCAL_OUT]	= nf_route_table_inet,
141	},
142};
143#endif
144
145void __init nft_chain_route_init(void)
146{
147#ifdef CONFIG_NF_TABLES_IPV6
148	nft_register_chain_type(&nft_chain_route_ipv6);
149#endif
150#ifdef CONFIG_NF_TABLES_IPV4
151	nft_register_chain_type(&nft_chain_route_ipv4);
152#endif
153#ifdef CONFIG_NF_TABLES_INET
154	nft_register_chain_type(&nft_chain_route_inet);
155#endif
156}
157
158void __exit nft_chain_route_fini(void)
159{
160#ifdef CONFIG_NF_TABLES_IPV6
161	nft_unregister_chain_type(&nft_chain_route_ipv6);
162#endif
163#ifdef CONFIG_NF_TABLES_IPV4
164	nft_unregister_chain_type(&nft_chain_route_ipv4);
165#endif
166#ifdef CONFIG_NF_TABLES_INET
167	nft_unregister_chain_type(&nft_chain_route_inet);
168#endif
169}
170