diff --git a/include/uapi/linux/netfilter_ipv4/ipt_NATTYPE.h b/include/uapi/linux/netfilter_ipv4/ipt_NATTYPE.h new file mode 100644 index 000000000000..f7e917b4d3a5 --- /dev/null +++ b/include/uapi/linux/netfilter_ipv4/ipt_NATTYPE.h @@ -0,0 +1,32 @@ +/* SPDX-License-Identifier: GPL-2.0-only WITH Linux-syscall-note */ +/* + * Copyright (c) 2016-2020, The Linux Foundation. All rights reserved. + */ + +#include +#ifndef _IPT_NATTYPE_H_target +#define _IPT_NATTYPE_H_target + +#define NATTYPE_TIMEOUT 300 + +enum nattype_mode { + MODE_DNAT, + MODE_FORWARD_IN, + MODE_FORWARD_OUT +}; + +enum nattype_type { + TYPE_PORT_ADDRESS_RESTRICTED, + TYPE_ENDPOINT_INDEPENDENT, + TYPE_ADDRESS_RESTRICTED +}; + + +struct ipt_nattype_info { + __u16 mode; + __u16 type; + __u32 padding; +}; + +#endif /*_IPT_NATTYPE_H_target*/ + diff --git a/net/ipv4/netfilter/Kconfig b/net/ipv4/netfilter/Kconfig index f17b402111ce..bdcf4e69d26f 100644 --- a/net/ipv4/netfilter/Kconfig +++ b/net/ipv4/netfilter/Kconfig @@ -230,6 +230,17 @@ config IP_NF_TARGET_MASQUERADE This is a backwards-compat option for the user's convenience (e.g. when running oldconfig). It selects NETFILTER_XT_TARGET_MASQUERADE. +config IP_NF_TARGET_NATTYPE_MODULE + tristate "NATTYPE target support" + depends on NF_NAT + default m if NETFILTER_ADVANCED=n + help + NATTYPE is a special case of NAT: used to support FULL Cone NAT + and ADDRESS Restricted Cone NAT. All incoming connections are + allowed if there is an outgoing connection using that port. + + To compile it as a module, choose M here. If unsure, say N. + config IP_NF_TARGET_NETMAP tristate "NETMAP target support" depends on NETFILTER_ADVANCED diff --git a/net/ipv4/netfilter/Makefile b/net/ipv4/netfilter/Makefile index 7c497c78105f..3459e263e55f 100644 --- a/net/ipv4/netfilter/Makefile +++ b/net/ipv4/netfilter/Makefile @@ -48,6 +48,7 @@ obj-$(CONFIG_IP_NF_MATCH_RPFILTER) += ipt_rpfilter.o # targets obj-$(CONFIG_IP_NF_TARGET_CLUSTERIP) += ipt_CLUSTERIP.o obj-$(CONFIG_IP_NF_TARGET_ECN) += ipt_ECN.o +obj-$(CONFIG_IP_NF_TARGET_NATTYPE_MODULE) += ipt_NATTYPE.o obj-$(CONFIG_IP_NF_TARGET_REJECT) += ipt_REJECT.o obj-$(CONFIG_IP_NF_TARGET_SYNPROXY) += ipt_SYNPROXY.o diff --git a/net/ipv4/netfilter/ipt_NATTYPE.c b/net/ipv4/netfilter/ipt_NATTYPE.c new file mode 100644 index 000000000000..32c23204ff61 --- /dev/null +++ b/net/ipv4/netfilter/ipt_NATTYPE.c @@ -0,0 +1,559 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* Copyright (c) 2016-2020, The Linux Foundation. All rights reserved. */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#if !defined(NATTYPE_DEBUG) +#define DEBUGP(type, args...) +#else +static const char * const types[] = {"TYPE_PORT_ADDRESS_RESTRICTED", + "TYPE_ENDPOINT_INDEPENDENT", + "TYPE_ADDRESS_RESTRICTED"}; +static const char * const modes[] = {"MODE_DNAT", "MODE_FORWARD_IN", + "MODE_FORWARD_OUT"}; +#define DEBUGP(args...) pr_debug(args) +#endif + +struct ipt_nattype { + struct list_head list; + struct timer_list timeout; + unsigned short proto; /* Protocol: TCP or UDP */ + struct nf_nat_ipv4_range range; /* LAN side src info*/ + unsigned short nat_port; /* Routed NAT port */ + unsigned int dest_addr; /* Original egress packets dst addr */ + unsigned short dest_port;/* Original egress packets destination port */ +}; + +static LIST_HEAD(nattype_list); +static DEFINE_SPINLOCK(nattype_lock); + +/* netfilter NATTYPE + * nattype_nte_debug_print() + */ +static void nattype_nte_debug_print(const struct ipt_nattype *nte, + const char *s) +{ + DEBUGP("%p: %s - proto[%d], src[%pI4:%d], nat[:%d], dest[%pI4:%d]\n", + nte, s, nte->proto, + &nte->range.min_ip, ntohs(nte->range.min.all), + ntohs(nte->nat_port), + &nte->dest_addr, ntohs(nte->dest_port)); +} + +/* netfilter NATTYPE nattype_free() + * Free the object. + */ +static void nattype_free(struct ipt_nattype *nte) +{ + nattype_nte_debug_print(nte, "free"); + kfree(nte); +} + +/* netfilter NATTYPE nattype_refresh_timer() + * Refresh the timer for this object. + */ +static bool nattype_refresh_timer(struct ipt_nattype *nte) +{ + if (del_timer(&nte->timeout)) { + nte->timeout.expires = jiffies + NATTYPE_TIMEOUT * HZ; + add_timer(&nte->timeout); + return true; + } + return false; +} + +/* netfilter NATTYPE nattype_timer_timeout() + * The timer has gone off, self-destruct + */ +static void nattype_timer_timeout(unsigned long in_nattype) +{ + struct ipt_nattype *nte = (void *)in_nattype; + + /* netfilter NATTYPE + * The race with list deletion is solved by ensuring + * that either this code or the list deletion code + * but not both will remove the oject. + */ + nattype_nte_debug_print(nte, "timeout"); + spin_lock_bh(&nattype_lock); + list_del(&nte->list); + spin_unlock_bh(&nattype_lock); + nattype_free(nte); +} + +/* netfilter NATTYPE nattype_packet_in_match() + * Ingress packet, try to match with this nattype entry. + */ +static bool nattype_packet_in_match(const struct ipt_nattype *nte, + struct sk_buff *skb, + const struct ipt_nattype_info *info) +{ + const struct iphdr *iph = ip_hdr(skb); + u16 dst_port = 0; + + /* If the protocols are not the same, no sense in looking + * further. + */ + if (nte->proto != iph->protocol) { + DEBUGP("%s: protocol failed: nte proto:", __func__); + DEBUGP(" %d, packet proto: %d\n", + nte->proto, iph->protocol); + return false; + } + + /* In ADDRESS_RESTRICT, the egress destination must match the source + * of this ingress packet. + */ + if (info->type == TYPE_ADDRESS_RESTRICTED) { + if (nte->dest_addr != iph->saddr) { + DEBUGP("%s: dest/src check", __func__); + DEBUGP(" failed: dest_addr: %pI4, src dest: %pI4\n", + &nte->dest_addr, &iph->saddr); + return false; + } + } + + /* Obtain the destination port value for TCP or UDP. The nattype + * entries are stored in native (not host). + */ + if (iph->protocol == IPPROTO_TCP) { + struct tcphdr _tcph; + struct tcphdr *tcph; + + tcph = skb_header_pointer(skb, ip_hdrlen(skb), + sizeof(_tcph), &_tcph); + if (!tcph) + return false; + dst_port = tcph->dest; + } else if (iph->protocol == IPPROTO_UDP) { + struct udphdr _udph; + struct udphdr *udph; + + udph = skb_header_pointer(skb, ip_hdrlen(skb), + sizeof(_udph), &_udph); + if (!udph) + return false; + dst_port = udph->dest; + } + + /* Our NAT port must match the ingress pacekt's + * destination packet. + */ + if (nte->nat_port != dst_port) { + DEBUGP("%s fail: ", __func__); + DEBUGP(" nat port: %d,dest_port: %d\n", + ntohs(nte->nat_port), ntohs(dst_port)); + return false; + } + + /* In either EI or AR mode, the ingress packet's src port + * can be anything. + */ + nattype_nte_debug_print(nte, "INGRESS MATCH"); + return true; +} + +/* netfilter NATTYPE nattype_compare + * Compare two entries, return true if relevant fields are the same. + */ +static bool nattype_compare(struct ipt_nattype *n1, struct ipt_nattype *n2) +{ + /* netfilter NATTYPE Protocol + * compare. + */ + if (n1->proto != n2->proto) { + DEBUGP("%s: protocol mismatch: %d:%d\n", + __func__, n1->proto, n2->proto); + return false; + } + + /* netfilter NATTYPE LAN Source compare. + * Since we always keep min/max values the same, + * just compare the min values. + */ + if (n1->range.min_ip != n2->range.min_ip) { + DEBUGP("%s: r.min_ip mismatch: %pI4:%pI4\n", + __func__, &n1->range.min_ip, &n2->range.min_ip); + return false; + } + + if (n1->range.min.all != n2->range.min.all) { + DEBUGP("%s: r.min mismatch: %d:%d\n", + __func__, + ntohs(n1->range.min.all), + ntohs(n2->range.min.all)); + return false; + } + + /* netfilter NATTYPE + * NAT port + */ + if (n1->nat_port != n2->nat_port) { + DEBUGP("%s: nat_port mistmatch: %d:%d\n", + __func__, ntohs(n1->nat_port), ntohs(n2->nat_port)); + return false; + } + + /* netfilter NATTYPE + * Destination compare + */ + if (n1->dest_addr != n2->dest_addr) { + DEBUGP("%s: dest_addr mismatch: %pI4:%pI4\n", + __func__, &n1->dest_addr, &n2->dest_addr); + return false; + } + + if (n1->dest_port != n2->dest_port) { + DEBUGP("%s: dest_port mismatch: %d:%d\n", + __func__, ntohs(n1->dest_port), ntohs(n2->dest_port)); + return false; + } + return true; +} + + /** + * netfilter NATTYPE nattype_nat() + * Ingress packet on PRE_ROUTING hook, find match, update conntrack + * to allow + **/ +static unsigned int nattype_nat(struct sk_buff *skb, + const struct xt_action_param *par) +{ + struct ipt_nattype *nte; + + if (par->hooknum != NF_INET_PRE_ROUTING) + return XT_CONTINUE; + spin_lock_bh(&nattype_lock); + list_for_each_entry(nte, &nattype_list, list) { + struct nf_conn *ct; + enum ip_conntrack_info ctinfo; + struct nf_nat_ipv4_range newrange; + unsigned int ret; + + if (!nattype_packet_in_match(nte, skb, par->targinfo)) + continue; + + /* Copy the LAN source data into the ingress' pacekts + * conntrack in the reply direction. + */ + newrange = nte->range; + spin_unlock_bh(&nattype_lock); + + /* netfilter NATTYPE Find the + * ingress packet's conntrack. + */ + ct = nf_ct_get(skb, &ctinfo); + if (!ct) { + DEBUGP("ingress packet conntrack not found\n"); + return XT_CONTINUE; + } + + /* Expand the ingress conntrack + * to include the reply as source + */ + DEBUGP("Expand ingress conntrack=%p, type=%d, src[%pI4:%d]\n", + ct, ctinfo, &newrange.min_ip, ntohs(newrange.min.all)); + ret = nf_nat_setup_info(ct, &newrange, NF_NAT_MANIP_DST); + DEBUGP("Expand returned: %d\n", ret); + return ret; + } + spin_unlock_bh(&nattype_lock); + return XT_CONTINUE; +} + +/* netfilter NATTYPE nattype_forward() + * Ingress and Egress packet forwarding hook + */ +static unsigned int nattype_forward(struct sk_buff *skb, + const struct xt_action_param *par) +{ + const struct iphdr *iph = ip_hdr(skb); + void *protoh = (void *)iph + iph->ihl * 4; + struct ipt_nattype *nte; + struct ipt_nattype *nte2; + struct nf_conn *ct; + enum ip_conntrack_info ctinfo; + const struct ipt_nattype_info *info = par->targinfo; + u16 nat_port; + + if (par->hooknum != NF_INET_FORWARD) + return XT_CONTINUE; + + /* Ingress packet, + * refresh the timer if we find an entry. + */ + if (info->mode == MODE_FORWARD_IN) { + spin_lock_bh(&nattype_lock); + list_for_each_entry(nte, &nattype_list, list) { + /* netfilter NATTYPE + * Compare the ingress packet with the existing + * entries looking for a match. + */ + if (!nattype_packet_in_match(nte, skb, info)) + continue; + + /* netfilter NATTYPE + * Refresh the timer, if we fail, break + * out and forward fail as though we never + * found the entry. + */ + if (!nattype_refresh_timer(nte)) + break; + + /* netfilter NATTYPE + * The entry is found and refreshed, the + * entry values should not change so print + * them outside the lock. + */ + spin_unlock_bh(&nattype_lock); + nattype_nte_debug_print(nte, "refresh"); + DEBUGP("FORWARD_IN_ACCEPT\n"); + return NF_ACCEPT; + } + spin_unlock_bh(&nattype_lock); + DEBUGP("FORWARD_IN_FAIL\n"); + return XT_CONTINUE; + } + + /* netfilter NATTYPE + * Egress packet, create a new rule in our list. If conntrack does + * not have an entry, skip this packet. + */ + ct = nf_ct_get(skb, &ctinfo); + if (!ct || (ctinfo == IP_CT_NEW && ctinfo == IP_CT_RELATED)) + return XT_CONTINUE; + + nat_port = ct->tuplehash[IP_CT_DIR_REPLY].tuple.dst.u.all; + + /* netfilter NATTYPE + * Allocate a new entry + */ + nte = kzalloc(sizeof(*nte), GFP_ATOMIC | __GFP_NOWARN); + if (!nte) { + DEBUGP("kernel malloc fail\n"); + return XT_CONTINUE; + } + + INIT_LIST_HEAD(&nte->list); + + nte->proto = iph->protocol; + nte->nat_port = nat_port; + nte->dest_addr = iph->daddr; + nte->range.min_ip = iph->saddr; + nte->range.max_ip = nte->range.min_ip; + + if (iph->protocol == IPPROTO_TCP) { + nte->range.min.tcp.port = ((struct tcphdr *)protoh)->source; + nte->range.max.tcp.port = nte->range.min.tcp.port; + nte->dest_port = ((struct tcphdr *)protoh)->dest; + } else if (iph->protocol == IPPROTO_UDP) { + nte->range.min.udp.port = ((struct udphdr *)protoh)->source; + nte->range.max.udp.port = nte->range.min.udp.port; + nte->dest_port = ((struct udphdr *)protoh)->dest; + } + nte->range.flags = (NF_NAT_RANGE_MAP_IPS | + NF_NAT_RANGE_PROTO_SPECIFIED); + + /* netfilter NATTYPE + * Initialize the self-destruct timer. + */ + init_timer(&nte->timeout); + nte->timeout.data = (unsigned long)nte; + nte->timeout.function = nattype_timer_timeout; + + /* netfilter NATTYPE + * We have created the new nte; however, it might not be unique. + * Search the list for a matching entry. If found, throw away + * the new entry and refresh the old. If not found, atomically + * insert the new entry on the list. + */ + spin_lock_bh(&nattype_lock); + list_for_each_entry(nte2, &nattype_list, list) { + if (!nattype_compare(nte, nte2)) + continue; + + /* netfilter NATTYPE + * If we can not refresh this entry, insert our new + * entry as this one is timed out and will be removed + * from the list shortly. + */ + if (!nattype_refresh_timer(nte2)) + break; + + /* netfilter NATTYPE + * Found and refreshed an existing entry. Its values + * do not change so print the values outside of the lock. + * + * Free up the new entry. + */ + spin_unlock_bh(&nattype_lock); + nattype_nte_debug_print(nte2, "refresh"); + nattype_free(nte); + return XT_CONTINUE; + } + + /* netfilter NATTYPE + * Add the new entry to the list. + */ + nte->timeout.expires = jiffies + (NATTYPE_TIMEOUT * HZ); + add_timer(&nte->timeout); + list_add(&nte->list, &nattype_list); + spin_unlock_bh(&nattype_lock); + nattype_nte_debug_print(nte, "ADD"); + return XT_CONTINUE; +} + +/* netfilter NATTYPE + * nattype_target() + * One of the iptables hooks has a packet for us to analyze, do so. + */ +static unsigned int nattype_target(struct sk_buff *skb, + const struct xt_action_param *par) +{ + const struct ipt_nattype_info *info = par->targinfo; + const struct iphdr *iph = ip_hdr(skb); + + /* netfilter NATTYPE + * The default behavior for Linux is PORT and ADDRESS restricted. So + * we do not need to create rules/entries if we are in that mode. + */ + if (info->type == TYPE_PORT_ADDRESS_RESTRICTED) + return XT_CONTINUE; + + /* netfilter NATTYPE + * Check if we have enough data in the skb. + */ + if (skb->len < ip_hdrlen(skb)) + return XT_CONTINUE; + + /* netfilter NATTYPE + * We can not perform endpoint filtering on anything but UDP and TCP. + */ + if (iph->protocol != IPPROTO_TCP && iph->protocol != IPPROTO_UDP) + return XT_CONTINUE; + + /* netfilter NATTYPE + * Check for LAND attack and ignore. + */ + if (iph->daddr == iph->saddr) + return XT_CONTINUE; + + /* netfilter NATTYPE + * Check that we have valid source and destination addresses. + */ + if (iph->daddr == (__be32)0 || iph->saddr == (__be32)0) + return XT_CONTINUE; + + DEBUGP("%s: type = %s, mode = %s\n", + __func_, types[info->type], modes[info->mode]); + + switch (info->mode) { + case MODE_DNAT: + return nattype_nat(skb, par); + case MODE_FORWARD_OUT: + case MODE_FORWARD_IN: + return nattype_forward(skb, par); + } + return XT_CONTINUE; +} + +/* netfilter NATTYPE + * nattype_check() + * check info (mode/type) set by iptables. + */ +static int nattype_check(const struct xt_tgchk_param *par) +{ + const struct ipt_nattype_info *info = par->targinfo; + struct list_head *cur, *tmp; + + if (info->type != TYPE_PORT_ADDRESS_RESTRICTED && + info->type != TYPE_ENDPOINT_INDEPENDENT && + info->type != TYPE_ADDRESS_RESTRICTED) { + DEBUGP("%s: unknown type: %d\n", __func__, info->type); + return -EINVAL; + } + + if (info->mode != MODE_DNAT && info->mode != MODE_FORWARD_IN && + info->mode != MODE_FORWARD_OUT) { + DEBUGP("%s: unknown mode - %d.\n", __func__, info->mode); + return -EINVAL; + } + + DEBUGP("%s: type = %s, mode = %s\n", + __func__, types[info->type], modes[info->mode]); + + if (par->hook_mask & ~((1 << NF_INET_PRE_ROUTING) | + (1 << NF_INET_FORWARD))) { + DEBUGP("%s: bad hooks %x.\n", __func__, par->hook_mask); + return -EINVAL; + } + + /* netfilter NATTYPE + * Remove all entries from the nattype list. + */ +drain: + spin_lock_bh(&nattype_lock); + list_for_each_safe(cur, tmp, &nattype_list) { + struct ipt_nattype *nte = (void *)cur; + + /* netfilter NATTYPE + * If the timeout is in process, it will tear + * us down. Since it is waiting on the spinlock + * we have to give up the spinlock to give the + * timeout on another CPU a chance to run. + */ + if (!del_timer(&nte->timeout)) { + spin_unlock_bh(&nattype_lock); + goto drain; + } + + DEBUGP("%p: removing from list\n", nte); + list_del(&nte->list); + spin_unlock_bh(&nattype_lock); + nattype_free(nte); + goto drain; + } + spin_unlock_bh(&nattype_lock); + return 0; +} + +static struct xt_target nattype = { + .name = "NATTYPE", + .family = NFPROTO_IPV4, + .target = nattype_target, + .checkentry = nattype_check, + .targetsize = sizeof(struct ipt_nattype_info), + .hooks = ((1 << NF_INET_PRE_ROUTING) | + (1 << NF_INET_FORWARD)), + .me = THIS_MODULE, +}; + +static int __init init(void) +{ + return xt_register_target(&nattype); +} + +static void __exit fini(void) +{ + xt_unregister_target(&nattype); +} + +module_init(init); +module_exit(fini); + +MODULE_LICENSE("GPL v2");