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