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); |