From 637516f2d4c0cd44ee6ac54280d8280f3aa088b2 Mon Sep 17 00:00:00 2001 From: Jan Engelhardt Date: Thu, 26 Mar 2009 20:37:00 +0100 Subject: [PATCH] RAWNAT: add extension's kernel and userspace modules RAWNAT provides stateless 1:1 network address translation. --- doc/changelog.txt | 2 + extensions/Kbuild | 2 +- extensions/Mbuild | 1 + extensions/compat_xtables.c | 20 ++- extensions/compat_xtables.h | 3 + extensions/compat_xtnu.h | 3 + extensions/libxt_RAWDNAT.c | 187 +++++++++++++++++++ extensions/libxt_RAWDNAT.man | 10 ++ extensions/libxt_RAWSNAT.c | 187 +++++++++++++++++++ extensions/libxt_RAWSNAT.man | 38 ++++ extensions/xt_RAWNAT.Kconfig | 8 + extensions/xt_RAWNAT.c | 336 +++++++++++++++++++++++++++++++++++ extensions/xt_RAWNAT.h | 9 + 13 files changed, 804 insertions(+), 2 deletions(-) create mode 100644 extensions/libxt_RAWDNAT.c create mode 100644 extensions/libxt_RAWDNAT.man create mode 100644 extensions/libxt_RAWSNAT.c create mode 100644 extensions/libxt_RAWSNAT.man create mode 100644 extensions/xt_RAWNAT.Kconfig create mode 100644 extensions/xt_RAWNAT.c create mode 100644 extensions/xt_RAWNAT.h diff --git a/doc/changelog.txt b/doc/changelog.txt index 34d37ed..3c23b0f 100644 --- a/doc/changelog.txt +++ b/doc/changelog.txt @@ -3,6 +3,8 @@ - fuzzy: fix bogus comparison logic leftover from move to new 1.4.3 API - ipp2p: fix bogus varargs call - ipp2p: fix typo in error message +- added rawpost table (for use with RAWNAT) +- added RAWSNAT/RAWDNAT targets Xtables-addons 1.14 (March 31 2009) diff --git a/extensions/Kbuild b/extensions/Kbuild index 559a3b9..019a28a 100644 --- a/extensions/Kbuild +++ b/extensions/Kbuild @@ -11,7 +11,7 @@ obj-${build_DHCPMAC} += xt_DHCPMAC.o obj-${build_ECHO} += xt_ECHO.o obj-${build_IPMARK} += xt_IPMARK.o obj-${build_LOGMARK} += xt_LOGMARK.o -obj-${build_RAWNAT} += iptable_rawpost.o ip6table_rawpost.o +obj-${build_RAWNAT} += xt_RAWNAT.o iptable_rawpost.o ip6table_rawpost.o obj-${build_SYSRQ} += xt_SYSRQ.o obj-${build_STEAL} += xt_STEAL.o obj-${build_TARPIT} += xt_TARPIT.o diff --git a/extensions/Mbuild b/extensions/Mbuild index 8c90683..294844b 100644 --- a/extensions/Mbuild +++ b/extensions/Mbuild @@ -4,6 +4,7 @@ obj-${build_DHCPMAC} += libxt_DHCPMAC.so libxt_dhcpmac.so obj-${build_ECHO} += libxt_ECHO.so obj-${build_IPMARK} += libxt_IPMARK.so obj-${build_LOGMARK} += libxt_LOGMARK.so +obj-${build_RAWNAT} += libxt_RAWDNAT.so libxt_RAWSNAT.so obj-${build_STEAL} += libxt_STEAL.so obj-${build_SYSRQ} += libxt_SYSRQ.so obj-${build_TARPIT} += libxt_TARPIT.so diff --git a/extensions/compat_xtables.c b/extensions/compat_xtables.c index 16c22eb..438047a 100644 --- a/extensions/compat_xtables.c +++ b/extensions/compat_xtables.c @@ -447,6 +447,24 @@ int xtnu_ip_route_output_key(void *net, struct rtable **rp, struct flowi *flp) return ip_route_output_flow(rp, flp, NULL, 0); } EXPORT_SYMBOL_GPL(xtnu_ip_route_output_key); + +void xtnu_proto_csum_replace4(__sum16 *sum, struct sk_buff *skb, + __be32 from, __be32 to, bool pseudohdr) +{ + __be32 diff[] = {~from, to}; + + if (skb->ip_summed != CHECKSUM_PARTIAL) { + *sum = csum_fold(csum_partial(diff, sizeof(diff), + ~csum_unfold(*sum))); + if (skb->ip_summed == CHECKSUM_COMPLETE && pseudohdr) + skb->csum = ~csum_partial(diff, sizeof(diff), + ~skb->csum); + } else if (pseudohdr) { + *sum = ~csum_fold(csum_partial(diff, sizeof(diff), + csum_unfold(*sum))); + } +} +EXPORT_SYMBOL_GPL(xtnu_proto_csum_replace4); #endif #if LINUX_VERSION_CODE <= KERNEL_VERSION(2, 6, 19) @@ -468,7 +486,7 @@ static inline __wsum xtnu_csum_unfold(__sum16 n) return (__force __wsum)n; } -static inline void xtnu_csum_replace4(__sum16 *sum, __be32 from, __be32 to) +void xtnu_csum_replace4(__sum16 *sum, __be32 from, __be32 to) { __be32 diff[] = {~from, to}; *sum = csum_fold(csum_partial((char *)diff, sizeof(diff), diff --git a/extensions/compat_xtables.h b/extensions/compat_xtables.h index 2a75d62..1187dfa 100644 --- a/extensions/compat_xtables.h +++ b/extensions/compat_xtables.h @@ -69,8 +69,11 @@ #if LINUX_VERSION_CODE <= KERNEL_VERSION(2, 6, 19) # define csum_replace2 xtnu_csum_replace2 +# define csum_replace4 xtnu_csum_replace4 #elif LINUX_VERSION_CODE <= KERNEL_VERSION(2, 6, 24) # define csum_replace2 nf_csum_replace2 +# define csum_replace4 nf_csum_replace4 +# define inet_proto_csum_replace4 xtnu_proto_csum_replace4 #endif #if !defined(NIP6) && !defined(NIP6_FMT) diff --git a/extensions/compat_xtnu.h b/extensions/compat_xtnu.h index 82510f1..0a19082 100644 --- a/extensions/compat_xtnu.h +++ b/extensions/compat_xtnu.h @@ -138,6 +138,9 @@ extern struct xt_match *xtnu_request_find_match(unsigned int, const char *, uint8_t); extern int xtnu_neigh_hh_output(struct hh_cache *, struct sk_buff *); extern void xtnu_csum_replace2(__u16 __bitwise *, __be16, __be16); +extern void xtnu_csum_replace4(__u16 __bitwise *, __be32, __be32); +extern void xtnu_proto_csum_replace4(__u16 __bitwise *, struct sk_buff *, + __be32, __be32, bool); extern int xtnu_skb_linearize(struct sk_buff *); #endif /* _COMPAT_XTNU_H */ diff --git a/extensions/libxt_RAWDNAT.c b/extensions/libxt_RAWDNAT.c new file mode 100644 index 0000000..6a43750 --- /dev/null +++ b/extensions/libxt_RAWDNAT.c @@ -0,0 +1,187 @@ +/* + * "RAWNAT" target extension for iptables + * Copyright © Jan Engelhardt, 2008 - 2009 + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License; either + * version 2 of the License, or any later version, as published by the + * Free Software Foundation. + */ +#include +#include +#include +#include +#include +#include +#include +#include "xt_RAWNAT.h" + +enum { + FLAGS_TO = 1 << 0, +}; + +static const struct option rawdnat_tg_opts[] = { + {.name = "to-destination", .has_arg = true, .val = 't'}, + {}, +}; + +static void rawdnat_tg_help(void) +{ + printf( +"RAWDNAT target options:\n" +" --to-destination addr[/mask] Address or network to map to\n" +); +} + +static int +rawdnat_tg4_parse(int c, char **argv, int invert, unsigned int *flags, + const void *entry, struct xt_entry_target **target) +{ + struct xt_rawnat_tginfo *info = (void *)(*target)->data; + struct in_addr *a; + unsigned int mask; + char *end; + + switch (c) { + case 't': + info->mask = 32; + end = strchr(optarg, '/'); + if (end != NULL) { + *end++ = '\0'; + if (!xtables_strtoui(end, NULL, &mask, 0, 32)) + xtables_param_act(XTF_BAD_VALUE, "RAWDNAT", + "--to-destination", optarg); + info->mask = mask; + } + a = xtables_numeric_to_ipaddr(optarg); + if (a == NULL) + xtables_param_act(XTF_BAD_VALUE, "RAWDNAT", + "--to-destination", optarg); + memcpy(&info->addr.in, a, sizeof(*a)); + *flags |= FLAGS_TO; + return true; + } + return false; +} + +static int +rawdnat_tg6_parse(int c, char **argv, int invert, unsigned int *flags, + const void *entry, struct xt_entry_target **target) +{ + struct xt_rawnat_tginfo *info = (void *)(*target)->data; + struct in6_addr *a; + unsigned int mask; + char *end; + + switch (c) { + case 't': + info->mask = 128; + end = strchr(optarg, '/'); + if (end != NULL) { + *end++ = '\0'; + if (!xtables_strtoui(end, NULL, &mask, 0, 32)) + xtables_param_act(XTF_BAD_VALUE, "RAWDNAT", + "--to-destination", optarg); + info->mask = mask; + } + a = xtables_numeric_to_ip6addr(optarg); + if (a == NULL) + xtables_param_act(XTF_BAD_VALUE, "RAWDNAT", + "--to-destination", optarg); + memcpy(&info->addr.in6, a, sizeof(*a)); + *flags |= FLAGS_TO; + return true; + } + return false; +} + +static void rawdnat_tg_check(unsigned int flags) +{ + if (!(flags & FLAGS_TO)) + xtables_error(PARAMETER_PROBLEM, "RAWDNAT: " + "\"--to-destination\" is required."); +} + +static void +rawdnat_tg4_print(const void *entry, const struct xt_entry_target *target, + int numeric) +{ + const struct xt_rawnat_tginfo *info = (const void *)target->data; + + if (!numeric && info->mask == 32) + printf("to-destination %s ", + xtables_ipaddr_to_anyname(&info->addr.in)); + else + printf("to-destination %s/%u ", + xtables_ipaddr_to_numeric(&info->addr.in), info->mask); +} + +static void +rawdnat_tg6_print(const void *entry, const struct xt_entry_target *target, + int numeric) +{ + const struct xt_rawnat_tginfo *info = (const void *)target->data; + + if (!numeric && info->mask == 128) + printf("to-destination %s ", + xtables_ip6addr_to_anyname(&info->addr.in6)); + else + printf("to-destination %s/%u ", + xtables_ip6addr_to_numeric(&info->addr.in6), info->mask); +} + +static void +rawdnat_tg4_save(const void *entry, const struct xt_entry_target *target) +{ + const struct xt_rawnat_tginfo *info = (const void *)target->data; + + printf("--to-destination %s/%u ", + xtables_ipaddr_to_numeric(&info->addr.in), + info->mask); +} + +static void +rawdnat_tg6_save(const void *entry, const struct xt_entry_target *target) +{ + const struct xt_rawnat_tginfo *info = (const void *)target->data; + + printf("--to-destination %s/%u ", + xtables_ip6addr_to_numeric(&info->addr.in6), + info->mask); +} + +static struct xtables_target rawdnat_tg4_reg = { + .version = XTABLES_VERSION, + .name = "RAWDNAT", + .revision = 0, + .family = PF_INET, + .size = XT_ALIGN(sizeof(struct xt_rawnat_tginfo)), + .userspacesize = XT_ALIGN(sizeof(struct xt_rawnat_tginfo)), + .help = rawdnat_tg_help, + .parse = rawdnat_tg4_parse, + .final_check = rawdnat_tg_check, + .print = rawdnat_tg4_print, + .save = rawdnat_tg4_save, + .extra_opts = rawdnat_tg_opts, +}; + +static struct xtables_target rawdnat_tg6_reg = { + .version = XTABLES_VERSION, + .name = "RAWDNAT", + .revision = 0, + .family = PF_INET6, + .size = XT_ALIGN(sizeof(struct xt_rawnat_tginfo)), + .userspacesize = XT_ALIGN(sizeof(struct xt_rawnat_tginfo)), + .help = rawdnat_tg_help, + .parse = rawdnat_tg6_parse, + .final_check = rawdnat_tg_check, + .print = rawdnat_tg6_print, + .save = rawdnat_tg6_save, + .extra_opts = rawdnat_tg_opts, +}; + +static void _init(void) +{ + xtables_register_target(&rawdnat_tg4_reg); + xtables_register_target(&rawdnat_tg6_reg); +} diff --git a/extensions/libxt_RAWDNAT.man b/extensions/libxt_RAWDNAT.man new file mode 100644 index 0000000..8ba2473 --- /dev/null +++ b/extensions/libxt_RAWDNAT.man @@ -0,0 +1,10 @@ +The \fBRAWDNAT\fR target will rewrite the destination address in the IP header, +much like the \fBNETMAP\fR target. +.TP +\fB--to-destination\fR \fIaddr\fR[\fB/\fR\fImask\fR] +Network address to map to. The resulting address will be constructed the +following way: All 'one' bits in the \fImask\fR are filled in from the new +\fIaddress\fR. All bits that are zero in the mask are filled in from the +original address. +.PP +See the \fBRAWSNAT\fR help entry for examples and constraints. diff --git a/extensions/libxt_RAWSNAT.c b/extensions/libxt_RAWSNAT.c new file mode 100644 index 0000000..f718df9 --- /dev/null +++ b/extensions/libxt_RAWSNAT.c @@ -0,0 +1,187 @@ +/* + * "RAWNAT" target extension for iptables + * Copyright © Jan Engelhardt, 2008 - 2009 + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License; either + * version 2 of the License, or any later version, as published by the + * Free Software Foundation. + */ +#include +#include +#include +#include +#include +#include +#include +#include "xt_RAWNAT.h" + +enum { + FLAGS_TO = 1 << 0, +}; + +static const struct option rawsnat_tg_opts[] = { + {.name = "to-source", .has_arg = true, .val = 't'}, + {}, +}; + +static void rawsnat_tg_help(void) +{ + printf( +"RAWSNAT target options:\n" +" --to-source addr[/mask] Address or network to map to\n" +); +} + +static int +rawsnat_tg4_parse(int c, char **argv, int invert, unsigned int *flags, + const void *entry, struct xt_entry_target **target) +{ + struct xt_rawnat_tginfo *info = (void *)(*target)->data; + struct in_addr *a; + unsigned int mask; + char *end; + + switch (c) { + case 't': + info->mask = 32; + end = strchr(optarg, '/'); + if (end != NULL) { + *end++ = '\0'; + if (!xtables_strtoui(end, NULL, &mask, 0, 32)) + xtables_param_act(XTF_BAD_VALUE, "RAWSNAT", + "--to-source", optarg); + info->mask = mask; + } + a = xtables_numeric_to_ipaddr(optarg); + if (a == NULL) + xtables_param_act(XTF_BAD_VALUE, "RAWSNAT", + "--to-source", optarg); + memcpy(&info->addr.in, a, sizeof(*a)); + *flags |= FLAGS_TO; + return true; + } + return false; +} + +static int +rawsnat_tg6_parse(int c, char **argv, int invert, unsigned int *flags, + const void *entry, struct xt_entry_target **target) +{ + struct xt_rawnat_tginfo *info = (void *)(*target)->data; + struct in6_addr *a; + unsigned int mask; + char *end; + + switch (c) { + case 't': + info->mask = 128; + end = strchr(optarg, '/'); + if (end != NULL) { + *end++ = '\0'; + if (!xtables_strtoui(end, NULL, &mask, 0, 32)) + xtables_param_act(XTF_BAD_VALUE, "RAWSNAT", + "--to-source", optarg); + info->mask = mask; + } + a = xtables_numeric_to_ip6addr(optarg); + if (a == NULL) + xtables_param_act(XTF_BAD_VALUE, "RAWSNAT", + "--to-source", optarg); + memcpy(&info->addr.in6, a, sizeof(*a)); + *flags |= FLAGS_TO; + return true; + } + return false; +} + +static void rawsnat_tg_check(unsigned int flags) +{ + if (!(flags & FLAGS_TO)) + xtables_error(PARAMETER_PROBLEM, "RAWSNAT: " + "\"--to-source\" is required."); +} + +static void +rawsnat_tg4_print(const void *entry, const struct xt_entry_target *target, + int numeric) +{ + const struct xt_rawnat_tginfo *info = (const void *)target->data; + + if (!numeric && info->mask == 32) + printf("to-source %s ", + xtables_ipaddr_to_anyname(&info->addr.in)); + else + printf("to-source %s/%u ", + xtables_ipaddr_to_numeric(&info->addr.in), info->mask); +} + +static void +rawsnat_tg6_print(const void *entry, const struct xt_entry_target *target, + int numeric) +{ + const struct xt_rawnat_tginfo *info = (const void *)target->data; + + if (!numeric && info->mask == 128) + printf("to-source %s ", + xtables_ip6addr_to_anyname(&info->addr.in6)); + else + printf("to-source %s/%u ", + xtables_ip6addr_to_numeric(&info->addr.in6), info->mask); +} + +static void +rawsnat_tg4_save(const void *entry, const struct xt_entry_target *target) +{ + const struct xt_rawnat_tginfo *info = (const void *)target->data; + + printf("--to-source %s/%u ", + xtables_ipaddr_to_numeric(&info->addr.in), + info->mask); +} + +static void +rawsnat_tg6_save(const void *entry, const struct xt_entry_target *target) +{ + const struct xt_rawnat_tginfo *info = (const void *)target->data; + + printf("--to-source %s/%u ", + xtables_ip6addr_to_numeric(&info->addr.in6), + info->mask); +} + +static struct xtables_target rawsnat_tg4_reg = { + .version = XTABLES_VERSION, + .name = "RAWSNAT", + .revision = 0, + .family = PF_INET, + .size = XT_ALIGN(sizeof(struct xt_rawnat_tginfo)), + .userspacesize = XT_ALIGN(sizeof(struct xt_rawnat_tginfo)), + .help = rawsnat_tg_help, + .parse = rawsnat_tg4_parse, + .final_check = rawsnat_tg_check, + .print = rawsnat_tg4_print, + .save = rawsnat_tg4_save, + .extra_opts = rawsnat_tg_opts, +}; + +static struct xtables_target rawsnat_tg6_reg = { + .version = XTABLES_VERSION, + .name = "RAWSNAT", + .revision = 0, + .family = PF_INET6, + .size = XT_ALIGN(sizeof(struct xt_rawnat_tginfo)), + .userspacesize = XT_ALIGN(sizeof(struct xt_rawnat_tginfo)), + .help = rawsnat_tg_help, + .parse = rawsnat_tg6_parse, + .final_check = rawsnat_tg_check, + .print = rawsnat_tg6_print, + .save = rawsnat_tg6_save, + .extra_opts = rawsnat_tg_opts, +}; + +static void _init(void) +{ + xtables_register_target(&rawsnat_tg4_reg); + xtables_register_target(&rawsnat_tg6_reg); +} diff --git a/extensions/libxt_RAWSNAT.man b/extensions/libxt_RAWSNAT.man new file mode 100644 index 0000000..d7f68b8 --- /dev/null +++ b/extensions/libxt_RAWSNAT.man @@ -0,0 +1,38 @@ +The \fBRAWSNAT\fR and \fBRAWDNAT\fP targets provide stateless network address +translation. +.PP +The \fBRAWSNAT\fR target will rewrite the source address in the IP header, much +like the \fBNETMAP\fP target. \fBRAWSNAT\fP (and \fBRAWDNAT\fP) may only be +used in the \fBraw\fP or \fBrawpost\fP tables, but can be used in all chains, +which makes it possible to change the source address either when the packet +enters the machine or when it leaves it. The reason for this table constraint +is that RAWNAT must happen outside of connection tracking. +.TP +\fB--to-source\fR \fIaddr\fR[\fB/\fR\fImask\fR] +Network address to map to. The resulting address will be constructed the +following way: All 'one' bits in the \fImask\fR are filled in from the new +\fIaddress\fR. All bits that are zero in the mask are filled in from the +original address. +.PP +As an example, changing the destination for packets forwarded from an internal +LAN to the internet: +.IP +-t raw -A PREROUTING -i lan0 -d 212.201.100.135 -j RAWDNAT --to-destination 199.181.132.250 +-t rawpost -A POSTROUTING -o lan0 -s 199.181.132.250 -j RAWSNAT --to-source 212.201.100.135 +.PP +Note that changing addresses may influence the route selection! Specifically, +it statically NATs packets, not connections, like the normal DNAT/SNAT targets +would do. Also note that it can transform already-NATed connections -- as said, +it is completely external to Netfilter's connection tracking/NAT. +.PP +If the machine itself generates packets that are to be rawnat'ed, you need a +rule in the OUTPUT chain instead, just like you would with the stateful NAT +targets. +.PP +It may be necessary that in doing so, you also need an extra RAWSNAT rule, to +override the automatic source address selection that the routing code does +before passing packets to iptables. If the connecting socket has not been +explicitly bound to an address, as is the common mode of operation, the address +that will be chosen is the primary address of the device through which the +packet would be routed with its initial destination address - the address as +seen before any RAWNAT takes place. diff --git a/extensions/xt_RAWNAT.Kconfig b/extensions/xt_RAWNAT.Kconfig new file mode 100644 index 0000000..8324c40 --- /dev/null +++ b/extensions/xt_RAWNAT.Kconfig @@ -0,0 +1,8 @@ +config NETFILTER_XT_TARGET_RAWNAT + tristate '"RAWNAT" raw address translation w/o conntrack' + depends on NETFILTER_XTABLES && NETFILTER_ADVANCED + depends on IP_NF_RAW || IP_NF6_RAW + ---help--- + This option adds the RAWSNAT and RAWDNAT targets which can do Network + Address Translation (no port translation) without requiring Netfilter + connection tracking. diff --git a/extensions/xt_RAWNAT.c b/extensions/xt_RAWNAT.c new file mode 100644 index 0000000..6184524 --- /dev/null +++ b/extensions/xt_RAWNAT.c @@ -0,0 +1,336 @@ +/* + * "RAWNAT" target extension for Xtables - untracked NAT + * Copyright © Jan Engelhardt, 2008 - 2009 + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License; either + * version 2 of the License, or any later version, as published by the + * Free Software Foundation. + */ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "compat_xtables.h" +#include "xt_RAWNAT.h" + +static inline __be32 +remask(__be32 addr, __be32 repl, unsigned int shift) +{ + uint32_t mask = (shift == 32) ? 0 : (~(uint32_t)0 >> shift); + return htonl((ntohl(addr) & mask) | (ntohl(repl) & ~mask)); +} + +static void +rawnat_ipv6_mask(__be32 *addr, const __be32 *repl, unsigned int mask) +{ + switch (mask) { + case 0: + break; + case 1 ... 31: + addr[0] = remask(addr[0], repl[0], mask); + break; + case 32: + addr[0] = repl[0]; + break; + case 33 ... 63: + addr[0] = repl[0]; + addr[1] = remask(addr[1], repl[1], mask - 64); + break; + case 64: + addr[0] = repl[0]; + addr[1] = repl[1]; + break; + case 65 ... 95: + addr[0] = repl[0]; + addr[1] = repl[1]; + addr[2] = remask(addr[2], repl[2], mask - 96); + case 96: + addr[0] = repl[0]; + addr[1] = repl[1]; + addr[2] = repl[2]; + break; + case 97 ... 127: + addr[0] = repl[0]; + addr[1] = repl[1]; + addr[2] = repl[2]; + addr[3] = remask(addr[3], repl[3], mask - 128); + break; + case 128: + addr[0] = repl[0]; + addr[1] = repl[1]; + addr[2] = repl[2]; + addr[3] = repl[3]; + break; + } +} + +static void rawnat4_update_l4(struct sk_buff *skb, __be32 oldip, __be32 newip) +{ + struct iphdr *iph = ip_hdr(skb); + void *transport_hdr = (void *)iph + ip_hdrlen(skb); + struct tcphdr *tcph; + struct udphdr *udph; + + switch (iph->protocol) { + case IPPROTO_TCP: + tcph = transport_hdr; + inet_proto_csum_replace4(&tcph->check, skb, oldip, newip, true); + break; + case IPPROTO_UDP: + case IPPROTO_UDPLITE: + udph = transport_hdr; + if (udph->check != 0 || skb->ip_summed == CHECKSUM_PARTIAL) { + inet_proto_csum_replace4(&udph->check, skb, + oldip, newip, true); + if (udph->check == 0) + udph->check = CSUM_MANGLED_0; + } + break; + } +} + +static unsigned int rawnat4_writable_part(const struct iphdr *iph) +{ + unsigned int wlen = sizeof(*iph); + + switch (iph->protocol) { + case IPPROTO_TCP: + wlen += sizeof(struct tcphdr); + break; + case IPPROTO_UDP: + wlen += sizeof(struct udphdr); + break; + } + return wlen; +} + +static unsigned int +rawsnat_tg4(struct sk_buff **pskb, const struct xt_target_param *par) +{ + const struct xt_rawnat_tginfo *info = par->targinfo; + struct iphdr *iph; + __be32 new_addr; + + iph = ip_hdr(*pskb); + new_addr = remask(iph->saddr, info->addr.ip, info->mask); + if (iph->saddr == new_addr) + return XT_CONTINUE; + + if (!skb_make_writable(pskb, rawnat4_writable_part(iph))) + return NF_DROP; + + iph = ip_hdr(*pskb); + csum_replace4(&iph->check, iph->saddr, new_addr); + rawnat4_update_l4(*pskb, iph->saddr, new_addr); + iph->saddr = new_addr; + return XT_CONTINUE; +} + +static unsigned int +rawdnat_tg4(struct sk_buff **pskb, const struct xt_target_param *par) +{ + const struct xt_rawnat_tginfo *info = par->targinfo; + struct iphdr *iph; + __be32 new_addr; + + iph = ip_hdr(*pskb); + new_addr = remask(iph->daddr, info->addr.ip, info->mask); + if (iph->daddr == new_addr) + return XT_CONTINUE; + + if (!skb_make_writable(pskb, rawnat4_writable_part(iph))) + return NF_DROP; + + iph = ip_hdr(*pskb); + csum_replace4(&iph->check, iph->daddr, new_addr); + rawnat4_update_l4(*pskb, iph->daddr, new_addr); + iph->daddr = new_addr; + return XT_CONTINUE; +} + +static bool rawnat6_prepare_l4(struct sk_buff **pskb, unsigned int *l4offset, + unsigned int *l4proto) +{ + static const unsigned int types[] = + {IPPROTO_TCP, IPPROTO_UDP, IPPROTO_UDPLITE}; + unsigned int i; + int err; + + *l4proto = NEXTHDR_MAX; + + for (i = 0; i < ARRAY_SIZE(types); ++i) { + err = ipv6_find_hdr(*pskb, l4offset, types[i], NULL); + if (err >= 0) { + *l4proto = types[i]; + break; + } + if (err != -ENOENT) + return false; + } + + switch (*l4proto) { + case IPPROTO_TCP: + if (!skb_make_writable(pskb, *l4offset + sizeof(struct tcphdr))) + return false; + break; + case IPPROTO_UDP: + case IPPROTO_UDPLITE: + if (!skb_make_writable(pskb, *l4offset + sizeof(struct udphdr))) + return false; + break; + } + + return true; +} + +static void rawnat6_update_l4(struct sk_buff *skb, unsigned int l4proto, + unsigned int l4offset, const struct in6_addr *oldip, + const struct in6_addr *newip) +{ + const struct ipv6hdr *iph = ipv6_hdr(skb); + struct tcphdr *tcph; + struct udphdr *udph; + unsigned int i; + + switch (l4proto) { + case IPPROTO_TCP: + tcph = (void *)iph + l4offset; + for (i = 0; i < 4; ++i) + inet_proto_csum_replace4(&tcph->check, skb, + oldip->s6_addr32[i], newip->s6_addr32[i], true); + break; + case IPPROTO_UDP: + case IPPROTO_UDPLITE: + udph = (void *)iph + l4offset; + if (udph->check != 0 || skb->ip_summed == CHECKSUM_PARTIAL) { + for (i = 0; i < 4; ++i) + inet_proto_csum_replace4(&udph->check, skb, + oldip->s6_addr32[i], + newip->s6_addr32[i], true); + if (udph->check == 0) + udph->check = CSUM_MANGLED_0; + } + break; + } +} + +static unsigned int +rawsnat_tg6(struct sk_buff **pskb, const struct xt_target_param *par) +{ + const struct xt_rawnat_tginfo *info = par->targinfo; + unsigned int l4offset, l4proto; + struct ipv6hdr *iph; + struct in6_addr new_addr; + + iph = ipv6_hdr(*pskb); + memcpy(&new_addr, &iph->saddr, sizeof(new_addr)); + rawnat_ipv6_mask(new_addr.s6_addr32, info->addr.ip6, info->mask); + if (ipv6_addr_cmp(&iph->saddr, &new_addr) == 0) + return XT_CONTINUE; + if (!rawnat6_prepare_l4(pskb, &l4offset, &l4proto)) + return NF_DROP; + iph = ipv6_hdr(*pskb); + rawnat6_update_l4(*pskb, l4proto, l4offset, &iph->saddr, &new_addr); + memcpy(&iph->saddr, &new_addr, sizeof(new_addr)); + return XT_CONTINUE; +} + +static unsigned int +rawdnat_tg6(struct sk_buff **pskb, const struct xt_target_param *par) +{ + const struct xt_rawnat_tginfo *info = par->targinfo; + unsigned int l4offset, l4proto; + struct ipv6hdr *iph; + struct in6_addr new_addr; + + iph = ipv6_hdr(*pskb); + memcpy(&new_addr, &iph->daddr, sizeof(new_addr)); + rawnat_ipv6_mask(new_addr.s6_addr32, info->addr.ip6, info->mask); + if (ipv6_addr_cmp(&iph->daddr, &new_addr) == 0) + return XT_CONTINUE; + if (!rawnat6_prepare_l4(pskb, &l4offset, &l4proto)) + return NF_DROP; + iph = ipv6_hdr(*pskb); + rawnat6_update_l4(*pskb, l4proto, l4offset, &iph->daddr, &new_addr); + memcpy(&iph->daddr, &new_addr, sizeof(new_addr)); + return XT_CONTINUE; +} + +static bool rawnat_tg_check(const struct xt_tgchk_param *par) +{ + if (strcmp(par->table, "raw") == 0 || + strcmp(par->table, "rawpost") == 0) + return true; + + printk(KERN_ERR KBUILD_MODNAME " may only be used in the \"raw\" or " + "\"rawpost\" table.\n"); + return false; +} + +static struct xt_target rawnat_tg_reg[] __read_mostly = { + { + .name = "RAWSNAT", + .revision = 0, + .family = NFPROTO_IPV4, + .target = rawsnat_tg4, + .targetsize = sizeof(struct xt_rawnat_tginfo), + .checkentry = rawnat_tg_check, + .me = THIS_MODULE, + }, + { + .name = "RAWSNAT", + .revision = 0, + .family = NFPROTO_IPV6, + .target = rawsnat_tg6, + .targetsize = sizeof(struct xt_rawnat_tginfo), + .checkentry = rawnat_tg_check, + .me = THIS_MODULE, + }, + { + .name = "RAWDNAT", + .revision = 0, + .family = NFPROTO_IPV4, + .target = rawdnat_tg4, + .targetsize = sizeof(struct xt_rawnat_tginfo), + .checkentry = rawnat_tg_check, + .me = THIS_MODULE, + }, + { + .name = "RAWDNAT", + .revision = 0, + .family = NFPROTO_IPV6, + .target = rawdnat_tg6, + .targetsize = sizeof(struct xt_rawnat_tginfo), + .checkentry = rawnat_tg_check, + .me = THIS_MODULE, + }, +}; + +static int __init rawnat_tg_init(void) +{ + return xt_register_targets(rawnat_tg_reg, ARRAY_SIZE(rawnat_tg_reg)); +} + +static void __exit rawnat_tg_exit(void) +{ + xt_unregister_targets(rawnat_tg_reg, ARRAY_SIZE(rawnat_tg_reg)); +} + +module_init(rawnat_tg_init); +module_exit(rawnat_tg_exit); +MODULE_AUTHOR("Jan Engelhardt "); +MODULE_DESCRIPTION("Xtables: conntrack-less raw NAT"); +MODULE_LICENSE("GPL"); +MODULE_ALIAS("ipt_RAWSNAT"); +MODULE_ALIAS("ipt_RAWDNAT"); +MODULE_ALIAS("ip6t_RAWSNAT"); +MODULE_ALIAS("ip6t_RAWDNAT"); diff --git a/extensions/xt_RAWNAT.h b/extensions/xt_RAWNAT.h new file mode 100644 index 0000000..308841c --- /dev/null +++ b/extensions/xt_RAWNAT.h @@ -0,0 +1,9 @@ +#ifndef _LINUX_NETFILTER_XT_TARGET_RAWNAT +#define _LINUX_NETFILTER_XT_TARGET_RAWNAT 1 + +struct xt_rawnat_tginfo { + union nf_inet_addr addr; + __u8 mask; +}; + +#endif /* _LINUX_NETFILTER_XT_TARGET_RAWNAT */