From patchwork Wed Jun 20 10:41:29 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: 932095 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="dmpShT/p"; dkim-atps=neutral Received: from vger.kernel.org (vger.kernel.org [209.132.180.67]) by ozlabs.org (Postfix) with ESMTP id 419hGC0LCRz9s7F for ; Wed, 20 Jun 2018 20:41:39 +1000 (AEST) Received: (majordomo@vger.kernel.org) by vger.kernel.org via listexpand id S1753952AbeFTKli (ORCPT ); Wed, 20 Jun 2018 06:41:38 -0400 Received: from mail-wr0-f194.google.com ([209.85.128.194]:45600 "EHLO mail-wr0-f194.google.com" rhost-flags-OK-OK-OK-OK) by vger.kernel.org with ESMTP id S1752871AbeFTKlg (ORCPT ); Wed, 20 Jun 2018 06:41:36 -0400 Received: by mail-wr0-f194.google.com with SMTP id o12-v6so2742338wrm.12 for ; Wed, 20 Jun 2018 03:41:35 -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=tGCTyHR3IbmsQQLmhRHtwYjIAPWOYMO0aMQyP3sYGow=; b=dmpShT/pL6wCQ0V57q/p9Q8z2Tn8BQLSBYD9LypgklWsDGJ5GH5+58rgborp2QeQxK 1sPuRlAub5y1nk6vvD8vjsyd0VGlOqetaHh3rXr3wz2WnSNHlc4sl/EWsm86z56O+Mg1 IfuGfbWLUP2rYiX83PkLaNA/O8J8/0aBTTNAK7iRFOldVqKmmG2gMijtsZquFoBmzqqM Sprx6wOr6wyQY8ZL8cw7031j/MidtKarrTDJGHHpArbZbyEs7sFFcN+f9c8Q4uOu1dbG HPt18EPmYgsSB8dVND6cOGmXmZMif5Z7SyHzX5l8D+FgofGBRsD7XtSCcfWHBds56/RX z29w== 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=tGCTyHR3IbmsQQLmhRHtwYjIAPWOYMO0aMQyP3sYGow=; b=dPFeBrVaO+hBolrRCtXlF2qFFjsANYzjaYOp9u2p3PsjPOfYqUr28jy/cMlM8w6UWR 9et8+Cg59QhHoo13K4g4ps2Q5PW6IlS5sCDtOuFOaRoN3Rq6T55sUYk/cOWIuYte1rwj VqnKNEKSsImVrLPW3V+15xBxq47Du3woFLKB8rW08jItd9YSMr6vlhIjC/zyRNMhqroO +m6IC7H6L3dkpQ0vDUNUbioeOmuq4CEvXeH9glOdeeLq3Ph360ZrYzwLKRDLWk0C9aaC yrURcikrgwuM/EJQVcNs53SmWty+5LPmlTTWTJcPwNzX1VhV+jauf+cpRUAFJT3TZnl6 Rqjg== X-Gm-Message-State: APt69E3eVuD1Slw744MXPbyNu7VGD6Dd7zfLQrOAmFpjk9jEJAZVNEZM 9U3XWX54NhXRMCasg/8n0D5UpRiz X-Google-Smtp-Source: ADUXVKJvleULx0azoyUY4Fpd+Vie6KFrx2BhWJSIQnm6feWh2K/C7OVlRn0561UUI6VNXvbY99nDAw== X-Received: by 2002:adf:fb92:: with SMTP id a18-v6mr16560803wrr.278.1529491295066; Wed, 20 Jun 2018 03:41:35 -0700 (PDT) Received: from ecklm-lapos.sch.bme.hu (ecklm-pi.sch.bme.hu. [152.66.210.28]) by smtp.gmail.com with ESMTPSA id y18-v6sm2198918wrl.53.2018.06.20.03.41.34 for (version=TLS1_2 cipher=ECDHE-RSA-AES128-GCM-SHA256 bits=128/128); Wed, 20 Jun 2018 03:41:34 -0700 (PDT) From: =?utf-8?b?TcOhdMOpIEVja2w=?= To: netfilter-devel@vger.kernel.org Subject: [PATCH nf-next] netfilter: Add native tproxy support for nf_tables Date: Wed, 20 Jun 2018 12:41:29 +0200 Message-Id: <20180620104130.2217-2-ecklm94@gmail.com> X-Mailer: git-send-email 2.17.1 In-Reply-To: <20180620104048.mcqwv5xeugiedeyj@sch.bme.hu> References: <20180620104048.mcqwv5xeugiedeyj@sch.bme.hu> MIME-Version: 1.0 Sender: netfilter-devel-owner@vger.kernel.org Precedence: bulk List-ID: X-Mailing-List: netfilter-devel@vger.kernel.org This patch is built on the commit not applied yet with the title: netfilter: Move nf_tproxy_assign_sock to nf_tproxy.h -- 8< -- A great portion of the code is taken from xt_TPROXY.c There are some changes compared to the iptables implementation: - tproxy statement is not terminal here - no transport protocol criterion is necessary to set target ip address Signed-off-by: Máté Eckl --- include/uapi/linux/netfilter/nf_tables.h | 16 ++ net/netfilter/Kconfig | 8 + net/netfilter/Makefile | 1 + net/netfilter/nft_tproxy.c | 297 +++++++++++++++++++++++ 4 files changed, 322 insertions(+) create mode 100644 net/netfilter/nft_tproxy.c diff --git a/include/uapi/linux/netfilter/nf_tables.h b/include/uapi/linux/netfilter/nf_tables.h index c9bf74b94f37..565e1cafb36f 100644 --- a/include/uapi/linux/netfilter/nf_tables.h +++ b/include/uapi/linux/netfilter/nf_tables.h @@ -1250,6 +1250,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/net/netfilter/Kconfig b/net/netfilter/Kconfig index 8abcefb8b418..39eed9f32fc8 100644 --- a/net/netfilter/Kconfig +++ b/net/netfilter/Kconfig @@ -631,6 +631,14 @@ config NFT_SOCKET This option allows matching for the presence or absence of a corresponding socket and its attributes. +config NFT_TPROXY + tristate "Netfilter nf_tables tproxy support" + depends on IPV6 || IPV6=n + select NF_TPROXY_IPV4 + select NF_TPROXY_IPV6 if NF_TABLES_IPV6 + help + This makes transparent proxy support available in nftables. + if NF_TABLES_NETDEV config NF_DUP_NETDEV diff --git a/net/netfilter/Makefile b/net/netfilter/Makefile index 44449389e527..2af51df46d71 100644 --- a/net/netfilter/Makefile +++ b/net/netfilter/Makefile @@ -104,6 +104,7 @@ obj-$(CONFIG_NFT_FIB_INET) += nft_fib_inet.o obj-$(CONFIG_NFT_FIB_NETDEV) += nft_fib_netdev.o obj-$(CONFIG_NF_OSF) += nf_osf.o obj-$(CONFIG_NFT_SOCKET) += nft_socket.o +obj-$(CONFIG_NFT_TPROXY) += nft_tproxy.o # nf_tables netdev obj-$(CONFIG_NFT_DUP_NETDEV) += nft_dup_netdev.o diff --git a/net/netfilter/nft_tproxy.c b/net/netfilter/nft_tproxy.c new file mode 100644 index 000000000000..bb756d633e1a --- /dev/null +++ b/net/netfilter/nft_tproxy.c @@ -0,0 +1,297 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +#include +#include +#include +#include +#include +#include +#include +#include + +struct nft_tproxy { + enum nft_registers sreg_addr:8; + enum nft_registers sreg_port:8; + u8 family; +}; + +static void nft_tproxy_eval_v4(const struct nft_expr *expr, + struct nft_regs *regs, + const struct nft_pktinfo *pkt) +{ + const struct nft_tproxy *priv = nft_expr_priv(expr); + struct sk_buff *skb = pkt->skb; + struct sock *sk = skb->sk; + const struct iphdr *iph = ip_hdr(skb); + struct udphdr _hdr, *hp; + __be32 taddr = 0; + __be16 tport = 0; + + hp = skb_header_pointer(skb, ip_hdrlen(skb), sizeof(_hdr), &_hdr); + if (!hp) + regs->verdict.code = NFT_BREAK; + + /* check if there's an ongoing connection on the packet + * addresses, this happens if the redirect already happened + * and the current packet belongs to an already established + * connection */ + sk = nf_tproxy_get_sock_v4(nft_net(pkt), skb, hp, iph->protocol, + iph->saddr, iph->daddr, + hp->source, hp->dest, + skb->dev, NF_TPROXY_LOOKUP_ESTABLISHED); + + if (priv->sreg_addr) + taddr = regs->data[priv->sreg_addr]; + taddr = nf_tproxy_laddr4(skb, taddr, iph->daddr); + + if (priv->sreg_port) { + tport = regs->data[priv->sreg_port]; + } + if (!tport) + tport = hp->dest; + + /* UDP has no TCP_TIME_WAIT state, so we never enter here */ + if (sk && sk->sk_state == TCP_TIME_WAIT) + /* reopening a TIME_WAIT connection needs special handling */ + sk = nf_tproxy_handle_time_wait4(nft_net(pkt), skb, taddr, tport, sk); + else if (!sk) + /* no, there's no established connection, check if + * there's a listener on the redirected addr/port */ + sk = nf_tproxy_get_sock_v4(nft_net(pkt), skb, hp, iph->protocol, + iph->saddr, taddr, + hp->source, tport, + skb->dev, NF_TPROXY_LOOKUP_LISTENER); + + if (sk && nf_tproxy_sk_is_transparent(sk)) { + nf_tproxy_assign_sock(skb, sk); + } +} + +#if IS_ENABLED(CONFIG_NF_TPROXY_IPV6) +static void nft_tproxy_eval_v6(const struct nft_expr *expr, + struct nft_regs *regs, + const struct nft_pktinfo *pkt) +{ + const struct nft_tproxy *priv = nft_expr_priv(expr); + struct sk_buff *skb = pkt->skb; + struct sock *sk = skb->sk; + const struct ipv6hdr *iph = ipv6_hdr(skb); + struct udphdr _hdr, *hp; + struct in6_addr taddr = {0}; + __be16 tport = 0; + int thoff = 0; + int l4proto; + + l4proto = ipv6_find_hdr(skb, &thoff, -1, NULL, NULL); + if (l4proto < 0) { + regs->verdict.code = NFT_BREAK; + return; + } + + hp = skb_header_pointer(skb, thoff, sizeof(_hdr), &_hdr); + if (hp == NULL) { + regs->verdict.code = NFT_BREAK; + return; + } + + /* check if there's an ongoing connection on the packet + * addresses, this happens if the redirect already happened + * and the current packet belongs to an already established + * connection */ + sk = nf_tproxy_get_sock_v6(nft_net(pkt), skb, thoff, hp, l4proto, + &iph->saddr, &iph->daddr, + hp->source, hp->dest, + nft_in(pkt), NF_TPROXY_LOOKUP_ESTABLISHED); + + if (priv->sreg_addr) + memcpy (&taddr, ®s->data[priv->sreg_addr], sizeof(taddr)); + taddr = *nf_tproxy_laddr6(skb, &taddr, &iph->daddr); + + if (priv->sreg_port) { + tport = regs->data[priv->sreg_port]; + } + if (!tport) + tport = hp->dest; + + /* UDP has no TCP_TIME_WAIT state, so we never enter here */ + if (sk && sk->sk_state == TCP_TIME_WAIT) { + /* reopening a TIME_WAIT connection needs special handling */ + sk = nf_tproxy_handle_time_wait6(skb, l4proto, thoff, + nft_net(pkt), + &taddr, + tport, + sk); + } + else if (!sk) + /* no there's no established connection, check if + * there's a listener on the redirected addr/port */ + sk = nf_tproxy_get_sock_v6(nft_net(pkt), skb, thoff, hp, + l4proto, &iph->saddr, &taddr, + hp->source, tport, + nft_in(pkt), NF_TPROXY_LOOKUP_LISTENER); + + /* NOTE: assign_sock consumes our sk reference */ + if (sk && nf_tproxy_sk_is_transparent(sk)) { + nf_tproxy_assign_sock(skb, sk); + return; + } + + regs->verdict.code = NFT_BREAK; +} +#endif + +static void nft_tproxy_eval(const struct nft_expr *expr, + struct nft_regs *regs, + const struct nft_pktinfo *pkt) +{ + const struct nft_tproxy *priv = nft_expr_priv(expr); + + switch (nft_pf(pkt)) { + case NFPROTO_IPV4: + switch (priv->family) { + case NFPROTO_IPV4: + case NFPROTO_INET: + nft_tproxy_eval_v4(expr, regs, pkt); + break; + default: + regs->verdict.code = NFT_BREAK; + break; + } + break; +#if IS_ENABLED(CONFIG_NF_TPROXY_IPV6) + case NFPROTO_IPV6: + switch (priv->family) { + case NFPROTO_IPV6: + case NFPROTO_INET: + nft_tproxy_eval_v6(expr, regs, pkt); + break; + default: + regs->verdict.code = NFT_BREAK; + break; + } + break; +#endif + } +} + +static const struct nla_policy nft_tproxy_policy[NFTA_TPROXY_MAX + 1] = { + [NFTA_TPROXY_FAMILY] = { .type = NLA_U32 }, + [NFTA_TPROXY_REG_ADDR] = { .type = NLA_U32 }, + [NFTA_TPROXY_REG_PORT] = { .type = NLA_U32 }, +}; + +static int nft_tproxy_init(const struct nft_ctx *ctx, + const struct nft_expr *expr, + const struct nlattr * const tb[]) +{ + struct nft_tproxy *priv = nft_expr_priv(expr); + unsigned int alen = 0, plen = 0; + int err; + + if (!tb[NFTA_TPROXY_FAMILY]) + return -EINVAL; + + switch(ctx->family) { + case NFPROTO_IPV4: +#if IS_ENABLED(CONFIG_NF_TPROXY_IPV6) + case NFPROTO_IPV6: +#endif + case NFPROTO_INET: + break; + default: + return -EOPNOTSUPP; + } + + priv->family = ntohl(nla_get_be32(tb[NFTA_TPROXY_FAMILY])); + if ((priv->family == NFPROTO_IPV4 && ctx->family == NFPROTO_IPV6) || + (priv->family == NFPROTO_IPV6 && ctx->family == NFPROTO_IPV4)) + return -EINVAL; + + switch (priv->family) { + case NFPROTO_IPV4: + alen = FIELD_SIZEOF(union nf_inet_addr, in); + break; +#if IS_ENABLED(CONFIG_NF_TPROXY_IPV6) + case NFPROTO_IPV6: + alen = FIELD_SIZEOF(union nf_inet_addr, in6); + break; +#endif + case NFPROTO_INET: + /* No address is specified here */ + break; + default: + return -EOPNOTSUPP; + } + + if (tb[NFTA_TPROXY_REG_ADDR]) { + priv->sreg_addr = nft_parse_register(tb[NFTA_TPROXY_REG_ADDR]); + err = nft_validate_register_load(priv->sreg_addr, alen); + if (err < 0) + return err; + } + + plen = sizeof(u16); + if (tb[NFTA_TPROXY_REG_PORT]) { + priv->sreg_port = nft_parse_register(tb[NFTA_TPROXY_REG_PORT]); + err = nft_validate_register_load(priv->sreg_port, plen); + if (err < 0) + return err; + } + + return 0; +} + +static int nft_tproxy_dump(struct sk_buff *skb, + const struct nft_expr *expr) +{ + const struct nft_tproxy *priv = nft_expr_priv(expr); + + if(nla_put_be32(skb, NFTA_TPROXY_FAMILY, htonl(priv->family))) + return -1; + + if (priv->sreg_addr) { + if (nft_dump_register(skb, NFTA_TPROXY_REG_ADDR, priv->sreg_addr)) + return -1; + } + + if (priv->sreg_port) { + if (nft_dump_register(skb, NFTA_TPROXY_REG_PORT, priv->sreg_port)) + return -1; + } + + return 0; +} + +static struct nft_expr_type nft_tproxy_type; +static const struct nft_expr_ops nft_tproxy_ops = { + .type = &nft_tproxy_type, + .size = NFT_EXPR_SIZE(sizeof(struct nft_tproxy)), + .eval = nft_tproxy_eval, + .init = nft_tproxy_init, + .dump = nft_tproxy_dump, +}; + +static struct nft_expr_type nft_tproxy_type __read_mostly = { + .name = "tproxy", + .ops = &nft_tproxy_ops, + .policy = nft_tproxy_policy, + .maxattr = NFTA_TPROXY_MAX, + .owner = THIS_MODULE, +}; + +static int __init nft_tproxy_module_init(void) +{ + return nft_register_expr(&nft_tproxy_type); +} + +static void __exit nft_tproxy_module_exit(void) +{ + nft_unregister_expr(&nft_tproxy_type); +} + +module_init(nft_tproxy_module_init); +module_exit(nft_tproxy_module_exit); + +MODULE_LICENSE("GPL"); +MODULE_AUTHOR("Máté Eckl"); +MODULE_DESCRIPTION("nf_tables tproxy support module"); +MODULE_ALIAS_NFT_EXPR("tproxy");