Add support for "synproxy" stateful object. For example (for TCP port 80 and using maps with saddr): table ip foo { synproxy https-synproxy { mss 1460 wscale 7 timestamp sack-perm } synproxy other-synproxy { mss 1460 wscale 5 } chain bar { tcp dport 80 synproxy name "https-synproxy" synproxy name ip saddr map { 192.168.1.0/24 : "https-synproxy", 192.168.2.0/24 : "other-synproxy" } } } Signed-off-by: Fernando Fernandez Mancera <ffmancera@xxxxxxxxxx> --- v1: initial patch v2: fix a typo v3: one argument per line v4: add python tests --- include/linux/netfilter/nf_tables.h | 3 +- include/rule.h | 11 +++ src/evaluate.c | 5 ++ src/json.c | 20 ++++- src/mnl.c | 8 ++ src/netlink.c | 8 ++ src/parser_bison.y | 124 +++++++++++++++++++++++++++- src/parser_json.c | 22 ++++- src/rule.c | 50 +++++++++++ src/scanner.l | 1 + src/statement.c | 1 + tests/py/ip/objects.t | 4 + 12 files changed, 251 insertions(+), 6 deletions(-) diff --git a/include/linux/netfilter/nf_tables.h b/include/linux/netfilter/nf_tables.h index 0ff932d..ed8881a 100644 --- a/include/linux/netfilter/nf_tables.h +++ b/include/linux/netfilter/nf_tables.h @@ -1481,7 +1481,8 @@ enum nft_ct_expectation_attributes { #define NFT_OBJECT_CT_TIMEOUT 7 #define NFT_OBJECT_SECMARK 8 #define NFT_OBJECT_CT_EXPECT 9 -#define __NFT_OBJECT_MAX 10 +#define NFT_OBJECT_SYNPROXY 10 +#define __NFT_OBJECT_MAX 11 #define NFT_OBJECT_MAX (__NFT_OBJECT_MAX - 1) /** diff --git a/include/rule.h b/include/rule.h index 0ef6aac..2708cbe 100644 --- a/include/rule.h +++ b/include/rule.h @@ -399,6 +399,12 @@ struct limit { uint32_t flags; }; +struct synproxy { + uint16_t mss; + uint8_t wscale; + uint32_t flags; +}; + struct secmark { char ctx[NFT_SECMARK_CTX_MAXLEN]; }; @@ -426,6 +432,7 @@ struct obj { struct ct_timeout ct_timeout; struct secmark secmark; struct ct_expect ct_expect; + struct synproxy synproxy; }; }; @@ -529,6 +536,8 @@ enum cmd_ops { * @CMD_OBJ_FLOWTABLES: flow tables * @CMD_OBJ_SECMARK: secmark * @CMD_OBJ_SECMARKS: multiple secmarks + * @CMD_OBJ_SYNPROXY: synproxy + * @CMD_OBJ_SYNPROXYS: multiple synproxys */ enum cmd_obj { CMD_OBJ_INVALID, @@ -561,6 +570,8 @@ enum cmd_obj { CMD_OBJ_SECMARK, CMD_OBJ_SECMARKS, CMD_OBJ_CT_EXPECT, + CMD_OBJ_SYNPROXY, + CMD_OBJ_SYNPROXYS, }; struct markup { diff --git a/src/evaluate.c b/src/evaluate.c index 29fe966..a56cd2a 100644 --- a/src/evaluate.c +++ b/src/evaluate.c @@ -3743,6 +3743,7 @@ static int cmd_evaluate_add(struct eval_ctx *ctx, struct cmd *cmd) case CMD_OBJ_CT_TIMEOUT: case CMD_OBJ_SECMARK: case CMD_OBJ_CT_EXPECT: + case CMD_OBJ_SYNPROXY: return obj_evaluate(ctx, cmd->object); default: BUG("invalid command object type %u\n", cmd->obj); @@ -3766,6 +3767,7 @@ static int cmd_evaluate_delete(struct eval_ctx *ctx, struct cmd *cmd) case CMD_OBJ_LIMIT: case CMD_OBJ_SECMARK: case CMD_OBJ_CT_EXPECT: + case CMD_OBJ_SYNPROXY: return 0; default: BUG("invalid command object type %u\n", cmd->obj); @@ -3911,6 +3913,8 @@ static int cmd_evaluate_list(struct eval_ctx *ctx, struct cmd *cmd) return cmd_evaluate_list_obj(ctx, cmd, NFT_OBJECT_SECMARK); case CMD_OBJ_CT_EXPECT: return cmd_evaluate_list_obj(ctx, cmd, NFT_OBJECT_CT_EXPECT); + case CMD_OBJ_SYNPROXY: + return cmd_evaluate_list_obj(ctx, cmd, NFT_OBJECT_SYNPROXY); case CMD_OBJ_COUNTERS: case CMD_OBJ_QUOTAS: case CMD_OBJ_CT_HELPERS: @@ -3918,6 +3922,7 @@ static int cmd_evaluate_list(struct eval_ctx *ctx, struct cmd *cmd) case CMD_OBJ_SETS: case CMD_OBJ_FLOWTABLES: case CMD_OBJ_SECMARKS: + case CMD_OBJ_SYNPROXYS: if (cmd->handle.table.name == NULL) return 0; if (table_lookup(&cmd->handle, &ctx->nft->cache) == NULL) diff --git a/src/json.c b/src/json.c index 55ce053..6adc801 100644 --- a/src/json.c +++ b/src/json.c @@ -282,8 +282,8 @@ static json_t *obj_print_json(const struct obj *obj) { const char *rate_unit = NULL, *burst_unit = NULL; const char *type = obj_type_name(obj->type); + json_t *root, *tmp, *flags; uint64_t rate, burst; - json_t *root, *tmp; root = json_pack("{s:s, s:s, s:s, s:I}", "family", family2str(obj->handle.family), @@ -368,6 +368,24 @@ static json_t *obj_print_json(const struct obj *obj) json_string(burst_unit)); } + json_object_update(root, tmp); + json_decref(tmp); + break; + case NFT_OBJECT_SYNPROXY: + flags = json_array(); + tmp = json_pack("{s:i, s:i}", + "mss", obj->synproxy.mss, + "wscale", obj->synproxy.wscale); + if (obj->synproxy.flags & NF_SYNPROXY_OPT_TIMESTAMP) + json_array_append_new(flags, json_string("timestamp")); + if (obj->synproxy.flags & NF_SYNPROXY_OPT_SACK_PERM) + json_array_append_new(flags, json_string("sack-perm")); + + if (json_array_size(flags) > 0) + json_object_set_new(tmp, "flags", flags); + else + json_decref(flags); + json_object_update(root, tmp); json_decref(tmp); break; diff --git a/src/mnl.c b/src/mnl.c index 8031bd6..57ff89f 100644 --- a/src/mnl.c +++ b/src/mnl.c @@ -1036,6 +1036,14 @@ int mnl_nft_obj_add(struct netlink_ctx *ctx, const struct cmd *cmd, nftnl_obj_set_str(nlo, NFTNL_OBJ_SECMARK_CTX, obj->secmark.ctx); break; + case NFT_OBJECT_SYNPROXY: + nftnl_obj_set_u16(nlo, NFTNL_OBJ_SYNPROXY_MSS, + obj->synproxy.mss); + nftnl_obj_set_u8(nlo, NFTNL_OBJ_SYNPROXY_WSCALE, + obj->synproxy.wscale); + nftnl_obj_set_u32(nlo, NFTNL_OBJ_SYNPROXY_FLAGS, + obj->synproxy.flags); + break; default: BUG("Unknown type %d\n", obj->type); break; diff --git a/src/netlink.c b/src/netlink.c index f8e1120..1e669e5 100644 --- a/src/netlink.c +++ b/src/netlink.c @@ -1030,6 +1030,14 @@ struct obj *netlink_delinearize_obj(struct netlink_ctx *ctx, obj->ct_expect.size = nftnl_obj_get_u8(nlo, NFTNL_OBJ_CT_EXPECT_SIZE); break; + case NFT_OBJECT_SYNPROXY: + obj->synproxy.mss = + nftnl_obj_get_u16(nlo, NFTNL_OBJ_SYNPROXY_MSS); + obj->synproxy.wscale = + nftnl_obj_get_u8(nlo, NFTNL_OBJ_SYNPROXY_WSCALE); + obj->synproxy.flags = + nftnl_obj_get_u32(nlo, NFTNL_OBJ_SYNPROXY_FLAGS); + break; } obj->type = type; diff --git a/src/parser_bison.y b/src/parser_bison.y index b7db1a2..3fccea6 100644 --- a/src/parser_bison.y +++ b/src/parser_bison.y @@ -151,6 +151,7 @@ int nft_lex(void *, void *, void *); struct counter *counter; struct quota *quota; struct secmark *secmark; + struct synproxy *synproxy; struct ct *ct; struct limit *limit; const struct datatype *datatype; @@ -461,6 +462,7 @@ int nft_lex(void *, void *, void *); %token COUNTERS "counters" %token QUOTAS "quotas" %token LIMITS "limits" +%token SYNPROXYS "synproxys" %token HELPERS "helpers" %token LOG "log" @@ -592,7 +594,7 @@ int nft_lex(void *, void *, void *); %type <flowtable> flowtable_block_alloc flowtable_block %destructor { flowtable_free($$); } flowtable_block_alloc -%type <obj> obj_block_alloc counter_block quota_block ct_helper_block ct_timeout_block ct_expect_block limit_block secmark_block +%type <obj> obj_block_alloc counter_block quota_block ct_helper_block ct_timeout_block ct_expect_block limit_block secmark_block synproxy_block %destructor { obj_free($$); } obj_block_alloc %type <list> stmt_list @@ -700,8 +702,8 @@ int nft_lex(void *, void *, void *); %type <expr> and_rhs_expr exclusive_or_rhs_expr inclusive_or_rhs_expr %destructor { expr_free($$); } and_rhs_expr exclusive_or_rhs_expr inclusive_or_rhs_expr -%type <obj> counter_obj quota_obj ct_obj_alloc limit_obj secmark_obj -%destructor { obj_free($$); } counter_obj quota_obj ct_obj_alloc limit_obj secmark_obj +%type <obj> counter_obj quota_obj ct_obj_alloc limit_obj secmark_obj synproxy_obj +%destructor { obj_free($$); } counter_obj quota_obj ct_obj_alloc limit_obj secmark_obj synproxy_obj %type <expr> relational_expr %destructor { expr_free($$); } relational_expr @@ -787,6 +789,9 @@ int nft_lex(void *, void *, void *); %destructor { xfree($$); } limit_config %type <secmark> secmark_config %destructor { xfree($$); } secmark_config +%type <synproxy> synproxy_config +%destructor { xfree($$); } synproxy_config +%type <val> synproxy_ts synproxy_sack %type <expr> tcp_hdr_expr %destructor { expr_free($$); } tcp_hdr_expr @@ -1012,6 +1017,10 @@ add_cmd : TABLE table_spec { $$ = cmd_alloc(CMD_ADD, CMD_OBJ_SECMARK, &$2, &@$, $3); } + | SYNPROXY obj_spec synproxy_obj + { + $$ = cmd_alloc(CMD_ADD, CMD_OBJ_SYNPROXY, &$2, &@$, $3); + } ; replace_cmd : RULE ruleid_spec rule @@ -1105,6 +1114,10 @@ create_cmd : TABLE table_spec { $$ = cmd_alloc(CMD_CREATE, CMD_OBJ_SECMARK, &$2, &@$, $3); } + | SYNPROXY obj_spec synproxy_obj + { + $$ = cmd_alloc(CMD_CREATE, CMD_OBJ_SYNPROXY, &$2, &@$, $3); + } ; insert_cmd : RULE rule_position rule @@ -1189,6 +1202,14 @@ delete_cmd : TABLE table_spec { $$ = cmd_alloc(CMD_DELETE, CMD_OBJ_SECMARK, &$2, &@$, NULL); } + | SYNPROXY obj_spec + { + $$ = cmd_alloc(CMD_DELETE, CMD_OBJ_SYNPROXY, &$2, &@$, NULL); + } + | SYNPROXY objid_spec + { + $$ = cmd_alloc(CMD_DELETE, CMD_OBJ_SYNPROXY, &$2, &@$, NULL); + } ; get_cmd : ELEMENT set_spec set_block_expr @@ -1273,6 +1294,18 @@ list_cmd : TABLE table_spec { $$ = cmd_alloc(CMD_LIST, CMD_OBJ_SECMARK, &$2, &@$, NULL); } + | SYNPROXYS ruleset_spec + { + $$ = cmd_alloc(CMD_LIST, CMD_OBJ_SYNPROXYS, &$2, &@$, NULL); + } + | SYNPROXYS TABLE table_spec + { + $$ = cmd_alloc(CMD_LIST, CMD_OBJ_SYNPROXYS, &$3, &@$, NULL); + } + | SYNPROXY obj_spec + { + $$ = cmd_alloc(CMD_LIST, CMD_OBJ_SYNPROXY, &$2, &@$, NULL); + } | RULESET ruleset_spec { $$ = cmd_alloc(CMD_LIST, CMD_OBJ_RULESET, &$2, &@$, NULL); @@ -1592,6 +1625,17 @@ table_block : /* empty */ { $$ = $<table>-1; } list_add_tail(&$4->list, &$1->objs); $$ = $1; } + | table_block SYNPROXY obj_identifier + obj_block_alloc '{' synproxy_block '}' + stmt_separator + { + $4->location = @3; + $4->type = NFT_OBJECT_SYNPROXY; + handle_merge(&$4->handle, &$3); + handle_free(&$3); + list_add_tail(&$4->list, &$1->objs); + $$ = $1; + } ; chain_block_alloc : /* empty */ @@ -1928,6 +1972,16 @@ secmark_block : /* empty */ { $$ = $<obj>-1; } } ; +synproxy_block : /* empty */ { $$ = $<obj>-1; } + | synproxy_block common_block + | synproxy_block stmt_separator + | synproxy_block synproxy_config + { + $1->synproxy = *$2; + $$ = $1; + } + ; + type_identifier : STRING { $$ = $1; } | MARK { $$ = xstrdup("mark"); } | DSCP { $$ = xstrdup("dscp"); } @@ -2788,6 +2842,12 @@ synproxy_stmt_alloc : SYNPROXY { $$ = synproxy_stmt_alloc(&@$); } + | SYNPROXY NAME stmt_expr + { + $$ = objref_stmt_alloc(&@$); + $$->objref.type = NFT_OBJECT_SYNPROXY; + $$->objref.expr = $3; + } ; synproxy_args : synproxy_arg @@ -2817,6 +2877,64 @@ synproxy_arg : MSS NUM } ; +synproxy_config : MSS NUM WSCALE NUM synproxy_ts synproxy_sack + { + struct synproxy *synproxy; + uint32_t flags = 0; + + synproxy = xzalloc(sizeof(*synproxy)); + synproxy->mss = $2; + flags |= NF_SYNPROXY_OPT_MSS; + synproxy->wscale = $4; + flags |= NF_SYNPROXY_OPT_WSCALE; + if ($5) + flags |= $5; + if ($6) + flags |= $6; + synproxy->flags = flags; + $$ = synproxy; + } + | MSS NUM stmt_separator WSCALE NUM stmt_separator synproxy_ts synproxy_sack + { + struct synproxy *synproxy; + uint32_t flags = 0; + + synproxy = xzalloc(sizeof(*synproxy)); + synproxy->mss = $2; + flags |= NF_SYNPROXY_OPT_MSS; + synproxy->wscale = $5; + flags |= NF_SYNPROXY_OPT_WSCALE; + if ($7) + flags |= $7; + if ($8) + flags |= $8; + synproxy->flags = flags; + $$ = synproxy; + } + ; + +synproxy_obj : synproxy_config + { + $$ = obj_alloc(&@$); + $$->type = NFT_OBJECT_SYNPROXY; + $$->synproxy = *$1; + } + ; + +synproxy_ts : /* empty */ { $$ = 0; } + | TIMESTAMP + { + $$ = NF_SYNPROXY_OPT_TIMESTAMP; + } + ; + +synproxy_sack : /* empty */ { $$ = 0; } + | SACKPERM + { + $$ = NF_SYNPROXY_OPT_SACK_PERM; + } + ; + primary_stmt_expr : symbol_expr { $$ = $1; } | integer_expr { $$ = $1; } | boolean_expr { $$ = $1; } diff --git a/src/parser_json.c b/src/parser_json.c index 183d9c9..bcac5e1 100644 --- a/src/parser_json.c +++ b/src/parser_json.c @@ -3019,8 +3019,9 @@ static struct cmd *json_parse_cmd_add_object(struct json_ctx *ctx, const char *family, *tmp, *rate_unit = "packets", *burst_unit = "bytes"; uint32_t l3proto = NFPROTO_UNSPEC; struct handle h = { 0 }; + int inv = 0, flags = 0; struct obj *obj; - int inv = 0; + json_t *jflags; if (json_unpack_err(ctx, root, "{s:s, s:s}", "family", &family, @@ -3196,6 +3197,25 @@ static struct cmd *json_parse_cmd_add_object(struct json_ctx *ctx, obj->limit.unit = seconds_from_unit(tmp); obj->limit.flags = inv ? NFT_LIMIT_F_INV : 0; break; + case CMD_OBJ_SYNPROXY: + obj->type = NFT_OBJECT_SYNPROXY; + if (json_unpack_err(ctx, root, "{s:i, s:i}", + "mss", &obj->synproxy.mss, + "wscale", &obj->synproxy.wscale)) { + obj_free(obj); + return NULL; + } + obj->synproxy.flags |= NF_SYNPROXY_OPT_MSS; + obj->synproxy.flags |= NF_SYNPROXY_OPT_WSCALE; + if (!json_unpack(root, "{s:o}", "flags", &jflags)) { + flags = json_parse_synproxy_flags(ctx, jflags); + if (flags < 0) { + obj_free(obj); + return NULL; + } + obj->synproxy.flags |= flags; + } + break; default: BUG("Invalid CMD '%d'", cmd_obj); } diff --git a/src/rule.c b/src/rule.c index 1912513..5bb1c1d 100644 --- a/src/rule.c +++ b/src/rule.c @@ -32,6 +32,7 @@ #include <linux/netfilter.h> #include <linux/netfilter_arp.h> #include <linux/netfilter_ipv4.h> +#include <linux/netfilter/nf_synproxy.h> #include <net/if.h> #include <linux/netfilter_bridge.h> @@ -1451,6 +1452,7 @@ void cmd_free(struct cmd *cmd) case CMD_OBJ_CT_EXPECT: case CMD_OBJ_LIMIT: case CMD_OBJ_SECMARK: + case CMD_OBJ_SYNPROXY: obj_free(cmd->object); break; case CMD_OBJ_FLOWTABLE: @@ -1542,6 +1544,7 @@ static int do_command_add(struct netlink_ctx *ctx, struct cmd *cmd, bool excl) case CMD_OBJ_CT_EXPECT: case CMD_OBJ_LIMIT: case CMD_OBJ_SECMARK: + case CMD_OBJ_SYNPROXY: return mnl_nft_obj_add(ctx, cmd, flags); case CMD_OBJ_FLOWTABLE: return mnl_nft_flowtable_add(ctx, cmd, flags); @@ -1627,6 +1630,8 @@ static int do_command_delete(struct netlink_ctx *ctx, struct cmd *cmd) return mnl_nft_obj_del(ctx, cmd, NFT_OBJECT_LIMIT); case CMD_OBJ_SECMARK: return mnl_nft_obj_del(ctx, cmd, NFT_OBJECT_SECMARK); + case CMD_OBJ_SYNPROXY: + return mnl_nft_obj_del(ctx, cmd, NFT_OBJECT_SYNPROXY); case CMD_OBJ_FLOWTABLE: return mnl_nft_flowtable_del(ctx, cmd); default: @@ -1778,6 +1783,22 @@ static void print_proto_timeout_policy(uint8_t l4, const uint32_t *timeout, nft_print(octx, " }%s", opts->stmt_separator); } +static const char *synproxy_sack_to_str(const uint32_t flags) +{ + if (flags & NF_SYNPROXY_OPT_SACK_PERM) + return "sack-perm"; + + return ""; +} + +static const char *synproxy_timestamp_to_str(const uint32_t flags) +{ + if (flags & NF_SYNPROXY_OPT_TIMESTAMP) + return "timestamp"; + + return ""; +} + static void obj_print_data(const struct obj *obj, struct print_fmt_options *opts, struct output_ctx *octx) @@ -1911,6 +1932,30 @@ static void obj_print_data(const struct obj *obj, nft_print(octx, "%s", opts->nl); } break; + case NFT_OBJECT_SYNPROXY: { + uint32_t flags = obj->synproxy.flags; + const char *sack_str = synproxy_sack_to_str(flags); + const char *ts_str = synproxy_timestamp_to_str(flags); + + nft_print(octx, " %s {", obj->handle.obj.name); + if (nft_output_handle(octx)) + nft_print(octx, " # handle %" PRIu64, obj->handle.handle.id); + + if (flags & NF_SYNPROXY_OPT_MSS) { + nft_print(octx, "%s%s%s", opts->nl, opts->tab, opts->tab); + nft_print(octx, "mss %u", obj->synproxy.mss); + } + if (flags & NF_SYNPROXY_OPT_WSCALE) { + nft_print(octx, "%s%s%s", opts->nl, opts->tab, opts->tab); + nft_print(octx, "wscale %u", obj->synproxy.wscale); + } + if (flags & (NF_SYNPROXY_OPT_TIMESTAMP | NF_SYNPROXY_OPT_SACK_PERM)) { + nft_print(octx, "%s%s%s", opts->nl, opts->tab, opts->tab); + nft_print(octx, "%s %s", ts_str, sack_str); + } + nft_print(octx, "%s", opts->stmt_separator); + } + break; default: nft_print(octx, " unknown {%s", opts->nl); break; @@ -1924,6 +1969,7 @@ static const char * const obj_type_name_array[] = { [NFT_OBJECT_LIMIT] = "limit", [NFT_OBJECT_CT_TIMEOUT] = "ct timeout", [NFT_OBJECT_SECMARK] = "secmark", + [NFT_OBJECT_SYNPROXY] = "synproxy", [NFT_OBJECT_CT_EXPECT] = "ct expectation", }; @@ -1941,6 +1987,7 @@ static uint32_t obj_type_cmd_array[NFT_OBJECT_MAX + 1] = { [NFT_OBJECT_LIMIT] = CMD_OBJ_LIMIT, [NFT_OBJECT_CT_TIMEOUT] = CMD_OBJ_CT_TIMEOUT, [NFT_OBJECT_SECMARK] = CMD_OBJ_SECMARK, + [NFT_OBJECT_SYNPROXY] = CMD_OBJ_SYNPROXY, [NFT_OBJECT_CT_EXPECT] = CMD_OBJ_CT_EXPECT, }; @@ -2308,6 +2355,9 @@ static int do_command_list(struct netlink_ctx *ctx, struct cmd *cmd) case CMD_OBJ_SECMARK: case CMD_OBJ_SECMARKS: return do_list_obj(ctx, cmd, NFT_OBJECT_SECMARK); + case CMD_OBJ_SYNPROXY: + case CMD_OBJ_SYNPROXYS: + return do_list_obj(ctx, cmd, NFT_OBJECT_SYNPROXY); case CMD_OBJ_FLOWTABLES: return do_list_flowtables(ctx, cmd); default: diff --git a/src/scanner.l b/src/scanner.l index fdf84ba..3de5a9e 100644 --- a/src/scanner.l +++ b/src/scanner.l @@ -330,6 +330,7 @@ addrstring ({macaddr}|{ip4addr}|{ip6addr}) "counters" { return COUNTERS; } "quotas" { return QUOTAS; } "limits" { return LIMITS; } +"synproxys" { return SYNPROXYS; } "log" { return LOG; } "prefix" { return PREFIX; } diff --git a/src/statement.c b/src/statement.c index 12689ee..5aa5b1e 100644 --- a/src/statement.c +++ b/src/statement.c @@ -209,6 +209,7 @@ static const char *objref_type[NFT_OBJECT_MAX + 1] = { [NFT_OBJECT_LIMIT] = "limit", [NFT_OBJECT_CT_TIMEOUT] = "ct timeout", [NFT_OBJECT_SECMARK] = "secmark", + [NFT_OBJECT_SYNPROXY] = "synproxy", [NFT_OBJECT_CT_EXPECT] = "ct expectation", }; diff --git a/tests/py/ip/objects.t b/tests/py/ip/objects.t index 35d0110..4f4f909 100644 --- a/tests/py/ip/objects.t +++ b/tests/py/ip/objects.t @@ -50,3 +50,7 @@ ct timeout set "cttime1";ok %ctexpect5 type ct expectation { protocol udp; dport 9876; timeout 2m; size 12; l3proto ip; };ok ct expectation set "ctexpect1";ok + +# synproxy +%synproxy1 type synproxy mss 1460 wscale 7;ok +%synproxy2 type synproxy mss 1460 wscale 7 timestamp sack-perm;ok -- 2.20.1