Skip to content

Commit

Permalink
netfilter: nf_tables: Add synproxy support
Browse files Browse the repository at this point in the history
Add synproxy support for nf_tables. This behaves like the iptables
synproxy target but it is structured in a way that allows us to propose
improvements in the future.

Signed-off-by: Fernando Fernandez Mancera <ffmancera@riseup.net>
Signed-off-by: Pablo Neira Ayuso <pablo@netfilter.org>
  • Loading branch information
ffmancera authored and ummakynes committed Jul 5, 2019
1 parent 6f7b841 commit ad49d86
Show file tree
Hide file tree
Showing 7 changed files with 325 additions and 0 deletions.
1 change: 1 addition & 0 deletions include/net/netfilter/nf_conntrack_synproxy.h
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
#ifndef _NF_CONNTRACK_SYNPROXY_H
#define _NF_CONNTRACK_SYNPROXY_H

#include <net/netfilter/nf_conntrack_seqadj.h>
#include <net/netns/generic.h>

struct nf_conn_synproxy {
Expand Down
5 changes: 5 additions & 0 deletions include/net/netfilter/nf_synproxy.h
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,11 @@ unsigned int ipv6_synproxy_hook(void *priv, struct sk_buff *skb,
const struct nf_hook_state *nhs);
int nf_synproxy_ipv6_init(struct synproxy_net *snet, struct net *net);
void nf_synproxy_ipv6_fini(struct synproxy_net *snet, struct net *net);
#else
static inline int
nf_synproxy_ipv6_init(struct synproxy_net *snet, struct net *net) { return 0; }
static inline void
nf_synproxy_ipv6_fini(struct synproxy_net *snet, struct net *net) {};
#endif /* CONFIG_IPV6 */

#endif /* _NF_SYNPROXY_SHARED_H */
4 changes: 4 additions & 0 deletions include/uapi/linux/netfilter/nf_synproxy.h
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,10 @@
#define NF_SYNPROXY_OPT_SACK_PERM 0x04
#define NF_SYNPROXY_OPT_TIMESTAMP 0x08
#define NF_SYNPROXY_OPT_ECN 0x10
#define NF_SYNPROXY_OPT_MASK (NF_SYNPROXY_OPT_MSS | \
NF_SYNPROXY_OPT_WSCALE | \
NF_SYNPROXY_OPT_SACK_PERM | \
NF_SYNPROXY_OPT_TIMESTAMP)

struct nf_synproxy_info {
__u8 options;
Expand Down
16 changes: 16 additions & 0 deletions include/uapi/linux/netfilter/nf_tables.h
Original file line number Diff line number Diff line change
Expand Up @@ -1551,6 +1551,22 @@ enum nft_osf_flags {
NFT_OSF_F_VERSION = (1 << 0),
};

/**
* enum nft_synproxy_attributes - nf_tables synproxy expression netlink attributes
*
* @NFTA_SYNPROXY_MSS: mss value sent to the backend (NLA_U16)
* @NFTA_SYNPROXY_WSCALE: wscale value sent to the backend (NLA_U8)
* @NFTA_SYNPROXY_FLAGS: flags (NLA_U32)
*/
enum nft_synproxy_attributes {
NFTA_SYNPROXY_UNSPEC,
NFTA_SYNPROXY_MSS,
NFTA_SYNPROXY_WSCALE,
NFTA_SYNPROXY_FLAGS,
__NFTA_SYNPROXY_MAX,
};
#define NFTA_SYNPROXY_MAX (__NFTA_SYNPROXY_MAX - 1)

/**
* enum nft_device_attributes - nf_tables device netlink attributes
*
Expand Down
11 changes: 11 additions & 0 deletions net/netfilter/Kconfig
Original file line number Diff line number Diff line change
Expand Up @@ -651,6 +651,17 @@ config NFT_TPROXY
help
This makes transparent proxy support available in nftables.

config NFT_SYNPROXY
tristate "Netfilter nf_tables SYNPROXY expression support"
depends on NF_CONNTRACK && NETFILTER_ADVANCED
select NETFILTER_SYNPROXY
select SYN_COOKIES
help
The SYNPROXY expression allows you to intercept TCP connections and
establish them using syncookies before they are passed on to the
server. This allows to avoid conntrack and server resource usage
during SYN-flood attacks.

if NF_TABLES_NETDEV

config NF_DUP_NETDEV
Expand Down
1 change: 1 addition & 0 deletions net/netfilter/Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -110,6 +110,7 @@ obj-$(CONFIG_NFT_SOCKET) += nft_socket.o
obj-$(CONFIG_NFT_OSF) += nft_osf.o
obj-$(CONFIG_NFT_TPROXY) += nft_tproxy.o
obj-$(CONFIG_NFT_XFRM) += nft_xfrm.o
obj-$(CONFIG_NFT_SYNPROXY) += nft_synproxy.o

obj-$(CONFIG_NFT_NAT) += nft_chain_nat.o

Expand Down
287 changes: 287 additions & 0 deletions net/netfilter/nft_synproxy.c
Original file line number Diff line number Diff line change
@@ -0,0 +1,287 @@
// SPDX-License-Identifier: GPL-2.0
#include <linux/types.h>
#include <net/ip.h>
#include <net/tcp.h>
#include <net/netlink.h>
#include <net/netfilter/nf_tables.h>
#include <net/netfilter/nf_conntrack.h>
#include <net/netfilter/nf_conntrack_synproxy.h>
#include <net/netfilter/nf_synproxy.h>
#include <linux/netfilter/nf_tables.h>
#include <linux/netfilter/nf_synproxy.h>

struct nft_synproxy {
struct nf_synproxy_info info;
};

static const struct nla_policy nft_synproxy_policy[NFTA_SYNPROXY_MAX + 1] = {
[NFTA_SYNPROXY_MSS] = { .type = NLA_U16 },
[NFTA_SYNPROXY_WSCALE] = { .type = NLA_U8 },
[NFTA_SYNPROXY_FLAGS] = { .type = NLA_U32 },
};

static void nft_synproxy_tcp_options(struct synproxy_options *opts,
const struct tcphdr *tcp,
struct synproxy_net *snet,
struct nf_synproxy_info *info,
struct nft_synproxy *priv)
{
this_cpu_inc(snet->stats->syn_received);
if (tcp->ece && tcp->cwr)
opts->options |= NF_SYNPROXY_OPT_ECN;

opts->options &= priv->info.options;
if (opts->options & NF_SYNPROXY_OPT_TIMESTAMP)
synproxy_init_timestamp_cookie(info, opts);
else
opts->options &= ~(NF_SYNPROXY_OPT_WSCALE |
NF_SYNPROXY_OPT_SACK_PERM |
NF_SYNPROXY_OPT_ECN);
}

static void nft_synproxy_eval_v4(const struct nft_expr *expr,
struct nft_regs *regs,
const struct nft_pktinfo *pkt,
const struct tcphdr *tcp,
struct tcphdr *_tcph,
struct synproxy_options *opts)
{
struct nft_synproxy *priv = nft_expr_priv(expr);
struct nf_synproxy_info info = priv->info;
struct net *net = nft_net(pkt);
struct synproxy_net *snet = synproxy_pernet(net);
struct sk_buff *skb = pkt->skb;

if (tcp->syn) {
/* Initial SYN from client */
nft_synproxy_tcp_options(opts, tcp, snet, &info, priv);
synproxy_send_client_synack(net, skb, tcp, opts);
consume_skb(skb);
regs->verdict.code = NF_STOLEN;
} else if (tcp->ack) {
/* ACK from client */
if (synproxy_recv_client_ack(net, skb, tcp, opts,
ntohl(tcp->seq))) {
consume_skb(skb);
regs->verdict.code = NF_STOLEN;
} else {
regs->verdict.code = NF_DROP;
}
}
}

#if IS_ENABLED(CONFIG_NF_TABLES_IPV6)
static void nft_synproxy_eval_v6(const struct nft_expr *expr,
struct nft_regs *regs,
const struct nft_pktinfo *pkt,
const struct tcphdr *tcp,
struct tcphdr *_tcph,
struct synproxy_options *opts)
{
struct nft_synproxy *priv = nft_expr_priv(expr);
struct nf_synproxy_info info = priv->info;
struct net *net = nft_net(pkt);
struct synproxy_net *snet = synproxy_pernet(net);
struct sk_buff *skb = pkt->skb;

if (tcp->syn) {
/* Initial SYN from client */
nft_synproxy_tcp_options(opts, tcp, snet, &info, priv);
synproxy_send_client_synack_ipv6(net, skb, tcp, opts);
consume_skb(skb);
regs->verdict.code = NF_STOLEN;
} else if (tcp->ack) {
/* ACK from client */
if (synproxy_recv_client_ack_ipv6(net, skb, tcp, opts,
ntohl(tcp->seq))) {
consume_skb(skb);
regs->verdict.code = NF_STOLEN;
} else {
regs->verdict.code = NF_DROP;
}
}
}
#endif /* CONFIG_NF_TABLES_IPV6*/

static void nft_synproxy_eval(const struct nft_expr *expr,
struct nft_regs *regs,
const struct nft_pktinfo *pkt)
{
struct synproxy_options opts = {};
struct sk_buff *skb = pkt->skb;
int thoff = pkt->xt.thoff;
const struct tcphdr *tcp;
struct tcphdr _tcph;

if (pkt->tprot != IPPROTO_TCP) {
regs->verdict.code = NFT_BREAK;
return;
}

if (nf_ip_checksum(skb, nft_hook(pkt), thoff, IPPROTO_TCP)) {
regs->verdict.code = NF_DROP;
return;
}

tcp = skb_header_pointer(skb, pkt->xt.thoff,
sizeof(struct tcphdr),
&_tcph);
if (!tcp) {
regs->verdict.code = NF_DROP;
return;
}

if (!synproxy_parse_options(skb, thoff, tcp, &opts)) {
regs->verdict.code = NF_DROP;
return;
}

switch (skb->protocol) {
case htons(ETH_P_IP):
nft_synproxy_eval_v4(expr, regs, pkt, tcp, &_tcph, &opts);
return;
#if IS_ENABLED(CONFIG_NF_TABLES_IPV6)
case htons(ETH_P_IPV6):
nft_synproxy_eval_v6(expr, regs, pkt, tcp, &_tcph, &opts);
return;
#endif
}
regs->verdict.code = NFT_BREAK;
}

static int nft_synproxy_init(const struct nft_ctx *ctx,
const struct nft_expr *expr,
const struct nlattr * const tb[])
{
struct synproxy_net *snet = synproxy_pernet(ctx->net);
struct nft_synproxy *priv = nft_expr_priv(expr);
u32 flags;
int err;

if (tb[NFTA_SYNPROXY_MSS])
priv->info.mss = ntohs(nla_get_be16(tb[NFTA_SYNPROXY_MSS]));
if (tb[NFTA_SYNPROXY_WSCALE])
priv->info.wscale = nla_get_u8(tb[NFTA_SYNPROXY_WSCALE]);
if (tb[NFTA_SYNPROXY_FLAGS]) {
flags = ntohl(nla_get_be32(tb[NFTA_SYNPROXY_FLAGS]));
if (flags & ~NF_SYNPROXY_OPT_MASK)
return -EOPNOTSUPP;
priv->info.options = flags;
}

err = nf_ct_netns_get(ctx->net, ctx->family);
if (err)
return err;

switch (ctx->family) {
case NFPROTO_IPV4:
err = nf_synproxy_ipv4_init(snet, ctx->net);
if (err)
goto nf_ct_failure;
break;
#if IS_ENABLED(CONFIG_NF_TABLES_IPV6)
case NFPROTO_IPV6:
err = nf_synproxy_ipv6_init(snet, ctx->net);
if (err)
goto nf_ct_failure;
break;
#endif
case NFPROTO_INET:
case NFPROTO_BRIDGE:
err = nf_synproxy_ipv4_init(snet, ctx->net);
if (err)
goto nf_ct_failure;
err = nf_synproxy_ipv6_init(snet, ctx->net);
if (err)
goto nf_ct_failure;
break;
}

return 0;

nf_ct_failure:
nf_ct_netns_put(ctx->net, ctx->family);
return err;
}

static void nft_synproxy_destroy(const struct nft_ctx *ctx,
const struct nft_expr *expr)
{
struct synproxy_net *snet = synproxy_pernet(ctx->net);

switch (ctx->family) {
case NFPROTO_IPV4:
nf_synproxy_ipv4_fini(snet, ctx->net);
break;
#if IS_ENABLED(CONFIG_NF_TABLES_IPV6)
case NFPROTO_IPV6:
nf_synproxy_ipv6_fini(snet, ctx->net);
break;
#endif
case NFPROTO_INET:
case NFPROTO_BRIDGE:
nf_synproxy_ipv4_fini(snet, ctx->net);
nf_synproxy_ipv6_fini(snet, ctx->net);
break;
}
nf_ct_netns_put(ctx->net, ctx->family);
}

static int nft_synproxy_dump(struct sk_buff *skb, const struct nft_expr *expr)
{
const struct nft_synproxy *priv = nft_expr_priv(expr);

if (nla_put_be16(skb, NFTA_SYNPROXY_MSS, htons(priv->info.mss)) ||
nla_put_u8(skb, NFTA_SYNPROXY_WSCALE, priv->info.wscale) ||
nla_put_be32(skb, NFTA_SYNPROXY_FLAGS, htonl(priv->info.options)))
goto nla_put_failure;

return 0;

nla_put_failure:
return -1;
}

static int nft_synproxy_validate(const struct nft_ctx *ctx,
const struct nft_expr *expr,
const struct nft_data **data)
{
return nft_chain_validate_hooks(ctx->chain, (1 << NF_INET_LOCAL_IN) |
(1 << NF_INET_FORWARD));
}

static struct nft_expr_type nft_synproxy_type;
static const struct nft_expr_ops nft_synproxy_ops = {
.eval = nft_synproxy_eval,
.size = NFT_EXPR_SIZE(sizeof(struct nft_synproxy)),
.init = nft_synproxy_init,
.destroy = nft_synproxy_destroy,
.dump = nft_synproxy_dump,
.type = &nft_synproxy_type,
.validate = nft_synproxy_validate,
};

static struct nft_expr_type nft_synproxy_type __read_mostly = {
.ops = &nft_synproxy_ops,
.name = "synproxy",
.owner = THIS_MODULE,
.policy = nft_synproxy_policy,
.maxattr = NFTA_SYNPROXY_MAX,
};

static int __init nft_synproxy_module_init(void)
{
return nft_register_expr(&nft_synproxy_type);
}

static void __exit nft_synproxy_module_exit(void)
{
return nft_unregister_expr(&nft_synproxy_type);
}

module_init(nft_synproxy_module_init);
module_exit(nft_synproxy_module_exit);

MODULE_LICENSE("GPL");
MODULE_AUTHOR("Fernando Fernandez <ffmancera@riseup.net>");
MODULE_ALIAS_NFT_EXPR("synproxy");

0 comments on commit ad49d86

Please sign in to comment.