src/openvpn/clinat.c
581bef87
 /*
  *  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.
  *
49979459
  *  Copyright (C) 2002-2018 OpenVPN Inc <sales@openvpn.net>
581bef87
  *
  *  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.
  *
caa54ac3
  *  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.
581bef87
  */
 
c110b289
 #ifdef HAVE_CONFIG_H
 #include "config.h"
 #elif defined(_MSC_VER)
 #include "config-msvc.h"
 #endif
 
581bef87
 #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,
81d882d5
           const struct client_nat_entry *e)
581bef87
 {
81d882d5
     if (dest->n >= MAX_CLIENT_NAT)
581bef87
     {
81d882d5
         msg(M_WARN, "WARNING: client-nat table overflow (max %d entries)", MAX_CLIENT_NAT);
         return false;
581bef87
     }
81d882d5
     else
581bef87
     {
81d882d5
         dest->entries[dest->n++] = *e;
         return true;
581bef87
     }
 }
 
 void
 print_client_nat_list(const struct client_nat_option_list *list, int msglevel)
 {
81d882d5
     struct gc_arena gc = gc_new();
     int i;
581bef87
 
81d882d5
     msg(msglevel, "*** CNAT list");
     if (list)
581bef87
     {
81d882d5
         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));
         }
581bef87
     }
81d882d5
     gc_free(&gc);
581bef87
 }
 
 struct client_nat_option_list *
81d882d5
 new_client_nat_list(struct gc_arena *gc)
581bef87
 {
81d882d5
     struct client_nat_option_list *ret;
     ALLOC_OBJ_CLEAR_GC(ret, struct client_nat_option_list, gc);
     return ret;
581bef87
 }
 
 struct client_nat_option_list *
81d882d5
 clone_client_nat_option_list(const struct client_nat_option_list *src, struct gc_arena *gc)
581bef87
 {
81d882d5
     struct client_nat_option_list *ret;
     ALLOC_OBJ_GC(ret, struct client_nat_option_list, gc);
     *ret = *src;
     return ret;
581bef87
 }
 
 void
81d882d5
 copy_client_nat_option_list(struct client_nat_option_list *dest,
                             const struct client_nat_option_list *src)
581bef87
 {
81d882d5
     int i;
     for (i = 0; i < src->n; ++i)
581bef87
     {
81d882d5
         if (!add_entry(dest, &src->entries[i]))
         {
             break;
         }
581bef87
     }
 }
 
 void
81d882d5
 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)
581bef87
 {
81d882d5
     struct client_nat_entry e;
     bool ok;
581bef87
 
81d882d5
     if (!strcmp(type, "snat"))
581bef87
     {
81d882d5
         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;
581bef87
     }
 
81d882d5
     e.network = getaddr(0, network, 0, &ok, NULL);
     if (!ok)
581bef87
     {
81d882d5
         msg(msglevel, "client-nat: bad network: %s", network);
         return;
581bef87
     }
81d882d5
     e.netmask = getaddr(0, netmask, 0, &ok, NULL);
     if (!ok)
581bef87
     {
81d882d5
         msg(msglevel, "client-nat: bad netmask: %s", netmask);
         return;
581bef87
     }
81d882d5
     e.foreign_network = getaddr(0, foreign_network, 0, &ok, NULL);
     if (!ok)
581bef87
     {
81d882d5
         msg(msglevel, "client-nat: bad foreign network: %s", foreign_network);
         return;
581bef87
     }
 
81d882d5
     add_entry(dest, &e);
581bef87
 }
 
 #if 0
 static void
81d882d5
 print_checksum(struct openvpn_iphdr *iph, const char *prefix)
581bef87
 {
81d882d5
     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++)
581bef87
     {
81d882d5
         i += 1;
         sum += *sptr;
581bef87
     }
81d882d5
     msg(M_INFO, "** CKSUM[%d] %s %08x", i, prefix, sum);
581bef87
 }
 #endif
 
 static void
81d882d5
 print_pkt(struct openvpn_iphdr *iph, const char *prefix, const int direction, const int msglevel)
581bef87
 {
81d882d5
     struct gc_arena gc = gc_new();
581bef87
 
81d882d5
     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));
581bef87
 
81d882d5
     gc_free(&gc);
581bef87
 }
 
 void
81d882d5
 client_nat_transform(const struct client_nat_option_list *list,
                      struct buffer *ipbuf,
                      const int direction)
581bef87
 {
81d882d5
     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;
581bef87
 
81d882d5
     if (check_debug_level(D_CLIENT_NAT))
     {
         print_pkt(&h->ip, "BEFORE", direction, D_CLIENT_NAT);
     }
581bef87
 
81d882d5
     for (i = 0; i < list->n; ++i)
581bef87
     {
81d882d5
         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;
         }
581bef87
 
81d882d5
         if (((addr & e->netmask) == *from) && !(amask & alog))
         {
             /* pre-adjust IP checksum */
             ADD_CHECKSUM_32(accumulate, addr);
581bef87
 
81d882d5
             /* do NAT transform */
             addr = (addr & ~e->netmask) | *to;
581bef87
 
81d882d5
             /* post-adjust IP checksum */
             SUB_CHECKSUM_32(accumulate, addr);
581bef87
 
81d882d5
             /* write the modified address to packet */
             *addr_ptr = addr;
581bef87
 
81d882d5
             /* mark as modified */
             alog |= amask;
         }
581bef87
     }
81d882d5
     if (alog)
581bef87
     {
81d882d5
         if (check_debug_level(D_CLIENT_NAT))
         {
             print_pkt(&h->ip, "AFTER", direction, D_CLIENT_NAT);
         }
581bef87
 
81d882d5
         ADJUST_CHECKSUM(accumulate, h->ip.check);
581bef87
 
81d882d5
         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);
             }
         }
581bef87
     }
 }