]>
Commit | Line | Data |
---|---|---|
d2912cb1 | 1 | // SPDX-License-Identifier: GPL-2.0-only |
6c472602 | 2 | /* |
6c472602 FW |
3 | * |
4 | * Generic part shared by ipv4 and ipv6 backends. | |
5 | */ | |
6 | ||
7 | #include <linux/kernel.h> | |
8 | #include <linux/init.h> | |
9 | #include <linux/module.h> | |
10 | #include <linux/netlink.h> | |
11 | #include <linux/netfilter.h> | |
12 | #include <linux/netfilter/nf_tables.h> | |
13 | #include <net/netfilter/nf_tables_core.h> | |
14 | #include <net/netfilter/nf_tables.h> | |
15 | #include <linux/in.h> | |
16 | #include <net/xfrm.h> | |
17 | ||
18 | static const struct nla_policy nft_xfrm_policy[NFTA_XFRM_MAX + 1] = { | |
19 | [NFTA_XFRM_KEY] = { .type = NLA_U32 }, | |
20 | [NFTA_XFRM_DIR] = { .type = NLA_U8 }, | |
21 | [NFTA_XFRM_SPNUM] = { .type = NLA_U32 }, | |
22 | [NFTA_XFRM_DREG] = { .type = NLA_U32 }, | |
23 | }; | |
24 | ||
25 | struct nft_xfrm { | |
26 | enum nft_xfrm_keys key:8; | |
345023b0 | 27 | u8 dreg; |
6c472602 FW |
28 | u8 dir; |
29 | u8 spnum; | |
48f19103 | 30 | u8 len; |
6c472602 FW |
31 | }; |
32 | ||
33 | static int nft_xfrm_get_init(const struct nft_ctx *ctx, | |
34 | const struct nft_expr *expr, | |
35 | const struct nlattr * const tb[]) | |
36 | { | |
37 | struct nft_xfrm *priv = nft_expr_priv(expr); | |
38 | unsigned int len = 0; | |
39 | u32 spnum = 0; | |
40 | u8 dir; | |
41 | ||
42 | if (!tb[NFTA_XFRM_KEY] || !tb[NFTA_XFRM_DIR] || !tb[NFTA_XFRM_DREG]) | |
43 | return -EINVAL; | |
44 | ||
45 | switch (ctx->family) { | |
46 | case NFPROTO_IPV4: | |
47 | case NFPROTO_IPV6: | |
48 | case NFPROTO_INET: | |
49 | break; | |
50 | default: | |
51 | return -EOPNOTSUPP; | |
52 | } | |
53 | ||
d86473bf | 54 | priv->key = ntohl(nla_get_be32(tb[NFTA_XFRM_KEY])); |
6c472602 FW |
55 | switch (priv->key) { |
56 | case NFT_XFRM_KEY_REQID: | |
57 | case NFT_XFRM_KEY_SPI: | |
58 | len = sizeof(u32); | |
59 | break; | |
60 | case NFT_XFRM_KEY_DADDR_IP4: | |
61 | case NFT_XFRM_KEY_SADDR_IP4: | |
62 | len = sizeof(struct in_addr); | |
63 | break; | |
64 | case NFT_XFRM_KEY_DADDR_IP6: | |
65 | case NFT_XFRM_KEY_SADDR_IP6: | |
66 | len = sizeof(struct in6_addr); | |
67 | break; | |
68 | default: | |
69 | return -EINVAL; | |
70 | } | |
71 | ||
72 | dir = nla_get_u8(tb[NFTA_XFRM_DIR]); | |
73 | switch (dir) { | |
74 | case XFRM_POLICY_IN: | |
75 | case XFRM_POLICY_OUT: | |
76 | priv->dir = dir; | |
77 | break; | |
78 | default: | |
79 | return -EINVAL; | |
80 | } | |
81 | ||
82 | if (tb[NFTA_XFRM_SPNUM]) | |
83 | spnum = ntohl(nla_get_be32(tb[NFTA_XFRM_SPNUM])); | |
84 | ||
85 | if (spnum >= XFRM_MAX_DEPTH) | |
86 | return -ERANGE; | |
87 | ||
88 | priv->spnum = spnum; | |
89 | ||
48f19103 | 90 | priv->len = len; |
345023b0 PNA |
91 | return nft_parse_register_store(ctx, tb[NFTA_XFRM_DREG], &priv->dreg, |
92 | NULL, NFT_DATA_VALUE, len); | |
6c472602 FW |
93 | } |
94 | ||
95 | /* Return true if key asks for daddr/saddr and current | |
96 | * state does have a valid address (BEET, TUNNEL). | |
97 | */ | |
98 | static bool xfrm_state_addr_ok(enum nft_xfrm_keys k, u8 family, u8 mode) | |
99 | { | |
100 | switch (k) { | |
101 | case NFT_XFRM_KEY_DADDR_IP4: | |
102 | case NFT_XFRM_KEY_SADDR_IP4: | |
103 | if (family == NFPROTO_IPV4) | |
104 | break; | |
105 | return false; | |
106 | case NFT_XFRM_KEY_DADDR_IP6: | |
107 | case NFT_XFRM_KEY_SADDR_IP6: | |
108 | if (family == NFPROTO_IPV6) | |
109 | break; | |
110 | return false; | |
111 | default: | |
112 | return true; | |
113 | } | |
114 | ||
115 | return mode == XFRM_MODE_BEET || mode == XFRM_MODE_TUNNEL; | |
116 | } | |
117 | ||
118 | static void nft_xfrm_state_get_key(const struct nft_xfrm *priv, | |
119 | struct nft_regs *regs, | |
1321a6af | 120 | const struct xfrm_state *state) |
6c472602 FW |
121 | { |
122 | u32 *dest = ®s->data[priv->dreg]; | |
123 | ||
1321a6af FW |
124 | if (!xfrm_state_addr_ok(priv->key, |
125 | state->props.family, | |
126 | state->props.mode)) { | |
6c472602 FW |
127 | regs->verdict.code = NFT_BREAK; |
128 | return; | |
129 | } | |
130 | ||
131 | switch (priv->key) { | |
132 | case NFT_XFRM_KEY_UNSPEC: | |
133 | case __NFT_XFRM_KEY_MAX: | |
134 | WARN_ON_ONCE(1); | |
135 | break; | |
136 | case NFT_XFRM_KEY_DADDR_IP4: | |
d86473bf | 137 | *dest = (__force __u32)state->id.daddr.a4; |
6c472602 FW |
138 | return; |
139 | case NFT_XFRM_KEY_DADDR_IP6: | |
140 | memcpy(dest, &state->id.daddr.in6, sizeof(struct in6_addr)); | |
141 | return; | |
142 | case NFT_XFRM_KEY_SADDR_IP4: | |
d86473bf | 143 | *dest = (__force __u32)state->props.saddr.a4; |
6c472602 FW |
144 | return; |
145 | case NFT_XFRM_KEY_SADDR_IP6: | |
146 | memcpy(dest, &state->props.saddr.in6, sizeof(struct in6_addr)); | |
147 | return; | |
148 | case NFT_XFRM_KEY_REQID: | |
149 | *dest = state->props.reqid; | |
150 | return; | |
151 | case NFT_XFRM_KEY_SPI: | |
d86473bf | 152 | *dest = (__force __u32)state->id.spi; |
6c472602 FW |
153 | return; |
154 | } | |
155 | ||
156 | regs->verdict.code = NFT_BREAK; | |
157 | } | |
158 | ||
159 | static void nft_xfrm_get_eval_in(const struct nft_xfrm *priv, | |
160 | struct nft_regs *regs, | |
161 | const struct nft_pktinfo *pkt) | |
162 | { | |
2294be0f | 163 | const struct sec_path *sp = skb_sec_path(pkt->skb); |
6c472602 FW |
164 | const struct xfrm_state *state; |
165 | ||
166 | if (sp == NULL || sp->len <= priv->spnum) { | |
167 | regs->verdict.code = NFT_BREAK; | |
168 | return; | |
169 | } | |
170 | ||
171 | state = sp->xvec[priv->spnum]; | |
1321a6af | 172 | nft_xfrm_state_get_key(priv, regs, state); |
6c472602 FW |
173 | } |
174 | ||
175 | static void nft_xfrm_get_eval_out(const struct nft_xfrm *priv, | |
176 | struct nft_regs *regs, | |
177 | const struct nft_pktinfo *pkt) | |
178 | { | |
179 | const struct dst_entry *dst = skb_dst(pkt->skb); | |
180 | int i; | |
181 | ||
182 | for (i = 0; dst && dst->xfrm; | |
183 | dst = ((const struct xfrm_dst *)dst)->child, i++) { | |
184 | if (i < priv->spnum) | |
185 | continue; | |
186 | ||
1321a6af | 187 | nft_xfrm_state_get_key(priv, regs, dst->xfrm); |
6c472602 FW |
188 | return; |
189 | } | |
190 | ||
191 | regs->verdict.code = NFT_BREAK; | |
192 | } | |
193 | ||
194 | static void nft_xfrm_get_eval(const struct nft_expr *expr, | |
195 | struct nft_regs *regs, | |
196 | const struct nft_pktinfo *pkt) | |
197 | { | |
198 | const struct nft_xfrm *priv = nft_expr_priv(expr); | |
199 | ||
200 | switch (priv->dir) { | |
201 | case XFRM_POLICY_IN: | |
202 | nft_xfrm_get_eval_in(priv, regs, pkt); | |
203 | break; | |
204 | case XFRM_POLICY_OUT: | |
205 | nft_xfrm_get_eval_out(priv, regs, pkt); | |
206 | break; | |
207 | default: | |
208 | WARN_ON_ONCE(1); | |
209 | regs->verdict.code = NFT_BREAK; | |
210 | break; | |
211 | } | |
212 | } | |
213 | ||
214 | static int nft_xfrm_get_dump(struct sk_buff *skb, | |
7d34aa3e | 215 | const struct nft_expr *expr, bool reset) |
6c472602 FW |
216 | { |
217 | const struct nft_xfrm *priv = nft_expr_priv(expr); | |
218 | ||
219 | if (nft_dump_register(skb, NFTA_XFRM_DREG, priv->dreg)) | |
220 | return -1; | |
221 | ||
222 | if (nla_put_be32(skb, NFTA_XFRM_KEY, htonl(priv->key))) | |
223 | return -1; | |
224 | if (nla_put_u8(skb, NFTA_XFRM_DIR, priv->dir)) | |
225 | return -1; | |
226 | if (nla_put_be32(skb, NFTA_XFRM_SPNUM, htonl(priv->spnum))) | |
227 | return -1; | |
228 | ||
229 | return 0; | |
230 | } | |
231 | ||
232 | static int nft_xfrm_validate(const struct nft_ctx *ctx, const struct nft_expr *expr, | |
233 | const struct nft_data **data) | |
234 | { | |
235 | const struct nft_xfrm *priv = nft_expr_priv(expr); | |
236 | unsigned int hooks; | |
237 | ||
238 | switch (priv->dir) { | |
239 | case XFRM_POLICY_IN: | |
240 | hooks = (1 << NF_INET_FORWARD) | | |
241 | (1 << NF_INET_LOCAL_IN) | | |
242 | (1 << NF_INET_PRE_ROUTING); | |
243 | break; | |
244 | case XFRM_POLICY_OUT: | |
245 | hooks = (1 << NF_INET_FORWARD) | | |
246 | (1 << NF_INET_LOCAL_OUT) | | |
247 | (1 << NF_INET_POST_ROUTING); | |
248 | break; | |
249 | default: | |
250 | WARN_ON_ONCE(1); | |
251 | return -EINVAL; | |
252 | } | |
253 | ||
254 | return nft_chain_validate_hooks(ctx->chain, hooks); | |
255 | } | |
256 | ||
48f19103 PNA |
257 | static bool nft_xfrm_reduce(struct nft_regs_track *track, |
258 | const struct nft_expr *expr) | |
259 | { | |
260 | const struct nft_xfrm *priv = nft_expr_priv(expr); | |
261 | const struct nft_xfrm *xfrm; | |
262 | ||
263 | if (!nft_reg_track_cmp(track, expr, priv->dreg)) { | |
264 | nft_reg_track_update(track, expr, priv->dreg, priv->len); | |
265 | return false; | |
266 | } | |
267 | ||
268 | xfrm = nft_expr_priv(track->regs[priv->dreg].selector); | |
269 | if (priv->key != xfrm->key || | |
270 | priv->dreg != xfrm->dreg || | |
271 | priv->dir != xfrm->dir || | |
272 | priv->spnum != xfrm->spnum) { | |
273 | nft_reg_track_update(track, expr, priv->dreg, priv->len); | |
274 | return false; | |
275 | } | |
276 | ||
277 | if (!track->regs[priv->dreg].bitwise) | |
278 | return true; | |
279 | ||
280 | return nft_expr_reduce_bitwise(track, expr); | |
281 | } | |
6c472602 FW |
282 | |
283 | static struct nft_expr_type nft_xfrm_type; | |
284 | static const struct nft_expr_ops nft_xfrm_get_ops = { | |
285 | .type = &nft_xfrm_type, | |
286 | .size = NFT_EXPR_SIZE(sizeof(struct nft_xfrm)), | |
287 | .eval = nft_xfrm_get_eval, | |
288 | .init = nft_xfrm_get_init, | |
289 | .dump = nft_xfrm_get_dump, | |
290 | .validate = nft_xfrm_validate, | |
48f19103 | 291 | .reduce = nft_xfrm_reduce, |
6c472602 FW |
292 | }; |
293 | ||
294 | static struct nft_expr_type nft_xfrm_type __read_mostly = { | |
295 | .name = "xfrm", | |
296 | .ops = &nft_xfrm_get_ops, | |
297 | .policy = nft_xfrm_policy, | |
298 | .maxattr = NFTA_XFRM_MAX, | |
299 | .owner = THIS_MODULE, | |
300 | }; | |
301 | ||
302 | static int __init nft_xfrm_module_init(void) | |
303 | { | |
304 | return nft_register_expr(&nft_xfrm_type); | |
305 | } | |
306 | ||
307 | static void __exit nft_xfrm_module_exit(void) | |
308 | { | |
309 | nft_unregister_expr(&nft_xfrm_type); | |
310 | } | |
311 | ||
312 | module_init(nft_xfrm_module_init); | |
313 | module_exit(nft_xfrm_module_exit); | |
314 | ||
315 | MODULE_LICENSE("GPL"); | |
316 | MODULE_DESCRIPTION("nf_tables: xfrm/IPSec matching"); | |
317 | MODULE_AUTHOR("Florian Westphal <fw@strlen.de>"); | |
318 | MODULE_AUTHOR("Máté Eckl <ecklm94@gmail.com>"); | |
319 | MODULE_ALIAS_NFT_EXPR("xfrm"); |