Commit | Line | Data |
---|---|---|
c1deb065 FW |
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 | |
16 | static 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); | |
f06ad944 | 29 | nft_set_pktinfo_ipv4(&pkt); |
c1deb065 FW |
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) { | |
46d6c5ae | 45 | err = ip_route_me_harder(state->net, state->sk, skb, RTN_UNSPEC); |
c1deb065 FW |
46 | if (err < 0) |
47 | ret = NF_DROP_ERR(err); | |
48 | } | |
49 | } | |
50 | return ret; | |
51 | } | |
52 | ||
53 | static 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 | |
65 | static 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); | |
f06ad944 | 77 | nft_set_pktinfo_ipv6(&pkt); |
c1deb065 FW |
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)))) { | |
46d6c5ae | 95 | err = nf_ip6_route_me_harder(state->net, state->sk, skb); |
c1deb065 FW |
96 | if (err < 0) |
97 | ret = NF_DROP_ERR(err); | |
98 | } | |
99 | ||
100 | return ret; | |
101 | } | |
102 | ||
103 | static 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 | |
115 | static 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 | ||
134 | static 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 | ||
145 | void __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 | ||
158 | void __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 | } |