blob: b08fd5493da12bf7ad8e12a2b93a184ed8016ff0 [file] [log] [blame]
/*
* OpenVPN -- An application to securely tunnel IP networks
* over a single TCP/UDP port, with support for SSL/TLS-based
* session authentication and key exchange,
* packet encryption, packet authentication, and
* packet compression.
*
* Copyright (C) 2002-2018 OpenVPN Inc <sales@openvpn.net>
*
* 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; if not, write to the Free Software Foundation, Inc.,
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*/
#ifdef HAVE_CONFIG_H
#include "config.h"
#elif defined(_MSC_VER)
#include "config-msvc.h"
#endif
#include "syshead.h"
#include "clinat.h"
#include "proto.h"
#include "socket.h"
#include "memdbg.h"
static bool
add_entry(struct client_nat_option_list *dest,
const struct client_nat_entry *e)
{
if (dest->n >= MAX_CLIENT_NAT)
{
msg(M_WARN, "WARNING: client-nat table overflow (max %d entries)", MAX_CLIENT_NAT);
return false;
}
else
{
dest->entries[dest->n++] = *e;
return true;
}
}
void
print_client_nat_list(const struct client_nat_option_list *list, int msglevel)
{
struct gc_arena gc = gc_new();
int i;
msg(msglevel, "*** CNAT list");
if (list)
{
for (i = 0; i < list->n; ++i)
{
const struct client_nat_entry *e = &list->entries[i];
msg(msglevel, " CNAT[%d] t=%d %s/%s/%s",
i,
e->type,
print_in_addr_t(e->network, IA_NET_ORDER, &gc),
print_in_addr_t(e->netmask, IA_NET_ORDER, &gc),
print_in_addr_t(e->foreign_network, IA_NET_ORDER, &gc));
}
}
gc_free(&gc);
}
struct client_nat_option_list *
new_client_nat_list(struct gc_arena *gc)
{
struct client_nat_option_list *ret;
ALLOC_OBJ_CLEAR_GC(ret, struct client_nat_option_list, gc);
return ret;
}
struct client_nat_option_list *
clone_client_nat_option_list(const struct client_nat_option_list *src, struct gc_arena *gc)
{
struct client_nat_option_list *ret;
ALLOC_OBJ_GC(ret, struct client_nat_option_list, gc);
*ret = *src;
return ret;
}
void
copy_client_nat_option_list(struct client_nat_option_list *dest,
const struct client_nat_option_list *src)
{
int i;
for (i = 0; i < src->n; ++i)
{
if (!add_entry(dest, &src->entries[i]))
{
break;
}
}
}
void
add_client_nat_to_option_list(struct client_nat_option_list *dest,
const char *type,
const char *network,
const char *netmask,
const char *foreign_network,
int msglevel)
{
struct client_nat_entry e;
bool ok;
if (!strcmp(type, "snat"))
{
e.type = CN_SNAT;
}
else if (!strcmp(type, "dnat"))
{
e.type = CN_DNAT;
}
else
{
msg(msglevel, "client-nat: type must be 'snat' or 'dnat'");
return;
}
e.network = getaddr(0, network, 0, &ok, NULL);
if (!ok)
{
msg(msglevel, "client-nat: bad network: %s", network);
return;
}
e.netmask = getaddr(0, netmask, 0, &ok, NULL);
if (!ok)
{
msg(msglevel, "client-nat: bad netmask: %s", netmask);
return;
}
e.foreign_network = getaddr(0, foreign_network, 0, &ok, NULL);
if (!ok)
{
msg(msglevel, "client-nat: bad foreign network: %s", foreign_network);
return;
}
add_entry(dest, &e);
}
#if 0
static void
print_checksum(struct openvpn_iphdr *iph, const char *prefix)
{
uint16_t *sptr;
unsigned int sum = 0;
int i = 0;
for (sptr = (uint16_t *)iph; (uint8_t *)sptr < (uint8_t *)iph + sizeof(struct openvpn_iphdr); sptr++)
{
i += 1;
sum += *sptr;
}
msg(M_INFO, "** CKSUM[%d] %s %08x", i, prefix, sum);
}
#endif
static void
print_pkt(struct openvpn_iphdr *iph, const char *prefix, const int direction, const int msglevel)
{
struct gc_arena gc = gc_new();
char *dirstr = "???";
if (direction == CN_OUTGOING)
{
dirstr = "OUT";
}
else if (direction == CN_INCOMING)
{
dirstr = "IN";
}
msg(msglevel, "** CNAT %s %s %s -> %s",
dirstr,
prefix,
print_in_addr_t(iph->saddr, IA_NET_ORDER, &gc),
print_in_addr_t(iph->daddr, IA_NET_ORDER, &gc));
gc_free(&gc);
}
void
client_nat_transform(const struct client_nat_option_list *list,
struct buffer *ipbuf,
const int direction)
{
struct ip_tcp_udp_hdr *h = (struct ip_tcp_udp_hdr *) BPTR(ipbuf);
int i;
uint32_t addr, *addr_ptr;
const uint32_t *from, *to;
int accumulate = 0;
unsigned int amask;
unsigned int alog = 0;
if (check_debug_level(D_CLIENT_NAT))
{
print_pkt(&h->ip, "BEFORE", direction, D_CLIENT_NAT);
}
for (i = 0; i < list->n; ++i)
{
const struct client_nat_entry *e = &list->entries[i]; /* current NAT rule */
if (e->type ^ direction)
{
addr = *(addr_ptr = &h->ip.daddr);
amask = 2;
}
else
{
addr = *(addr_ptr = &h->ip.saddr);
amask = 1;
}
if (direction)
{
from = &e->foreign_network;
to = &e->network;
}
else
{
from = &e->network;
to = &e->foreign_network;
}
if (((addr & e->netmask) == *from) && !(amask & alog))
{
/* pre-adjust IP checksum */
ADD_CHECKSUM_32(accumulate, addr);
/* do NAT transform */
addr = (addr & ~e->netmask) | *to;
/* post-adjust IP checksum */
SUB_CHECKSUM_32(accumulate, addr);
/* write the modified address to packet */
*addr_ptr = addr;
/* mark as modified */
alog |= amask;
}
}
if (alog)
{
if (check_debug_level(D_CLIENT_NAT))
{
print_pkt(&h->ip, "AFTER", direction, D_CLIENT_NAT);
}
ADJUST_CHECKSUM(accumulate, h->ip.check);
if (h->ip.protocol == OPENVPN_IPPROTO_TCP)
{
if (BLEN(ipbuf) >= sizeof(struct openvpn_iphdr) + sizeof(struct openvpn_tcphdr))
{
ADJUST_CHECKSUM(accumulate, h->u.tcp.check);
}
}
else if (h->ip.protocol == OPENVPN_IPPROTO_UDP)
{
if (BLEN(ipbuf) >= sizeof(struct openvpn_iphdr) + sizeof(struct openvpn_udphdr))
{
ADJUST_CHECKSUM(accumulate, h->u.udp.check);
}
}
}
}