From 7a981b17b5f4e5820f7e72baedd8ffca7701b281 Mon Sep 17 00:00:00 2001 From: Jan Engelhardt Date: Tue, 29 Jan 2008 03:57:08 +0100 Subject: [PATCH] Initial commit. Populate the iptables-addons repository with two modules, xt_TARPIT and xt_TEE, as a starting point. Signed-off-by: Jan Engelhardt --- .gitignore | 25 ++++ INSTALL | 58 ++++++++ Makefile.am | 4 + README | 7 + autogen.sh | 4 + configure.ac | 62 +++++++++ extensions/Kbuild | 4 + extensions/Makefile.am | 34 +++++ extensions/libxt_TARPIT.c | 34 +++++ extensions/libxt_TARPIT.man | 33 +++++ extensions/libxt_TEE.c | 111 ++++++++++++++++ extensions/xt_TARPIT.Kconfig | 16 +++ extensions/xt_TARPIT.c | 237 +++++++++++++++++++++++++++++++++ extensions/xt_TEE.Kconfig | 9 ++ extensions/xt_TEE.c | 248 +++++++++++++++++++++++++++++++++++ extensions/xt_TEE.h | 8 ++ 16 files changed, 894 insertions(+) create mode 100644 .gitignore create mode 100644 INSTALL create mode 100644 Makefile.am create mode 100644 README create mode 100755 autogen.sh create mode 100644 configure.ac create mode 100644 extensions/Kbuild create mode 100644 extensions/Makefile.am create mode 100644 extensions/libxt_TARPIT.c create mode 100644 extensions/libxt_TARPIT.man create mode 100644 extensions/libxt_TEE.c create mode 100644 extensions/xt_TARPIT.Kconfig create mode 100644 extensions/xt_TARPIT.c create mode 100644 extensions/xt_TEE.Kconfig create mode 100644 extensions/xt_TEE.c create mode 100644 extensions/xt_TEE.h diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..9d09822 --- /dev/null +++ b/.gitignore @@ -0,0 +1,25 @@ +.*.cmd +*.ko +*.la +*.lo +*.loT +*.mod.c +*.o +.deps +.libs +.tmp_versions +Makefile +Makefile.in +Module.symvers + +/aclocal.m4 +/autom4te*.cache +/compile +/config.* +/configure +/depcomp +/install-sh +/libtool +/ltmain.sh +/missing +/stamp-h1 diff --git a/INSTALL b/INSTALL new file mode 100644 index 0000000..0102b49 --- /dev/null +++ b/INSTALL @@ -0,0 +1,58 @@ + +Prerequirements +=============== + + * iptables-devel 1.4.1 + + * kernel-source + + +Compiling +========= + +./configure [options] + +--with-kbuild= + + Specifies the path to the kernel build output directory. We + need it for building the kernel extensions. For example, on + openSUSE: + + --with-kbuild=/usr/src/linux-obj/x86_64/default + +--with-ksource= + + Specifies the path to the kernel source directory. This is + currently needed for building the userspace extensions because + we use unsanitized kernel headers, but the option MAY + DISAPPEAR IN FUTURE. + + --with-ksource=/usr/src/linux + +--with-iptables= + + Specifies the path to the directory where we may find + xtables.h, should it not be within the standard C compiler + include path, or if you want to override it. The directory + will be checked for xtables.h and include/xtables.h. (This is + to support the following specs:) + + --with-iptables=/usr/src/iptables + --with-iptables=/usr/src/iptables/include + --with-iptables=/opt/iptables/include + +--with-iptdir= + + Specifies the path to where the newly built extensions should + be installed when `make install` is run. It uses the same + default as the iptables package, ${libexecdir}/iptables. + + +Note to distribution packagers +============================== + +Except for --with-kbuild, distributions should not have a need to +supply any other flags (besides --prefix=/usr and perhaps +--libdir=/usr/lib64, etc.) to configure when all prerequired packages +are installed. If iptables-devel is installed, necessary headers +should be in /usr/include, so --with-iptables is not needed. diff --git a/Makefile.am b/Makefile.am new file mode 100644 index 0000000..ad77607 --- /dev/null +++ b/Makefile.am @@ -0,0 +1,4 @@ +# -*- Makefile -*- + +AUTOMAKE_OPTIONS = foreign subdir-objects +SUBDIRS = extensions diff --git a/README b/README new file mode 100644 index 0000000..1c73d36 --- /dev/null +++ b/README @@ -0,0 +1,7 @@ +iptables-addons +=============== + +iptables-addons is what previously has been patch-o-matic and +patch-o-matic-ng. Extensions that do not need immediate kernel +patching are collected here in this repository and can immediately be +built against a kernel and iptables. diff --git a/autogen.sh b/autogen.sh new file mode 100755 index 0000000..62a89e1 --- /dev/null +++ b/autogen.sh @@ -0,0 +1,4 @@ +#!/bin/bash + +autoreconf -fi; +rm -Rf autom4te*.cache; diff --git a/configure.ac b/configure.ac new file mode 100644 index 0000000..eea6810 --- /dev/null +++ b/configure.ac @@ -0,0 +1,62 @@ + +AC_INIT([iptables-addons], [1.4.1]) +AC_CONFIG_HEADERS([config.h]) +AC_PROG_INSTALL +AM_INIT_AUTOMAKE +AC_PROG_CC +AM_PROG_CC_C_O +AC_DISABLE_STATIC +AC_PROG_LIBTOOL + +kbuilddir="/lib/modules/$(uname -r)/build"; +ksourcedir="/lib/modules/$(uname -r)/source"; +AC_ARG_WITH([kbuild], + AS_HELP_STRING([--with-kbuild=PATH], + [Path to kernel build directory [[/lib/modules/CURRENT/build]]]), + [kbuilddir="$withval"]) +AC_ARG_WITH([ksource], + AS_HELP_STRING([--with-ksource=PATH], + [Path to kernel source directory [[/lib/modules/CURRENT/source]]]), + [ksourcedir="$withval"]) +AC_ARG_WITH([iptables], + AS_HELP_STRING([--with-iptables=PATH], + [Path to the iptables includes [[PREFIX/include]]]), + [iptables_location="$withval"]) +AC_ARG_WITH([iptdir], + AS_HELP_STRING([--with-iptdir=PATH], + [Path to iptables modules [[LIBEXECDIR/iptables]]]), + [iptdir="$withval"], + [iptdir='${libexecdir}/iptables']) + +AC_CHECK_HEADER([netinet/ip6.h], [], [AC_MSG_ERROR(but we need that for IPv6)]) + +AC_MSG_CHECKING([xtables.h presence]) +if [[ -n "$iptables_location" ]]; then + if [[ -f "$iptables_location/xtables.h" ]]; then + AC_MSG_RESULT([$iptables_location/xtables.h]) + iptables_CFLAGS="-I$iptables_location"; + elif [[ -f "$iptables_location/include/xtables.h" ]]; then + AC_MSG_RESULT([$iptables_location/include/xtables.h]) + iptables_CFLAGS="-I$iptables_location/include"; + fi; +fi; +if [[ -z "$iptables_CFLAGS" ]]; then + if [[ -f "$includedir/xtables.h" ]]; then + AC_MSG_RESULT([$includedir/xtables.h]) + else + AC_MSG_RESULT([no]) + fi; +fi; + +regular_CFLAGS="-D_LARGEFILE_SOURCE=1 -D_LARGE_FILES -D_FILE_OFFSET_BITS=64 \ + -D_REENTRANT -Wall -Waggregate-return -Wmissing-declarations \ + -Wmissing-prototypes -Wredundant-decls -Wshadow -Wstrict-prototypes \ + -Winline -pipe -DIPTABLES_VERSION=\\\"$PACKAGE_VERSION\\\" \ + -DIPT_LIB_DIR=\\\"\${iptdir}\\\" -DIP6T_LIB_DIR=\\\"\${iptdir}\\\""; +kinclude_CFLAGS="-I\"$kbuilddir/include\" -I\"$ksourcedir/include\""; + +AC_SUBST([regular_CFLAGS iptables_CFLAGS kinclude_CFLAGS]) +AC_SUBST([kbuilddir]) +AC_SUBST([ksourcedir]) +AC_SUBST([iptdir]) +AC_OUTPUT([Makefile extensions/Makefile]) diff --git a/extensions/Kbuild b/extensions/Kbuild new file mode 100644 index 0000000..96aca1a --- /dev/null +++ b/extensions/Kbuild @@ -0,0 +1,4 @@ +# -*- Makefile -*- + +obj-m += xt_TARPIT.o +obj-m += xt_TEE.o diff --git a/extensions/Makefile.am b/extensions/Makefile.am new file mode 100644 index 0000000..92403d1 --- /dev/null +++ b/extensions/Makefile.am @@ -0,0 +1,34 @@ +# -*- Makefile -*- + +AUTOMAKE_OPTIONS = foreign subdir-objects +abssrcdir = $(shell readlink -f ${srcdir}) + +regular_CFLAGS := @regular_CFLAGS@ +iptables_CFLAGS := @iptables_CFLAGS@ +kinclude_CFLAGS := @kinclude_CFLAGS@ +AM_CFLAGS = ${regular_CFLAGS} ${iptables_CFLAGS} ${kinclude_CFLAGS} \ + -D_INIT=$*_init +AM_LDFLAGS = -module -avoid-version +ipt_LTLIBRARIES = \ + libxt_TARPIT.la \ + libxt_TEE.la + +# +# Call out to kbuild +# +.PHONY: modules modules_install clean_modules + +all-local: modules + +install-exec-local: modules_install + +clean-local: clean_modules + +modules: + make -C ${kbuilddir} M=${abssrcdir} modules; + +modules_install: + make -C ${kbuilddir} M=${abssrcdir} INSTALL_MOD_PATH=${DESTDIR} modules_install; + +clean_modules: + make -C ${kbuilddir} M=${abssrcdir} clean; diff --git a/extensions/libxt_TARPIT.c b/extensions/libxt_TARPIT.c new file mode 100644 index 0000000..5946aa6 --- /dev/null +++ b/extensions/libxt_TARPIT.c @@ -0,0 +1,34 @@ +#include +#include +#include + +static void tarpit_tg_help(void) +{ + printf("TARPIT takes no options\n\n"); +} + +static int tarpit_tg_parse(int c, char **argv, int invert, unsigned int *flags, + const void *entry, struct xt_entry_target **target) +{ + return 0; +} + +static void tarpit_tg_check(unsigned int flags) +{ +} + +static struct xtables_target tarpit_tg_reg = { + .version = IPTABLES_VERSION, + .name = "TARPIT", + .family = AF_INET, + .size = XT_ALIGN(0), + .userspacesize = XT_ALIGN(0), + .help = tarpit_tg_help, + .parse = tarpit_tg_parse, + .final_check = tarpit_tg_check, +}; + +static void _init(void) +{ + xtables_register_target(&tarpit_tg_reg); +} diff --git a/extensions/libxt_TARPIT.man b/extensions/libxt_TARPIT.man new file mode 100644 index 0000000..77b6c6e --- /dev/null +++ b/extensions/libxt_TARPIT.man @@ -0,0 +1,33 @@ ++Captures and holds incoming TCP connections using no local per-connection ++resources. Connections are accepted, but immediately switched to the persist ++state (0 byte window), in which the remote side stops sending data and asks to ++continue every 60-240 seconds. Attempts to close the connection are ignored, ++forcing the remote side to time out the connection in 12-24 minutes. ++ ++This offers similar functionality to LaBrea ++ but does not require dedicated hardware or ++IPs. Any TCP port that you would normally DROP or REJECT can instead become a ++tarpit. ++ ++To tarpit connections to TCP port 80 destined for the current machine: ++.IP ++-A INPUT -p tcp -m tcp --dport 80 -j TARPIT ++.P ++To significantly slow down Code Red/Nimda-style scans of unused address space, ++forward unused ip addresses to a Linux box not acting as a router (e.g. "ip ++route 10.0.0.0 255.0.0.0 ip.of.linux.box" on a Cisco), enable IP forwarding on ++the Linux box, and add: ++.IP ++-A FORWARD -p tcp -j TARPIT ++.IP ++-A FORWARD -j DROP ++.TP ++NOTE: ++If you use the conntrack module while you are using TARPIT, you should also use ++the NOTRACK target, or the kernel will unnecessarily allocate resources for ++each TARPITted connection. To TARPIT incoming connections to the standard IRC ++port while using conntrack, you could: ++.IP ++-t raw -A PREROUTING -p tcp --dport 6667 -j NOTRACK ++.IP ++-A INPUT -p tcp --dport 6667 -j TARPIT diff --git a/extensions/libxt_TEE.c b/extensions/libxt_TEE.c new file mode 100644 index 0000000..e4373dd --- /dev/null +++ b/extensions/libxt_TEE.c @@ -0,0 +1,111 @@ +/* + * libxt_TEE + * + * Copyright © Sebastian Claßen , 2007 + * Copyright © CC Computer Consultants GmbH, 2007 - 2008 + * Jan Engelhardt + */ +#include +#include +#include +#include +#include +#include + +#include +#include +#include + +#include +#include +#include +#include "xt_TEE.h" + +enum { + FLAG_GATEWAY = 1 << 0, +}; + +static const struct option tee_tg_opts[] = { + {.name = "gateway", .has_arg = true, .val = 'g'}, + {}, +}; + +static void tee_tg_help(void) +{ + printf( +"TEE target options:\n" +" --gateway IPADDR Route packet via the gateway given by address\n" +"\n"); +} + +static int tee_tg_parse(int c, char **argv, int invert, unsigned int *flags, + const void *entry, struct xt_entry_target **target) +{ + struct xt_tee_tginfo *info = (void *)(*target)->data; + const struct in_addr *ia; + + switch (c) { + case 'g': + if (*flags & FLAG_GATEWAY) + exit_error(PARAMETER_PROBLEM, + "Cannot specify --gw more than once"); + + if (check_inverse(optarg, &invert, NULL, 0)) + exit_error(PARAMETER_PROBLEM, + "Unexpected \"!\" after --gateway"); + + ia = numeric_to_ipaddr(optarg); + if (ia == NULL) + exit_error(PARAMETER_PROBLEM, + "Invalid IP address %s", optarg); + + memcpy(&info->gw, &ia, sizeof(ia)); + *flags |= FLAG_GATEWAY; + return true; + } + + return false; +} + +static void tee_tg_check(unsigned int flags) +{ + if (flags == 0) + exit_error(PARAMETER_PROBLEM, "TEE target: " + "--gateway parameter required"); +} + +static void tee_tg_print(const void *ip, const struct xt_entry_target *target, + int numeric) +{ + const struct xt_tee_tginfo *info = (const void *)target->data; + + if (numeric) + printf("TEE gw:%s ", ipaddr_to_anyname(&info->gw.in)); + else + printf("TEE gw:%s ", ipaddr_to_numeric(&info->gw.in)); +} + +static void tee_tg_save(const void *ip, const struct xt_entry_target *target) +{ + const struct xt_tee_tginfo *info = (const void *)target->data; + + printf("--gateway %s ", ipaddr_to_numeric(&info->gw.in)); +} + +static struct xtables_target tee_tg_reg = { + .name = "TEE", + .version = IPTABLES_VERSION, + .size = XT_ALIGN(sizeof(struct xt_tee_tginfo)), + .userspacesize = XT_ALIGN(sizeof(struct xt_tee_tginfo)), + .help = tee_tg_help, + .parse = tee_tg_parse, + .final_check = tee_tg_check, + .print = tee_tg_print, + .save = tee_tg_save, + .extra_opts = tee_tg_opts, +}; + +static void _init(void) +{ + xtables_register_target(&tee_tg_reg); +} diff --git a/extensions/xt_TARPIT.Kconfig b/extensions/xt_TARPIT.Kconfig new file mode 100644 index 0000000..69ae7a2 --- /dev/null +++ b/extensions/xt_TARPIT.Kconfig @@ -0,0 +1,16 @@ +config NETFILTER_XT_TARGET_TARPIT + tristate '"TARPIT" target support' + depends on NETFILTER_XTABLES + ---help--- + Adds a TARPIT target to iptables, which captures and holds incoming TCP + connections using no local per-connection resources. Connections are + accepted, but immediately switched to the persist state (0 byte + window), in which the remote side stops sending data and asks to + continue every 60-240 seconds. Attempts to close the connection are + ignored, forcing the remote side to time out the connection in 12-24 + minutes. + + This offers similar functionality to LaBrea + , but does not require dedicated + hardware or IPs. Any TCP port that you would normally DROP or REJECT + can instead become a tar pit. diff --git a/extensions/xt_TARPIT.c b/extensions/xt_TARPIT.c new file mode 100644 index 0000000..3338176 --- /dev/null +++ b/extensions/xt_TARPIT.c @@ -0,0 +1,237 @@ +/* + * Kernel module to capture and hold incoming TCP connections using + * no local per-connection resources. + * + * Based on ipt_REJECT.c and offering functionality similar to + * LaBrea . + * + * Copyright (c) 2002 Aaron Hopkins + * + * This program 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. + * + * This program 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 this program; if not, write to the Free Software + * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + * + * Goal: + * - Allow incoming TCP connections to be established. + * - Passing data should result in the connection being switched to the + * persist state (0 byte window), in which the remote side stops sending + * data and asks to continue every 60 seconds. + * - Attempts to shut down the connection should be ignored completely, so + * the remote side ends up having to time it out. + * + * This means: + * - Reply to TCP SYN,!ACK,!RST,!FIN with SYN-ACK, window 5 bytes + * - Reply to TCP SYN,ACK,!RST,!FIN with RST to prevent spoofing + * - Reply to TCP !SYN,!RST,!FIN with ACK, window 0 bytes, rate-limited + */ + +#include +#include +#include +#include +#ifdef CONFIG_BRIDGE_NETFILTER +# include +#endif +#include +#include + +static inline void tarpit_tcp(struct sk_buff *oldskb, unsigned int hook) +{ + struct tcphdr _otcph, *oth, *tcph; + unsigned int addr_type; + struct sk_buff *nskb; + struct iphdr *niph; + u_int16_t tmp; + + /* A truncated TCP header is not going to be useful */ + if (oldskb->len < ip_hdrlen(oldskb) + sizeof(struct tcphdr)) + return; + + oth = skb_header_pointer(oldskb, ip_hdrlen(oldskb), + sizeof(_otcph), &_otcph); + if (oth == NULL) + return; + + /* No replies for RST, FIN or !SYN,!ACK */ + if (oth->rst || oth->fin || (!oth->syn && !oth->ack)) + return; + + /* Rate-limit replies to !SYN,ACKs */ +#if 0 + if (!oth->syn && oth->ack) + if (!xrlim_allow(&ort->u.dst, HZ)) + return; +#endif + + /* Check checksum. */ + if (nf_ip_checksum(oldskb, hook, ip_hdrlen(oldskb), IPPROTO_TCP)) + return; + + /* + * Copy skb (even if skb is about to be dropped, we cannot just + * clone it because there may be other things, such as tcpdump, + * interested in it) + */ + nskb = skb_copy_expand(oldskb, LL_MAX_HEADER, + skb_tailroom(oldskb), GFP_ATOMIC); + if (nskb == NULL) + return; + + /* This packet will not be the same as the other: clear nf fields */ + nf_reset(nskb); + nskb->mark = 0; + skb_init_secmark(nskb); + + skb_shinfo(nskb)->gso_size = 0; + skb_shinfo(nskb)->gso_segs = 0; + skb_shinfo(nskb)->gso_type = 0; + + tcph = (struct tcphdr *)(skb_network_header(nskb) + ip_hdrlen(nskb)); + + /* Swap source and dest */ + niph = ip_hdr(nskb); + niph->daddr = xchg(&niph->saddr, niph->daddr); + tmp = tcph->source; + tcph->source = tcph->dest; + tcph->dest = tmp; + + /* Truncate to length (no data) */ + tcph->doff = sizeof(struct tcphdr) / 4; + skb_trim(nskb, ip_hdrlen(nskb) + sizeof(struct tcphdr)); + niph->tot_len = htons(nskb->len); + + /* Use supplied sequence number or make a new one */ + tcph->seq = oth->ack ? oth->ack_seq : 0; + + /* Our SYN-ACKs must have a >0 window */ + tcph->window = (oth->syn && !oth->ack) ? htons(5) : 0; + tcph->urg_ptr = 0; + + /* Reset flags */ + ((u_int8_t *)tcph)[13] = 0; + + if (oth->syn && oth->ack) { + tcph->rst = true; + tcph->ack_seq = false; + } else { + tcph->syn = oth->syn; + tcph->ack = 1; + tcph->ack_seq = htonl(ntohl(oth->seq) + oth->syn); + } + + /* Adjust TCP checksum */ + tcph->check = 0; + tcph->check = tcp_v4_check(sizeof(struct tcphdr), niph->saddr, + niph->daddr, csum_partial((char *)tcph, + sizeof(struct tcphdr), 0)); + + /* Set DF, id = 0 */ + niph->frag_off = htons(IP_DF); + niph->id = 0; + +#ifdef CONFIG_BRIDGE_NETFILTER + if (hook != NF_INET_FORWARD || (nskb->nf_bridge != NULL && + nskb->nf_bridge->mask & BRNF_BRIDGED)) +#else + if (hook != NF_INET_FORWARD) +#endif + addr_type = RTN_LOCAL; + + if (ip_route_me_harder(nskb, addr_type)) + goto free_nskb; + + nskb->ip_summed = CHECKSUM_NONE; + + /* Adjust IP TTL */ + niph->ttl = dst_metric(nskb->dst, RTAX_HOPLIMIT); + + /* Adjust IP checksum */ + niph->check = 0; + niph->check = ip_fast_csum(skb_network_header(nskb), niph->ihl); + + /* "Never happens" */ + if (nskb->len > dst_mtu(nskb->dst)) + goto free_nskb; + + nf_ct_attach(nskb, oldskb); + + NF_HOOK(PF_INET, NF_INET_LOCAL_OUT, nskb, NULL, nskb->dst->dev, + dst_output); + return; + + free_nskb: + kfree_skb(nskb); +} + +static unsigned int +tarpit_tg(struct sk_buff *skb, const struct net_device *in, + const struct net_device *out, unsigned int hooknum, + const struct xt_target *target, const void *targinfo) +{ + const struct iphdr *iph = ip_hdr(skb); + const struct rtable *rt = (const void *)skb->dst; + + /* Do we have an input route cache entry? (Not in PREROUTING.) */ + if (rt == NULL) + return NF_DROP; + + /* No replies to physical multicast/broadcast */ + /* skb != PACKET_OTHERHOST handled by ip_rcv() */ + if (skb->pkt_type != PACKET_HOST) + return NF_DROP; + + /* Now check at the protocol level */ + if (rt->rt_flags & (RTCF_BROADCAST | RTCF_MULTICAST)) + return NF_DROP; + + /* + * Our naive response construction does not deal with IP + * options, and probably should not try. + */ + if (ip_hdrlen(skb) != sizeof(struct iphdr)) + return NF_DROP; + + /* We are not interested in fragments */ + if (iph->frag_off & htons(IP_OFFSET)) + return NF_DROP; + + tarpit_tcp(skb, hooknum); + return NF_DROP; +} + +static struct xt_target tarpit_tg_reg __read_mostly = { + .name = "TARPIT", + .family = AF_INET, + .table = "filter", + .hooks = (1 << NF_INET_LOCAL_IN) | (1 << NF_INET_FORWARD), + .proto = IPPROTO_TCP, + .target = tarpit_tg, + .me = THIS_MODULE, +}; + +static int __init tarpit_tg_init(void) +{ + return xt_register_target(&tarpit_tg_reg); +} + +static void __exit tarpit_tg_exit(void) +{ + xt_unregister_target(&tarpit_tg_reg); +} + +module_init(tarpit_tg_init); +module_exit(tarpit_tg_exit); +MODULE_AUTHOR("Jan Engelhardt "); +MODULE_DESCRIPTION("Xtables: \"TARPIT\", capture and hold TCP connections"); +MODULE_LICENSE("GPL"); +MODULE_ALIAS("ipt_TARPIT"); diff --git a/extensions/xt_TEE.Kconfig b/extensions/xt_TEE.Kconfig new file mode 100644 index 0000000..894c019 --- /dev/null +++ b/extensions/xt_TEE.Kconfig @@ -0,0 +1,9 @@ +config NETFILTER_XT_TARGET_TEE + tristate '"TEE" target support' + depends on NETFILTER_XTABLES + depends on NETFILTER_ADVANCED + depends on IP_NF_MANGLE || IP6_NF_MANGLE + ---help--- + This option adds a "TEE" target, which enables you to duplicate + packets and route those duplicates to a different gateway. + The target has to be used inside the mangle table. diff --git a/extensions/xt_TEE.c b/extensions/xt_TEE.c new file mode 100644 index 0000000..265d82f --- /dev/null +++ b/extensions/xt_TEE.c @@ -0,0 +1,248 @@ +/* + * This implements the TEE target. + * + * Copyright (C) 2007 Sebastian Claßen and + * CC Computer Consultants GmbH, 2007 + * + * based on ipt_ROUTE.c from Cédric de Launois + * + * This software is distributed under GNU GPL v2, 1991 + */ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#ifdef CONFIG_NETFILTER_XT_TARGET_TEE +# include +#else +# include "xt_TEE.h" +#endif + +#if defined(CONFIG_NF_CONNTRACK) || defined(CONFIG_NF_CONNTRACK_MODULE) +# define WITH_CONNTRACK 1 +# include +static struct nf_conn tee_track; +#endif + +static const union nf_inet_addr zero_address; + +/* + * Try to route the packet according to the routing keys specified in + * route_info. Keys are : + * - ifindex : + * 0 if no oif preferred, + * otherwise set to the index of the desired oif + * - route_info->gateway : + * 0 if no gateway specified, + * otherwise set to the next host to which the pkt must be routed + * If success, skb->dev is the output device to which the packet must + * be sent and skb->dst is not NULL + * + * RETURN: false - if an error occured + * true - if the packet was succesfully routed to the + * destination desired + */ +static bool tee_routing(struct sk_buff *skb, + const struct xt_tee_tginfo *info) +{ + int err; + struct rtable *rt; + struct iphdr *iph = ip_hdr(skb); + struct flowi fl = { + .nl_u = { + .ip4_u = { + .daddr = info->gw.ip, + .tos = RT_TOS(iph->tos), + .scope = RT_SCOPE_UNIVERSE, + } + } + }; + + /* Trying to route the packet using the standard routing table. */ + err = ip_route_output_key(&rt, &fl); + if (err != 0) { + if (net_ratelimit()) + pr_debug(KBUILD_MODNAME + ": could not route packet (%d)", err); + return false; + } + + /* Drop old route. */ + dst_release(skb->dst); + skb->dst = NULL; + + /* + * Success if no oif specified or if the oif correspond to the + * one desired. + * [SC]: always the case, because we have no oif. + */ + skb->dst = &rt->u.dst; + skb->dev = skb->dst->dev; + skb->protocol = htons(ETH_P_IP); + return true; +} + +/* + * Stolen from ip_finish_output2 + * PRE : skb->dev is set to the device we are leaving by + * skb->dst is not NULL + * POST: the packet is sent with the link layer header pushed + * the packet is destroyed + */ +static void tee_ip_direct_send(struct sk_buff *skb) +{ + const struct dst_entry *dst = skb->dst; + const struct net_device *dev = dst->dev; + unsigned int hh_len = LL_RESERVED_SPACE(dev); + + /* Be paranoid, rather than too clever. */ + if (unlikely(skb_headroom(skb) < hh_len && dev->header_ops != NULL)) { + struct sk_buff *skb2; + + skb2 = skb_realloc_headroom(skb, LL_RESERVED_SPACE(dev)); + if (skb2 == NULL) { + kfree_skb(skb); + return; + } + if (skb->sk != NULL) + skb_set_owner_w(skb2, skb->sk); + kfree_skb(skb); + skb = skb2; + } + + if (dst->hh != NULL) { + neigh_hh_output(dst->hh, skb); + } else if (dst->neighbour != NULL) { + dst->neighbour->output(skb); + } else { + if (net_ratelimit()) + pr_debug(KBUILD_MODNAME "no hdr & no neighbour cache!\n"); + kfree_skb(skb); + } +} + +/* + * To detect and deter routed packet loopback when using the --tee option, we + * take a page out of the raw.patch book: on the copied skb, we set up a fake + * ->nfct entry, pointing to the local &route_tee_track. We skip routing + * packets when we see they already have that ->nfct. + */ +static unsigned int +tee_tg(struct sk_buff *skb, const struct net_device *in, + const struct net_device *out, unsigned int hooknum, + const struct xt_target *target, const void *targinfo) +{ + const struct xt_tee_tginfo *info = targinfo; + +#ifdef WITH_CONNTRACK + if (skb->nfct == &tee_track.ct_general) { + /* + * Loopback - a packet we already routed, is to be + * routed another time. Avoid that, now. + */ + if (net_ratelimit()) + pr_debug(KBUILD_MODNAME "loopback - DROP!\n"); + return NF_DROP; + } +#endif + + /* + * If we are in INPUT, the checksum must be recalculated since + * the length could have changed as a result of defragmentation. + */ + if (hooknum == NF_INET_LOCAL_IN) { + struct iphdr *iph = ip_hdr(skb); + iph->check = 0; + iph->check = ip_fast_csum((unsigned char *)iph, iph->ihl); + } + + /* + * Copy the skb, and route the copy. Will later return %XT_CONTINUE for + * the original skb, which should continue on its way as if nothing has + * happened. The copy should be independantly delivered to the TEE --gw. + */ + skb = skb_copy(skb, GFP_ATOMIC); + if (skb == NULL) { + if (net_ratelimit()) + pr_debug(KBUILD_MODNAME "copy failed!\n"); + return XT_CONTINUE; + } + +#ifdef WITH_CONNTRACK + /* + * Tell conntrack to forget this packet since it may get confused + * when a packet is leaving with dst address == our address. + * Good idea? Dunno. Need advice. + * + * NEW: mark the skb with our &tee_track, so we avoid looping + * on any already routed packet. + */ + nf_conntrack_put(skb->nfct); + skb->nfct = &tee_track.ct_general; + skb->nfctinfo = IP_CT_NEW; + nf_conntrack_get(skb->nfct); +#endif + + if (tee_routing(skb, info)) + tee_ip_direct_send(skb); + + return XT_CONTINUE; +} + +static bool tee_tg_check(const char *tablename, const void *entry, + const struct xt_target *target, void *targinfo, + unsigned int hook_mask) +{ + const struct xt_tee_tginfo *info = targinfo; + + /* 0.0.0.0 and :: not allowed */ + return memcmp(&info->gw, &zero_address, sizeof(zero_address)) != 0; +} + +static struct xt_target tee_tg_reg __read_mostly = { + .name = "TEE", + .family = AF_INET, + .table = "mangle", + .target = tee_tg, + .targetsize = sizeof(struct xt_tee_tginfo), + .checkentry = tee_tg_check, + .me = THIS_MODULE, +}; + +static int __init tee_tg_init(void) +{ +#ifdef WITH_CONNTRACK + /* + * Set up fake conntrack (stolen from raw.patch): + * - to never be deleted, not in any hashes + */ + atomic_set(&tee_track.ct_general.use, 1); + + /* - and look it like as a confirmed connection */ + set_bit(IPS_CONFIRMED_BIT, &tee_track.status); + + /* Initialize fake conntrack so that NAT will skip it */ + tee_track.status |= IPS_NAT_DONE_MASK; +#endif + + return xt_register_target(&tee_tg_reg); +} + +static void __exit tee_tg_exit(void) +{ + xt_unregister_target(&tee_tg_reg); + /* [SC]: shoud not we cleanup tee_track here? */ +} + +module_init(tee_tg_init); +module_exit(tee_tg_exit); +MODULE_AUTHOR("Sebastian Claßen "); +MODULE_AUTHOR("Jan Engelhardt "); +MODULE_DESCRIPTION("Xtables: Reroute packet copy"); +MODULE_LICENSE("GPL"); +MODULE_ALIAS("ipt_TEE"); diff --git a/extensions/xt_TEE.h b/extensions/xt_TEE.h new file mode 100644 index 0000000..83fa768 --- /dev/null +++ b/extensions/xt_TEE.h @@ -0,0 +1,8 @@ +#ifndef _XT_TEE_TARGET_H +#define _XT_TEE_TARGET_H + +struct xt_tee_tginfo { + union nf_inet_addr gw; +}; + +#endif /* _XT_TEE_TARGET_H */