.. | .. |
---|
10 | 10 | #include <linux/netlink.h> |
---|
11 | 11 | #include <linux/netfilter.h> |
---|
12 | 12 | #include <linux/netfilter/nf_tables.h> |
---|
| 13 | +#include <linux/sctp.h> |
---|
13 | 14 | #include <net/netfilter/nf_tables_core.h> |
---|
14 | 15 | #include <net/netfilter/nf_tables.h> |
---|
| 16 | +#include <net/sctp/sctp.h> |
---|
15 | 17 | #include <net/tcp.h> |
---|
16 | 18 | |
---|
17 | 19 | struct nft_exthdr { |
---|
.. | .. |
---|
31 | 33 | return 1; |
---|
32 | 34 | else |
---|
33 | 35 | return opt[offset + 1]; |
---|
| 36 | +} |
---|
| 37 | + |
---|
| 38 | +static int nft_skb_copy_to_reg(const struct sk_buff *skb, int offset, u32 *dest, unsigned int len) |
---|
| 39 | +{ |
---|
| 40 | + if (len % NFT_REG32_SIZE) |
---|
| 41 | + dest[len / NFT_REG32_SIZE] = 0; |
---|
| 42 | + |
---|
| 43 | + return skb_copy_bits(skb, offset, dest, len); |
---|
34 | 44 | } |
---|
35 | 45 | |
---|
36 | 46 | static void nft_exthdr_ipv6_eval(const struct nft_expr *expr, |
---|
.. | .. |
---|
54 | 64 | } |
---|
55 | 65 | offset += priv->offset; |
---|
56 | 66 | |
---|
57 | | - dest[priv->len / NFT_REG32_SIZE] = 0; |
---|
58 | | - if (skb_copy_bits(pkt->skb, offset, dest, priv->len) < 0) |
---|
| 67 | + if (nft_skb_copy_to_reg(pkt->skb, offset, dest, priv->len) < 0) |
---|
59 | 68 | goto err; |
---|
60 | 69 | return; |
---|
61 | 70 | err: |
---|
.. | .. |
---|
151 | 160 | } |
---|
152 | 161 | offset += priv->offset; |
---|
153 | 162 | |
---|
154 | | - dest[priv->len / NFT_REG32_SIZE] = 0; |
---|
155 | | - if (skb_copy_bits(pkt->skb, offset, dest, priv->len) < 0) |
---|
| 163 | + if (nft_skb_copy_to_reg(pkt->skb, offset, dest, priv->len) < 0) |
---|
156 | 164 | goto err; |
---|
157 | 165 | return; |
---|
158 | 166 | err: |
---|
.. | .. |
---|
168 | 176 | if (!pkt->tprot_set || pkt->tprot != IPPROTO_TCP) |
---|
169 | 177 | return NULL; |
---|
170 | 178 | |
---|
171 | | - tcph = skb_header_pointer(pkt->skb, pkt->xt.thoff, sizeof(*tcph), buffer); |
---|
| 179 | + tcph = skb_header_pointer(pkt->skb, nft_thoff(pkt), sizeof(*tcph), buffer); |
---|
172 | 180 | if (!tcph) |
---|
173 | 181 | return NULL; |
---|
174 | 182 | |
---|
.. | .. |
---|
176 | 184 | if (*tcphdr_len < sizeof(*tcph) || *tcphdr_len > len) |
---|
177 | 185 | return NULL; |
---|
178 | 186 | |
---|
179 | | - return skb_header_pointer(pkt->skb, pkt->xt.thoff, *tcphdr_len, buffer); |
---|
| 187 | + return skb_header_pointer(pkt->skb, nft_thoff(pkt), *tcphdr_len, buffer); |
---|
180 | 188 | } |
---|
181 | 189 | |
---|
182 | 190 | static void nft_exthdr_tcp_eval(const struct nft_expr *expr, |
---|
.. | .. |
---|
208 | 216 | if (priv->flags & NFT_EXTHDR_F_PRESENT) { |
---|
209 | 217 | *dest = 1; |
---|
210 | 218 | } else { |
---|
211 | | - dest[priv->len / NFT_REG32_SIZE] = 0; |
---|
| 219 | + if (priv->len % NFT_REG32_SIZE) |
---|
| 220 | + dest[priv->len / NFT_REG32_SIZE] = 0; |
---|
212 | 221 | memcpy(dest, opt + offset, priv->len); |
---|
213 | 222 | } |
---|
214 | 223 | |
---|
.. | .. |
---|
234 | 243 | |
---|
235 | 244 | tcph = nft_tcp_header_pointer(pkt, sizeof(buff), buff, &tcphdr_len); |
---|
236 | 245 | if (!tcph) |
---|
237 | | - return; |
---|
| 246 | + goto err; |
---|
238 | 247 | |
---|
| 248 | + if (skb_ensure_writable(pkt->skb, nft_thoff(pkt) + tcphdr_len)) |
---|
| 249 | + goto err; |
---|
| 250 | + |
---|
| 251 | + tcph = (struct tcphdr *)(pkt->skb->data + nft_thoff(pkt)); |
---|
239 | 252 | opt = (u8 *)tcph; |
---|
| 253 | + |
---|
240 | 254 | for (i = sizeof(*tcph); i < tcphdr_len - 1; i += optl) { |
---|
241 | 255 | union { |
---|
242 | 256 | __be16 v16; |
---|
.. | .. |
---|
249 | 263 | continue; |
---|
250 | 264 | |
---|
251 | 265 | if (i + optl > tcphdr_len || priv->len + priv->offset > optl) |
---|
252 | | - return; |
---|
253 | | - |
---|
254 | | - if (skb_ensure_writable(pkt->skb, |
---|
255 | | - pkt->xt.thoff + i + priv->len)) |
---|
256 | | - return; |
---|
257 | | - |
---|
258 | | - tcph = nft_tcp_header_pointer(pkt, sizeof(buff), buff, |
---|
259 | | - &tcphdr_len); |
---|
260 | | - if (!tcph) |
---|
261 | | - return; |
---|
| 266 | + goto err; |
---|
262 | 267 | |
---|
263 | 268 | offset = i + priv->offset; |
---|
264 | 269 | |
---|
.. | .. |
---|
301 | 306 | |
---|
302 | 307 | return; |
---|
303 | 308 | } |
---|
| 309 | + return; |
---|
| 310 | +err: |
---|
| 311 | + regs->verdict.code = NFT_BREAK; |
---|
| 312 | +} |
---|
| 313 | + |
---|
| 314 | +static void nft_exthdr_tcp_strip_eval(const struct nft_expr *expr, |
---|
| 315 | + struct nft_regs *regs, |
---|
| 316 | + const struct nft_pktinfo *pkt) |
---|
| 317 | +{ |
---|
| 318 | + u8 buff[sizeof(struct tcphdr) + MAX_TCP_OPTION_SPACE]; |
---|
| 319 | + struct nft_exthdr *priv = nft_expr_priv(expr); |
---|
| 320 | + unsigned int i, tcphdr_len, optl; |
---|
| 321 | + struct tcphdr *tcph; |
---|
| 322 | + u8 *opt; |
---|
| 323 | + |
---|
| 324 | + tcph = nft_tcp_header_pointer(pkt, sizeof(buff), buff, &tcphdr_len); |
---|
| 325 | + if (!tcph) |
---|
| 326 | + goto err; |
---|
| 327 | + |
---|
| 328 | + if (skb_ensure_writable(pkt->skb, nft_thoff(pkt) + tcphdr_len)) |
---|
| 329 | + goto drop; |
---|
| 330 | + |
---|
| 331 | + tcph = (struct tcphdr *)(pkt->skb->data + nft_thoff(pkt)); |
---|
| 332 | + opt = (u8 *)tcph; |
---|
| 333 | + |
---|
| 334 | + for (i = sizeof(*tcph); i < tcphdr_len - 1; i += optl) { |
---|
| 335 | + unsigned int j; |
---|
| 336 | + |
---|
| 337 | + optl = optlen(opt, i); |
---|
| 338 | + if (priv->type != opt[i]) |
---|
| 339 | + continue; |
---|
| 340 | + |
---|
| 341 | + if (i + optl > tcphdr_len) |
---|
| 342 | + goto drop; |
---|
| 343 | + |
---|
| 344 | + for (j = 0; j < optl; ++j) { |
---|
| 345 | + u16 n = TCPOPT_NOP; |
---|
| 346 | + u16 o = opt[i+j]; |
---|
| 347 | + |
---|
| 348 | + if ((i + j) % 2 == 0) { |
---|
| 349 | + o <<= 8; |
---|
| 350 | + n <<= 8; |
---|
| 351 | + } |
---|
| 352 | + inet_proto_csum_replace2(&tcph->check, pkt->skb, htons(o), |
---|
| 353 | + htons(n), false); |
---|
| 354 | + } |
---|
| 355 | + memset(opt + i, TCPOPT_NOP, optl); |
---|
| 356 | + return; |
---|
| 357 | + } |
---|
| 358 | + |
---|
| 359 | + /* option not found, continue. This allows to do multiple |
---|
| 360 | + * option removals per rule. |
---|
| 361 | + */ |
---|
| 362 | + return; |
---|
| 363 | +err: |
---|
| 364 | + regs->verdict.code = NFT_BREAK; |
---|
| 365 | + return; |
---|
| 366 | +drop: |
---|
| 367 | + /* can't remove, no choice but to drop */ |
---|
| 368 | + regs->verdict.code = NF_DROP; |
---|
| 369 | +} |
---|
| 370 | + |
---|
| 371 | +static void nft_exthdr_sctp_eval(const struct nft_expr *expr, |
---|
| 372 | + struct nft_regs *regs, |
---|
| 373 | + const struct nft_pktinfo *pkt) |
---|
| 374 | +{ |
---|
| 375 | + unsigned int offset = nft_thoff(pkt) + sizeof(struct sctphdr); |
---|
| 376 | + struct nft_exthdr *priv = nft_expr_priv(expr); |
---|
| 377 | + u32 *dest = ®s->data[priv->dreg]; |
---|
| 378 | + const struct sctp_chunkhdr *sch; |
---|
| 379 | + struct sctp_chunkhdr _sch; |
---|
| 380 | + |
---|
| 381 | + if (pkt->tprot != IPPROTO_SCTP) |
---|
| 382 | + goto err; |
---|
| 383 | + |
---|
| 384 | + do { |
---|
| 385 | + sch = skb_header_pointer(pkt->skb, offset, sizeof(_sch), &_sch); |
---|
| 386 | + if (!sch || !sch->length) |
---|
| 387 | + break; |
---|
| 388 | + |
---|
| 389 | + if (sch->type == priv->type) { |
---|
| 390 | + if (priv->flags & NFT_EXTHDR_F_PRESENT) { |
---|
| 391 | + nft_reg_store8(dest, true); |
---|
| 392 | + return; |
---|
| 393 | + } |
---|
| 394 | + if (priv->offset + priv->len > ntohs(sch->length) || |
---|
| 395 | + offset + ntohs(sch->length) > pkt->skb->len) |
---|
| 396 | + break; |
---|
| 397 | + |
---|
| 398 | + if (nft_skb_copy_to_reg(pkt->skb, offset + priv->offset, |
---|
| 399 | + dest, priv->len) < 0) |
---|
| 400 | + break; |
---|
| 401 | + return; |
---|
| 402 | + } |
---|
| 403 | + offset += SCTP_PAD4(ntohs(sch->length)); |
---|
| 404 | + } while (offset < pkt->skb->len); |
---|
| 405 | +err: |
---|
| 406 | + if (priv->flags & NFT_EXTHDR_F_PRESENT) |
---|
| 407 | + nft_reg_store8(dest, false); |
---|
| 408 | + else |
---|
| 409 | + regs->verdict.code = NFT_BREAK; |
---|
304 | 410 | } |
---|
305 | 411 | |
---|
306 | 412 | static const struct nla_policy nft_exthdr_policy[NFTA_EXTHDR_MAX + 1] = { |
---|
.. | .. |
---|
410 | 516 | priv->len); |
---|
411 | 517 | } |
---|
412 | 518 | |
---|
| 519 | +static int nft_exthdr_tcp_strip_init(const struct nft_ctx *ctx, |
---|
| 520 | + const struct nft_expr *expr, |
---|
| 521 | + const struct nlattr * const tb[]) |
---|
| 522 | +{ |
---|
| 523 | + struct nft_exthdr *priv = nft_expr_priv(expr); |
---|
| 524 | + |
---|
| 525 | + if (tb[NFTA_EXTHDR_SREG] || |
---|
| 526 | + tb[NFTA_EXTHDR_DREG] || |
---|
| 527 | + tb[NFTA_EXTHDR_FLAGS] || |
---|
| 528 | + tb[NFTA_EXTHDR_OFFSET] || |
---|
| 529 | + tb[NFTA_EXTHDR_LEN]) |
---|
| 530 | + return -EINVAL; |
---|
| 531 | + |
---|
| 532 | + if (!tb[NFTA_EXTHDR_TYPE]) |
---|
| 533 | + return -EINVAL; |
---|
| 534 | + |
---|
| 535 | + priv->type = nla_get_u8(tb[NFTA_EXTHDR_TYPE]); |
---|
| 536 | + priv->op = NFT_EXTHDR_OP_TCPOPT; |
---|
| 537 | + |
---|
| 538 | + return 0; |
---|
| 539 | +} |
---|
| 540 | + |
---|
413 | 541 | static int nft_exthdr_ipv4_init(const struct nft_ctx *ctx, |
---|
414 | 542 | const struct nft_expr *expr, |
---|
415 | 543 | const struct nlattr * const tb[]) |
---|
.. | .. |
---|
470 | 598 | return nft_exthdr_dump_common(skb, priv); |
---|
471 | 599 | } |
---|
472 | 600 | |
---|
| 601 | +static int nft_exthdr_dump_strip(struct sk_buff *skb, const struct nft_expr *expr) |
---|
| 602 | +{ |
---|
| 603 | + const struct nft_exthdr *priv = nft_expr_priv(expr); |
---|
| 604 | + |
---|
| 605 | + return nft_exthdr_dump_common(skb, priv); |
---|
| 606 | +} |
---|
| 607 | + |
---|
473 | 608 | static const struct nft_expr_ops nft_exthdr_ipv6_ops = { |
---|
474 | 609 | .type = &nft_exthdr_type, |
---|
475 | 610 | .size = NFT_EXPR_SIZE(sizeof(struct nft_exthdr)), |
---|
.. | .. |
---|
502 | 637 | .dump = nft_exthdr_dump_set, |
---|
503 | 638 | }; |
---|
504 | 639 | |
---|
| 640 | +static const struct nft_expr_ops nft_exthdr_tcp_strip_ops = { |
---|
| 641 | + .type = &nft_exthdr_type, |
---|
| 642 | + .size = NFT_EXPR_SIZE(sizeof(struct nft_exthdr)), |
---|
| 643 | + .eval = nft_exthdr_tcp_strip_eval, |
---|
| 644 | + .init = nft_exthdr_tcp_strip_init, |
---|
| 645 | + .dump = nft_exthdr_dump_strip, |
---|
| 646 | +}; |
---|
| 647 | + |
---|
| 648 | +static const struct nft_expr_ops nft_exthdr_sctp_ops = { |
---|
| 649 | + .type = &nft_exthdr_type, |
---|
| 650 | + .size = NFT_EXPR_SIZE(sizeof(struct nft_exthdr)), |
---|
| 651 | + .eval = nft_exthdr_sctp_eval, |
---|
| 652 | + .init = nft_exthdr_init, |
---|
| 653 | + .dump = nft_exthdr_dump, |
---|
| 654 | +}; |
---|
| 655 | + |
---|
505 | 656 | static const struct nft_expr_ops * |
---|
506 | 657 | nft_exthdr_select_ops(const struct nft_ctx *ctx, |
---|
507 | 658 | const struct nlattr * const tb[]) |
---|
.. | .. |
---|
521 | 672 | return &nft_exthdr_tcp_set_ops; |
---|
522 | 673 | if (tb[NFTA_EXTHDR_DREG]) |
---|
523 | 674 | return &nft_exthdr_tcp_ops; |
---|
524 | | - break; |
---|
| 675 | + return &nft_exthdr_tcp_strip_ops; |
---|
525 | 676 | case NFT_EXTHDR_OP_IPV6: |
---|
526 | 677 | if (tb[NFTA_EXTHDR_DREG]) |
---|
527 | 678 | return &nft_exthdr_ipv6_ops; |
---|
.. | .. |
---|
532 | 683 | return &nft_exthdr_ipv4_ops; |
---|
533 | 684 | } |
---|
534 | 685 | break; |
---|
| 686 | + case NFT_EXTHDR_OP_SCTP: |
---|
| 687 | + if (tb[NFTA_EXTHDR_DREG]) |
---|
| 688 | + return &nft_exthdr_sctp_ops; |
---|
| 689 | + break; |
---|
535 | 690 | } |
---|
536 | 691 | |
---|
537 | 692 | return ERR_PTR(-EOPNOTSUPP); |
---|