blob: fb28b27d6b234bbdb400e8a89bf2ef15c267c917 [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 "dhcp.h"
#include "socket.h"
#include "error.h"
#include "memdbg.h"
static int
get_dhcp_message_type(const struct dhcp *dhcp, const int optlen)
{
const uint8_t *p = (uint8_t *) (dhcp + 1);
int i;
for (i = 0; i < optlen; ++i)
{
const uint8_t type = p[i];
const int room = optlen - i;
if (type == DHCP_END) /* didn't find what we were looking for */
{
return -1;
}
else if (type == DHCP_PAD) /* no-operation */
{
}
else if (type == DHCP_MSG_TYPE) /* what we are looking for */
{
if (room >= 3)
{
if (p[i+1] == 1) /* option length should be 1 */
{
return p[i+2]; /* return message type */
}
}
return -1;
}
else /* some other option */
{
if (room >= 2)
{
const int len = p[i+1]; /* get option length */
i += (len + 1); /* advance to next option */
}
}
}
return -1;
}
static in_addr_t
do_extract(struct dhcp *dhcp, int optlen)
{
uint8_t *p = (uint8_t *) (dhcp + 1);
int i;
in_addr_t ret = 0;
for (i = 0; i < optlen; )
{
const uint8_t type = p[i];
const int room = optlen - i;
if (type == DHCP_END)
{
break;
}
else if (type == DHCP_PAD)
{
++i;
}
else if (type == DHCP_ROUTER)
{
if (room >= 2)
{
const int len = p[i+1]; /* get option length */
if (len <= (room-2))
{
/* get router IP address */
if (!ret && len >= 4 && (len & 3) == 0)
{
memcpy(&ret, p+i+2, 4);
ret = ntohl(ret);
}
{
/* delete the router option */
uint8_t *dest = p + i;
const int owlen = len + 2; /* len of data to overwrite */
uint8_t *src = dest + owlen;
uint8_t *end = p + optlen;
const int movlen = end - src;
if (movlen > 0)
{
memmove(dest, src, movlen); /* overwrite router option */
}
memset(end - owlen, DHCP_PAD, owlen); /* pad tail */
}
}
else
{
break;
}
}
else
{
break;
}
}
else /* some other option */
{
if (room >= 2)
{
const int len = p[i+1]; /* get option length */
i += (len + 2); /* advance to next option */
}
else
{
break;
}
}
}
return ret;
}
static uint16_t
udp_checksum(const uint8_t *buf,
const int len_udp,
const uint8_t *src_addr,
const uint8_t *dest_addr)
{
uint16_t word16;
uint32_t sum = 0;
int i;
/* make 16 bit words out of every two adjacent 8 bit words and */
/* calculate the sum of all 16 bit words */
for (i = 0; i < len_udp; i += 2)
{
word16 = ((buf[i] << 8) & 0xFF00) + ((i + 1 < len_udp) ? (buf[i+1] & 0xFF) : 0);
sum += word16;
}
/* add the UDP pseudo header which contains the IP source and destination addresses */
for (i = 0; i < 4; i += 2)
{
word16 = ((src_addr[i] << 8) & 0xFF00) + (src_addr[i+1] & 0xFF);
sum += word16;
}
for (i = 0; i < 4; i += 2)
{
word16 = ((dest_addr[i] << 8) & 0xFF00) + (dest_addr[i+1] & 0xFF);
sum += word16;
}
/* the protocol number and the length of the UDP packet */
sum += (uint16_t) OPENVPN_IPPROTO_UDP + (uint16_t) len_udp;
/* keep only the last 16 bits of the 32 bit calculated sum and add the carries */
while (sum >> 16)
{
sum = (sum & 0xFFFF) + (sum >> 16);
}
/* Take the one's complement of sum */
return ((uint16_t) ~sum);
}
in_addr_t
dhcp_extract_router_msg(struct buffer *ipbuf)
{
struct dhcp_full *df = (struct dhcp_full *) BPTR(ipbuf);
const int optlen = BLEN(ipbuf) - (sizeof(struct openvpn_iphdr) + sizeof(struct openvpn_udphdr) + sizeof(struct dhcp));
if (optlen >= 0
&& df->ip.protocol == OPENVPN_IPPROTO_UDP
&& df->udp.source == htons(BOOTPS_PORT)
&& df->udp.dest == htons(BOOTPC_PORT)
&& df->dhcp.op == BOOTREPLY)
{
const int message_type = get_dhcp_message_type(&df->dhcp, optlen);
if (message_type == DHCPACK || message_type == DHCPOFFER)
{
/* get the router IP address while padding out all DHCP router options */
const in_addr_t ret = do_extract(&df->dhcp, optlen);
/* recompute the UDP checksum */
df->udp.check = 0;
df->udp.check = htons(udp_checksum((uint8_t *) &df->udp,
sizeof(struct openvpn_udphdr) + sizeof(struct dhcp) + optlen,
(uint8_t *)&df->ip.saddr,
(uint8_t *)&df->ip.daddr));
/* only return the extracted Router address if DHCPACK */
if (message_type == DHCPACK)
{
if (ret)
{
struct gc_arena gc = gc_new();
msg(D_ROUTE, "Extracted DHCP router address: %s", print_in_addr_t(ret, 0, &gc));
gc_free(&gc);
}
return ret;
}
}
}
return 0;
}