From patchwork Fri Jun 29 14:38:47 2018 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 8bit X-Patchwork-Submitter: =?utf-8?b?TcOhdMOpIEVja2w=?= X-Patchwork-Id: 936946 X-Patchwork-Delegate: pablo@netfilter.org Return-Path: X-Original-To: incoming@patchwork.ozlabs.org Delivered-To: patchwork-incoming@bilbo.ozlabs.org Authentication-Results: ozlabs.org; spf=none (mailfrom) smtp.mailfrom=vger.kernel.org (client-ip=209.132.180.67; helo=vger.kernel.org; envelope-from=netfilter-devel-owner@vger.kernel.org; receiver=) Authentication-Results: ozlabs.org; dmarc=fail (p=none dis=none) header.from=gmail.com Authentication-Results: ozlabs.org; dkim=fail reason="signature verification failed" (2048-bit key; unprotected) header.d=gmail.com header.i=@gmail.com header.b="qJFlBJWk"; dkim-atps=neutral Received: from vger.kernel.org (vger.kernel.org [209.132.180.67]) by ozlabs.org (Postfix) with ESMTP id 41HK5t5X5Dz9ryk for ; Sat, 30 Jun 2018 00:38:58 +1000 (AEST) Received: (majordomo@vger.kernel.org) by vger.kernel.org via listexpand id S932377AbeF2Oi5 (ORCPT ); Fri, 29 Jun 2018 10:38:57 -0400 Received: from mail-wm0-f66.google.com ([74.125.82.66]:33763 "EHLO mail-wm0-f66.google.com" rhost-flags-OK-OK-OK-OK) by vger.kernel.org with ESMTP id S932131AbeF2Oi4 (ORCPT ); Fri, 29 Jun 2018 10:38:56 -0400 Received: by mail-wm0-f66.google.com with SMTP id z6-v6so2186421wma.0 for ; Fri, 29 Jun 2018 07:38:55 -0700 (PDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=gmail.com; s=20161025; h=from:to:subject:date:message-id:in-reply-to:references:mime-version :content-transfer-encoding; bh=9Ko87yF1Go8mBeFbLfV8WCQgDMRSlBiI0LR1oluNhz4=; b=qJFlBJWkO4wsvKpK23+UJauWu+MPWoampMot6dKZtfIaZrSMaVvcb+kzKtVNqgWcQd z2N9y8zrHPhM1MTX0ht3ODN0N+NvGghZBieFiU+n7jo0raGOj70wxik+qivYv6a+sOUP EjT3XI6hWQ7XJVaXbI4JagPuHgfuTkk3aQRSYBOvRnkuEQYqapevHEdUrQPo8Y0qA2Va z1b1p+cQBgq4NMDgCYg3se/rDyBlvXgrKn8KTqFZ2/16qQpp/bRcGLHnjCzjy+uf9fXf vDowYwM7X6eLafLVENEn5/YMvuYHG0c76EsrSjNHKGWExbOpsiU7/6ptVVBJWVW+aJuP vVbA== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20161025; h=x-gm-message-state:from:to:subject:date:message-id:in-reply-to :references:mime-version:content-transfer-encoding; bh=9Ko87yF1Go8mBeFbLfV8WCQgDMRSlBiI0LR1oluNhz4=; b=JehitrKhF2fYyxriI9zxVO9s6CDXHcAAvXfvli/qg+bcHb/hIgsTLcKYMrbf6goNoh GfBs5PgJ+qSIWiLPuEld2LGOF1Q/5SuEw1zuzoWUVM0qn0JpxdXUgKTM8rnuKLC/zRri KAThF4WpyV/GZ8pK8B64AFx16dm5McCeTOxj3X+I9TG+QupT+PMkelLSVIlrsY+wQ1ix V/Tbg9gqwwEuXxmxwT72uEfcpTUCHy8IY8OKzgowOxGLYkp4WYdTvT+iQoM8s/aHgGa2 hOdovVtQTR5EVjJSeEoe1JtRQZcr3APdgShuqR3iFB1pqfREWjyGSmiBP4nUGhPdRXIy tRaw== X-Gm-Message-State: APt69E2eAIKAYDy+TCidFWT6svGWKuBbmqrBh3bYOmhVym4TTR4MPXKC WZBtvCXvnN62UdLGsTYi4llycRMm X-Google-Smtp-Source: AAOMgpfewwQNq9r/zZpe1txOJ9R5oVLg2EpyoVQ9If+32FmpsbkaUjYw72aib9ZDpCUqKykRDXdCZw== X-Received: by 2002:a1c:e043:: with SMTP id x64-v6mr2045816wmg.58.1530283134581; Fri, 29 Jun 2018 07:38:54 -0700 (PDT) Received: from ecklm-lapos.localdomain (ecklm-pi.sch.bme.hu. [152.66.210.28]) by smtp.gmail.com with ESMTPSA id l67-v6sm2322015wmb.22.2018.06.29.07.38.54 for (version=TLS1_2 cipher=ECDHE-RSA-AES128-GCM-SHA256 bits=128/128); Fri, 29 Jun 2018 07:38:54 -0700 (PDT) From: =?utf-8?b?TcOhdMOpIEVja2w=?= To: netfilter-devel@vger.kernel.org Subject: [PATCH v2 nft] Add tproxy support Date: Fri, 29 Jun 2018 16:38:47 +0200 Message-Id: <20180629143847.28956-1-ecklm94@gmail.com> X-Mailer: git-send-email 2.18.0 In-Reply-To: <20180620104130.2217-3-ecklm94@gmail.com> References: <20180620104130.2217-3-ecklm94@gmail.com> MIME-Version: 1.0 Sender: netfilter-devel-owner@vger.kernel.org Precedence: bulk List-ID: X-Mailing-List: netfilter-devel@vger.kernel.org v2: - tproxy statement without arguments is not supported - Add transport protocol matching criterion to address evaluation. - Specify network layer protocol in inet tables -- 8< -- This patch adds support for transparent proxy functionality which is supported in ip, ip6 and inet tables. The syntax is the following: tproxy [{|ip|ip6}] to {|:|:} It looks for a socket listening on the specified address or port and assigns it to the matching packet. In an inet table, a packet matches for both families until address is specified. Network protocol family has to be specified **only** in inet tables if address is specified. As transparent proxy support is implemented for sockets with layer 4 information, a transport protocol header criterion has to be set in the same rule. eg. 'meta l4proto tcp' or 'udp dport 4444' Example ruleset: table ip x { chain y { type filter hook prerouting priority -150; policy accept; tcp dport ntp tproxy to 1.1.1.1 udp dport ssh tproxy to :2222 } } table ip6 x { chain y { type filter hook prerouting priority -150; policy accept; tcp dport ntp tproxy to [dead::beef] udp dport ssh tproxy to :2222 } } table inet x { chain y { type filter hook prerouting priority -150; policy accept; tcp dport 321 tproxy to :ssh tcp dport 99 tproxy ip to 1.1.1.1:999 udp dport 155 tproxy ip6 to [dead::beef]:smux } } Signed-off-by: Máté Eckl --- include/linux/netfilter/nf_tables.h | 16 ++++++ include/statement.h | 11 ++++ src/evaluate.c | 84 +++++++++++++++++++++++++++++ src/netlink_delinearize.c | 53 ++++++++++++++++++ src/netlink_linearize.c | 41 ++++++++++++++ src/parser_bison.y | 44 +++++++++++++++ src/scanner.l | 2 + src/statement.c | 45 ++++++++++++++++ 8 files changed, 296 insertions(+) diff --git a/include/linux/netfilter/nf_tables.h b/include/linux/netfilter/nf_tables.h index 88e0ca1..d98cebb 100644 --- a/include/linux/netfilter/nf_tables.h +++ b/include/linux/netfilter/nf_tables.h @@ -1231,6 +1231,22 @@ enum nft_nat_attributes { }; #define NFTA_NAT_MAX (__NFTA_NAT_MAX - 1) +/** + * enum nft_tproxy_attributes - nf_tables tproxy expression netlink attributes + * + * NFTA_TPROXY_FAMILY: Target address family (NLA_U32: nft_registers) + * NFTA_TPROXY_REG_ADDR: Target address register (NLA_U32: nft_registers) + * NFTA_TPROXY_REG_PORT: Target port register (NLA_U32: nft_registers) + */ +enum nft_tproxy_attributes { + NFTA_TPROXY_UNSPEC, + NFTA_TPROXY_FAMILY, + NFTA_TPROXY_REG_ADDR, + NFTA_TPROXY_REG_PORT, + __NFTA_TPROXY_MAX +}; +#define NFTA_TPROXY_MAX (__NFTA_TPROXY_MAX - 1) + /** * enum nft_masq_attributes - nf_tables masquerade expression attributes * diff --git a/include/statement.h b/include/statement.h index 5a907aa..7840e9d 100644 --- a/include/statement.h +++ b/include/statement.h @@ -128,6 +128,15 @@ struct nat_stmt { extern struct stmt *nat_stmt_alloc(const struct location *loc, enum nft_nat_etypes type); +struct tproxy_stmt { + struct expr *addr; + struct expr *port; + uint8_t family; + uint8_t table_family; /* only used for printing the rule */ +}; + +extern struct stmt *tproxy_stmt_alloc(const struct location *loc); + struct queue_stmt { struct expr *queue; uint16_t flags; @@ -271,6 +280,7 @@ enum stmt_types { STMT_LOG, STMT_REJECT, STMT_NAT, + STMT_TPROXY, STMT_QUEUE, STMT_CT, STMT_SET, @@ -337,6 +347,7 @@ struct stmt { struct limit_stmt limit; struct reject_stmt reject; struct nat_stmt nat; + struct tproxy_stmt tproxy; struct queue_stmt queue; struct quota_stmt quota; struct ct_stmt ct; diff --git a/src/evaluate.c b/src/evaluate.c index 9ff2c0b..bde38ce 100644 --- a/src/evaluate.c +++ b/src/evaluate.c @@ -2481,6 +2481,88 @@ static int stmt_evaluate_nat(struct eval_ctx *ctx, struct stmt *stmt) return 0; } +static int stmt_evaluate_tproxy(struct eval_ctx *ctx, struct stmt *stmt) +{ + const struct datatype *dtype; + int err, len; + + switch (ctx->pctx.family) { + case NFPROTO_IPV4: + case NFPROTO_IPV6: + case NFPROTO_INET: + break; + default: + return stmt_error(ctx, stmt, + "tproxy is only supported for IPv4/IPv6/INET"); + } + + if (ctx->pctx.protocol[PROTO_BASE_TRANSPORT_HDR].desc == NULL) + return stmt_error(ctx, stmt, "Transparent proxy support requires" + " transport protocol match"); + + if (!stmt->tproxy.addr && !stmt->tproxy.port) + return stmt_error(ctx, stmt, "Either address or port must be specified!"); + + if (ctx->pctx.family != NFPROTO_INET) { + if (stmt->tproxy.family != NFPROTO_UNSPEC) + return stmt_error(ctx, stmt, "Family can only be specified in inet tables."); + stmt->tproxy.family = ctx->pctx.family; + } + else { + const struct proto_desc *nproto = + ctx->pctx.protocol[PROTO_BASE_NETWORK_HDR].desc; + if ((nproto == &proto_ip && stmt->tproxy.family == NFPROTO_IPV6) || + (nproto == &proto_ip6 && stmt->tproxy.family == NFPROTO_IPV4)) + /* this prevents us from rules like + * ip protocol tcp tproxy ip6 to [dead::beef] + */ + return stmt_error(ctx, stmt, + "Conflicting network layer protocols."); + } + + if (stmt->tproxy.addr != NULL) { + if (stmt->tproxy.addr->ops->type == EXPR_RANGE) + return stmt_error(ctx, stmt, "Address ranges are not supported for tproxy."); + if (ctx->pctx.family == NFPROTO_INET) { + switch (stmt->tproxy.family) { + case NFPROTO_IPV4: + dtype = &ipaddr_type; + len = 4 * BITS_PER_BYTE; + break; + case NFPROTO_IPV6: + dtype = &ip6addr_type; + len = 16 * BITS_PER_BYTE; + break; + default: + return stmt_error(ctx, stmt, + "Family must be specified in tproxy statement with address for inet tables."); + } + err = stmt_evaluate_arg(ctx, stmt, dtype, len, + BYTEORDER_BIG_ENDIAN, + &stmt->tproxy.addr); + if (err < 0) + return err; + } + else { + err = evaluate_addr(ctx, stmt, &stmt->tproxy.addr); + if (err < 0) + return err; + } + } + + if (stmt->tproxy.port != NULL) { + if (stmt->tproxy.port->ops->type == EXPR_RANGE) + return stmt_error(ctx, stmt, "Port ranges are not suppoerted for tproxy."); + if (stmt->tproxy.family == NFPROTO_UNSPEC) + stmt->tproxy.family = NFPROTO_INET; + err = nat_evaluate_transport(ctx, stmt, &stmt->tproxy.port); + if (err < 0) + return err; + } + + return 0; +} + static int stmt_evaluate_dup(struct eval_ctx *ctx, struct stmt *stmt) { int err; @@ -2764,6 +2846,8 @@ int stmt_evaluate(struct eval_ctx *ctx, struct stmt *stmt) return stmt_evaluate_reject(ctx, stmt); case STMT_NAT: return stmt_evaluate_nat(ctx, stmt); + case STMT_TPROXY: + return stmt_evaluate_tproxy(ctx, stmt); case STMT_QUEUE: return stmt_evaluate_queue(ctx, stmt); case STMT_DUP: diff --git a/src/netlink_delinearize.c b/src/netlink_delinearize.c index 31d6242..b811135 100644 --- a/src/netlink_delinearize.c +++ b/src/netlink_delinearize.c @@ -969,6 +969,50 @@ out_err: xfree(stmt); } +static void netlink_parse_tproxy(struct netlink_parse_ctx *ctx, + const struct location *loc, + const struct nftnl_expr *nle) +{ + struct stmt *stmt; + struct expr *addr, *port; + enum nft_registers reg; + + stmt = tproxy_stmt_alloc(loc); + stmt->tproxy.family = nftnl_expr_get_u32(nle, NFTNL_EXPR_TPROXY_FAMILY); + stmt->tproxy.table_family = ctx->table->handle.family; + + reg = netlink_parse_register(nle, NFTNL_EXPR_TPROXY_REG_ADDR); + if (reg) { + addr = netlink_get_register(ctx, loc, reg); + + switch (stmt->tproxy.family) { + case NFPROTO_IPV4: + expr_set_type(addr, &ipaddr_type, BYTEORDER_BIG_ENDIAN); + break; + case NFPROTO_IPV6: + expr_set_type(addr, &ip6addr_type, BYTEORDER_BIG_ENDIAN); + break; + default: + netlink_error(ctx, loc, + "tproxy address must be IPv4 or IPv6"); + goto err; + } + stmt->tproxy.addr = addr; + } + + reg = netlink_parse_register(nle, NFTNL_EXPR_TPROXY_REG_PORT); + if (reg) { + port = netlink_get_register(ctx, loc, reg); + expr_set_type(port, &inet_service_type, BYTEORDER_BIG_ENDIAN); + stmt->tproxy.port = port; + } + + ctx->stmt = stmt; + return; +err: + xfree(stmt); +} + static void netlink_parse_masq(struct netlink_parse_ctx *ctx, const struct location *loc, const struct nftnl_expr *nle) @@ -1362,6 +1406,7 @@ static const struct { { .name = "range", .parse = netlink_parse_range }, { .name = "reject", .parse = netlink_parse_reject }, { .name = "nat", .parse = netlink_parse_nat }, + { .name = "tproxy", .parse = netlink_parse_tproxy }, { .name = "notrack", .parse = netlink_parse_notrack }, { .name = "masq", .parse = netlink_parse_masq }, { .name = "redir", .parse = netlink_parse_redir }, @@ -2432,6 +2477,14 @@ static void rule_parse_postprocess(struct netlink_parse_ctx *ctx, struct rule *r expr_postprocess(&rctx, &stmt->nat.proto); } break; + case STMT_TPROXY: + if (stmt->tproxy.addr) + expr_postprocess(&rctx, &stmt->tproxy.addr); + if (stmt->tproxy.port) { + payload_dependency_reset(&rctx.pdctx); + expr_postprocess(&rctx, &stmt->tproxy.port); + } + break; case STMT_REJECT: stmt_reject_postprocess(&rctx); break; diff --git a/src/netlink_linearize.c b/src/netlink_linearize.c index 8471e83..aa00564 100644 --- a/src/netlink_linearize.c +++ b/src/netlink_linearize.c @@ -1071,6 +1071,45 @@ static void netlink_gen_nat_stmt(struct netlink_linearize_ctx *ctx, nftnl_rule_add_expr(ctx->nlr, nle); } +static void netlink_gen_tproxy_stmt(struct netlink_linearize_ctx *ctx, + const struct stmt *stmt) +{ + struct nftnl_expr *nle; + enum nft_registers addr_reg; + enum nft_registers port_reg; + int registers = 0; + const int family = stmt->tproxy.family; + int nftnl_reg_port; + + nle = alloc_nft_expr("tproxy"); + + nftnl_expr_set_u32(nle, NFTNL_EXPR_TPROXY_FAMILY, family); + + nftnl_reg_port = NFTNL_EXPR_TPROXY_REG_PORT; + + if (stmt->tproxy.addr) { + addr_reg = get_register(ctx, NULL); + registers++; + netlink_gen_expr(ctx, stmt->tproxy.addr, addr_reg); + netlink_put_register(nle, NFTNL_EXPR_TPROXY_REG_ADDR, + addr_reg); + } + + if (stmt->tproxy.port) { + port_reg = get_register(ctx, NULL); + registers++; + netlink_gen_expr(ctx, stmt->tproxy.port, port_reg); + netlink_put_register(nle, nftnl_reg_port, port_reg); + } + + while (registers > 0) { + release_register(ctx, NULL); + registers--; + } + + nftnl_rule_add_expr(ctx->nlr, nle); +} + static void netlink_gen_dup_stmt(struct netlink_linearize_ctx *ctx, const struct stmt *stmt) { @@ -1301,6 +1340,8 @@ static void netlink_gen_stmt(struct netlink_linearize_ctx *ctx, return netlink_gen_reject_stmt(ctx, stmt); case STMT_NAT: return netlink_gen_nat_stmt(ctx, stmt); + case STMT_TPROXY: + return netlink_gen_tproxy_stmt(ctx, stmt); case STMT_DUP: return netlink_gen_dup_stmt(ctx, stmt); case STMT_QUEUE: diff --git a/src/parser_bison.y b/src/parser_bison.y index 98bfeba..fe3c10b 100644 --- a/src/parser_bison.y +++ b/src/parser_bison.y @@ -192,6 +192,8 @@ int nft_lex(void *, void *, void *); %token SOCKET "socket" %token TRANSPARENT "transparent" +%token TPROXY "tproxy" + %token HOOK "hook" %token DEVICE "device" %token DEVICES "devices" @@ -572,6 +574,9 @@ int nft_lex(void *, void *, void *); %type nat_stmt nat_stmt_alloc masq_stmt masq_stmt_alloc redir_stmt redir_stmt_alloc %destructor { stmt_free($$); } nat_stmt nat_stmt_alloc masq_stmt masq_stmt_alloc redir_stmt redir_stmt_alloc %type nf_nat_flags nf_nat_flag offset_opt +%type tproxy_stmt +%destructor { stmt_free($$); } tproxy_stmt +%type tproxy_family_spec %type queue_stmt queue_stmt_alloc %destructor { stmt_free($$); } queue_stmt queue_stmt_alloc %type queue_stmt_flags queue_stmt_flag @@ -2082,6 +2087,7 @@ stmt : verdict_stmt | quota_stmt | reject_stmt | nat_stmt + | tproxy_stmt | queue_stmt | ct_stmt | masq_stmt @@ -2477,6 +2483,44 @@ nat_stmt_alloc : SNAT { $$ = nat_stmt_alloc(&@$, NFT_NAT_SNAT); } | DNAT { $$ = nat_stmt_alloc(&@$, NFT_NAT_DNAT); } ; +tproxy_family_spec : IP { $$ = NFPROTO_IPV4; } + | IP6 { $$ = NFPROTO_IPV6; } + ; + +tproxy_stmt : TPROXY TO stmt_expr + { + $$ = tproxy_stmt_alloc(&@$); + $$->tproxy.family = NFPROTO_UNSPEC; + $$->tproxy.addr = $3; + } + | TPROXY tproxy_family_spec TO stmt_expr + { + $$ = tproxy_stmt_alloc(&@$); + $$->tproxy.family = $2; + $$->tproxy.addr = $4; + } + | TPROXY TO COLON stmt_expr + { + $$ = tproxy_stmt_alloc(&@$); + $$->tproxy.family = NFPROTO_UNSPEC; + $$->tproxy.port = $4; + } + | TPROXY TO stmt_expr COLON stmt_expr + { + $$ = tproxy_stmt_alloc(&@$); + $$->tproxy.family = NFPROTO_UNSPEC; + $$->tproxy.addr = $3; + $$->tproxy.port = $5; + } + | TPROXY tproxy_family_spec TO stmt_expr COLON stmt_expr + { + $$ = tproxy_stmt_alloc(&@$); + $$->tproxy.family = $2; + $$->tproxy.addr = $4; + $$->tproxy.port = $6; + } + ; + primary_stmt_expr : symbol_expr { $$ = $1; } | integer_expr { $$ = $1; } | boolean_expr { $$ = $1; } diff --git a/src/scanner.l b/src/scanner.l index ed01b5e..703700f 100644 --- a/src/scanner.l +++ b/src/scanner.l @@ -261,6 +261,8 @@ addrstring ({macaddr}|{ip4addr}|{ip6addr}) "socket" { return SOCKET; } "transparent" { return TRANSPARENT;} +"tproxy" { return TPROXY; } + "accept" { return ACCEPT; } "drop" { return DROP; } "continue" { return CONTINUE; } diff --git a/src/statement.c b/src/statement.c index 6f5e666..56d8d80 100644 --- a/src/statement.c +++ b/src/statement.c @@ -760,6 +760,51 @@ struct stmt *fwd_stmt_alloc(const struct location *loc) return stmt_alloc(loc, &fwd_stmt_ops); } +static void tproxy_stmt_print(const struct stmt *stmt, struct output_ctx *octx) +{ + nft_print(octx, "tproxy"); + + if (stmt->tproxy.table_family == NFPROTO_INET && + stmt->tproxy.family != NFPROTO_INET) + nft_print(octx, " %s", nfproto_family_name(stmt->tproxy.family)); + nft_print(octx, " to"); + if (stmt->tproxy.addr) { + nft_print(octx, " "); + if (stmt->tproxy.addr->ops->type == EXPR_VALUE && + stmt->tproxy.addr->dtype->type == TYPE_IP6ADDR) { + nft_print(octx, "["); + expr_print(stmt->tproxy.addr, octx); + nft_print(octx, "]"); + } else { + expr_print(stmt->tproxy.addr, octx); + } + } + if (stmt->tproxy.port && stmt->tproxy.port->ops->type == EXPR_VALUE) { + if (!stmt->tproxy.addr) + nft_print(octx, " "); + nft_print(octx, ":"); + expr_print(stmt->tproxy.port, octx); + } +} + +static void tproxy_stmt_destroy(struct stmt *stmt) +{ + expr_free(stmt->tproxy.addr); + expr_free(stmt->tproxy.port); +} + +static const struct stmt_ops tproxy_stmt_ops = { + .type = STMT_TPROXY, + .name = "tproxy", + .print = tproxy_stmt_print, + .destroy = tproxy_stmt_destroy, +}; + +struct stmt *tproxy_stmt_alloc(const struct location *loc) +{ + return stmt_alloc(loc, &tproxy_stmt_ops); +} + static void xt_stmt_print(const struct stmt *stmt, struct output_ctx *octx) { xt_stmt_xlate(stmt);