11541Srgrimes// SPDX-License-Identifier: GPL-2.0-only
21541Srgrimes/*
31541Srgrimes * Copyright (c) 2014 Arturo Borrero Gonzalez <arturo@debian.org>
41541Srgrimes */
51541Srgrimes
61541Srgrimes#include <linux/kernel.h>
71541Srgrimes#include <linux/init.h>
81541Srgrimes#include <linux/module.h>
91541Srgrimes#include <linux/netlink.h>
101541Srgrimes#include <linux/netfilter.h>
111541Srgrimes#include <linux/netfilter/nf_tables.h>
121541Srgrimes#include <net/netfilter/nf_nat.h>
131541Srgrimes#include <net/netfilter/nf_nat_redirect.h>
141541Srgrimes#include <net/netfilter/nf_tables.h>
151541Srgrimes
161541Srgrimesstruct nft_redir {
171541Srgrimes	u8			sreg_proto_min;
181541Srgrimes	u8			sreg_proto_max;
191541Srgrimes	u16			flags;
201541Srgrimes};
211541Srgrimes
221541Srgrimesstatic const struct nla_policy nft_redir_policy[NFTA_REDIR_MAX + 1] = {
231541Srgrimes	[NFTA_REDIR_REG_PROTO_MIN]	= { .type = NLA_U32 },
241541Srgrimes	[NFTA_REDIR_REG_PROTO_MAX]	= { .type = NLA_U32 },
251541Srgrimes	[NFTA_REDIR_FLAGS]		=
261541Srgrimes		NLA_POLICY_MASK(NLA_BE32, NF_NAT_RANGE_MASK),
271541Srgrimes};
281541Srgrimes
291541Srgrimesstatic int nft_redir_validate(const struct nft_ctx *ctx,
3050477Speter			      const struct nft_expr *expr,
311541Srgrimes			      const struct nft_data **data)
321541Srgrimes{
332165Spaul	int err;
342165Spaul
352165Spaul	err = nft_chain_validate_dependency(ctx->chain, NFT_CHAIN_T_NAT);
3623888Swollman	if (err < 0)
3712453Sbde		return err;
3883366Sjulian
3912453Sbde	return nft_chain_validate_hooks(ctx->chain,
4012453Sbde					(1 << NF_INET_PRE_ROUTING) |
4138482Swollman					(1 << NF_INET_LOCAL_OUT));
4212453Sbde}
4355205Speter
441541Srgrimesstatic int nft_redir_init(const struct nft_ctx *ctx,
451541Srgrimes			  const struct nft_expr *expr,
461541Srgrimes			  const struct nlattr * const tb[])
471541Srgrimes{
481541Srgrimes	struct nft_redir *priv = nft_expr_priv(expr);
491541Srgrimes	unsigned int plen;
501541Srgrimes	int err;
511541Srgrimes
521541Srgrimes	plen = sizeof_field(struct nf_nat_range, min_proto.all);
531541Srgrimes	if (tb[NFTA_REDIR_REG_PROTO_MIN]) {
541541Srgrimes		err = nft_parse_register_load(tb[NFTA_REDIR_REG_PROTO_MIN],
551541Srgrimes					      &priv->sreg_proto_min, plen);
561541Srgrimes		if (err < 0)
571541Srgrimes			return err;
5838482Swollman
591541Srgrimes		if (tb[NFTA_REDIR_REG_PROTO_MAX]) {
601541Srgrimes			err = nft_parse_register_load(tb[NFTA_REDIR_REG_PROTO_MAX],
611541Srgrimes						      &priv->sreg_proto_max,
621541Srgrimes						      plen);
6338482Swollman			if (err < 0)
6438482Swollman				return err;
651541Srgrimes		} else {
6681501Sjulian			priv->sreg_proto_max = priv->sreg_proto_min;
6781501Sjulian		}
6882824Sjulian
6981501Sjulian		priv->flags |= NF_NAT_RANGE_PROTO_SPECIFIED;
7081501Sjulian	}
7182824Sjulian
7281501Sjulian	if (tb[NFTA_REDIR_FLAGS])
7381501Sjulian		priv->flags = ntohl(nla_get_be32(tb[NFTA_REDIR_FLAGS]));
7481501Sjulian
7581501Sjulian	return nf_ct_netns_get(ctx->net, ctx->family);
7681501Sjulian}
7781501Sjulian
7881501Sjulianstatic int nft_redir_dump(struct sk_buff *skb,
7982824Sjulian			  const struct nft_expr *expr, bool reset)
8083366Sjulian{
8181501Sjulian	const struct nft_redir *priv = nft_expr_priv(expr);
821541Srgrimes
831541Srgrimes	if (priv->sreg_proto_min) {
841541Srgrimes		if (nft_dump_register(skb, NFTA_REDIR_REG_PROTO_MIN,
851541Srgrimes				      priv->sreg_proto_min))
861541Srgrimes			goto nla_put_failure;
871541Srgrimes		if (nft_dump_register(skb, NFTA_REDIR_REG_PROTO_MAX,
8881501Sjulian				      priv->sreg_proto_max))
8981501Sjulian			goto nla_put_failure;
9081501Sjulian	}
9181501Sjulian
921541Srgrimes	if (priv->flags != 0 &&
9382824Sjulian	    nla_put_be32(skb, NFTA_REDIR_FLAGS, htonl(priv->flags)))
941541Srgrimes			goto nla_put_failure;
9581501Sjulian
9681501Sjulian	return 0;
9781501Sjulian
9881501Sjuliannla_put_failure:
9981501Sjulian	return -1;
10017047Swollman}
1011541Srgrimes
10228270Swollmanstatic void nft_redir_eval(const struct nft_expr *expr,
1031541Srgrimes			   struct nft_regs *regs,
1041541Srgrimes			   const struct nft_pktinfo *pkt)
1051541Srgrimes{
1061541Srgrimes	const struct nft_redir *priv = nft_expr_priv(expr);
1071541Srgrimes	struct nf_nat_range2 range;
108136692Sandre
109136692Sandre	memset(&range, 0, sizeof(range));
110136692Sandre	range.flags = priv->flags;
111136692Sandre	if (priv->sreg_proto_min) {
112136692Sandre		range.min_proto.all = (__force __be16)
113136692Sandre			nft_reg_load16(&regs->data[priv->sreg_proto_min]);
1141541Srgrimes		range.max_proto.all = (__force __be16)
1151541Srgrimes			nft_reg_load16(&regs->data[priv->sreg_proto_max]);
1161541Srgrimes	}
1176223Swollman
1186223Swollman	switch (nft_pf(pkt)) {
1196223Swollman	case NFPROTO_IPV4:
1206223Swollman		regs->verdict.code = nf_nat_redirect_ipv4(pkt->skb, &range,
1211541Srgrimes							  nft_hook(pkt));
1221541Srgrimes		break;
1231541Srgrimes#ifdef CONFIG_NF_TABLES_IPV6
1241541Srgrimes	case NFPROTO_IPV6:
1251541Srgrimes		regs->verdict.code = nf_nat_redirect_ipv6(pkt->skb, &range,
1261541Srgrimes							  nft_hook(pkt));
1276223Swollman		break;
12878064Sume#endif
1291541Srgrimes	default:
1301541Srgrimes		WARN_ON_ONCE(1);
1311541Srgrimes		break;
1321541Srgrimes	}
1331541Srgrimes}
134108533Sschweikh
1351541Srgrimesstatic void
1361541Srgrimesnft_redir_ipv4_destroy(const struct nft_ctx *ctx, const struct nft_expr *expr)
1371541Srgrimes{
1381541Srgrimes	nf_ct_netns_put(ctx->net, NFPROTO_IPV4);
1391541Srgrimes}
1401541Srgrimes
1411541Srgrimesstatic struct nft_expr_type nft_redir_ipv4_type;
1421541Srgrimesstatic const struct nft_expr_ops nft_redir_ipv4_ops = {
1431541Srgrimes	.type		= &nft_redir_ipv4_type,
1441541Srgrimes	.size		= NFT_EXPR_SIZE(sizeof(struct nft_redir)),
1451541Srgrimes	.eval		= nft_redir_eval,
1461541Srgrimes	.init		= nft_redir_init,
1471541Srgrimes	.destroy	= nft_redir_ipv4_destroy,
1481541Srgrimes	.dump		= nft_redir_dump,
1491541Srgrimes	.validate	= nft_redir_validate,
1501541Srgrimes	.reduce		= NFT_REDUCE_READONLY,
1511541Srgrimes};
1521541Srgrimes
1531541Srgrimesstatic struct nft_expr_type nft_redir_ipv4_type __read_mostly = {
1541541Srgrimes	.family		= NFPROTO_IPV4,
1551541Srgrimes	.name		= "redir",
1561541Srgrimes	.ops		= &nft_redir_ipv4_ops,
1571541Srgrimes	.policy		= nft_redir_policy,
1581541Srgrimes	.maxattr	= NFTA_REDIR_MAX,
1591541Srgrimes	.owner		= THIS_MODULE,
1601541Srgrimes};
1611541Srgrimes
1621541Srgrimes#ifdef CONFIG_NF_TABLES_IPV6
1631541Srgrimesstatic void
1641541Srgrimesnft_redir_ipv6_destroy(const struct nft_ctx *ctx, const struct nft_expr *expr)
16517047Swollman{
1666223Swollman	nf_ct_netns_put(ctx->net, NFPROTO_IPV6);
1676223Swollman}
1681541Srgrimes
1691541Srgrimesstatic struct nft_expr_type nft_redir_ipv6_type;
170101975Salfredstatic const struct nft_expr_ops nft_redir_ipv6_ops = {
1711541Srgrimes	.type		= &nft_redir_ipv6_type,
1721541Srgrimes	.size		= NFT_EXPR_SIZE(sizeof(struct nft_redir)),
1731541Srgrimes	.eval		= nft_redir_eval,
1741541Srgrimes	.init		= nft_redir_init,
1751541Srgrimes	.destroy	= nft_redir_ipv6_destroy,
1761541Srgrimes	.dump		= nft_redir_dump,
1776223Swollman	.validate	= nft_redir_validate,
1781541Srgrimes	.reduce		= NFT_REDUCE_READONLY,
1791541Srgrimes};
1801541Srgrimes
18155205Speterstatic struct nft_expr_type nft_redir_ipv6_type __read_mostly = {
18217047Swollman	.family		= NFPROTO_IPV6,
18332995Sbde	.name		= "redir",
18432995Sbde	.ops		= &nft_redir_ipv6_ops,
18532995Sbde	.policy		= nft_redir_policy,
18632995Sbde	.maxattr	= NFTA_REDIR_MAX,
18732995Sbde	.owner		= THIS_MODULE,
1881541Srgrimes};
18917047Swollman#endif
19025201Swollman
19123888Swollman#ifdef CONFIG_NF_TABLES_INET
19223888Swollmanstatic void
193137386Sphknft_redir_inet_destroy(const struct nft_ctx *ctx, const struct nft_expr *expr)
194137386Sphk{
195137386Sphk	nf_ct_netns_put(ctx->net, NFPROTO_INET);
19617047Swollman}
19717047Swollman
198137386Sphkstatic struct nft_expr_type nft_redir_inet_type;
199157366Srwatsonstatic const struct nft_expr_ops nft_redir_inet_ops = {
20092719Salfred	.type		= &nft_redir_inet_type,
20192719Salfred	.size		= NFT_EXPR_SIZE(sizeof(struct nft_redir)),
20292719Salfred	.eval		= nft_redir_eval,
20393008Sbde	.init		= nft_redir_init,
20492719Salfred	.destroy	= nft_redir_inet_destroy,
20593008Sbde	.dump		= nft_redir_dump,
20692719Salfred	.validate	= nft_redir_validate,
20792719Salfred	.reduce		= NFT_REDUCE_READONLY,
20893008Sbde};
209157370Srwatson
21092719Salfredstatic struct nft_expr_type nft_redir_inet_type __read_mostly = {
211151888Srwatson	.family		= NFPROTO_INET,
212151888Srwatson	.name		= "redir",
21392719Salfred	.ops		= &nft_redir_inet_ops,
21492719Salfred	.policy		= nft_redir_policy,
21592719Salfred	.maxattr	= NFTA_REDIR_MAX,
21692719Salfred	.owner		= THIS_MODULE,
21793008Sbde};
21893008Sbde
21917047Swollmanstatic int __init nft_redir_module_init_inet(void)
22017047Swollman{
22142902Sfenner	return nft_register_expr(&nft_redir_inet_type);
22292719Salfred}
22392719Salfred#else
22492719Salfredstatic inline int nft_redir_module_init_inet(void) { return 0; }
22523888Swollman#endif
22623888Swollman
22725201Swollmanstatic int __init nft_redir_module_init(void)
22823888Swollman{
22923888Swollman	int ret = nft_register_expr(&nft_redir_ipv4_type);
23025201Swollman
23125201Swollman	if (ret)
23225201Swollman		return ret;
23323888Swollman
23492719Salfred#ifdef CONFIG_NF_TABLES_IPV6
23593008Sbde	ret = nft_register_expr(&nft_redir_ipv6_type);
23693008Sbde	if (ret) {
23793008Sbde		nft_unregister_expr(&nft_redir_ipv4_type);
23893008Sbde		return ret;
23993008Sbde	}
24092719Salfred#endif
24193008Sbde
242122875Srwatson	ret = nft_redir_module_init_inet();
24317047Swollman	if (ret < 0) {
24417047Swollman		nft_unregister_expr(&nft_redir_ipv4_type);
245136692Sandre#ifdef CONFIG_NF_TABLES_IPV6
246148965Sobrien		nft_unregister_expr(&nft_redir_ipv6_type);
247136692Sandre#endif
248136692Sandre		return ret;
249157366Srwatson	}
25092719Salfred
251136692Sandre	return ret;
252136692Sandre}
253136692Sandre
25492719Salfredstatic void __exit nft_redir_module_exit(void)
25593008Sbde{
25692719Salfred	nft_unregister_expr(&nft_redir_ipv4_type);
25792719Salfred#ifdef CONFIG_NF_TABLES_IPV6
25893008Sbde	nft_unregister_expr(&nft_redir_ipv6_type);
259157370Srwatson#endif
260136692Sandre#ifdef CONFIG_NF_TABLES_INET
261151888Srwatson	nft_unregister_expr(&nft_redir_inet_type);
262136692Sandre#endif
26392719Salfred}
26492719Salfred
265136692Sandremodule_init(nft_redir_module_init);
266136692Sandremodule_exit(nft_redir_module_exit);
26792719Salfred
268136692SandreMODULE_LICENSE("GPL");
269136692SandreMODULE_AUTHOR("Arturo Borrero Gonzalez <arturo@debian.org>");
270136692SandreMODULE_ALIAS_NFT_EXPR("redir");
271136692SandreMODULE_DESCRIPTION("Netfilter nftables redirect support");
272136692Sandre