diff options
| author | Stephen Suryaputra <ssuryaextr@gmail.com> | 2019-06-20 12:19:59 -0400 |
|---|---|---|
| committer | Pablo Neira Ayuso <pablo@netfilter.org> | 2019-06-21 12:35:51 -0400 |
| commit | dbb5281a1f84b2f93032d4864c211ce8a20811a7 (patch) | |
| tree | a204da3a545973d53b5f9ae4672cc34934d86775 | |
| parent | f76c7bfca4326140d86ab86168214ef447177bc0 (diff) | |
netfilter: nf_tables: add support for matching IPv4 options
This is the kernel change for the overall changes with this description:
Add capability to have rules matching IPv4 options. This is developed
mainly to support dropping of IP packets with loose and/or strict source
route route options.
Signed-off-by: Stephen Suryaputra <ssuryaextr@gmail.com>
Signed-off-by: Pablo Neira Ayuso <pablo@netfilter.org>
| -rw-r--r-- | include/uapi/linux/netfilter/nf_tables.h | 2 | ||||
| -rw-r--r-- | net/ipv4/ip_options.c | 1 | ||||
| -rw-r--r-- | net/netfilter/nft_exthdr.c | 133 |
3 files changed, 136 insertions, 0 deletions
diff --git a/include/uapi/linux/netfilter/nf_tables.h b/include/uapi/linux/netfilter/nf_tables.h index 31a6b8f7ff73..c6c8ec5c7c00 100644 --- a/include/uapi/linux/netfilter/nf_tables.h +++ b/include/uapi/linux/netfilter/nf_tables.h | |||
| @@ -730,10 +730,12 @@ enum nft_exthdr_flags { | |||
| 730 | * | 730 | * |
| 731 | * @NFT_EXTHDR_OP_IPV6: match against ipv6 extension headers | 731 | * @NFT_EXTHDR_OP_IPV6: match against ipv6 extension headers |
| 732 | * @NFT_EXTHDR_OP_TCP: match against tcp options | 732 | * @NFT_EXTHDR_OP_TCP: match against tcp options |
| 733 | * @NFT_EXTHDR_OP_IPV4: match against ipv4 options | ||
| 733 | */ | 734 | */ |
| 734 | enum nft_exthdr_op { | 735 | enum nft_exthdr_op { |
| 735 | NFT_EXTHDR_OP_IPV6, | 736 | NFT_EXTHDR_OP_IPV6, |
| 736 | NFT_EXTHDR_OP_TCPOPT, | 737 | NFT_EXTHDR_OP_TCPOPT, |
| 738 | NFT_EXTHDR_OP_IPV4, | ||
| 737 | __NFT_EXTHDR_OP_MAX | 739 | __NFT_EXTHDR_OP_MAX |
| 738 | }; | 740 | }; |
| 739 | #define NFT_EXTHDR_OP_MAX (__NFT_EXTHDR_OP_MAX - 1) | 741 | #define NFT_EXTHDR_OP_MAX (__NFT_EXTHDR_OP_MAX - 1) |
diff --git a/net/ipv4/ip_options.c b/net/ipv4/ip_options.c index 3db31bb9df50..ddaa01ec2bce 100644 --- a/net/ipv4/ip_options.c +++ b/net/ipv4/ip_options.c | |||
| @@ -473,6 +473,7 @@ error: | |||
| 473 | *info = htonl((pp_ptr-iph)<<24); | 473 | *info = htonl((pp_ptr-iph)<<24); |
| 474 | return -EINVAL; | 474 | return -EINVAL; |
| 475 | } | 475 | } |
| 476 | EXPORT_SYMBOL(__ip_options_compile); | ||
| 476 | 477 | ||
| 477 | int ip_options_compile(struct net *net, | 478 | int ip_options_compile(struct net *net, |
| 478 | struct ip_options *opt, struct sk_buff *skb) | 479 | struct ip_options *opt, struct sk_buff *skb) |
diff --git a/net/netfilter/nft_exthdr.c b/net/netfilter/nft_exthdr.c index 45c8a6c07783..8032b2937c7f 100644 --- a/net/netfilter/nft_exthdr.c +++ b/net/netfilter/nft_exthdr.c | |||
| @@ -62,6 +62,103 @@ err: | |||
| 62 | regs->verdict.code = NFT_BREAK; | 62 | regs->verdict.code = NFT_BREAK; |
| 63 | } | 63 | } |
| 64 | 64 | ||
| 65 | /* find the offset to specified option. | ||
| 66 | * | ||
| 67 | * If target header is found, its offset is set in *offset and return option | ||
| 68 | * number. Otherwise, return negative error. | ||
| 69 | * | ||
| 70 | * If the first fragment doesn't contain the End of Options it is considered | ||
| 71 | * invalid. | ||
| 72 | */ | ||
| 73 | static int ipv4_find_option(struct net *net, struct sk_buff *skb, | ||
| 74 | unsigned int *offset, int target) | ||
| 75 | { | ||
| 76 | unsigned char optbuf[sizeof(struct ip_options) + 40]; | ||
| 77 | struct ip_options *opt = (struct ip_options *)optbuf; | ||
| 78 | struct iphdr *iph, _iph; | ||
| 79 | unsigned int start; | ||
| 80 | bool found = false; | ||
| 81 | __be32 info; | ||
| 82 | int optlen; | ||
| 83 | |||
| 84 | iph = skb_header_pointer(skb, 0, sizeof(_iph), &_iph); | ||
| 85 | if (!iph) | ||
| 86 | return -EBADMSG; | ||
| 87 | start = sizeof(struct iphdr); | ||
| 88 | |||
| 89 | optlen = iph->ihl * 4 - (int)sizeof(struct iphdr); | ||
| 90 | if (optlen <= 0) | ||
| 91 | return -ENOENT; | ||
| 92 | |||
| 93 | memset(opt, 0, sizeof(struct ip_options)); | ||
| 94 | /* Copy the options since __ip_options_compile() modifies | ||
| 95 | * the options. | ||
| 96 | */ | ||
| 97 | if (skb_copy_bits(skb, start, opt->__data, optlen)) | ||
| 98 | return -EBADMSG; | ||
| 99 | opt->optlen = optlen; | ||
| 100 | |||
| 101 | if (__ip_options_compile(net, opt, NULL, &info)) | ||
| 102 | return -EBADMSG; | ||
| 103 | |||
| 104 | switch (target) { | ||
| 105 | case IPOPT_SSRR: | ||
| 106 | case IPOPT_LSRR: | ||
| 107 | if (!opt->srr) | ||
| 108 | break; | ||
| 109 | found = target == IPOPT_SSRR ? opt->is_strictroute : | ||
| 110 | !opt->is_strictroute; | ||
| 111 | if (found) | ||
| 112 | *offset = opt->srr + start; | ||
| 113 | break; | ||
| 114 | case IPOPT_RR: | ||
| 115 | if (!opt->rr) | ||
| 116 | break; | ||
| 117 | *offset = opt->rr + start; | ||
| 118 | found = true; | ||
| 119 | break; | ||
| 120 | case IPOPT_RA: | ||
| 121 | if (!opt->router_alert) | ||
| 122 | break; | ||
| 123 | *offset = opt->router_alert + start; | ||
| 124 | found = true; | ||
| 125 | break; | ||
| 126 | default: | ||
| 127 | return -EOPNOTSUPP; | ||
| 128 | } | ||
| 129 | return found ? target : -ENOENT; | ||
| 130 | } | ||
| 131 | |||
| 132 | static void nft_exthdr_ipv4_eval(const struct nft_expr *expr, | ||
| 133 | struct nft_regs *regs, | ||
| 134 | const struct nft_pktinfo *pkt) | ||
| 135 | { | ||
| 136 | struct nft_exthdr *priv = nft_expr_priv(expr); | ||
| 137 | u32 *dest = ®s->data[priv->dreg]; | ||
| 138 | struct sk_buff *skb = pkt->skb; | ||
| 139 | unsigned int offset; | ||
| 140 | int err; | ||
| 141 | |||
| 142 | if (skb->protocol != htons(ETH_P_IP)) | ||
| 143 | goto err; | ||
| 144 | |||
| 145 | err = ipv4_find_option(nft_net(pkt), skb, &offset, priv->type); | ||
| 146 | if (priv->flags & NFT_EXTHDR_F_PRESENT) { | ||
| 147 | *dest = (err >= 0); | ||
| 148 | return; | ||
| 149 | } else if (err < 0) { | ||
| 150 | goto err; | ||
| 151 | } | ||
| 152 | offset += priv->offset; | ||
| 153 | |||
| 154 | dest[priv->len / NFT_REG32_SIZE] = 0; | ||
| 155 | if (skb_copy_bits(pkt->skb, offset, dest, priv->len) < 0) | ||
| 156 | goto err; | ||
| 157 | return; | ||
| 158 | err: | ||
| 159 | regs->verdict.code = NFT_BREAK; | ||
| 160 | } | ||
| 161 | |||
| 65 | static void * | 162 | static void * |
| 66 | nft_tcp_header_pointer(const struct nft_pktinfo *pkt, | 163 | nft_tcp_header_pointer(const struct nft_pktinfo *pkt, |
| 67 | unsigned int len, void *buffer, unsigned int *tcphdr_len) | 164 | unsigned int len, void *buffer, unsigned int *tcphdr_len) |
| @@ -315,6 +412,28 @@ static int nft_exthdr_tcp_set_init(const struct nft_ctx *ctx, | |||
| 315 | return nft_validate_register_load(priv->sreg, priv->len); | 412 | return nft_validate_register_load(priv->sreg, priv->len); |
| 316 | } | 413 | } |
| 317 | 414 | ||
| 415 | static int nft_exthdr_ipv4_init(const struct nft_ctx *ctx, | ||
| 416 | const struct nft_expr *expr, | ||
| 417 | const struct nlattr * const tb[]) | ||
| 418 | { | ||
| 419 | struct nft_exthdr *priv = nft_expr_priv(expr); | ||
| 420 | int err = nft_exthdr_init(ctx, expr, tb); | ||
| 421 | |||
| 422 | if (err < 0) | ||
| 423 | return err; | ||
| 424 | |||
| 425 | switch (priv->type) { | ||
| 426 | case IPOPT_SSRR: | ||
| 427 | case IPOPT_LSRR: | ||
| 428 | case IPOPT_RR: | ||
| 429 | case IPOPT_RA: | ||
| 430 | break; | ||
| 431 | default: | ||
| 432 | return -EOPNOTSUPP; | ||
| 433 | } | ||
| 434 | return 0; | ||
| 435 | } | ||
| 436 | |||
| 318 | static int nft_exthdr_dump_common(struct sk_buff *skb, const struct nft_exthdr *priv) | 437 | static int nft_exthdr_dump_common(struct sk_buff *skb, const struct nft_exthdr *priv) |
| 319 | { | 438 | { |
| 320 | if (nla_put_u8(skb, NFTA_EXTHDR_TYPE, priv->type)) | 439 | if (nla_put_u8(skb, NFTA_EXTHDR_TYPE, priv->type)) |
| @@ -361,6 +480,14 @@ static const struct nft_expr_ops nft_exthdr_ipv6_ops = { | |||
| 361 | .dump = nft_exthdr_dump, | 480 | .dump = nft_exthdr_dump, |
| 362 | }; | 481 | }; |
| 363 | 482 | ||
| 483 | static const struct nft_expr_ops nft_exthdr_ipv4_ops = { | ||
| 484 | .type = &nft_exthdr_type, | ||
| 485 | .size = NFT_EXPR_SIZE(sizeof(struct nft_exthdr)), | ||
| 486 | .eval = nft_exthdr_ipv4_eval, | ||
| 487 | .init = nft_exthdr_ipv4_init, | ||
| 488 | .dump = nft_exthdr_dump, | ||
| 489 | }; | ||
| 490 | |||
| 364 | static const struct nft_expr_ops nft_exthdr_tcp_ops = { | 491 | static const struct nft_expr_ops nft_exthdr_tcp_ops = { |
| 365 | .type = &nft_exthdr_type, | 492 | .type = &nft_exthdr_type, |
| 366 | .size = NFT_EXPR_SIZE(sizeof(struct nft_exthdr)), | 493 | .size = NFT_EXPR_SIZE(sizeof(struct nft_exthdr)), |
| @@ -401,6 +528,12 @@ nft_exthdr_select_ops(const struct nft_ctx *ctx, | |||
| 401 | if (tb[NFTA_EXTHDR_DREG]) | 528 | if (tb[NFTA_EXTHDR_DREG]) |
| 402 | return &nft_exthdr_ipv6_ops; | 529 | return &nft_exthdr_ipv6_ops; |
| 403 | break; | 530 | break; |
| 531 | case NFT_EXTHDR_OP_IPV4: | ||
| 532 | if (ctx->family != NFPROTO_IPV6) { | ||
| 533 | if (tb[NFTA_EXTHDR_DREG]) | ||
| 534 | return &nft_exthdr_ipv4_ops; | ||
| 535 | } | ||
| 536 | break; | ||
| 404 | } | 537 | } |
| 405 | 538 | ||
| 406 | return ERR_PTR(-EOPNOTSUPP); | 539 | return ERR_PTR(-EOPNOTSUPP); |
