Skip to content

Commit 99d1712

Browse files
Florian Westphalummakynes
authored andcommitted
netfilter: exthdr: tcp option set support
This allows setting 2 and 4 byte quantities in the tcp option space. Main purpose is to allow native replacement for xt_TCPMSS to work around pmtu blackholes. Writes to kind and len are now allowed at the moment, it does not seem useful to do this as it causes corruption of the tcp option space. We can always lift this restriction later if a use-case appears. Signed-off-by: Florian Westphal <[email protected]> Signed-off-by: Pablo Neira Ayuso <[email protected]>
1 parent 5e7d695 commit 99d1712

File tree

2 files changed

+165
-3
lines changed

2 files changed

+165
-3
lines changed

include/uapi/linux/netfilter/nf_tables.h

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -732,7 +732,8 @@ enum nft_exthdr_op {
732732
* @NFTA_EXTHDR_OFFSET: extension header offset (NLA_U32)
733733
* @NFTA_EXTHDR_LEN: extension header length (NLA_U32)
734734
* @NFTA_EXTHDR_FLAGS: extension header flags (NLA_U32)
735-
* @NFTA_EXTHDR_OP: option match type (NLA_U8)
735+
* @NFTA_EXTHDR_OP: option match type (NLA_U32)
736+
* @NFTA_EXTHDR_SREG: option match type (NLA_U32)
736737
*/
737738
enum nft_exthdr_attributes {
738739
NFTA_EXTHDR_UNSPEC,
@@ -742,6 +743,7 @@ enum nft_exthdr_attributes {
742743
NFTA_EXTHDR_LEN,
743744
NFTA_EXTHDR_FLAGS,
744745
NFTA_EXTHDR_OP,
746+
NFTA_EXTHDR_SREG,
745747
__NFTA_EXTHDR_MAX
746748
};
747749
#define NFTA_EXTHDR_MAX (__NFTA_EXTHDR_MAX - 1)

net/netfilter/nft_exthdr.c

Lines changed: 162 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88
* Development of this code funded by Astaro AG (http://www.astaro.com/)
99
*/
1010

11+
#include <asm/unaligned.h>
1112
#include <linux/kernel.h>
1213
#include <linux/init.h>
1314
#include <linux/module.h>
@@ -23,6 +24,7 @@ struct nft_exthdr {
2324
u8 len;
2425
u8 op;
2526
enum nft_registers dreg:8;
27+
enum nft_registers sreg:8;
2628
u8 flags;
2729
};
2830

@@ -124,6 +126,88 @@ static void nft_exthdr_tcp_eval(const struct nft_expr *expr,
124126
regs->verdict.code = NFT_BREAK;
125127
}
126128

129+
static void nft_exthdr_tcp_set_eval(const struct nft_expr *expr,
130+
struct nft_regs *regs,
131+
const struct nft_pktinfo *pkt)
132+
{
133+
u8 buff[sizeof(struct tcphdr) + MAX_TCP_OPTION_SPACE];
134+
struct nft_exthdr *priv = nft_expr_priv(expr);
135+
unsigned int i, optl, tcphdr_len, offset;
136+
struct tcphdr *tcph;
137+
u8 *opt;
138+
u32 src;
139+
140+
tcph = nft_tcp_header_pointer(pkt, sizeof(buff), buff, &tcphdr_len);
141+
if (!tcph)
142+
return;
143+
144+
opt = (u8 *)tcph;
145+
for (i = sizeof(*tcph); i < tcphdr_len - 1; i += optl) {
146+
union {
147+
u8 octet;
148+
__be16 v16;
149+
__be32 v32;
150+
} old, new;
151+
152+
optl = optlen(opt, i);
153+
154+
if (priv->type != opt[i])
155+
continue;
156+
157+
if (i + optl > tcphdr_len || priv->len + priv->offset > optl)
158+
return;
159+
160+
if (!skb_make_writable(pkt->skb, pkt->xt.thoff + i + priv->len))
161+
return;
162+
163+
tcph = nft_tcp_header_pointer(pkt, sizeof(buff), buff,
164+
&tcphdr_len);
165+
if (!tcph)
166+
return;
167+
168+
src = regs->data[priv->sreg];
169+
offset = i + priv->offset;
170+
171+
switch (priv->len) {
172+
case 2:
173+
old.v16 = get_unaligned((u16 *)(opt + offset));
174+
new.v16 = src;
175+
176+
switch (priv->type) {
177+
case TCPOPT_MSS:
178+
/* increase can cause connection to stall */
179+
if (ntohs(old.v16) <= ntohs(new.v16))
180+
return;
181+
break;
182+
}
183+
184+
if (old.v16 == new.v16)
185+
return;
186+
187+
put_unaligned(new.v16, (u16*)(opt + offset));
188+
inet_proto_csum_replace2(&tcph->check, pkt->skb,
189+
old.v16, new.v16, false);
190+
break;
191+
case 4:
192+
new.v32 = src;
193+
old.v32 = get_unaligned((u32 *)(opt + offset));
194+
195+
if (old.v32 == new.v32)
196+
return;
197+
198+
put_unaligned(new.v32, (u32*)(opt + offset));
199+
inet_proto_csum_replace4(&tcph->check, pkt->skb,
200+
old.v32, new.v32, false);
201+
break;
202+
default:
203+
WARN_ON_ONCE(1);
204+
break;
205+
}
206+
207+
return;
208+
}
209+
}
210+
127211
static const struct nla_policy nft_exthdr_policy[NFTA_EXTHDR_MAX + 1] = {
128212
[NFTA_EXTHDR_DREG] = { .type = NLA_U32 },
129213
[NFTA_EXTHDR_TYPE] = { .type = NLA_U8 },
@@ -180,6 +264,55 @@ static int nft_exthdr_init(const struct nft_ctx *ctx,
180264
NFT_DATA_VALUE, priv->len);
181265
}
182266

267+
static int nft_exthdr_tcp_set_init(const struct nft_ctx *ctx,
268+
const struct nft_expr *expr,
269+
const struct nlattr * const tb[])
270+
{
271+
struct nft_exthdr *priv = nft_expr_priv(expr);
272+
u32 offset, len, flags = 0, op = NFT_EXTHDR_OP_IPV6;
273+
int err;
274+
275+
if (!tb[NFTA_EXTHDR_SREG] ||
276+
!tb[NFTA_EXTHDR_TYPE] ||
277+
!tb[NFTA_EXTHDR_OFFSET] ||
278+
!tb[NFTA_EXTHDR_LEN])
279+
return -EINVAL;
280+
281+
if (tb[NFTA_EXTHDR_DREG] || tb[NFTA_EXTHDR_FLAGS])
282+
return -EINVAL;
283+
284+
err = nft_parse_u32_check(tb[NFTA_EXTHDR_OFFSET], U8_MAX, &offset);
285+
if (err < 0)
286+
return err;
287+
288+
err = nft_parse_u32_check(tb[NFTA_EXTHDR_LEN], U8_MAX, &len);
289+
if (err < 0)
290+
return err;
291+
292+
if (offset < 2)
293+
return -EOPNOTSUPP;
294+
295+
switch (len) {
296+
case 2: break;
297+
case 4: break;
298+
default:
299+
return -EOPNOTSUPP;
300+
}
301+
302+
err = nft_parse_u32_check(tb[NFTA_EXTHDR_OP], U8_MAX, &op);
303+
if (err < 0)
304+
return err;
305+
306+
priv->type = nla_get_u8(tb[NFTA_EXTHDR_TYPE]);
307+
priv->offset = offset;
308+
priv->len = len;
309+
priv->sreg = nft_parse_register(tb[NFTA_EXTHDR_SREG]);
310+
priv->flags = flags;
311+
priv->op = op;
312+
313+
return nft_validate_register_load(priv->sreg, priv->len);
314+
}
315+
183316
static int nft_exthdr_dump_common(struct sk_buff *skb, const struct nft_exthdr *priv)
184317
{
185318
if (nla_put_u8(skb, NFTA_EXTHDR_TYPE, priv->type))
@@ -208,6 +341,16 @@ static int nft_exthdr_dump(struct sk_buff *skb, const struct nft_expr *expr)
208341
return nft_exthdr_dump_common(skb, priv);
209342
}
210343

344+
static int nft_exthdr_dump_set(struct sk_buff *skb, const struct nft_expr *expr)
345+
{
346+
const struct nft_exthdr *priv = nft_expr_priv(expr);
347+
348+
if (nft_dump_register(skb, NFTA_EXTHDR_SREG, priv->sreg))
349+
return -1;
350+
351+
return nft_exthdr_dump_common(skb, priv);
352+
}
353+
211354
static struct nft_expr_type nft_exthdr_type;
212355
static const struct nft_expr_ops nft_exthdr_ipv6_ops = {
213356
.type = &nft_exthdr_type,
@@ -225,6 +368,14 @@ static const struct nft_expr_ops nft_exthdr_tcp_ops = {
225368
.dump = nft_exthdr_dump,
226369
};
227370

371+
static const struct nft_expr_ops nft_exthdr_tcp_set_ops = {
372+
.type = &nft_exthdr_type,
373+
.size = NFT_EXPR_SIZE(sizeof(struct nft_exthdr)),
374+
.eval = nft_exthdr_tcp_set_eval,
375+
.init = nft_exthdr_tcp_set_init,
376+
.dump = nft_exthdr_dump_set,
377+
};
378+
228379
static const struct nft_expr_ops *
229380
nft_exthdr_select_ops(const struct nft_ctx *ctx,
230381
const struct nlattr * const tb[])
@@ -234,12 +385,21 @@ nft_exthdr_select_ops(const struct nft_ctx *ctx,
234385
if (!tb[NFTA_EXTHDR_OP])
235386
return &nft_exthdr_ipv6_ops;
236387

388+
if (tb[NFTA_EXTHDR_SREG] && tb[NFTA_EXTHDR_DREG])
389+
return ERR_PTR(-EOPNOTSUPP);
390+
237391
op = ntohl(nla_get_u32(tb[NFTA_EXTHDR_OP]));
238392
switch (op) {
239393
case NFT_EXTHDR_OP_TCPOPT:
240-
return &nft_exthdr_tcp_ops;
394+
if (tb[NFTA_EXTHDR_SREG])
395+
return &nft_exthdr_tcp_set_ops;
396+
if (tb[NFTA_EXTHDR_DREG])
397+
return &nft_exthdr_tcp_ops;
398+
break;
241399
case NFT_EXTHDR_OP_IPV6:
242-
return &nft_exthdr_ipv6_ops;
400+
if (tb[NFTA_EXTHDR_DREG])
401+
return &nft_exthdr_ipv6_ops;
402+
break;
243403
}
244404

245405
return ERR_PTR(-EOPNOTSUPP);

0 commit comments

Comments
 (0)