diff options
Diffstat (limited to 'release/src/linux/linux/net/ipv4/netfilter/ipt_TRIGGER.c')
-rw-r--r-- | release/src/linux/linux/net/ipv4/netfilter/ipt_TRIGGER.c | 357 |
1 files changed, 357 insertions, 0 deletions
diff --git a/release/src/linux/linux/net/ipv4/netfilter/ipt_TRIGGER.c b/release/src/linux/linux/net/ipv4/netfilter/ipt_TRIGGER.c new file mode 100644 index 00000000..99e7dfe7 --- /dev/null +++ b/release/src/linux/linux/net/ipv4/netfilter/ipt_TRIGGER.c @@ -0,0 +1,357 @@ +/* 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 <linux/types.h> +#include <linux/ip.h> +#include <linux/timer.h> +#include <linux/module.h> +#include <linux/netfilter.h> +#include <linux/netdevice.h> +#include <linux/if.h> +#include <linux/inetdevice.h> +#include <net/protocol.h> +#include <net/checksum.h> + +#include <linux/netfilter_ipv4.h> +#include <linux/netfilter_ipv4/ip_tables.h> +#include <linux/netfilter_ipv4/ip_conntrack.h> +#include <linux/netfilter_ipv4/ip_conntrack_core.h> +#include <linux/netfilter_ipv4/ip_conntrack_tuple.h> +#include <linux/netfilter_ipv4/ip_autofw.h> +#include <linux/netfilter_ipv4/lockhelp.h> +#include <linux/netfilter_ipv4/ip_nat_rule.h> +#include <linux/netfilter_ipv4/ipt_TRIGGER.h> + +/* This rwlock protects the main hash table, protocol/helper/expected + * registrations, conntrack timers*/ +#define ASSERT_READ_LOCK(x) MUST_BE_READ_LOCKED(&ip_conntrack_lock) +#define ASSERT_WRITE_LOCK(x) MUST_BE_WRITE_LOCKED(&ip_conntrack_lock) + +#include <linux/netfilter_ipv4/listhelp.h> + +#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 */ + struct ipt_trigger_ports ports; /* Trigger and related ports */ + u_int8_t reply; /* Confirm a reply connection */ +}; + +LIST_HEAD(trigger_list); +//DECLARE_LOCK(ip_trigger_lock); + +static void trigger_refresh(struct ipt_trigger *trig, unsigned long extra_jiffies) +{ + DEBUGP("%s: \n", __FUNCTION__); + IP_NF_ASSERT(trig); + WRITE_LOCK(&ip_conntrack_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); + } + + WRITE_UNLOCK(&ip_conntrack_lock); +} + +static void __del_trigger(struct ipt_trigger *trig) +{ + DEBUGP("%s: \n", __FUNCTION__); + IP_NF_ASSERT(trig); + MUST_BE_WRITE_LOCKED(&ip_conntrack_lock); + + /* delete from 'trigger_list' */ + list_del(&trig->list); + kfree(trig); +} + +static void trigger_timeout(unsigned long ul_trig) +{ + struct ipt_trigger *trig= (void *) ul_trig; + + DEBUGP("trigger list %p timed out\n", trig); + WRITE_LOCK(&ip_conntrack_lock); + __del_trigger(trig); + WRITE_UNLOCK(&ip_conntrack_lock); +} + +static unsigned int +add_new_trigger(struct ipt_trigger *trig) +{ + struct ipt_trigger *new; + + DEBUGP("!!!!!!!!!!!! %s !!!!!!!!!!!\n", __FUNCTION__); + WRITE_LOCK(&ip_conntrack_lock); + new = (struct ipt_trigger *) + kmalloc(sizeof(struct ipt_trigger), GFP_ATOMIC); + + if (!new) { + WRITE_UNLOCK(&ip_conntrack_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(&trigger_list, &new->list); + /* add and start timer if required */ + init_timer(&new->timeout); + new->timeout.data = (unsigned long)new; + new->timeout.function = trigger_timeout; + new->timeout.expires = jiffies + (TRIGGER_TIMEOUT * HZ); + add_timer(&new->timeout); + + WRITE_UNLOCK(&ip_conntrack_lock); + + return 0; +} + +static inline int trigger_out_matched(const struct ipt_trigger *i, + const u_int16_t proto, const u_int16_t dport) +{ + /* DEBUGP("%s: i=%p, proto= %d, dport=%d.\n", __FUNCTION__, i, proto, dport); + DEBUGP("%s: Got one, mproto= %d, mport[0..1]=%d, %d.\n", __FUNCTION__, + i->mproto, i->ports.mport[0], i->ports.mport[1]); */ + + return ((i->mproto == proto) && (i->ports.mport[0] <= dport) + && (i->ports.mport[1] >= dport)); +} + +static unsigned int +trigger_out(struct sk_buff **pskb, + unsigned int hooknum, + const struct net_device *in, + const struct net_device *out, + const void *targinfo, + void *userinfo) +{ + const struct ipt_trigger_info *info = targinfo; + struct ipt_trigger trig, *found; + const struct iphdr *iph = (*pskb)->nh.iph; + 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 'trigger_list'. */ + found = LIST_FIND(&trigger_list, trigger_out_matched, + struct ipt_trigger *, iph->protocol, ntohs(tcph->dest)); + + if (found) { + /* Yeah, it exists. We need to update(delay) the destroying timer. */ + trigger_refresh(found, 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; + memcpy(&trig.ports, &info->ports, sizeof(struct ipt_trigger_ports)); + add_new_trigger(&trig); /* Add the new 'trig' to list '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) +{ + /* 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]); */ + u_int16_t rproto = i->rproto; + + 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 **pskb, + unsigned int hooknum, + const struct net_device *in, + const struct net_device *out, + const void *targinfo, + void *userinfo) +{ + struct ipt_trigger *found; + const struct iphdr *iph = (*pskb)->nh.iph; + struct tcphdr *tcph = (void *)iph + iph->ihl*4; /* Might be TCP, UDP */ + + /* Check if the trigger-ed range has already existed in 'trigger_list'. */ + found = LIST_FIND(&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, 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 **pskb, + unsigned int hooknum, + const struct net_device *in, + const struct net_device *out, + const void *targinfo, + void *userinfo) +{ + struct ipt_trigger *found; + const struct iphdr *iph = (*pskb)->nh.iph; + struct tcphdr *tcph = (void *)iph + iph->ihl*4; /* Might be TCP, UDP */ + struct ip_conntrack *ct; + enum ip_conntrack_info ctinfo; + struct ip_nat_multi_range newrange; + + IP_NF_ASSERT(hooknum == NF_IP_PRE_ROUTING); + /* Check if the trigger-ed range has already existed in 'trigger_list'. */ + found = LIST_FIND(&trigger_list, trigger_in_matched, + struct ipt_trigger *, iph->protocol, ntohs(tcph->dest)); + + 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 = ip_conntrack_get(*pskb, &ctinfo); + IP_NF_ASSERT(ct && (ctinfo == IP_CT_NEW)); + + DEBUGP("%s: got ", __FUNCTION__); + DUMP_TUPLE(&ct->tuplehash[IP_CT_DIR_ORIGINAL].tuple); + + /* Alter the destination of imcoming packet. */ + newrange = ((struct ip_nat_multi_range) + { 1, { { IP_NAT_RANGE_MAP_IPS, + found->srcip, found->srcip, + { 0 }, { 0 } + } } }); + + /* Hand modified range to generic setup. */ + return ip_nat_setup_info(ct, &newrange, hooknum); +} + +static unsigned int +trigger_target(struct sk_buff **pskb, + unsigned int hooknum, + const struct net_device *in, + const struct net_device *out, + const void *targinfo, + void *userinfo) +{ + const struct ipt_trigger_info *info = targinfo; + const struct iphdr *iph = (*pskb)->nh.iph; + + /* 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(pskb, hooknum, in, out, targinfo, userinfo); + else if (info->type == IPT_TRIGGER_IN) + return trigger_in(pskb, hooknum, in, out, targinfo, userinfo); + else if (info->type == IPT_TRIGGER_DNAT) + return trigger_dnat(pskb, hooknum, in, out, targinfo, userinfo); + + return IPT_CONTINUE; +} + +static int +trigger_check(const char *tablename, + const struct ipt_entry *e, + void *targinfo, + unsigned int targinfosize, + unsigned int hook_mask) +{ + const struct ipt_trigger_info *info = targinfo; + struct list_head *cur_item, *tmp_item; + + if ((strcmp(tablename, "mangle") == 0)) { + DEBUGP("trigger_check: bad table `%s'.\n", tablename); + return 0; + } + if (targinfosize != IPT_ALIGN(sizeof(*info))) { + DEBUGP("trigger_check: size %u.\n", targinfosize); + return 0; + } + if (hook_mask & ~((1 << NF_IP_PRE_ROUTING) | (1 << NF_IP_FORWARD))) { + DEBUGP("trigger_check: bad hooks %x.\n", hook_mask); + return 0; + } + if (info->proto) { + if (info->proto != IPPROTO_TCP && info->proto != IPPROTO_UDP) { + DEBUGP("trigger_check: bad proto %d.\n", info->proto); + return 0; + } + } + 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 0; + } + } + + /* Empty the 'trigger_list' */ + list_for_each_safe(cur_item, tmp_item, &trigger_list) { + struct ipt_trigger *trig = (void *)cur_item; + + DEBUGP("%s: list_for_each_safe(): %p.\n", __FUNCTION__, trig); + del_timer(&trig->timeout); + __del_trigger(trig); + } + + return 1; +} + +static struct ipt_target redirect_reg += { { NULL, NULL }, "TRIGGER", trigger_target, trigger_check, NULL, + THIS_MODULE }; + +static int __init init(void) +{ + return ipt_register_target(&redirect_reg); +} + +static void __exit fini(void) +{ + ipt_unregister_target(&redirect_reg); +} + +module_init(init); +module_exit(fini); |