Merge "netfilter: Port Trigger: Support added for Port Trigger Feature"

This commit is contained in:
qctecmdr 2021-05-05 01:22:06 -07:00 • committed by Gerrit - the friendly Code Review server
commit 10a04be7c5
5 changed files with 632 additions and 1 deletions

View file

@ -0,0 +1,33 @@
/* SPDX-License-Identifier: GPL-2.0-only WITH Linux-syscall-note */
/* Code copied from
https://source.codeaurora.org/external/ubicom/ubicom32/tree/ubicom-linux-dist-3.2.1/linux-2.6.x/include/linux/netfilter_ipv4/ipt_PORTTRIGGER.h
*/
#include <linux/types.h>
#ifndef _IPT_PORTTRIGGER_H_target
#define _IPT_PORTTRIGGER_H_target
#define TRIGGER_TIMEOUT 600
#define IPT_MULTI_PORTS 15
enum porttrigger_mode {
MODE_TRIGGER_DNAT,
MODE_TRIGGER_FORWARD_IN,
MODE_TRIGGER_FORWARD_OUT
};
struct ipt_mport {
unsigned short pflags; /* Port flags */
unsigned short ports[IPT_MULTI_PORTS]; /* Ports */
};
struct ipt_porttrigger_info {
enum porttrigger_mode mode;
unsigned short trigger_proto;
unsigned short forward_proto;
unsigned int timer;
struct ipt_mport trigger_ports;
struct ipt_mport forward_ports;
};
#endif /*_IPT_PORTTRIGGER_H_target*/

View file

@ -241,6 +241,18 @@ config IP_NF_TARGET_NATTYPE_MODULE
To compile it as a module, choose M here. If unsure, say N.
config IP_NF_TARGET_TRIGGER_MODULE
tristate "TRIGGER target support"
depends on NF_NAT && QGKI
default m if NETFILTER_ADVANCED=n
help
TRIGGER is a special case of NAT: used to support Port Triggering
Feature in kernel. All the incoming connections are allowed on the
forward port/port range if there is a corresponding outgoing
connection on the trigger port/port range.
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

View file

@ -49,6 +49,7 @@ obj-$(CONFIG_IP_NF_MATCH_RPFILTER) += ipt_rpfilter.o
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_TRIGGER_MODULE) += ipt_TRIGGER.o
obj-$(CONFIG_IP_NF_TARGET_REJECT) += ipt_REJECT.o
obj-$(CONFIG_IP_NF_TARGET_SYNPROXY) += ipt_SYNPROXY.o

View file

@ -0,0 +1,585 @@
// SPDX-License-Identifier: GPL-2.0-only
#ifndef _IPT_PORTTRIGGER_target
#define _IPT_PORTTRIGGER_target
/* net/ipv4/netfilter/ipt_TRIGGER.c
* Porttrigger kernel side implementation.
*
* (C) Copyright 2011, Ubicom, Inc.
*
* This file is part of the Ubicom32 Linux Kernel Port.
*
* The Ubicom32 Linux Kernel Port is free software: you can redistribute
* it and/or modify it under the terms of the GNU General Public License
* as published by the Free Software Foundation, either version 2 of the
* License, or (at your option) any later version.
*
* The Ubicom32 Linux Kernel Port is distributed in the hope that it
* will be useful, but WITHOUT ANY WARRANTY; without even the implied
* warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See
* the GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with the Ubicom32 Linux Kernel Port. If not,
* see <http://www.gnu.org/licenses/>.
*
* Ubicom32 implementation derived from Cameo's implementation(with many thanks):
*
*/
#include <linux/types.h>
#include <linux/ip.h>
#include <linux/udp.h>
#include <linux/netfilter.h>
#include <linux/netfilter_ipv4.h>
#include <linux/module.h>
#include <net/protocol.h>
#include <net/checksum.h>
#include <net/ip.h>
#include <linux/tcp.h>
#include <net/netfilter/nf_conntrack.h>
#include <net/netfilter/nf_conntrack_core.h>
#include <net/netfilter/nf_nat.h>
#include <linux/netfilter/x_tables.h>
#include <linux/netfilter_ipv4/ipt_TRIGGER.h>
#include <linux/atomic.h>
#define PORTTRIGGER_DEBUG
#if !defined(PORTTRIGGER_DEBUG)
#define DEBUGP(type, args...)
#else
static const char * const modes[] = {"MODE_TRIGGER_DNAT", "MODE_TRIGGER_FORWARD_IN",
"MODE_TRIGGER_FORWARD_OUT"};
#define DEBUGP(args...) pr_debug(args)
#endif
/* TODO: Add magic value checks to data structure.
*/
struct ipt_porttrigger {
struct list_head list;
struct timer_list timeout;
unsigned int src_ip;
unsigned int dst_ip;
unsigned short trigger_proto; /* Protocol: TCP or UDP */
unsigned short forward_proto; /* Protocol: TCP or UDP */
struct ipt_mport trigger_ports;
struct ipt_mport forward_ports;
};
/* TODO: It might be better to use a hash table for performance in
* heavy traffic.
*/
static LIST_HEAD(trigger_list);
static DEFINE_SPINLOCK(porttrigger_lock);
/* porttrigger_pte_debug_print()
*/
static void porttrigger_pte_debug_print(const struct ipt_porttrigger *pte, const char *s)
{
#if defined(PORTTRIGGER_DEBUG)
DEBUGP("%p: %s - trigger_proto[%d], forward_proto[%d], src_ip[%pI4], dst_ip[%pI4]\n",
pte, s,
pte->trigger_proto, pte->forward_proto,
&pte->src_ip, &pte->dst_ip);
#endif
}
/* porttrigger_free()
* Free the object.
*/
static void porttrigger_free(struct ipt_porttrigger *pte)
{
porttrigger_pte_debug_print(pte, "free");
kfree(pte);
}
/* trigger_refresh_timer()
* Refresh the timer for this object.
*/
static bool trigger_refresh_timer(struct ipt_porttrigger *pte, unsigned long extra_jiffies)
{
lockdep_assert_held(&porttrigger_lock);
if (extra_jiffies == 0)
extra_jiffies = TRIGGER_TIMEOUT * HZ;
if (del_timer(&pte->timeout)) {
pte->timeout.expires = jiffies + extra_jiffies;
add_timer(&pte->timeout);
return true;
}
return false;
}
/* porttrigger_timer_timeout()
* The timer has gone off, self-destruct.
*/
static void porttrigger_timer_timeout(struct timer_list *t)
{
struct ipt_porttrigger *pte = from_timer(pte, t, timeout);
/* 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.
*/
spin_lock_bh(&porttrigger_lock);
porttrigger_pte_debug_print(pte, "timeout");
list_del(&pte->list);
spin_unlock_bh(&porttrigger_lock);
porttrigger_free(pte);
}
/* porttrigger_ports_match()
* Common check port match function for eggress and ingress packets.
*/
static inline bool porttrigger_ports_match(const struct ipt_mport *minfo, u_int16_t port)
{
unsigned int i, m;
unsigned short pflags = minfo->pflags;
for (i = 0, m = 1; i < IPT_MULTI_PORTS; i++, m <<= 1) {
unsigned short s, e;
/* Short list termination check is indicated by a range entry (pflags set)
* and a start value of the last valid port value (which clearly cannot be a range).
* Abort checking the rest of the list.
*/
if ((pflags & m) && minfo->ports[i] == 65535)
break;
/* Get the start and end ports from the array.
*/
s = minfo->ports[i];
e = minfo->ports[i];
DEBUGP("Start port: %d End port: %d\n", s, e);
if (unlikely(pflags & m)) {
/* If the pflags is set, this means the next element in the array is the
* end port of the range instead of a start.
*/
e = minfo->ports[++i];
/* Shift the mask to left for the next pflags check
*/
m <<= 1;
}
if ((htons(port) >= s) && (htons(port) <= e))
return true;
}
DEBUGP("ports = %d don't match\n", htons(port));
return false;
}
/* porttrigger_packet_in_match()
* Ingress packet check match entry function.
*/
static inline bool porttrigger_packet_in_match(const struct ipt_porttrigger *pte,
const unsigned short proto,
const unsigned short dport,
const unsigned int src_ip)
{
DEBUGP("src_ip = %pI4, pte->src_ip = %pI4 proto = %d, dport = %d in match\n",
&src_ip, &pte->src_ip, proto, htons(dport));
/* Check LAND attack. If the ingress packet's src ip is equal to the pte's src ip
* (which is the LAN PC's ip), ignore the packet.
*/
if (src_ip == pte->src_ip) {
DEBUGP("LAND attack: ingress packet's src ip is equal to LAN src ip\n");
return false;
}
/* The protocol values can be TCP, UDP or both (0). Check whether we have a
* valid protocol or not.
*/
if (pte->forward_proto && pte->forward_proto != proto) {
DEBUGP("Invalid forward_proto: %d\n", pte->forward_proto);
return false;
}
if (!porttrigger_ports_match(&pte->forward_ports, dport))
return false;
DEBUGP("pte->forward_proto = %d, dport: %d\n", pte->forward_proto, htons(dport));
return true;
}
/* porttrigger_packet_out_match()
* Egress packet check match entry function.
*/
static inline bool porttrigger_packet_out_match(const unsigned short trigger_proto,
const unsigned short protocol,
const struct ipt_mport *trigger_ports,
unsigned short dport)
{
DEBUGP("protocol = %d, dport = %d out match\n", protocol, htons(dport));
if (trigger_proto && trigger_proto != protocol) {
DEBUGP("Invalid trigger_proto: %d\n", trigger_proto);
return false;
}
if (!porttrigger_ports_match(trigger_ports, dport))
return false;
DEBUGP("trigger_proto = %d, dport: %d\n", trigger_proto, htons(dport));
return true;
}
/* porttrigger_nat()
* Ingress packet on PRE_ROUTING hook, find match, update conntrack to allow
*/
static unsigned int porttrigger_nat(struct sk_buff *skb, const struct xt_action_param *par)
{
struct ipt_porttrigger *pte;
unsigned short dst_port = 0;
enum ip_conntrack_info ctinfo;
struct nf_conn *ct;
enum ip_conntrack_dir dir;
unsigned int src_addr;
unsigned short protocol;
DEBUGP("%s", __func__);
if (xt_hooknum(par) != NF_INET_PRE_ROUTING)
return XT_CONTINUE;
ct = nf_ct_get(skb, &ctinfo);
if (!ct) {
DEBUGP("ingress packet conntrack not found\n");
return XT_CONTINUE;
}
dir = CTINFO2DIR(ctinfo);
src_addr = ct->tuplehash[dir].tuple.src.u3.ip;
dst_port = ct->tuplehash[dir].tuple.dst.u.all;
protocol = ct->tuplehash[dir].tuple.dst.protonum;
DEBUGP("%s: src_addr: %pI4 dst_port = %d protocol = %d\n",
__func__, &src_addr, htons(dst_port), protocol);
spin_lock_bh(&porttrigger_lock);
list_for_each_entry(pte, &trigger_list, list) {
struct nf_nat_range2 newrange;
if (!porttrigger_packet_in_match(pte, protocol, dst_port, ntohl(src_addr)))
continue;
newrange.flags = NF_NAT_RANGE_MAP_IPS;
newrange.min_addr.ip = pte->src_ip;
newrange.max_addr.ip = pte->src_ip;
spin_unlock_bh(&porttrigger_lock);
DEBUGP("DNAT: src_addr: %pI4 dst_port: %d protocol: %d\n",
&src_addr, htons(dst_port), protocol);
DEBUGP("DNAT: LAN src IP %pI4\n", &pte->src_ip);
return nf_nat_setup_info(ct, &newrange, NF_NAT_MANIP_DST);
}
spin_unlock_bh(&porttrigger_lock);
return XT_CONTINUE;
}
/* porttrigger_forward()
* Ingress and Egress packet forwarding hook
*/
static unsigned int porttrigger_forward(struct sk_buff *skb, const struct xt_action_param *par)
{
const struct ipt_porttrigger_info *info = par->targinfo;
struct ipt_porttrigger *pte;
struct ipt_porttrigger *pte2;
unsigned short dst_port = 0, dst_tcp_port = 0, dst_udp_port = 0;
unsigned short dst_dccp_port = 0, dst_sctp_port = 0;
enum ip_conntrack_info ctinfo;
struct nf_conn *ct;
enum ip_conntrack_dir dir;
unsigned int src_addr;
unsigned short protocol;
if (xt_hooknum(par) != NF_INET_POST_ROUTING)
return XT_CONTINUE;
ct = nf_ct_get(skb, &ctinfo);
if (!ct) {
DEBUGP("%s: conntrack not found", __func__);
return XT_CONTINUE;
}
dir = CTINFO2DIR(ctinfo);
src_addr = ct->tuplehash[dir].tuple.src.u3.ip;
dst_port = ct->tuplehash[dir].tuple.dst.u.all;
protocol = ct->tuplehash[dir].tuple.dst.protonum;
DEBUGP("%s: src_addr: %pI4 dst_port = %d protocol = %d\n", __func__,
&src_addr, dst_port, protocol);
/* Ingress packet, refresh the timer if we find an entry.
*/
if (info->mode == MODE_TRIGGER_FORWARD_IN) {
DEBUGP("porttrigger_forward_in\n");
spin_lock_bh(&porttrigger_lock);
list_for_each_entry(pte, &trigger_list, list) {
/* Compare the ingress packet with the existing
* entries looking for a match.
*/
if (!porttrigger_packet_in_match(pte, protocol, dst_port, ntohl(src_addr)))
continue;
/* Refresh the timer, if we fail, break
* out and forward fail as though we never
* found the entry.
*/
if (!trigger_refresh_timer(pte, info->timer * HZ))
break;
/* The entry is found and refreshed, but the entry values
* can be changed by another thread in the MODE_TRIGGER_FORWARD_OUT case,
* so print them inside the lock.
*/
porttrigger_pte_debug_print(pte, "refresh");
spin_unlock_bh(&porttrigger_lock);
DEBUGP("FORWARD_IN_ACCEPT: src_addr: %pI4 dst_port: %d protocol: %d\n",
&src_addr, dst_port, protocol);
return NF_ACCEPT;
}
spin_unlock_bh(&porttrigger_lock);
DEBUGP("FORWARD_IN_FAIL\n");
return XT_CONTINUE;
}
/* Egress packet, create a new rule in our list. If we don't have a rule for this port
* do not create any rule.
*/
DEBUGP("porttrigger_forward_out: Check for the match\n");
if (!porttrigger_packet_out_match(info->trigger_proto, protocol,
&info->trigger_ports, dst_port))
return XT_CONTINUE;
/* Allocate a new entry
*/
DEBUGP("Create a new entry\n");
pte = kzalloc(sizeof(*pte), GFP_ATOMIC | __GFP_NOWARN);
if (!pte) {
DEBUGP("kernel malloc fail\n");
return XT_CONTINUE;
}
INIT_LIST_HEAD(&pte->list);
//pte->src_ip = ntohl(src_addr);
pte->src_ip = src_addr;
pte->trigger_proto = protocol;
pte->forward_proto = info->forward_proto;
memcpy(&pte->trigger_ports, &info->trigger_ports, sizeof(struct ipt_mport));
memcpy(&pte->forward_ports, &info->forward_ports, sizeof(struct ipt_mport));
DEBUGP("porttrigger_forward_out new entry: src_addr: %pI4 trigger_proto = %d forward_proto = %d \n",
&pte->src_ip, pte->trigger_proto, pte->forward_proto);
/* We have created the new pte; 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(&porttrigger_lock);
list_for_each_entry(pte2, &trigger_list, list) {
if (!porttrigger_packet_out_match(pte2->trigger_proto, protocol,
&pte2->trigger_ports, dst_port))
continue;
/* 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 (!trigger_refresh_timer(pte2, info->timer * HZ))
break;
/* We have an entry in the list with this dst_port (trigger port) and protocol,
* but the src ip may be different than this new one,
* so refresh the src_ip of the found pte.
*/
//pte2->src_ip = ntohl(src_addr);
pte2->src_ip = src_addr;
/* It is not good to use debug message inside the lock, but we are changing the
* value, and we have to print it here by paying the price under debugs on.
*/
porttrigger_pte_debug_print(pte2, "refresh");
/* 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(&porttrigger_lock);
porttrigger_free(pte);
return NF_ACCEPT;
}
/* Initialize the self-destruct timer.
*/
DEBUGP("Init timer");
timer_setup(&pte->timeout, porttrigger_timer_timeout, 0);
/* Add the new entry to the list.
*/
pte->timeout.expires = jiffies + (TRIGGER_TIMEOUT * HZ);
add_timer(&pte->timeout);
list_add(&pte->list, &trigger_list);
porttrigger_pte_debug_print(pte, "ADD");
spin_unlock_bh(&porttrigger_lock);
return NF_ACCEPT;
}
/* porttrigger_target()
* One of the iptables hooks has a packet for us to analyze, do so.
*/
static unsigned int porttrigger_target(struct sk_buff *skb, const struct xt_action_param *par)
{
const struct ipt_porttrigger_info *info = par->targinfo;
enum ip_conntrack_info ctinfo;
struct nf_conn *ct;
enum ip_conntrack_dir dir;
unsigned int src_addr;
unsigned int dst_addr;
unsigned short protocol;
/* Check if we have enough data in the skb.
*/
if (skb->len < ip_hdrlen(skb)) {
DEBUGP("skb is too short for IP header\n");
return XT_CONTINUE;
}
ct = nf_ct_get(skb, &ctinfo);
if (!ct) {
DEBUGP("%s: conntrack not found\n", __func__);
return XT_CONTINUE;
}
dir = CTINFO2DIR(ctinfo);
src_addr = ct->tuplehash[dir].tuple.src.u3.ip;
dst_addr = ct->tuplehash[dir].tuple.dst.u3.ip;
protocol = ct->tuplehash[dir].tuple.dst.protonum;
DEBUGP("%s: src_addr = %pI4 dst_addr = %pI4 protocol = %d\n",
__func__, &src_addr, &dst_addr, protocol);
/* We can not perform porttriggering on anything but UDP and TCP.
*/
if (protocol != IPPROTO_TCP && protocol != IPPROTO_UDP)
return XT_CONTINUE;
/* Check for LAND attack and ignore.
*/
if (dst_addr == src_addr)
return XT_CONTINUE;
/* Check that we have valid source and destination addresses.
*/
if (dst_addr == (__be32)0 || src_addr == (__be32)0)
return XT_CONTINUE;
DEBUGP("info->mode is %d\n", info->mode);
DEBUGP("%s: mode = %s\n", __func__, modes[info->mode]);
/* TODO: why have mode at all since par->hooknum provides this information?
*/
switch (info->mode) {
case MODE_TRIGGER_DNAT:
return porttrigger_nat(skb, par);
case MODE_TRIGGER_FORWARD_OUT:
case MODE_TRIGGER_FORWARD_IN:
return porttrigger_forward(skb, par);
}
return XT_CONTINUE;
}
/* porttrigger_check()
* check info set by iptables
*/
static int porttrigger_check(const struct xt_tgchk_param *par)
{
const struct ipt_porttrigger_info *info = par->targinfo;
struct list_head *cur, *tmp;
if (info->mode == MODE_TRIGGER_DNAT && (strcmp(par->table, "nat") != 0)) {
DEBUGP("%s: bad table '%s'.\n", __func__, par->table);
return -EINVAL;
}
if (par->hook_mask & ~((1 << NF_INET_PRE_ROUTING) | (1 << NF_INET_POST_ROUTING))) {
DEBUGP("%s: bad hooks %x.\n", __func__, par->hook_mask);
return -EINVAL;
}
if (info->forward_proto != IPPROTO_TCP && info->forward_proto != IPPROTO_UDP &&
info->forward_proto != 0) {
DEBUGP("%s: bad trigger proto '%d'.\n", __func__, info->forward_proto);
return -EINVAL;
}
/* If the last entry in the port list is a start and it expects an end port entry,
* porttrigger_ports_match() function will derefence beyond the end of the array.
* Make sure we are not handed a bad port list.
*/
if ((info->forward_ports.pflags & (1 << IPT_MULTI_PORTS)) &&
info->forward_ports.ports[IPT_MULTI_PORTS - 1] != 65535) {
DEBUGP("%s: bad forward_ports list\n", __func__);
return -EINVAL;
}
if ((info->trigger_ports.pflags & (1 << IPT_MULTI_PORTS)) &&
info->trigger_ports.ports[IPT_MULTI_PORTS - 1] != 65535) {
DEBUGP("%s: bad trigger_ports list\n", __func__);
return -EINVAL;
}
/* Remove all entries from the trigger list.
*/
drain:
spin_lock_bh(&porttrigger_lock);
list_for_each_safe(cur, tmp, &trigger_list) {
struct ipt_porttrigger *pte = (void *)cur;
/* 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(&pte->timeout)) {
spin_unlock_bh(&porttrigger_lock);
goto drain;
}
DEBUGP("%p: removing from list\n", pte);
list_del(&pte->list);
spin_unlock_bh(&porttrigger_lock);
porttrigger_free(pte);
goto drain;
}
spin_unlock_bh(&porttrigger_lock);
return 0;
}
static struct xt_target trigger = {
.name = "TRIGGER",
.family = NFPROTO_IPV4,
.target = porttrigger_target,
.checkentry = porttrigger_check,
.targetsize = sizeof(struct ipt_porttrigger_info),
.hooks = ((1 << NF_INET_PRE_ROUTING) | (1 << NF_INET_POST_ROUTING)),
.me = THIS_MODULE,
};
static int __init init(void)
{
return xt_register_target(&trigger);
}
static void __exit fini(void)
{
xt_unregister_target(&trigger);
}
module_init(init);
module_exit(fini);
MODULE_LICENSE("GPL v2");
#endif /* _IPT_PORTTRIGGER_target */

View file

@ -59,7 +59,7 @@ nf_nat_masquerade_ipv4(struct sk_buff *skb, unsigned int hooknum,
newrange.max_proto = range->max_proto;
/* Hand modified range to generic setup. */
#if defined(CONFIG_IP_NF_TARGET_NATTYPE_MODULE)
#if defined(CONFIG_IP_NF_TARGET_NATTYPE_MODULE) || defined(CONFIG_IP_NF_TARGET_TRIGGER_MODULE)
nf_nat_setup_info(ct, &newrange, NF_NAT_MANIP_SRC);
return XT_CONTINUE;
#else