From 7536b725b43648e55da75e841178f5ab6a589bee Mon Sep 17 00:00:00 2001 From: Amit Kumar Date: Mon, 11 Mar 2024 08:45:23 +0000 Subject: [PATCH] ipt-trigger: package for kernel module to support port trigger --- ipt-trigger/Makefile | 61 +++ ipt-trigger/src/ipt_TRIGGER.h | 26 ++ ipt-trigger/src/ipv4/Makefile | 1 + ipt-trigger/src/ipv4/ipt_TRIGGER.c | 407 +++++++++++++++++ ipt-trigger/src/ipv6/Makefile | 1 + ipt-trigger/src/ipv6/ip6t_TRIGGER.c | 429 ++++++++++++++++++ port-trigger/Makefile | 54 +++ port-trigger/files/etc/config/port-trigger | 1 + port-trigger/files/etc/init.d/port-trigger | 21 + .../files/lib/port-trigger/port_trigger.sh | 157 +++++++ 10 files changed, 1158 insertions(+) create mode 100644 ipt-trigger/Makefile create mode 100644 ipt-trigger/src/ipt_TRIGGER.h create mode 100644 ipt-trigger/src/ipv4/Makefile create mode 100644 ipt-trigger/src/ipv4/ipt_TRIGGER.c create mode 100644 ipt-trigger/src/ipv6/Makefile create mode 100644 ipt-trigger/src/ipv6/ip6t_TRIGGER.c create mode 100644 port-trigger/Makefile create mode 100644 port-trigger/files/etc/config/port-trigger create mode 100644 port-trigger/files/etc/init.d/port-trigger create mode 100755 port-trigger/files/lib/port-trigger/port_trigger.sh diff --git a/ipt-trigger/Makefile b/ipt-trigger/Makefile new file mode 100644 index 000000000..624ed0a5c --- /dev/null +++ b/ipt-trigger/Makefile @@ -0,0 +1,61 @@ +# +# Copyright (C) 2024 IOPSYS Software Solutions AB +# + +include $(TOPDIR)/rules.mk +include $(INCLUDE_DIR)/kernel.mk + +PKG_NAME:=ipt-trigger +PKG_VERSION:=1.0.0 +PKG_LICENSE:=GPL-2.0 + +include $(INCLUDE_DIR)/package.mk + +define KernelPackage/ipt-trigger + SUBMENU:=Other modules + TITLE:=Kernel module for iptables port trigger + FILES:=$(PKG_BUILD_DIR)/ipv4/ipt_TRIGGER.ko + AUTOLOAD:=$(call AutoLoad,30,ipt_TRIGGER,1) + KCONFIG:= +endef + +define KernelPackage/ip6t-trigger + SUBMENU:=Other modules + TITLE:=Kernel module for ip6tables port trigger + DEPENDS+=+(TARGET_brcmbca):kmod-nf-nat + FILES:=$(PKG_BUILD_DIR)/ipv6/ip6t_TRIGGER.ko + AUTOLOAD:=$(call AutoLoad,30,ip6t_TRIGGER,1) + KCONFIG:= +endef + +define KernelPackage/ipt-trigger/description + Kernel module to enable port trigger for iptables +endef + +define KernelPackage/ip6t-trigger/description + Kernel module to enable port trigger for ip6tables +endef + +ifeq ($(CONFIG_TARGET_brcmbca),y) + include ../../broadcom/bcmkernel/bcm-kernel-toolchain.mk +endif + +define Build/Prepare + $(CP) -rf ./src/* $(PKG_BUILD_DIR)/ + $(CP) $(PKG_BUILD_DIR)/ipt_TRIGGER.h $(LINUX_DIR)/include/linux/netfilter_ipv4/ +endef + +define Build/InstallDev + $(INSTALL_DIR) $(1)/include/linux/netfilter_ipv4 + $(CP) $(PKG_BUILD_DIR)/ipt_TRIGGER.h $(1)/include/linux/netfilter_ipv4/ +endef + +KERNEL_MAKE_FLAGS += -I$(LINUX_DIR)/include + +define Build/Compile + $(KERNEL_MAKE) M="$(PKG_BUILD_DIR)/ipv4/" modules + $(KERNEL_MAKE) M="$(PKG_BUILD_DIR)/ipv6/" modules +endef + +$(eval $(call KernelPackage,ipt-trigger)) +$(eval $(call KernelPackage,ip6t-trigger)) diff --git a/ipt-trigger/src/ipt_TRIGGER.h b/ipt-trigger/src/ipt_TRIGGER.h new file mode 100644 index 000000000..0c8c29f2c --- /dev/null +++ b/ipt-trigger/src/ipt_TRIGGER.h @@ -0,0 +1,26 @@ +#ifndef _IPT_TRIGGER_H_target +#define _IPT_TRIGGER_H_target + +#define TRIGGER_TIMEOUT 600 /* 600 secs */ + +enum ipt_trigger_type +{ + IPT_TRIGGER_DNAT = 1, + IPT_TRIGGER_IN = 2, + IPT_TRIGGER_OUT = 3, + IPT_TRIGGER_REFRESH = 4 +}; + +struct ipt_trigger_ports { + u_int16_t mport[2]; /* Related destination port range */ + u_int16_t rport[2]; /* Port range to map related destination port range to */ +}; + +struct ipt_trigger_info { + enum ipt_trigger_type type; + u_int16_t proto; /* Related protocol */ + u_int16_t trigger_timeout; /* Auto disable duration */ + struct ipt_trigger_ports ports; +}; + +#endif /*_IPT_TRIGGER_H_target*/ diff --git a/ipt-trigger/src/ipv4/Makefile b/ipt-trigger/src/ipv4/Makefile new file mode 100644 index 000000000..af942fc56 --- /dev/null +++ b/ipt-trigger/src/ipv4/Makefile @@ -0,0 +1 @@ +obj-m +=ipt_TRIGGER.o diff --git a/ipt-trigger/src/ipv4/ipt_TRIGGER.c b/ipt-trigger/src/ipv4/ipt_TRIGGER.c new file mode 100644 index 000000000..acf37f3cc --- /dev/null +++ b/ipt-trigger/src/ipv4/ipt_TRIGGER.c @@ -0,0 +1,407 @@ +/* Kernel module to match the port-ranges, trigger related port-ranges, + * and alters the destination to a local IP address. + * + * Copyright (C) 2003, CyberTAN Corporation + * All Rights Reserved. + * + * Description: + * This is kernel module for port-triggering. + * + * The module follows the Netfilter framework, called extended packet + * matching modules. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include + +/* This rwlock protects the main hash table, protocol/helper/expected + * registrations, conntrack timers*/ + + +static DEFINE_SPINLOCK(nf_trigger_lock); + + + +#define NF_IP_PRE_ROUTING 0 +#define NF_IP_FORWARD 2 +#define IPT_CONTINUE XT_CONTINUE + + + +/***********************lock help**********************/ +#define MUST_BE_READ_LOCKED(l) +#define MUST_BE_WRITE_LOCKED(l) + + +#define LOCK_BH(l) spin_lock_bh(l) +#define UNLOCK_BH(l) spin_unlock_bh(l) + +#define ASSERT_READ_LOCK(x) MUST_BE_READ_LOCKED(&nf_trigger_lock) +#define ASSERT_WRITE_LOCK(x) MUST_BE_WRITE_LOCKED(&nf_trigger_lock) + + + + +/***********************list help**********************/ +#define LIST_FIND(head, cmpfn, type, args...) \ +({ \ + const struct list_head *__i, *__j = NULL; \ + \ + ASSERT_READ_LOCK(head); \ + list_for_each(__i, (head)) \ + if (cmpfn((const type)__i , ## args)) { \ + __j = __i; \ + break; \ + } \ + (type)__j; \ +}) + +static inline int +__list_cmp_same(const void *p1, const void *p2) { return p1 == p2; } + +static inline void +list_prepend(struct list_head *head, void *new) +{ + ASSERT_WRITE_LOCK(head); + list_add(new, head); +} + +#define list_named_find(head, name) \ +LIST_FIND(head, __list_cmp_name, void *, name) + +MODULE_LICENSE("GPL"); +MODULE_AUTHOR("Netfilter Core Team "); +MODULE_DESCRIPTION("iptables trigger target module"); + +#if 0 +#define DEBUGP printk +#else +#define DEBUGP(format, args...) +#endif + +struct ipt_trigger { + struct list_head list; /* Trigger list */ + struct timer_list timeout; /* Timer for list destroying */ + u_int32_t srcip; /* Outgoing source address */ + u_int32_t dstip; /* Outgoing destination address */ + u_int16_t mproto; /* Trigger protocol */ + u_int16_t rproto; /* Related protocol */ + u_int16_t trigger_timeout; /* Auto disable duration */ + struct ipt_trigger_ports ports; /* Trigger and related ports */ + u_int8_t reply; /* Confirm a reply connection */ +}; + +LIST_HEAD(ipt_trigger_list); + +static void trigger_refresh(struct ipt_trigger *trig, unsigned long extra_jiffies) +{ + DEBUGP("%s: \n", __FUNCTION__); + LOCK_BH(&nf_trigger_lock); + /* Need del_timer for race avoidance (may already be dying). */ + if (del_timer(&trig->timeout)) { + trig->timeout.expires = jiffies + extra_jiffies; + add_timer(&trig->timeout); + } + + UNLOCK_BH(&nf_trigger_lock); +} + +static void __del_trigger(struct ipt_trigger *trig) +{ + DEBUGP("%s: \n", __FUNCTION__); + MUST_BE_WRITE_LOCKED(&nf_trigger_lock); + + /* delete from 'ipt_trigger_list' */ + list_del(&trig->list); + kfree(trig); +} + +static void trigger_timeout(struct timer_list *t) +{ + struct ipt_trigger *trig = from_timer(trig, t, timeout); + + DEBUGP("trigger list %p timed out\n", trig); + LOCK_BH(&nf_trigger_lock); + __del_trigger(trig); + UNLOCK_BH(&nf_trigger_lock); +} + +static unsigned int +add_new_trigger(struct ipt_trigger *trig) +{ + struct ipt_trigger *new = NULL; + + DEBUGP("!!!!!!!!!!!! %s !!!!!!!!!!!\n", __FUNCTION__); + + LOCK_BH(&nf_trigger_lock); + new = (struct ipt_trigger *) + kmalloc(sizeof(struct ipt_trigger), GFP_ATOMIC); + + if (!new) { + UNLOCK_BH(&nf_trigger_lock); + DEBUGP("%s: OOM allocating trigger list\n", __FUNCTION__); + return -ENOMEM; + } + + memset(new, 0, sizeof(*trig)); + INIT_LIST_HEAD(&new->list); + memcpy(new, trig, sizeof(*trig)); + + /* add to global table of trigger */ + list_prepend(&ipt_trigger_list, &new->list); + + /* add and start timer if required */ + timer_setup(&new->timeout, trigger_timeout, 0); + mod_timer(&new->timeout, jiffies + (trig->trigger_timeout * HZ)); + + UNLOCK_BH(&nf_trigger_lock); + + return 0; +} + +/* + * Service-Name OutBound InBound + * 1. TMD UDP:1000 TCP/UDP:2000..2010 + * 2. WOKAO UDP:1000 TCP/UDP:3000..3010 + * 3. net2phone-1 UDP:6801 TCP:30000..30000 + * 4. net2phone-2 UDP:6801 UDP:30000..30000 + * + * For supporting to use the same outgoing port to trigger different port rules, + * it should check the inbound protocol and port range value. If all conditions + * are matched, it is a same trigger item, else it needs to create a new one. + */ +static inline int trigger_out_matched(const struct ipt_trigger *i, + const u_int16_t proto, const u_int16_t dport, const struct ipt_trigger_info *info) +{ + DEBUGP("%s: i=%p, proto= %d, dport=%d.\n", __FUNCTION__, i, proto, dport); + DEBUGP("%s: Got one, mproto= %d, mport[0..1]=%d, %d, ", __FUNCTION__, + i->mproto, i->ports.mport[0], i->ports.mport[1]); + DEBUGP("rproto= %d, rport[0..1]=%d, %d.\n", + i->rproto, i->ports.rport[0], i->ports.rport[1]); + + return ((i->mproto == proto) && + (i->ports.mport[0] <= dport) && + (i->ports.mport[1] >= dport) && + (i->rproto == info->proto) && + (i->ports.rport[0] == info->ports.rport[0]) && + (i->ports.rport[1] == info->ports.rport[1])); +} + +static unsigned int +trigger_out(struct sk_buff *skb, + unsigned int hooknum, + const void *targinfo) +{ + const struct ipt_trigger_info *info = targinfo; + struct ipt_trigger trig, *found; + const struct iphdr *iph = ip_hdr(skb); + struct tcphdr *tcph = (void *)iph + iph->ihl*4; /* Might be TCP, UDP */ + + DEBUGP("############# %s ############\n", __FUNCTION__); + /* Check if the trigger range has already existed in 'ipt_trigger_list'. */ + found = LIST_FIND(&ipt_trigger_list, trigger_out_matched, + struct ipt_trigger *, iph->protocol, ntohs(tcph->dest), info); + + + if (found) { + /* Yeah, it exists. We need to update(delay) the destroying timer. */ + trigger_refresh(found, info->trigger_timeout * HZ); + /* In order to allow multiple hosts use the same port range, we update + the 'saddr' after previous trigger has a reply connection. */ + if (found->reply) + found->srcip = iph->saddr; + } + else { + /* Create new trigger */ + memset(&trig, 0, sizeof(trig)); + trig.srcip = iph->saddr; + trig.mproto = iph->protocol; + trig.rproto = info->proto; + trig.trigger_timeout = info->trigger_timeout; + memcpy(&trig.ports, &info->ports, sizeof(struct ipt_trigger_ports)); + add_new_trigger(&trig); /* Add the new 'trig' to list 'ipt_trigger_list'. */ + } + + return IPT_CONTINUE; /* We don't block any packet. */ +} + +static inline int trigger_in_matched(const struct ipt_trigger *i, + const u_int16_t proto, const u_int16_t dport) +{ + u_int16_t rproto = i->rproto; + + DEBUGP("%s: i=%p, proto= %d, dport=%d.\n", __FUNCTION__, i, proto, dport); + DEBUGP("%s: Got one, rproto= %d, rport[0..1]=%d, %d.\n", __FUNCTION__, + i->rproto, i->ports.rport[0], i->ports.rport[1]); + + if (!rproto) + rproto = proto; + + return ((rproto == proto) && (i->ports.rport[0] <= dport) + && (i->ports.rport[1] >= dport)); +} + +static unsigned int +trigger_in(struct sk_buff *skb, + unsigned int hooknum, + const void *targinfo) +{ + const struct ipt_trigger_info *info = targinfo; + struct ipt_trigger *found; + const struct iphdr *iph = ip_hdr(skb); + struct tcphdr *tcph = (void *)iph + iph->ihl*4; /* Might be TCP, UDP */ + /* Check if the trigger-ed range has already existed in 'ipt_trigger_list'. */ + found = LIST_FIND(&ipt_trigger_list, trigger_in_matched, + struct ipt_trigger *, iph->protocol, ntohs(tcph->dest)); + if (found) { + DEBUGP("############# %s ############\n", __FUNCTION__); + /* Yeah, it exists. We need to update(delay) the destroying timer. */ + trigger_refresh(found, info->trigger_timeout * HZ); + return NF_ACCEPT; /* Accept it, or the imcoming packet could be + dropped in the FORWARD chain */ + } + + return IPT_CONTINUE; /* Our job is the interception. */ +} + +static unsigned int +trigger_dnat(struct sk_buff *skb, + unsigned int hooknum, + const void *targinfo) +{ + struct ipt_trigger *found = NULL; + const struct iphdr *iph = ip_hdr(skb); + struct tcphdr *tcph = (void *)iph + iph->ihl*4; /* Might be TCP, UDP */ + struct nf_conn *ct = NULL; + enum ip_conntrack_info ctinfo; + struct nf_nat_range2 newrange; + + DEBUGP("############# %s ############%d\n", __FUNCTION__, __LINE__); + /* Check if the trigger-ed range has already existed in 'ipt_trigger_list'. */ + found = LIST_FIND(&ipt_trigger_list, trigger_in_matched, + struct ipt_trigger *, iph->protocol, ntohs(tcph->dest)); + if (found) { + DEBUGP("############# %s ############%d srcip:%d\n", __FUNCTION__, __LINE__, found->srcip); + } + + if (!found || !found->srcip) + return IPT_CONTINUE; /* We don't block any packet. */ + + DEBUGP("############# %s ############\n", __FUNCTION__); + found->reply = 1; /* Confirm there has been a reply connection. */ + ct = nf_ct_get(skb, &ctinfo); + + DEBUGP("%s: got ", __FUNCTION__); + + + /* Alter the destination of imcoming packet. */ + /* Transfer from original range. */ + memset(&newrange.min_addr, 0, sizeof(newrange.min_addr)); + memset(&newrange.max_addr, 0, sizeof(newrange.max_addr)); + memset(&newrange.min_proto, 0, sizeof(newrange.min_proto)); + memset(&newrange.max_proto, 0, sizeof(newrange.max_proto)); + newrange.flags = NF_NAT_RANGE_MAP_IPS; + newrange.min_addr.ip = found->srcip; + newrange.max_addr.ip = found->srcip; + DEBUGP("%s: found->srcip = %x\n", __FUNCTION__, found->srcip); + + /* Hand modified range to generic setup. */ + return nf_nat_setup_info(ct, &newrange, NF_NAT_MANIP_DST); +} + +static unsigned int +trigger_target(struct sk_buff *skb, + const struct xt_action_param *par) +{ + const struct ipt_trigger_info *info = par->targinfo; + const struct iphdr *iph = ip_hdr(skb); + unsigned int hooknum = xt_hooknum(par); + + DEBUGP("%s: type = %s\n", __FUNCTION__, + (info->type == IPT_TRIGGER_DNAT) ? "dnat" : + (info->type == IPT_TRIGGER_IN) ? "in" : "out"); + + /* The Port-trigger only supports TCP and UDP. */ + if ((iph->protocol != IPPROTO_TCP) && (iph->protocol != IPPROTO_UDP)) + return IPT_CONTINUE; + + if (info->type == IPT_TRIGGER_OUT) + return trigger_out(skb, hooknum, info); + else if (info->type == IPT_TRIGGER_IN) + return trigger_in(skb, hooknum, info); + else if (info->type == IPT_TRIGGER_DNAT) + return trigger_dnat(skb, hooknum, info); + + return IPT_CONTINUE; +} +static int +trigger_check(const struct xt_tgchk_param *par) +{ + const struct ipt_trigger_info *info = par->targinfo; + + if ((strcmp(par->table, "mangle") == 0)) { + DEBUGP("trigger_check: bad table `%s'.\n", par->table); + return -EINVAL; + } + if (par->hook_mask & ~((1 << NF_IP_PRE_ROUTING) | (1 << NF_IP_FORWARD))) { + DEBUGP("trigger_check: bad hooks %x.\n", par->hook_mask); + return -EINVAL; + } + + if (info->proto) { + if (info->proto != IPPROTO_TCP && info->proto != IPPROTO_UDP) { + DEBUGP("trigger_check: bad proto %d.\n", info->proto); + return -EINVAL; + } + } + if (info->type == IPT_TRIGGER_OUT) { + if (!info->ports.mport[0] || !info->ports.rport[0]) { + DEBUGP("trigger_check: Try 'iptbles -j TRIGGER -h' for help.\n"); + return -EINVAL; + } + } + + return 0; +} + + +static struct xt_target redirect_reg = { + .name = "TRIGGER", + .family = NFPROTO_IPV4, + .target = trigger_target, + .targetsize = sizeof(struct ipt_trigger_info), + .checkentry = trigger_check, + .me = THIS_MODULE, +}; + +static int __init init(void) +{ + return xt_register_target(&redirect_reg); +} + +static void __exit fini(void) +{ + xt_unregister_target(&redirect_reg); +} + +module_init(init); +module_exit(fini); diff --git a/ipt-trigger/src/ipv6/Makefile b/ipt-trigger/src/ipv6/Makefile new file mode 100644 index 000000000..b4eaf2a27 --- /dev/null +++ b/ipt-trigger/src/ipv6/Makefile @@ -0,0 +1 @@ +obj-m +=ip6t_TRIGGER.o diff --git a/ipt-trigger/src/ipv6/ip6t_TRIGGER.c b/ipt-trigger/src/ipv6/ip6t_TRIGGER.c new file mode 100644 index 000000000..be0c58e60 --- /dev/null +++ b/ipt-trigger/src/ipv6/ip6t_TRIGGER.c @@ -0,0 +1,429 @@ +/* Kernel module to match the port-ranges, trigger related port-ranges, + * and alters the destination to a local IPv6 address. + * + * Copyright (C) 2024, IOPSYS + * All Rights Reserved. + * + * Description: + * This is kernel module for port-triggering. + * + * The module follows the Netfilter framework, called extended packet + * matching modules. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include + +/* This rwlock protects the main hash table, protocol/helper/expected + * registrations, conntrack timers*/ + + +static DEFINE_SPINLOCK(nf_trigger_lock); + + + +#define NF_IP_PRE_ROUTING 0 +#define NF_IP_FORWARD 2 +#define IPT_CONTINUE XT_CONTINUE + + + +/***********************lock help**********************/ +#define MUST_BE_READ_LOCKED(l) +#define MUST_BE_WRITE_LOCKED(l) + + +#define LOCK_BH(l) spin_lock_bh(l) +#define UNLOCK_BH(l) spin_unlock_bh(l) + +#define ASSERT_READ_LOCK(x) MUST_BE_READ_LOCKED(&nf_trigger_lock) +#define ASSERT_WRITE_LOCK(x) MUST_BE_WRITE_LOCKED(&nf_trigger_lock) + + + + +/***********************list help**********************/ +#define LIST_FIND(head, cmpfn, type, args...) \ +({ \ + const struct list_head *__i, *__j = NULL; \ + \ + ASSERT_READ_LOCK(head); \ + list_for_each(__i, (head)) \ + if (cmpfn((const type)__i , ## args)) { \ + __j = __i; \ + break; \ + } \ + (type)__j; \ +}) + +static inline int +__list_cmp_same(const void *p1, const void *p2) { return p1 == p2; } + +static inline void +list_prepend(struct list_head *head, void *new) +{ + ASSERT_WRITE_LOCK(head); + list_add(new, head); +} + +#define list_named_find(head, name) \ +LIST_FIND(head, __list_cmp_name, void *, name) + +MODULE_LICENSE("GPL"); +MODULE_AUTHOR("IOPSYS Network Team"); +MODULE_DESCRIPTION("iptables trigger target module"); + +#if 0 +#define DEBUGP printk +#else +#define DEBUGP(format, args...) +#endif + +struct ipt_trigger { + struct list_head list; /* Trigger list */ + struct timer_list timeout; /* Timer for list destroying */ + struct in6_addr srcip; /* Outgoing source address */ + struct in6_addr dstip; /* Outgoing destination address */ + u_int16_t mproto; /* Trigger protocol */ + u_int16_t rproto; /* Related protocol */ + u_int16_t trigger_timeout; /* Auto disable duration */ + struct ipt_trigger_ports ports; /* Trigger and related ports */ + u_int8_t reply; /* Confirm a reply connection */ +}; + +LIST_HEAD(ipt_trigger_list); + +static unsigned char *ipv6_header_get_L4_header_offset(const struct ipv6hdr *ip6h_p) +{ + unsigned int ext_head_count = 8; + const struct ipv6_opt_hdr *ip_ext_p; + unsigned int payload_offset = 0; + char *tcpudp_hdr = NULL; + uint8_t nextHdr_p; + + nextHdr_p = ip6h_p->nexthdr; + ip_ext_p = (const struct ipv6_opt_hdr *)(ip6h_p + 1); + payload_offset = sizeof(struct ipv6hdr); + + do { + if ((nextHdr_p == IPPROTO_TCP) || (nextHdr_p == IPPROTO_UDP)) { + tcpudp_hdr = (unsigned char *)ip6h_p + payload_offset; + break; + } + + payload_offset += (ip_ext_p->hdrlen + 1U) << 3U; + nextHdr_p = ip_ext_p->nexthdr; + ip_ext_p = (struct ipv6_opt_hdr *)((uint8_t *)ip6h_p + payload_offset); + ext_head_count--; /* at most 8 extension headers */ + } while (ext_head_count); + + return tcpudp_hdr; +} + +static void trigger_refresh(struct ipt_trigger *trig, unsigned long extra_jiffies) +{ + DEBUGP("%s: \n", __FUNCTION__); + LOCK_BH(&nf_trigger_lock); + /* Need del_timer for race avoidance (may already be dying). */ + if (del_timer(&trig->timeout)) { + trig->timeout.expires = jiffies + extra_jiffies; + add_timer(&trig->timeout); + } + + UNLOCK_BH(&nf_trigger_lock); +} + +static void __del_trigger(struct ipt_trigger *trig) +{ + DEBUGP("%s: \n", __FUNCTION__); + MUST_BE_WRITE_LOCKED(&nf_trigger_lock); + + /* delete from 'ipt_trigger_list' */ + list_del(&trig->list); + kfree(trig); +} + +static void trigger_timeout(struct timer_list *t) +{ + struct ipt_trigger *trig = from_timer(trig, t, timeout); + + DEBUGP("trigger list %p timed out\n", trig); + LOCK_BH(&nf_trigger_lock); + __del_trigger(trig); + UNLOCK_BH(&nf_trigger_lock); +} + +static unsigned int +add_new_trigger(struct ipt_trigger *trig) +{ + struct ipt_trigger *new = NULL; + + DEBUGP("!!!!!!!!!!!! %s !!!!!!!!!!!\n", __FUNCTION__); + + LOCK_BH(&nf_trigger_lock); + new = (struct ipt_trigger *) + kmalloc(sizeof(struct ipt_trigger), GFP_ATOMIC); + + if (!new) { + UNLOCK_BH(&nf_trigger_lock); + DEBUGP("%s: OOM allocating trigger list\n", __FUNCTION__); + return -ENOMEM; + } + + memset(new, 0, sizeof(*trig)); + INIT_LIST_HEAD(&new->list); + memcpy(new, trig, sizeof(*trig)); + + /* add to global table of trigger */ + list_prepend(&ipt_trigger_list, &new->list); + + /* add and start timer if required */ + timer_setup(&new->timeout, trigger_timeout, 0); + mod_timer(&new->timeout, jiffies + (trig->trigger_timeout * HZ)); + + UNLOCK_BH(&nf_trigger_lock); + + return 0; +} + +/* + * Service-Name OutBound InBound + * 1. TMD UDP:1000 TCP/UDP:2000..2010 + * 2. WOKAO UDP:1000 TCP/UDP:3000..3010 + * 3. net2phone-1 UDP:6801 TCP:30000..30000 + * 4. net2phone-2 UDP:6801 UDP:30000..30000 + * + * For supporting to use the same outgoing port to trigger different port rules, + * it should check the inbound protocol and port range value. If all conditions + * are matched, it is a same trigger item, else it needs to create a new one. + */ +static inline int trigger_out_matched(const struct ipt_trigger *i, + const u_int16_t proto, const u_int16_t dport, const struct ipt_trigger_info *info) +{ + DEBUGP("%s: i=%p, proto= %d, dport=%d.\n", __FUNCTION__, i, proto, dport); + DEBUGP("%s: Got one, mproto= %d, mport[0..1]=%d, %d, ", __FUNCTION__, + i->mproto, i->ports.mport[0], i->ports.mport[1]); + DEBUGP("rproto= %d, rport[0..1]=%d, %d.\n", + i->rproto, i->ports.rport[0], i->ports.rport[1]); + + return ((i->mproto == proto) && + (i->ports.mport[0] <= dport) && + (i->ports.mport[1] >= dport) && + (i->rproto == info->proto) && + (i->ports.rport[0] == info->ports.rport[0]) && + (i->ports.rport[1] == info->ports.rport[1])); +} + +static unsigned int +trigger_out(struct sk_buff *skb, + unsigned int hooknum, + const void *targinfo) +{ + const struct ipt_trigger_info *info = targinfo; + struct ipt_trigger trig, *found; + const struct ipv6hdr *ip6h = ipv6_hdr(skb); + struct tcphdr *tcph = (struct tcphdr*)ipv6_header_get_L4_header_offset(ip6h); /* Might be TCP, UDP */ + + DEBUGP("############# %s ############\n", __FUNCTION__); + /* Check if the trigger range has already existed in 'ipt_trigger_list'. */ + found = LIST_FIND(&ipt_trigger_list, trigger_out_matched, + struct ipt_trigger *, ip6h->nexthdr, ntohs(tcph->dest), info); + + + if (found) { + /* Yeah, it exists. We need to update(delay) the destroying timer. */ + trigger_refresh(found, info->trigger_timeout * HZ); + /* In order to allow multiple hosts use the same port range, we update + the 'saddr' after previous trigger has a reply connection. */ + if (found->reply) + found->srcip = ip6h->saddr; + } + else { + /* Create new trigger */ + memset(&trig, 0, sizeof(trig)); + memcpy(&trig.srcip, &ip6h->saddr, sizeof(trig.srcip)); + trig.mproto = ip6h->nexthdr; + trig.rproto = info->proto; + trig.trigger_timeout = info->trigger_timeout; + memcpy(&trig.ports, &info->ports, sizeof(struct ipt_trigger_ports)); + add_new_trigger(&trig); /* Add the new 'trig' to list 'ipt_trigger_list'. */ + } + + return IPT_CONTINUE; /* We don't block any packet. */ +} + +static inline int trigger_in_matched(const struct ipt_trigger *i, + const u_int16_t proto, const u_int16_t dport) +{ + u_int16_t rproto = i->rproto; + + DEBUGP("%s: i=%p, proto= %d, dport=%d.\n", __FUNCTION__, i, proto, dport); + DEBUGP("%s: Got one, rproto= %d, rport[0..1]=%d, %d.\n", __FUNCTION__, + i->rproto, i->ports.rport[0], i->ports.rport[1]); + + if (!rproto) + rproto = proto; + + return ((rproto == proto) && (i->ports.rport[0] <= dport) + && (i->ports.rport[1] >= dport)); +} + +static unsigned int +trigger_in(struct sk_buff *skb, + unsigned int hooknum, + const void *targinfo) +{ + const struct ipt_trigger_info *info = targinfo; + struct ipt_trigger *found; + const struct ipv6hdr *ip6h = ipv6_hdr(skb); + struct tcphdr *tcph =(struct tcphdr*)ipv6_header_get_L4_header_offset(ip6h); /* Might be TCP, UDP */ + /* Check if the trigger-ed range has already existed in 'ipt_trigger_list'. */ + found = LIST_FIND(&ipt_trigger_list, trigger_in_matched, + struct ipt_trigger *, ip6h->nexthdr, ntohs(tcph->dest)); + if (found) { + DEBUGP("############# %s ############\n", __FUNCTION__); + /* Yeah, it exists. We need to update(delay) the destroying timer. */ + trigger_refresh(found, info->trigger_timeout * HZ); + return NF_ACCEPT; /* Accept it, or the imcoming packet could be + dropped in the FORWARD chain */ + } + + return IPT_CONTINUE; /* Our job is the interception. */ +} + +static unsigned int +trigger_dnat(struct sk_buff *skb, + unsigned int hooknum, + const void *targinfo) +{ + struct ipt_trigger *found = NULL; + const struct ipv6hdr *ip6h = ipv6_hdr(skb); + struct tcphdr *tcph =(struct tcphdr*)ipv6_header_get_L4_header_offset(ip6h); /* Might be TCP, UDP */ + struct nf_conn *ct = NULL; + enum ip_conntrack_info ctinfo; + struct nf_nat_range2 newrange; + + /* Check if the trigger-ed range has already existed in 'ipt_trigger_list'. */ + found = LIST_FIND(&ipt_trigger_list, trigger_in_matched, + struct ipt_trigger *, ip6h->nexthdr, ntohs(tcph->dest)); + + if (!found) + return IPT_CONTINUE; /* We don't block any packet. */ + + DEBUGP("############# %s ############\n", __FUNCTION__); + found->reply = 1; /* Confirm there has been a reply connection. */ + ct = nf_ct_get(skb, &ctinfo); + + DEBUGP("%s: got ", __FUNCTION__); + + + /* Alter the destination of imcoming packet. */ + /* Transfer from original range. */ + memset(&newrange.min_addr, 0, sizeof(newrange.min_addr)); + memset(&newrange.max_addr, 0, sizeof(newrange.max_addr)); + memset(&newrange.min_proto, 0, sizeof(newrange.min_proto)); + memset(&newrange.max_proto, 0, sizeof(newrange.max_proto)); + newrange.flags = NF_NAT_RANGE_MAP_IPS; + memcpy(&newrange.min_addr.ip, &found->srcip, sizeof(newrange.min_addr.ip)); + memcpy(&newrange.max_addr.ip, &found->srcip, sizeof(newrange.max_addr.ip)); + DEBUGP("%s: found->srcip = %x\n", __FUNCTION__, found->srcip); + + /* Hand modified range to generic setup. */ + return nf_nat_setup_info(ct, &newrange, NF_NAT_MANIP_DST); +} + +static unsigned int +trigger_target(struct sk_buff *skb, + const struct xt_action_param *par) +{ + const struct ipt_trigger_info *info = par->targinfo; + const struct ipv6hdr *ip6h = ipv6_hdr(skb); + unsigned int hooknum = xt_hooknum(par); + + DEBUGP("%s: type = %s\n", __FUNCTION__, + (info->type == IPT_TRIGGER_DNAT) ? "dnat" : + (info->type == IPT_TRIGGER_IN) ? "in" : "out"); + + /* The Port-trigger only supports TCP and UDP. */ + if ((ip6h->nexthdr != IPPROTO_TCP) && (ip6h->nexthdr != IPPROTO_UDP)) + return IPT_CONTINUE; + + if (info->type == IPT_TRIGGER_OUT) + return trigger_out(skb, hooknum, info); + else if (info->type == IPT_TRIGGER_IN) + return trigger_in(skb, hooknum, info); + else if (info->type == IPT_TRIGGER_DNAT) + return trigger_dnat(skb, hooknum, info); + + return IPT_CONTINUE; +} +static int +trigger_check(const struct xt_tgchk_param *par) +{ + const struct ipt_trigger_info *info = par->targinfo; + + if ((strcmp(par->table, "mangle") == 0)) { + DEBUGP("trigger_check: bad table `%s'.\n", par->table); + return -EINVAL; + } + if (par->hook_mask & ~((1 << NF_IP_PRE_ROUTING) | (1 << NF_IP_FORWARD))) { + DEBUGP("trigger_check: bad hooks %x.\n", par->hook_mask); + return -EINVAL; + } + + if (info->proto) { + if (info->proto != IPPROTO_TCP && info->proto != IPPROTO_UDP) { + DEBUGP("trigger_check: bad proto %d.\n", info->proto); + return -EINVAL; + } + } + if (info->type == IPT_TRIGGER_OUT) { + if (!info->ports.mport[0] || !info->ports.rport[0]) { + DEBUGP("trigger_check: Try 'iptbles -j TRIGGER -h' for help.\n"); + return -EINVAL; + } + } + + return 0; +} + + +static struct xt_target redirect_reg = { + .name = "TRIGGER", + .family = NFPROTO_IPV6, + .target = trigger_target, + .targetsize = sizeof(struct ipt_trigger_info), + .checkentry = trigger_check, + .me = THIS_MODULE, +}; + +static int __init init(void) +{ + return xt_register_target(&redirect_reg); +} + +static void __exit fini(void) +{ + xt_unregister_target(&redirect_reg); +} + +module_init(init); +module_exit(fini); diff --git a/port-trigger/Makefile b/port-trigger/Makefile new file mode 100644 index 000000000..69cef06cf --- /dev/null +++ b/port-trigger/Makefile @@ -0,0 +1,54 @@ +# +# Copyright (C) 2024 IOPSYS Software Solutions AB +# + +include $(TOPDIR)/rules.mk + +PKG_NAME:=port-trigger +PKG_VERSION:=1.0.0 + +LOCAL_DEV:=0 +ifneq ($(LOCAL_DEV),1) +PKG_SOURCE_PROTO:=git +PKG_SOURCE_URL:=https://dev.iopsys.eu/network/port-trigger.git +PKG_SOURCE_VERSION:=715fa689e5c22721d8ccd9d4e1cbe290caca3662 +PKG_SOURCE:=$(PKG_NAME)-$(PKG_VERSION)-$(PKG_SOURCE_VERSION).tar.gz +PKG_MIRROR_HASH:=skip +endif + +PKG_LICENSE:=BSD-3-Clause +PKG_LICENSE_FILES:=LICENSE + +include $(INCLUDE_DIR)/package.mk +include ../bbfdm/bbfdm.mk + +define Package/port-trigger + SECTION:=utils + CATEGORY:=Utilities + TITLE:=Port Trigger Daemon + DEPENDS:=+libuci +libubox +libubus +libblobmsg-json +libjson-c +libbbfdm-api +kmod-ipt-trigger +kmod-ip6t-trigger +iptables-mod-nfqueue +endef + +define Package/port-trigger/description + Manage port trigger +endef + +ifeq ($(LOCAL_DEV),1) +define Build/Prepare + $(CP) -rf ./port-trigger/* $(PKG_BUILD_DIR)/ +endef +endif + +define Package/port-trigger/install + $(INSTALL_DIR) $(1)/etc/config + $(INSTALL_DIR) $(1)/etc/init.d + $(INSTALL_DIR) $(1)/lib/port-trigger + $(CP) ./files/* $(1)/ + + $(INSTALL_BIN) ./files/etc/init.d/port-trigger $(1)/etc/init.d/ + $(INSTALL_DATA) ./files/etc/config/port-trigger $(1)/etc/config/ + $(INSTALL_DATA) ./files/lib/port-trigger/port_trigger.sh $(1)/lib/port-trigger/ + $(call BbfdmInstallPlugin,$(1),$(PKG_BUILD_DIR)/bbf_plugin/libporttrigger.so) +endef + +$(eval $(call BuildPackage,port-trigger)) diff --git a/port-trigger/files/etc/config/port-trigger b/port-trigger/files/etc/config/port-trigger new file mode 100644 index 000000000..fcb1b88b8 --- /dev/null +++ b/port-trigger/files/etc/config/port-trigger @@ -0,0 +1 @@ +#port trigger uci file diff --git a/port-trigger/files/etc/init.d/port-trigger b/port-trigger/files/etc/init.d/port-trigger new file mode 100644 index 000000000..efb699737 --- /dev/null +++ b/port-trigger/files/etc/init.d/port-trigger @@ -0,0 +1,21 @@ +#!/bin/sh /etc/rc.common + +START=65 +STOP=20 +USE_PROCD=1 + +. /lib/port-trigger/port_trigger.sh + +start_service() { + port_trigger_handling +} + +service_triggers() +{ + procd_add_reload_trigger firewall + procd_add_reload_trigger port-trigger +} + +reload_service() { + start +} diff --git a/port-trigger/files/lib/port-trigger/port_trigger.sh b/port-trigger/files/lib/port-trigger/port_trigger.sh new file mode 100755 index 000000000..0f4c77873 --- /dev/null +++ b/port-trigger/files/lib/port-trigger/port_trigger.sh @@ -0,0 +1,157 @@ +#!/bin/sh + +. /lib/functions.sh + +process_port_trigger() { + local rule_id="$1" + local is_enabled="" + local duration="" + local trigger_dport="" + local trigger_dport_end="" + local protocol="" + local interface="" + local open_dport="" + local open_dport_end="" + local open_protocol="" + local ptg_id="" + local IP_RULE="" + local IP6_RULE="" + local IP_RULE_FWD="" + + get_port_trigger() { + local ptg_name + config_get ptg_name "$1" "name" + if [ "$ptg_name" == "$2" ]; then + ptg_id="$1" + return + fi + } + + ptg_id="" + config_get name "$rule_id" "port_trigger" + config_foreach get_port_trigger "port_trigger" "$name" + [ -z "$ptg_id" ] && return + + is_enabled=$(uci -q get port-trigger."$ptg_id".enable) + + if [ -z "$is_enabled" ] || [ "$is_enabled" = "0" ]; then + return + fi + + protocol=$(uci -q get port-trigger."$ptg_id".protocol) + [ -z "$protocol" ] && return + + if [ "$protocol" = "UDP" ] || [ "$protocol" = "udp" ]; then + IP_RULE="$IP_RULE -p udp" + IP6_RULE="$IP6_RULE -p udp" + IP_RULE_FWD="$IP_RULE_FWD -p udp" + elif [ "$protocol" = "TCP" ] || [ "$protocol" = "tcp" ]; then + IP_RULE="$IP_RULE -p tcp" + IP6_RULE="$IP6_RULE -p tcp" + IP_RULE_FWD="$IP_RULE_FWD -p tcp" + else + return + fi + + trigger_dport=$(uci -q get port-trigger."$ptg_id".port) + [ -z "$trigger_dport" ] && return + IP_RULE="$IP_RULE --dport $trigger_dport" + IP6_RULE="$IP6_RULE --dport $trigger_dport" + + trigger_dport_end=$(uci -q get port-trigger."$ptg_id".end_port_range) + if [ -n "$trigger_dport_end" ]; then + IP_RULE="$IP_RULE:$trigger_dport" + IP6_RULE="$IP6_RULE:$trigger_dport" + fi + + config_get open_protocol "$rule_id" "protocol" + if [ "$open_protocol" = "UDP" ] || [ "$open_protocol" = "udp" ]; then + IP_RULE="$IP_RULE -j TRIGGER --trigger-type out --trigger-proto udp" + IP6_RULE="$IP6_RULE -j TRIGGER --trigger-type out --trigger-proto udp" + elif [ "$open_protocol" = "TCP" ] || [ "$open_protocol" = "tcp" ]; then + IP_RULE="$IP_RULE -j TRIGGER --trigger-type out --trigger-proto tcp" + IP6_RULE="$IP6_RULE -j TRIGGER --trigger-type out --trigger-proto tcp" + else + return + fi + + config_get open_dport "$rule_id" "port" + [ -z "$open_dport" ] && return + IP_RULE="$IP_RULE --trigger-match $open_dport" + IP6_RULE="$IP6_RULE --trigger-match $open_dport" + IP_RULE_FWD="$IP_RULE_FWD --dport $open_dport" + + config_get open_dport_end "$rule_id" "end_port_range" + if [ -z "$open_dport_end" ]; then + IP_RULE="$IP_RULE --trigger-relate $open_dport" + IP6_RULE="$IP6_RULE --trigger-relate $open_dport" + else + IP_RULE="$IP_RULE-$open_dport_end --trigger-relate $open_dport-$open_dport_end" + IP6_RULE="$IP6_RULE-$open_dport_end --trigger-relate $open_dport-$open_dport_end" + IP_RULE_FWD="$IP_RULE_FWD:$open_dport_end" + fi + + duration=$(uci -q get port-trigger."$ptg_id".auto_disable_duration) + if [ -n "$duration" ]; then + IP_RULE="$IP_RULE --trigger-timeout $duration" + IP6_RULE="$IP6_RULE --trigger-timeout $duration" + fi + + interface=$(uci -q get port-trigger."$ptg_id".src) + [ -z "$interface" ] && return + device=$(uci -q get network.$interface.device) + IP_RULE_1="iptables -w -t nat -A prerouting_porttrigger -i $device $IP_RULE" + echo "$IP_RULE_1">>/tmp/port_trigger_iptables + + IP_RULE_1="ip6tables -w -t nat -A prerouting_porttrigger -i $device $IP6_RULE" + echo "$IP_RULE_1">>/tmp/port_trigger_ip6tables + + if [ -n "$duration" ]; then + echo "iptables -w -t filter -A forwarding_wan_porttrigger $IP_RULE_FWD -j TRIGGER --trigger-type in --trigger-timeout $duration">>/tmp/port_trigger_iptables + echo "ip6tables -w -t filter -A forwarding_wan_porttrigger $IP_RULE_FWD -j TRIGGER --trigger-type in --trigger-timeout $duration">>/tmp/port_trigger_ip6tables + + echo "iptables -w -t nat -A prerouting_wan_porttrigger $IP_RULE_FWD -j TRIGGER --trigger-type dnat --trigger-timeout $duration">>/tmp/port_trigger_iptables + else + echo "iptables -w -t filter -A forwarding_wan_porttrigger $IP_RULE_FWD -j TRIGGER --trigger-type in">>/tmp/port_trigger_iptables + echo "ip6tables -w -t filter -A forwarding_wan_porttrigger $IP_RULE_FWD -j TRIGGER --trigger-type in">>/tmp/port_trigger_ip6tables + + echo "iptables -w -t nat -A prerouting_wan_porttrigger $IP_RULE_FWD -j TRIGGER --trigger-type dnat">>/tmp/port_trigger_iptables + fi +} + +port_trigger_handling() { + rm /tmp/port_trigger_iptables 2> /dev/null + rm /tmp/port_trigger_ip6tables 2> /dev/null + touch /tmp/port_trigger_iptables + touch /tmp/port_trigger_ip6tables + + echo "iptables -w -t nat -F prerouting_porttrigger 2> /dev/null">>/tmp/port_trigger_iptables + echo "iptables -w -t filter -F forwarding_wan_porttrigger 2> /dev/null">>/tmp/port_trigger_iptables + echo "iptables -w -t nat -F prerouting_wan_porttrigger 2> /dev/null">>/tmp/port_trigger_iptables + echo "ip6tables -w -t nat -F prerouting_porttrigger 2> /dev/null">>/tmp/port_trigger_ip6tables + echo "ip6tables -w -t filter -F forwarding_wan_porttrigger 2> /dev/null">>/tmp/port_trigger_ip6tables + + echo "iptables -w -t nat -N prerouting_porttrigger 2> /dev/null">>/tmp/port_trigger_iptables + ret=$? + [ $ret -eq 0 ] && echo "iptables -w -t nat -I PREROUTING -j prerouting_porttrigger 2> /dev/null">>/tmp/port_trigger_iptables + echo "iptables -w -t filter -N forwarding_wan_porttrigger 2> /dev/null">>/tmp/port_trigger_iptables + ret=$? + [ $ret -eq 0 ] && echo "iptables -w -t filter -I forwarding_wan_rule -j forwarding_wan_porttrigger 2> /dev/null">>/tmp/port_trigger_iptables + echo "iptables -w -t nat -N prerouting_wan_porttrigger 2> /dev/null">>/tmp/port_trigger_iptables + ret=$? + [ $ret -eq 0 ] && echo "iptables -w -t nat -I prerouting_wan_rule -j prerouting_wan_porttrigger 2> /dev/null">>/tmp/port_trigger_iptables + + echo "ip6tables -w -t nat -N prerouting_porttrigger 2> /dev/null">>/tmp/port_trigger_ip6tables + ret=$? + [ $ret -eq 0 ] && echo "ip6tables -w -t nat -I PREROUTING -j prerouting_porttrigger 2> /dev/null">>/tmp/port_trigger_ip6tables + echo "ip6tables -w -t filter -N forwarding_wan_porttrigger 2> /dev/null">>/tmp/port_trigger_ip6tables + ret=$? + [ $ret -eq 0 ] && echo "ip6tables -w -t filter -I forwarding_wan_rule -j forwarding_wan_porttrigger 2> /dev/null">>/tmp/port_trigger_ip6tables + + # Load /etc/config/port-trigger UCI file + config_load port-trigger + config_foreach process_port_trigger rule + + sh /tmp/port_trigger_iptables + sh /tmp/port_trigger_ip6tables +}