Dear Developers, I developed a patch for implementing 1:1 NAT (something similar to the iptables NETMAP target). This is useful in situations when you have the same (private) network address behind clients. For example, consider the following scenario:
-lan1--192.168.0.0/24-- -lan2--192.168.0.0/24-- | | gw1 192.168.0.1 gw2 192.168.0.1 | | [tunnel]-----OpenVPN server---[tunnel] | [tunnel] | clients… The clients have to access to both the machines in lan1 and lan2, This patch allow to map all the address of a network, e.g. [to g1] push "netmap 172.16.1.0/24 192.168.0.0/24" [to g2] push "netmap 172.16.2.0/24 192.168.0.0/24" The clients can access to, e.g. 192.168.0.79 on lan1 using the IP 172.16.1.79. Regards, Andrea :: e n d i a n :: security with passion :: andrea bonomi :: http://www.endian.com :: a.bon...@endian.com Signed-off-by: Andrea Bonomi <a.bon...@endian.com> --- src/openvpn/Makefile.am | 1 + src/openvpn/forward.c | 10 +- src/openvpn/netmap.c | 238 +++++++++++++++++++++++++++++++++++ src/openvpn/netmap.h | 53 ++++++++ src/openvpn/openvpn.vcxproj | 1 + src/openvpn/openvpn.vcxproj.filters | 3 + src/openvpn/options.c | 19 +++ src/openvpn/options.h | 10 ++ 8 files changed, 333 insertions(+), 2 deletions(-) create mode 100644 src/openvpn/netmap.c create mode 100644 src/openvpn/netmap.h diff --git a/src/openvpn/Makefile.am b/src/openvpn/Makefile.am index 5d38628..eee5117 100644 --- a/src/openvpn/Makefile.am +++ b/src/openvpn/Makefile.am @@ -73,6 +73,7 @@ openvpn_SOURCES = \ mtu.c mtu.h \ mudp.c mudp.h \ multi.c multi.h \ + netmap.c netmap.h \ ntlm.c ntlm.h \ occ.c occ.h occ-inline.h \ pkcs11.c pkcs11.h pkcs11_backend.h \ diff --git a/src/openvpn/forward.c b/src/openvpn/forward.c index 57c7846..fc52cae 100644 --- a/src/openvpn/forward.c +++ b/src/openvpn/forward.c @@ -1026,15 +1026,21 @@ process_ipv4_header (struct context *c, unsigned int flags, struct buffer *buf) * The --passtos and --mssfix options require * us to examine the IPv4 header. */ + #if PASSTOS_CAPABILITY - if (flags & (PIPV4_PASSTOS|PIPV4_MSSFIX)) + if (flags & (PIPV4_PASSTOS|PIPV4_MSSFIX) || c->options.netmap_enabled) #else - if (flags & PIPV4_MSSFIX) + if (flags & PIPV4_MSSFIX || c->options.netmap_enabled) #endif { struct buffer ipbuf = *buf; if (is_ipv4 (TUNNEL_TYPE (c->c1.tuntap), &ipbuf)) { + if (c->options.netmap_enabled) + { + do_netmap(&c->options.netmaps, flags, &ipbuf); + } + #if PASSTOS_CAPABILITY /* extract TOS from IP header */ if (flags & PIPV4_PASSTOS) diff --git a/src/openvpn/netmap.c b/src/openvpn/netmap.c new file mode 100644 index 0000000..52fe25b --- /dev/null +++ b/src/openvpn/netmap.c @@ -0,0 +1,238 @@ +/* + * Copyright (c) 2012 Endian + * Endian GmbH/Srl + * Bergweg 41 Via Monte + * 39057 Eppan/Appiano + * ITALIEN/ITALIA + * i...@endian.com + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 + * as published by the Free Software Foundation. + * + * 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 (see the file COPYING included with this + * distribution); if not, write to the Free Software Foundation, Inc., + * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + * Author: Andrea Bonomi <a.bon...@endian.com> + */ + +#include "syshead.h" +#include "common.h" +#include "error.h" +#include "netmap.h" +#include "socket.h" +#include "proto.h" +#include "forward.h" + + +/** + * Parse a network address in CIDR notation or an IP address + */ +static bool +parse_cidr (const char *cidr, int *addr, int *mask, bool *has_mask) +{ + unsigned int a, b, c, d, netmask, l; + *addr = 0; + *mask = 0; + *has_mask = false; + l = sscanf (cidr, "%u.%u.%u.%u/%u", &a, &b, &c, &d, &netmask); + if (l == 4 || l == 5) { + if (l == 4) { + netmask = 0; + } + if (a < 256 && b < 256 && c < 256 && d < 256 && netmask <= 32) { + uint32_t uaddr = a<<24 | b<<16 | c<<8 | d; + if (l == 4) { /* the netmask was not specified */ + *addr = htonl (uaddr); + } else { + *mask = htonl((0xffffffff >> (32 - netmask )) << (32 - netmask)); + *addr = htonl (uaddr) & *mask; + *has_mask = true; + } + return true; + } + } + return false; +} + +/* + * Calculate the IP checksum and update the IP header + */ + +static void +update_ip_checksum(struct openvpn_iphdr *iph) { + const char *t = (const char *)iph; + const char *end = t + OPENVPN_IPH_GET_LEN (iph->version_len) - 1; + uint32_t sum = 0; + iph->check = 0; + + while (t < end) { + sum += (((char)*t << 8) & 0xff00); + t++; + sum += (char)*t & 0xff; + t++; + if (sum > 0xffff) + sum = (sum & 0xffff) + (sum >> 16); + } + + iph->check = htons((uint16_t) ~sum); +} + +/* + * Check if the source/destination address have to be remapped. + * If the package direction is outgoing change the source address, + * otherwise change the destination address. + */ + +void +do_netmap (struct netmap_option_list *netmaps, unsigned int flags, struct buffer *ipbuf) { + struct openvpn_iphdr *iph = (struct openvpn_iphdr *) BPTR (ipbuf); + struct netmap_option *n; + bool package_updated = false; + int i; + +#ifdef NETMAP_DEBUG + struct in_addr s; + struct in_addr d; + s.s_addr = iph->saddr; + d.s_addr = iph->daddr; + msg (M_INFO, "outgoing: %c", flags & PIPV4_OUTGOING ? 'y' : 'n'); + msg (M_INFO, "source: %s 0x%08x", inet_ntoa(s), iph->saddr); + msg (M_INFO, "destination: %s 0x%08x", inet_ntoa(d), iph->daddr); + msg (M_INFO, "netmaps size: %d", netmaps ? netmaps->size : -1); +#endif + + if (!netmaps) { + return; + } + + if (flags & PIPV4_OUTGOING) { /* outgoing (received package) */ + for (i = 0, n = netmaps->netmaps; i < netmaps->size; i++, n++) { + +#ifdef NETMAP_DEBUG + msg (M_INFO, "netmap %d#: destination: 0x%08x source: 0x%08x netmap: 0x%08x", i, n->destination, n->source, n->netmap_mask); +#endif + + if (n->destination == (iph->saddr & n->netmap_mask)) { + iph->saddr = n->source | (iph->saddr & ~n->netmap_mask); + package_updated = true; +#ifdef NETMAP_DEBUG + s.s_addr = iph->saddr; + msg (M_INFO, "netmap %d#: source changed to: %s 0x%08x", i, inet_ntoa(s), iph->saddr); +#endif + } + + if (n->destination == (iph->daddr & n->netmap_mask)) { + iph->daddr = n->source | (iph->daddr & ~n->netmap_mask); + package_updated = true; +#ifdef NETMAP_DEBUG + d.s_addr = iph->daddr; + msg (M_INFO, "netmap %d#: destination changed to: %s 0x%08x", i, inet_ntoa(d), iph->daddr); +#endif + } + + } + } else { /* incoming (package to be sent) */ + + for (i = 0, n = netmaps->netmaps; i < netmaps->size; i++, n++) { + +#ifdef NETMAP_DEBUG + msg (M_INFO, "netmap %d#: destination: 0x%08x source: 0x%08x netmap: 0x%08x", i, n->destination, n->source, n->netmap_mask); +#endif + + if (n->source == (iph->saddr & n->netmap_mask)) { + iph->saddr = n->destination | (iph->saddr & ~n->netmap_mask); + package_updated = true; +#ifdef NETMAP_DEBUG + s.s_addr = iph->saddr; + msg (M_INFO, "netmap %d#: source changed to: %s 0x%08x", i, inet_ntoa(s), iph->saddr); +#endif + } + + if (n->source == (iph->daddr & n->netmap_mask)) { + iph->daddr = n->destination | (iph->daddr & ~n->netmap_mask); + package_updated = true; +#ifdef NETMAP_DEBUG + d.s_addr = iph->daddr; + msg (M_INFO, "netmap %d#: destination changed to: %s 0x%08x", i, inet_ntoa(d), iph->daddr); +#endif + } + + } + + } + + if (package_updated) { + update_ip_checksum(iph); + } + +} + +/* + * Add a new netmap option + */ +bool +add_netmap_to_option_list (struct netmap_option_list *netmaps, const char *source, + const char *destination, const char *netmask, int msglevel) +{ + struct netmap_option *no; + int netmask1; + bool has_netmask1; + int netmask2; + bool has_netmask2; + if (netmaps->size >= MAX_NETMAPS_DEFAULT) { + msg (M_FATAL, PACKAGE_NAME " NETMAP: cannot add more than %d netmaps", MAX_NETMAPS_DEFAULT); + } + no = &netmaps->netmaps[netmaps->size]; + + if (!source || !parse_cidr(source, &no->source, &netmask1, &has_netmask1)) { + msg (msglevel, " NETMAP: Cannot parse source IP: %s", source); + return false; + } + + if (!destination || !parse_cidr(destination, &no->destination, &netmask2, &has_netmask2)) { + msg (msglevel, " NETMAP: Cannot parse destination IP: %s", destination); + return false; + } + + if (netmask1 != netmask2) { + msg (msglevel, " NETMAP: Source and destination netmasks must be the same"); + return false; + } + + if (!netmask) { + /* netmap not specified, use the netmask from the cidr or the default netmask */ + if (has_netmask1) { + no->netmap_mask = netmask1; + } else { + /* default netmask 255.255.255.255 */ + no->netmap_mask = 0xffffffff; + } + + } else { + struct in_addr addr; + if (openvpn_inet_aton(netmask, &addr) != OIA_IP) { + msg (msglevel, " NETMAP: Cannot parse netmask: %s", netmask); + return false; + } + + if (netmask1 != -1 && htonl((0xffffffff >> (32 - netmask1 )) << (32 - netmask1) != addr.s_addr)) { + msg (msglevel, " NETMAP: You canot use both CIDR and dot-decimal netmasks"); + return false; + } + + no->netmap_mask = addr.s_addr; + no->source = no->source & no->netmap_mask; + no->destination = no->destination & no->netmap_mask; + } + + netmaps->size++; + return true; +} diff --git a/src/openvpn/netmap.h b/src/openvpn/netmap.h new file mode 100644 index 0000000..24f9a2b --- /dev/null +++ b/src/openvpn/netmap.h @@ -0,0 +1,53 @@ +/* + * Copyright (c) 2012 Endian + * Endian GmbH/Srl + * Bergweg 41 Via Monte + * 39057 Eppan/Appiano + * ITALIEN/ITALIA + * i...@endian.com + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 + * as published by the Free Software Foundation. + * + * 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 (see the file COPYING included with this + * distribution); if not, write to the Free Software Foundation, Inc., + * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + * Author: Andrea Bonomi <a.bon...@endian.com> + */ + +/* + * Support routines for adding/deleting network maps. + */ + +#ifndef NETMAP_H +#define NETMAP_H + +#include "buffer.h" + +/* #define NETMAP_DEBUG */ + +#define MAX_NETMAPS_DEFAULT 32 + +struct netmap_option { + uint32_t source; + uint32_t destination; + uint32_t netmap_mask; +}; + +struct netmap_option_list { + int size; + struct netmap_option netmaps[MAX_NETMAPS_DEFAULT]; +}; + +void do_netmap (struct netmap_option_list *netmaps, unsigned int flags, struct buffer *ipbuf); +bool add_netmap_to_option_list (struct netmap_option_list *netmaps, const char *source, const char *destination, const char *netmask, int msglevel); + +#endif diff --git a/src/openvpn/openvpn.vcxproj b/src/openvpn/openvpn.vcxproj index 3b2340e..633b881 100755 --- a/src/openvpn/openvpn.vcxproj +++ b/src/openvpn/openvpn.vcxproj @@ -128,6 +128,7 @@ <ClCompile Include="mtu.c" /> <ClCompile Include="mudp.c" /> <ClCompile Include="multi.c" /> + <ClCompile Include="netmap.c" /> <ClCompile Include="ntlm.c" /> <ClCompile Include="occ.c" /> <ClCompile Include="openvpn.c" /> diff --git a/src/openvpn/openvpn.vcxproj.filters b/src/openvpn/openvpn.vcxproj.filters index 40336ba..4b26efb 100644 --- a/src/openvpn/openvpn.vcxproj.filters +++ b/src/openvpn/openvpn.vcxproj.filters @@ -108,6 +108,9 @@ <ClCompile Include="multi.c"> <Filter>Source Files</Filter> </ClCompile> + <ClCompile Include="netmap.c"> + <Filter>Source Files</Filter> + </ClCompile> <ClCompile Include="ntlm.c"> <Filter>Source Files</Filter> </ClCompile> diff --git a/src/openvpn/options.c b/src/openvpn/options.c index d25bbea..7e5684e 100644 --- a/src/openvpn/options.c +++ b/src/openvpn/options.c @@ -239,6 +239,9 @@ static const char usage_message[] = " Add 'bypass-dns' flag to similarly bypass tunnel for DNS.\n" "--redirect-private [flags]: Like --redirect-gateway, but omit actually changing\n" " the default gateway. Useful when pushing private subnets.\n" + "--netmap source dest [netmask]: Add a 1:1 mapping of network address\n" + " 'source' is the local network to be remapped into 'dest'\n" + " on the other side of the tunnel\n" #ifdef ENABLE_CLIENT_NAT "--client-nat snat|dnat network netmask alias : on client add 1-to-1 NAT rule.\n" #endif @@ -870,6 +873,7 @@ init_options (struct options *o, const bool init_gc) } #endif /* WIN32 */ #endif /* P2MP_SERVER */ + o->netmaps.size = 0; } void @@ -2759,6 +2763,9 @@ pre_pull_save (struct options *o) o->pre_pull->tuntap_options = o->tuntap_options; o->pre_pull->tuntap_options_defined = true; o->pre_pull->foreign_option_index = o->foreign_option_index; + o->pre_pull->netmaps = o->netmaps; + o->pre_pull->netmaps_defined = true; + if (o->routes) { o->pre_pull->routes = clone_route_option_list(o->routes, &o->gc); @@ -2789,6 +2796,10 @@ pre_pull_restore (struct options *o) if (pp->tuntap_options_defined) o->tuntap_options = pp->tuntap_options; + CLEAR (o->netmaps); + if (pp->netmaps_defined) + o->netmaps = pp->netmaps; + if (pp->routes_defined) { rol_check_alloc (o); @@ -5115,6 +5126,14 @@ add_option (struct options *options, } options->max_routes = max_routes; } + else if (streq (p[0], "netmap") && p[1]) + { + VERIFY_PERMISSION (OPT_P_ROUTE); + if (!add_netmap_to_option_list(&options->netmaps, p[1], p[2], p[3], msglevel)) { + goto err; + } + options->netmap_enabled = true; + } else if (streq (p[0], "route-gateway") && p[1]) { VERIFY_PERMISSION (OPT_P_ROUTE_EXTRAS); diff --git a/src/openvpn/options.h b/src/openvpn/options.h index 306520b..7b697d5 100644 --- a/src/openvpn/options.h +++ b/src/openvpn/options.h @@ -34,6 +34,7 @@ #include "common.h" #include "mtu.h" #include "route.h" +#include "netmap.h" #include "tun.h" #include "socket.h" #include "plugin.h" @@ -76,6 +77,9 @@ struct options_pre_pull struct client_nat_option_list *client_nat; #endif + bool netmaps_defined; + struct netmap_option_list netmaps; + int foreign_option_index; }; @@ -347,6 +351,11 @@ struct options struct client_nat_option_list *client_nat; #endif + /** Network map */ + int max_netmaps; + struct netmap_option_list netmaps; + bool netmap_enabled; + #ifdef ENABLE_OCC /* Enable options consistency check between peers */ bool occ; @@ -624,6 +633,7 @@ struct options #define OPT_P_SOCKBUF (1<<25) #define OPT_P_SOCKFLAGS (1<<26) #define OPT_P_CONNECTION (1<<27) +#define OPT_P_NETMAP (1<<28) #define OPT_P_DEFAULT (~(OPT_P_INSTANCE|OPT_P_PULL_MODE)) -- 1.7.9.5