Blame view
net/netfilter/nft_exthdr.c
12.7 KB
d2912cb15 treewide: Replace... |
1 |
// SPDX-License-Identifier: GPL-2.0-only |
96518518c netfilter: add nf... |
2 3 4 |
/* * Copyright (c) 2008 Patrick McHardy <kaber@trash.net> * |
96518518c netfilter: add nf... |
5 6 |
* Development of this code funded by Astaro AG (http://www.astaro.com/) */ |
99d1712bc netfilter: exthdr... |
7 |
#include <asm/unaligned.h> |
96518518c netfilter: add nf... |
8 |
#include <linux/kernel.h> |
96518518c netfilter: add nf... |
9 10 11 |
#include <linux/netlink.h> #include <linux/netfilter.h> #include <linux/netfilter/nf_tables.h> |
d0103158c netfilter: nf_tab... |
12 |
#include <net/netfilter/nf_tables_core.h> |
96518518c netfilter: add nf... |
13 |
#include <net/netfilter/nf_tables.h> |
935b7f643 netfilter: nft_ex... |
14 |
#include <net/tcp.h> |
96518518c netfilter: add nf... |
15 16 17 18 19 |
struct nft_exthdr { u8 type; u8 offset; u8 len; |
935b7f643 netfilter: nft_ex... |
20 |
u8 op; |
96518518c netfilter: add nf... |
21 |
enum nft_registers dreg:8; |
99d1712bc netfilter: exthdr... |
22 |
enum nft_registers sreg:8; |
c078ca3b0 netfilter: nft_ex... |
23 |
u8 flags; |
96518518c netfilter: add nf... |
24 |
}; |
935b7f643 netfilter: nft_ex... |
25 26 27 28 29 30 31 32 33 34 35 36 |
static unsigned int optlen(const u8 *opt, unsigned int offset) { /* Beware zero-length options: make finite progress */ if (opt[offset] <= TCPOPT_NOP || opt[offset + 1] == 0) return 1; else return opt[offset + 1]; } static void nft_exthdr_ipv6_eval(const struct nft_expr *expr, struct nft_regs *regs, const struct nft_pktinfo *pkt) |
96518518c netfilter: add nf... |
37 38 |
{ struct nft_exthdr *priv = nft_expr_priv(expr); |
49499c3e6 netfilter: nf_tab... |
39 |
u32 *dest = ®s->data[priv->dreg]; |
540436c80 netfilter: nft_ex... |
40 |
unsigned int offset = 0; |
96518518c netfilter: add nf... |
41 42 43 |
int err; err = ipv6_find_hdr(pkt->skb, &offset, priv->type, NULL, NULL); |
c078ca3b0 netfilter: nft_ex... |
44 |
if (priv->flags & NFT_EXTHDR_F_PRESENT) { |
b42833667 netfilter: nf_tab... |
45 |
nft_reg_store8(dest, err >= 0); |
c078ca3b0 netfilter: nft_ex... |
46 47 |
return; } else if (err < 0) { |
96518518c netfilter: add nf... |
48 |
goto err; |
c078ca3b0 netfilter: nft_ex... |
49 |
} |
96518518c netfilter: add nf... |
50 |
offset += priv->offset; |
49499c3e6 netfilter: nf_tab... |
51 |
dest[priv->len / NFT_REG32_SIZE] = 0; |
fad136ea0 netfilter: nf_tab... |
52 |
if (skb_copy_bits(pkt->skb, offset, dest, priv->len) < 0) |
96518518c netfilter: add nf... |
53 54 55 |
goto err; return; err: |
a55e22e92 netfilter: nf_tab... |
56 |
regs->verdict.code = NFT_BREAK; |
96518518c netfilter: add nf... |
57 |
} |
dbb5281a1 netfilter: nf_tab... |
58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 |
/* find the offset to specified option. * * If target header is found, its offset is set in *offset and return option * number. Otherwise, return negative error. * * If the first fragment doesn't contain the End of Options it is considered * invalid. */ static int ipv4_find_option(struct net *net, struct sk_buff *skb, unsigned int *offset, int target) { unsigned char optbuf[sizeof(struct ip_options) + 40]; struct ip_options *opt = (struct ip_options *)optbuf; struct iphdr *iph, _iph; unsigned int start; bool found = false; __be32 info; int optlen; iph = skb_header_pointer(skb, 0, sizeof(_iph), &_iph); if (!iph) return -EBADMSG; start = sizeof(struct iphdr); optlen = iph->ihl * 4 - (int)sizeof(struct iphdr); if (optlen <= 0) return -ENOENT; memset(opt, 0, sizeof(struct ip_options)); /* Copy the options since __ip_options_compile() modifies * the options. */ if (skb_copy_bits(skb, start, opt->__data, optlen)) return -EBADMSG; opt->optlen = optlen; if (__ip_options_compile(net, opt, NULL, &info)) return -EBADMSG; switch (target) { case IPOPT_SSRR: case IPOPT_LSRR: if (!opt->srr) break; found = target == IPOPT_SSRR ? opt->is_strictroute : !opt->is_strictroute; if (found) *offset = opt->srr + start; break; case IPOPT_RR: if (!opt->rr) break; *offset = opt->rr + start; found = true; break; case IPOPT_RA: if (!opt->router_alert) break; *offset = opt->router_alert + start; found = true; break; default: return -EOPNOTSUPP; } return found ? target : -ENOENT; } static void nft_exthdr_ipv4_eval(const struct nft_expr *expr, struct nft_regs *regs, const struct nft_pktinfo *pkt) { struct nft_exthdr *priv = nft_expr_priv(expr); u32 *dest = ®s->data[priv->dreg]; struct sk_buff *skb = pkt->skb; unsigned int offset; int err; if (skb->protocol != htons(ETH_P_IP)) goto err; err = ipv4_find_option(nft_net(pkt), skb, &offset, priv->type); if (priv->flags & NFT_EXTHDR_F_PRESENT) { |
b42833667 netfilter: nf_tab... |
140 |
nft_reg_store8(dest, err >= 0); |
dbb5281a1 netfilter: nf_tab... |
141 142 143 144 145 146 147 148 149 150 151 152 153 |
return; } else if (err < 0) { goto err; } offset += priv->offset; dest[priv->len / NFT_REG32_SIZE] = 0; if (skb_copy_bits(pkt->skb, offset, dest, priv->len) < 0) goto err; return; err: regs->verdict.code = NFT_BREAK; } |
a18177008 netfilter: exthdr... |
154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 |
static void * nft_tcp_header_pointer(const struct nft_pktinfo *pkt, unsigned int len, void *buffer, unsigned int *tcphdr_len) { struct tcphdr *tcph; if (!pkt->tprot_set || pkt->tprot != IPPROTO_TCP) return NULL; tcph = skb_header_pointer(pkt->skb, pkt->xt.thoff, sizeof(*tcph), buffer); if (!tcph) return NULL; *tcphdr_len = __tcp_hdrlen(tcph); if (*tcphdr_len < sizeof(*tcph) || *tcphdr_len > len) return NULL; return skb_header_pointer(pkt->skb, pkt->xt.thoff, *tcphdr_len, buffer); } |
935b7f643 netfilter: nft_ex... |
173 174 175 176 177 178 179 180 181 182 |
static void nft_exthdr_tcp_eval(const struct nft_expr *expr, struct nft_regs *regs, const struct nft_pktinfo *pkt) { u8 buff[sizeof(struct tcphdr) + MAX_TCP_OPTION_SPACE]; struct nft_exthdr *priv = nft_expr_priv(expr); unsigned int i, optl, tcphdr_len, offset; u32 *dest = ®s->data[priv->dreg]; struct tcphdr *tcph; u8 *opt; |
a18177008 netfilter: exthdr... |
183 |
tcph = nft_tcp_header_pointer(pkt, sizeof(buff), buff, &tcphdr_len); |
935b7f643 netfilter: nft_ex... |
184 185 186 187 188 189 190 191 192 193 194 195 196 197 |
if (!tcph) goto err; opt = (u8 *)tcph; for (i = sizeof(*tcph); i < tcphdr_len - 1; i += optl) { optl = optlen(opt, i); if (priv->type != opt[i]) continue; if (i + optl > tcphdr_len || priv->len + priv->offset > optl) goto err; offset = i + priv->offset; |
3c1fece88 netfilter: nft_ex... |
198 199 200 201 202 203 |
if (priv->flags & NFT_EXTHDR_F_PRESENT) { *dest = 1; } else { dest[priv->len / NFT_REG32_SIZE] = 0; memcpy(dest, opt + offset, priv->len); } |
935b7f643 netfilter: nft_ex... |
204 205 206 207 208 |
return; } err: |
3c1fece88 netfilter: nft_ex... |
209 210 211 212 |
if (priv->flags & NFT_EXTHDR_F_PRESENT) *dest = 0; else regs->verdict.code = NFT_BREAK; |
935b7f643 netfilter: nft_ex... |
213 |
} |
99d1712bc netfilter: exthdr... |
214 215 216 217 218 219 220 221 222 |
static void nft_exthdr_tcp_set_eval(const struct nft_expr *expr, struct nft_regs *regs, const struct nft_pktinfo *pkt) { u8 buff[sizeof(struct tcphdr) + MAX_TCP_OPTION_SPACE]; struct nft_exthdr *priv = nft_expr_priv(expr); unsigned int i, optl, tcphdr_len, offset; struct tcphdr *tcph; u8 *opt; |
99d1712bc netfilter: exthdr... |
223 224 225 226 227 228 229 230 |
tcph = nft_tcp_header_pointer(pkt, sizeof(buff), buff, &tcphdr_len); if (!tcph) return; opt = (u8 *)tcph; for (i = sizeof(*tcph); i < tcphdr_len - 1; i += optl) { union { |
99d1712bc netfilter: exthdr... |
231 232 233 234 235 236 237 238 239 240 241 |
__be16 v16; __be32 v32; } old, new; optl = optlen(opt, i); if (priv->type != opt[i]) continue; if (i + optl > tcphdr_len || priv->len + priv->offset > optl) return; |
7418ee4c8 netfilter: nf_tab... |
242 243 |
if (skb_ensure_writable(pkt->skb, pkt->xt.thoff + i + priv->len)) |
99d1712bc netfilter: exthdr... |
244 245 246 247 248 249 |
return; tcph = nft_tcp_header_pointer(pkt, sizeof(buff), buff, &tcphdr_len); if (!tcph) return; |
99d1712bc netfilter: exthdr... |
250 251 252 253 254 |
offset = i + priv->offset; switch (priv->len) { case 2: old.v16 = get_unaligned((u16 *)(opt + offset)); |
2e34328b3 netfilter: nft_ex... |
255 256 |
new.v16 = (__force __be16)nft_reg_load16( ®s->data[priv->sreg]); |
99d1712bc netfilter: exthdr... |
257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 |
switch (priv->type) { case TCPOPT_MSS: /* increase can cause connection to stall */ if (ntohs(old.v16) <= ntohs(new.v16)) return; break; } if (old.v16 == new.v16) return; put_unaligned(new.v16, (u16*)(opt + offset)); inet_proto_csum_replace2(&tcph->check, pkt->skb, old.v16, new.v16, false); break; case 4: |
2e34328b3 netfilter: nft_ex... |
274 |
new.v32 = regs->data[priv->sreg]; |
99d1712bc netfilter: exthdr... |
275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 |
old.v32 = get_unaligned((u32 *)(opt + offset)); if (old.v32 == new.v32) return; put_unaligned(new.v32, (u32*)(opt + offset)); inet_proto_csum_replace4(&tcph->check, pkt->skb, old.v32, new.v32, false); break; default: WARN_ON_ONCE(1); break; } return; } } |
96518518c netfilter: add nf... |
292 293 294 295 296 |
static const struct nla_policy nft_exthdr_policy[NFTA_EXTHDR_MAX + 1] = { [NFTA_EXTHDR_DREG] = { .type = NLA_U32 }, [NFTA_EXTHDR_TYPE] = { .type = NLA_U8 }, [NFTA_EXTHDR_OFFSET] = { .type = NLA_U32 }, [NFTA_EXTHDR_LEN] = { .type = NLA_U32 }, |
c078ca3b0 netfilter: nft_ex... |
297 |
[NFTA_EXTHDR_FLAGS] = { .type = NLA_U32 }, |
f5b5702ac netfilter: exthdr... |
298 299 |
[NFTA_EXTHDR_OP] = { .type = NLA_U32 }, [NFTA_EXTHDR_SREG] = { .type = NLA_U32 }, |
96518518c netfilter: add nf... |
300 301 302 303 304 305 306 |
}; static int nft_exthdr_init(const struct nft_ctx *ctx, const struct nft_expr *expr, const struct nlattr * const tb[]) { struct nft_exthdr *priv = nft_expr_priv(expr); |
935b7f643 netfilter: nft_ex... |
307 |
u32 offset, len, flags = 0, op = NFT_EXTHDR_OP_IPV6; |
21a9e0f15 netfilter: nft_ex... |
308 |
int err; |
96518518c netfilter: add nf... |
309 |
|
935b7f643 netfilter: nft_ex... |
310 311 312 313 |
if (!tb[NFTA_EXTHDR_DREG] || !tb[NFTA_EXTHDR_TYPE] || !tb[NFTA_EXTHDR_OFFSET] || !tb[NFTA_EXTHDR_LEN]) |
96518518c netfilter: add nf... |
314 |
return -EINVAL; |
36b701fae netfilter: nf_tab... |
315 316 317 |
err = nft_parse_u32_check(tb[NFTA_EXTHDR_OFFSET], U8_MAX, &offset); if (err < 0) return err; |
4da449ae1 netfilter: nft_ex... |
318 |
|
36b701fae netfilter: nf_tab... |
319 320 321 |
err = nft_parse_u32_check(tb[NFTA_EXTHDR_LEN], U8_MAX, &len); if (err < 0) return err; |
4da449ae1 netfilter: nft_ex... |
322 |
|
c078ca3b0 netfilter: nft_ex... |
323 324 325 326 327 328 329 330 |
if (tb[NFTA_EXTHDR_FLAGS]) { err = nft_parse_u32_check(tb[NFTA_EXTHDR_FLAGS], U8_MAX, &flags); if (err < 0) return err; if (flags & ~NFT_EXTHDR_F_PRESENT) return -EINVAL; } |
935b7f643 netfilter: nft_ex... |
331 332 333 334 335 |
if (tb[NFTA_EXTHDR_OP]) { err = nft_parse_u32_check(tb[NFTA_EXTHDR_OP], U8_MAX, &op); if (err < 0) return err; } |
96518518c netfilter: add nf... |
336 |
priv->type = nla_get_u8(tb[NFTA_EXTHDR_TYPE]); |
4da449ae1 netfilter: nft_ex... |
337 338 |
priv->offset = offset; priv->len = len; |
b1c96ed37 netfilter: nf_tab... |
339 |
priv->dreg = nft_parse_register(tb[NFTA_EXTHDR_DREG]); |
c078ca3b0 netfilter: nft_ex... |
340 |
priv->flags = flags; |
935b7f643 netfilter: nft_ex... |
341 |
priv->op = op; |
96518518c netfilter: add nf... |
342 |
|
1ec10212f netfilter: nf_tab... |
343 344 |
return nft_validate_register_store(ctx, priv->dreg, NULL, NFT_DATA_VALUE, priv->len); |
96518518c netfilter: add nf... |
345 |
} |
99d1712bc netfilter: exthdr... |
346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388 389 390 391 392 393 |
static int nft_exthdr_tcp_set_init(const struct nft_ctx *ctx, const struct nft_expr *expr, const struct nlattr * const tb[]) { struct nft_exthdr *priv = nft_expr_priv(expr); u32 offset, len, flags = 0, op = NFT_EXTHDR_OP_IPV6; int err; if (!tb[NFTA_EXTHDR_SREG] || !tb[NFTA_EXTHDR_TYPE] || !tb[NFTA_EXTHDR_OFFSET] || !tb[NFTA_EXTHDR_LEN]) return -EINVAL; if (tb[NFTA_EXTHDR_DREG] || tb[NFTA_EXTHDR_FLAGS]) return -EINVAL; err = nft_parse_u32_check(tb[NFTA_EXTHDR_OFFSET], U8_MAX, &offset); if (err < 0) return err; err = nft_parse_u32_check(tb[NFTA_EXTHDR_LEN], U8_MAX, &len); if (err < 0) return err; if (offset < 2) return -EOPNOTSUPP; switch (len) { case 2: break; case 4: break; default: return -EOPNOTSUPP; } err = nft_parse_u32_check(tb[NFTA_EXTHDR_OP], U8_MAX, &op); if (err < 0) return err; priv->type = nla_get_u8(tb[NFTA_EXTHDR_TYPE]); priv->offset = offset; priv->len = len; priv->sreg = nft_parse_register(tb[NFTA_EXTHDR_SREG]); priv->flags = flags; priv->op = op; return nft_validate_register_load(priv->sreg, priv->len); } |
dbb5281a1 netfilter: nf_tab... |
394 395 396 397 398 399 400 401 402 403 404 405 406 407 408 409 410 411 412 413 414 |
static int nft_exthdr_ipv4_init(const struct nft_ctx *ctx, const struct nft_expr *expr, const struct nlattr * const tb[]) { struct nft_exthdr *priv = nft_expr_priv(expr); int err = nft_exthdr_init(ctx, expr, tb); if (err < 0) return err; switch (priv->type) { case IPOPT_SSRR: case IPOPT_LSRR: case IPOPT_RR: case IPOPT_RA: break; default: return -EOPNOTSUPP; } return 0; } |
5e7d695a4 netfilter: exthdr... |
415 |
static int nft_exthdr_dump_common(struct sk_buff *skb, const struct nft_exthdr *priv) |
96518518c netfilter: add nf... |
416 |
{ |
96518518c netfilter: add nf... |
417 418 419 420 421 422 |
if (nla_put_u8(skb, NFTA_EXTHDR_TYPE, priv->type)) goto nla_put_failure; if (nla_put_be32(skb, NFTA_EXTHDR_OFFSET, htonl(priv->offset))) goto nla_put_failure; if (nla_put_be32(skb, NFTA_EXTHDR_LEN, htonl(priv->len))) goto nla_put_failure; |
c078ca3b0 netfilter: nft_ex... |
423 424 |
if (nla_put_be32(skb, NFTA_EXTHDR_FLAGS, htonl(priv->flags))) goto nla_put_failure; |
935b7f643 netfilter: nft_ex... |
425 426 |
if (nla_put_be32(skb, NFTA_EXTHDR_OP, htonl(priv->op))) goto nla_put_failure; |
96518518c netfilter: add nf... |
427 428 429 430 431 |
return 0; nla_put_failure: return -1; } |
5e7d695a4 netfilter: exthdr... |
432 433 434 435 436 437 438 439 440 |
static int nft_exthdr_dump(struct sk_buff *skb, const struct nft_expr *expr) { const struct nft_exthdr *priv = nft_expr_priv(expr); if (nft_dump_register(skb, NFTA_EXTHDR_DREG, priv->dreg)) return -1; return nft_exthdr_dump_common(skb, priv); } |
99d1712bc netfilter: exthdr... |
441 442 443 444 445 446 447 448 449 |
static int nft_exthdr_dump_set(struct sk_buff *skb, const struct nft_expr *expr) { const struct nft_exthdr *priv = nft_expr_priv(expr); if (nft_dump_register(skb, NFTA_EXTHDR_SREG, priv->sreg)) return -1; return nft_exthdr_dump_common(skb, priv); } |
935b7f643 netfilter: nft_ex... |
450 451 452 453 454 455 456 |
static const struct nft_expr_ops nft_exthdr_ipv6_ops = { .type = &nft_exthdr_type, .size = NFT_EXPR_SIZE(sizeof(struct nft_exthdr)), .eval = nft_exthdr_ipv6_eval, .init = nft_exthdr_init, .dump = nft_exthdr_dump, }; |
dbb5281a1 netfilter: nf_tab... |
457 458 459 460 461 462 463 |
static const struct nft_expr_ops nft_exthdr_ipv4_ops = { .type = &nft_exthdr_type, .size = NFT_EXPR_SIZE(sizeof(struct nft_exthdr)), .eval = nft_exthdr_ipv4_eval, .init = nft_exthdr_ipv4_init, .dump = nft_exthdr_dump, }; |
935b7f643 netfilter: nft_ex... |
464 |
static const struct nft_expr_ops nft_exthdr_tcp_ops = { |
ef1f7df91 netfilter: nf_tab... |
465 |
.type = &nft_exthdr_type, |
96518518c netfilter: add nf... |
466 |
.size = NFT_EXPR_SIZE(sizeof(struct nft_exthdr)), |
935b7f643 netfilter: nft_ex... |
467 |
.eval = nft_exthdr_tcp_eval, |
96518518c netfilter: add nf... |
468 469 |
.init = nft_exthdr_init, .dump = nft_exthdr_dump, |
ef1f7df91 netfilter: nf_tab... |
470 |
}; |
99d1712bc netfilter: exthdr... |
471 472 473 474 475 476 477 |
static const struct nft_expr_ops nft_exthdr_tcp_set_ops = { .type = &nft_exthdr_type, .size = NFT_EXPR_SIZE(sizeof(struct nft_exthdr)), .eval = nft_exthdr_tcp_set_eval, .init = nft_exthdr_tcp_set_init, .dump = nft_exthdr_dump_set, }; |
935b7f643 netfilter: nft_ex... |
478 479 480 481 482 483 484 485 |
static const struct nft_expr_ops * nft_exthdr_select_ops(const struct nft_ctx *ctx, const struct nlattr * const tb[]) { u32 op; if (!tb[NFTA_EXTHDR_OP]) return &nft_exthdr_ipv6_ops; |
99d1712bc netfilter: exthdr... |
486 487 |
if (tb[NFTA_EXTHDR_SREG] && tb[NFTA_EXTHDR_DREG]) return ERR_PTR(-EOPNOTSUPP); |
5fd02ebe6 netfilter: fix a ... |
488 |
op = ntohl(nla_get_be32(tb[NFTA_EXTHDR_OP])); |
935b7f643 netfilter: nft_ex... |
489 490 |
switch (op) { case NFT_EXTHDR_OP_TCPOPT: |
99d1712bc netfilter: exthdr... |
491 492 493 494 495 |
if (tb[NFTA_EXTHDR_SREG]) return &nft_exthdr_tcp_set_ops; if (tb[NFTA_EXTHDR_DREG]) return &nft_exthdr_tcp_ops; break; |
935b7f643 netfilter: nft_ex... |
496 |
case NFT_EXTHDR_OP_IPV6: |
99d1712bc netfilter: exthdr... |
497 498 499 |
if (tb[NFTA_EXTHDR_DREG]) return &nft_exthdr_ipv6_ops; break; |
dbb5281a1 netfilter: nf_tab... |
500 501 502 503 504 505 |
case NFT_EXTHDR_OP_IPV4: if (ctx->family != NFPROTO_IPV6) { if (tb[NFTA_EXTHDR_DREG]) return &nft_exthdr_ipv4_ops; } break; |
935b7f643 netfilter: nft_ex... |
506 507 508 509 |
} return ERR_PTR(-EOPNOTSUPP); } |
d0103158c netfilter: nf_tab... |
510 |
struct nft_expr_type nft_exthdr_type __read_mostly = { |
ef1f7df91 netfilter: nf_tab... |
511 |
.name = "exthdr", |
d4ef38354 netfilter: Remove... |
512 |
.select_ops = nft_exthdr_select_ops, |
96518518c netfilter: add nf... |
513 514 |
.policy = nft_exthdr_policy, .maxattr = NFTA_EXTHDR_MAX, |
ef1f7df91 netfilter: nf_tab... |
515 |
.owner = THIS_MODULE, |
96518518c netfilter: add nf... |
516 |
}; |