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: &pkt, skb, state); |
29 | nft_set_pktinfo_ipv4(pkt: &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: &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(net: state->net, sk: state->sk, skb, addr_type: RTN_UNSPEC); |
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: &pkt, skb, state); |
77 | nft_set_pktinfo_ipv6(pkt: &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: &pkt, priv); |
89 | if (ret == NF_ACCEPT && |
90 | (memcmp(p: &ipv6_hdr(skb)->saddr, q: &saddr, size: sizeof(saddr)) || |
91 | memcmp(p: &ipv6_hdr(skb)->daddr, q: &daddr, size: 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(net: state->net, sk: state->sk, skb); |
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: &pkt, skb, state); |
128 | break; |
129 | } |
130 | |
131 | return nft_do_chain(pkt: &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 | } |
170 | |