/*
* Copyright (c) 2004 Niels Provos <provos@citi.umich.edu>
* All rights reserved.
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* 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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* 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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
*/
/*
* dhcpclient.c
*
* Copyright (c) 2004 Marius Aamodt Eriksen <marius@monkey.org>
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
* 1. Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* 2. Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
*
* THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
* IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
* IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
* NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
* DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
* THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
* THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*
* $Id: dhcpclient.c,v 1.6 2005/07/20 21:13:11 provos Exp $
*/
#include <sys/param.h>
#include <sys/types.h>
#ifdef HAVE_CONFIG_H
#include "config.h"
#endif
#include <sys/queue.h>
#include <sys/tree.h>
#include <pcap.h>
#include <stdlib.h>
#include <err.h>
#include <syslog.h>
#include <string.h>
#include <syslog.h>
#include <dnet.h>
#include <event.h>
#include "honeyd.h"
#include "interface.h"
#include "arp.h"
#include "template.h"
#include "dhcpclient.h"
extern rand_t *honeyd_rand;
int need_dhcp = 0; /* set to one if a configuration specifies dhcp */
static struct timeval _timeout_tv = {1, 0};
#define NTRIES 10
static int _pack_request(struct dhcpclient_req *, void *, size_t *);
static int _pack_release(struct dhcpclient_req *, void *, size_t *);
static int _bcast(struct template *,
int (*)(struct dhcpclient_req *, void *, size_t *));
static int _unicast(struct template *,
int (*)(struct dhcpclient_req *, void *, size_t *));
static void _dhcp_timeout_cb(int, short, void *);
static void _dhcp_reply(struct template *, u_char *, size_t);
int
dhcp_getconf(struct template *tmpl)
{
struct dhcpclient_req *req = tmpl->dhcp_req;
struct interface *inter = tmpl->inter;
if (req == NULL) {
req = calloc(1, sizeof(struct dhcpclient_req));
if (req == NULL)
err(1, "%s: calloc");
tmpl->dhcp_req = req;
}
syslog(LOG_NOTICE, "[%s] trying DHCP", inter->if_ent.intf_name);
if (req->state != 0) {
warnx("Aborting DHCP configuration in progress");
dhcp_abort(tmpl);
}
/* For now, do the pcap in here independently. */
gettimeofday(&req->timer, NULL);
assert(tmpl->ethernet_addr);
req->state |= DHREQ_STATE_WAITANS | DHREQ_STATE_BUSY;
req->xid = rand_uint32(honeyd_rand);
req->ea = tmpl->ethernet_addr->addr_eth;
if (_bcast(tmpl, _pack_request) < 0)
return (-1);
/*
* XXX - This sort of timeout policy is bad. We should be
* exponentially backing off.
*/
req->ntries = 0;
evtimer_set(&req->timeoutev, _dhcp_timeout_cb, tmpl);
evtimer_add(&req->timeoutev, &_timeout_tv);
return (0);
}
int
dhcp_release(struct template *tmpl)
{
struct dhcpclient_req *req = tmpl->dhcp_req;
if (!(req->nc.defined & NC_HOSTADDR))
return (-1);
return (_unicast(tmpl, _pack_release));
}
void
dhcp_abort(struct template *tmpl)
{
struct dhcpclient_req *req = tmpl->dhcp_req;
if (req == NULL) {
struct addr *eth_addr = tmpl->ethernet_addr;
syslog(LOG_WARNING,
"%s: called without request on template %s",
__func__,
eth_addr != NULL ? addr_ntoa(eth_addr) : tmpl->name);
return;
}
if (req->state == 0)
return;
event_del(&req->timeoutev);
req->state = 0;
}
static void
_dhcp_timeout_cb(int fd, short ev, void *arg)
{
struct template *tmpl = arg;
struct interface* inter = tmpl->inter;
struct dhcpclient_req *req = tmpl->dhcp_req;
struct timeval timeout_tv;
assert(inter != NULL);
if (req->ntries++ > NTRIES) {
printf("aborting dhclient on interface %s after %d tries\n",
inter->if_ent.intf_name, req->ntries);
dhcp_abort(tmpl);
return;
}
_bcast(tmpl, _pack_request);
if (req->ntries < 5)
timeout_tv.tv_sec = 1;
else
/* backoff on sending dhcp requests */
timeout_tv.tv_sec = 0x01 << (req->ntries - 4);
/* set a limit on the backoff */
if (timeout_tv.tv_sec > 128)
timeout_tv.tv_sec = 128;
timeout_tv.tv_usec = 0;
evtimer_add(&req->timeoutev, &timeout_tv);
}
static void
netconf_mknetmask(struct addr *ipaddr, struct addr *ipmask)
{
uint32_t mask;
u_int bits = 32;
mask = ntohl(ipmask->addr_ip);
while (!(mask & 0x1) && bits > 0) {
mask >>= 1;
bits--;
}
ipaddr->addr_bits = bits;
}
static void
_dhcp_reply(struct template *tmpl, u_char *buf, size_t buflen)
{
struct dhcpclient_req *req = tmpl->dhcp_req;
struct dhcp_msg *msg = (struct dhcp_msg *)buf;
size_t optlen = buflen - sizeof(*msg), opt1len;
uint8_t *p, *end, opt1, *opt1p;
short replyreq = 0, ack = 0, done = 0;
struct netconf nc;
struct addr *which = NULL, ipmask;
if (req->xid != msg->dh_xid)
return;
memset(&nc, 0, sizeof(nc));
memset(&ipmask, 0, sizeof(ipmask));
/* Parse the options on the reply message */
p = (u_char *)msg + sizeof(*msg);
end = p + optlen;
while (p < end) {
opt1 = *p++;
if (p == end)
break;
if (opt1 != 0x00 && done)
goto optdone;
switch (opt1) {
case 0x00:
continue;
case 0xff:
done = 1;
continue;
default:
opt1len = *p++;
if (p + opt1len >= end)
goto optdone;
opt1p = p;
p += opt1len;
break;
}
switch (opt1) {
case DH_SUBNETMASK:
nc.defined |= NC_MASK;
which = &ipmask;
break;
case DH_ROUTER:
nc.defined |= NC_GWADDR;
which = &nc.gwaddr;
break;
case DH_NS:
nc.defined |= NC_NSADDR;
which = &nc.nsaddr[0];
break;
case DH_SERVIDENT:
which = &req->servident;
break;
default:
break;
}
switch(opt1) {
case DH_MSGTYPE:
if (req->state & DHREQ_STATE_WAITANS &&
*opt1p == DH_MSGTYPE_OFFER)
replyreq = 1;
if (req->state & DHREQ_STATE_WAITACK &&
*opt1p == DH_MSGTYPE_ACK)
ack = 1;
break;
case DH_DOMAINNAME: {
size_t len = MIN(sizeof(nc.domain) - 1, opt1len);
memcpy(nc.domain, opt1p, len);
nc.domain[len] = '\0';
nc.defined |= NC_DOMAIN;
break;
}
case DH_SERVIDENT:
case DH_SUBNETMASK:
case DH_NS:
case DH_ROUTER: {
uint32_t addr;
if (opt1len < IP_ADDR_LEN)
goto optdone;
memcpy(&addr, opt1p, sizeof(addr));
addr = /* ntohl( */addr/* ) */;
addr_pack(which, ADDR_TYPE_IP, IP_ADDR_BITS,
&addr, sizeof(addr));
break;
}
default:
break;
}
}
optdone:
/*
* XXX - Does not warn if the error is on the last one.. make
* opterr instead.
*/
if (p < end)
warnx("Error processing options");
if (ack || replyreq) {
uint32_t ipaddr;
req->nc = nc;
ipaddr = /* ntohl( */msg->dh_yiaddr/* ) */;
addr_pack(&req->nc.hostaddr, ADDR_TYPE_IP, IP_ADDR_BITS,
&ipaddr, sizeof(ipaddr));
req->nc.defined |= NC_HOSTADDR;
}
if (replyreq) {
req->state &= ~DHREQ_STATE_WAITANS;
req->state |= DHREQ_STATE_WAITACK;
_bcast(tmpl, _pack_request);
}
if (ack) {
struct addr addr = req->nc.hostaddr;
struct interface *inter = tmpl->inter;
syslog(LOG_NOTICE, "[%s] got DHCP offer: %s",
inter->if_ent.intf_name, addr_ntoa(&addr));
dhcp_abort(tmpl);
if (template_find(addr_ntoa(&addr)) != NULL) {
syslog(LOG_WARNING,
"%s: Already got a template named %s",
__func__, addr_ntoa(&addr));
return;
}
/* We are done - tell the template about our luck */
template_remove(tmpl);
free(tmpl->name);
tmpl->name = strdup(addr_ntoa(&addr));
if (tmpl->name == NULL)
err(1, "%s: strdup", __func__);
template_insert(tmpl);
/* Update our ARP table */
syslog(LOG_DEBUG, "Updating ARP binding: %s -> %s",
addr_ntoa(tmpl->ethernet_addr), tmpl->name);
template_remove_arp(tmpl);
template_post_arp(tmpl, &addr);
/* Callback for central configuration here. */
if (ipmask.addr_type != 0)
netconf_mknetmask(&req->nc.hostaddr, &ipmask);
else
req->nc.hostaddr.addr_bits = 24;
}
}
/*
* Receives a UDP packet from port 68 to port 67 from the Honeyd packet
* dispatcher and attempts to find the correct template for it.
*/
void
dhcp_recv_cb(struct eth_hdr *eth, struct ip_hdr *ip, u_short iplen)
{
struct arp_req *arp;
struct template *tmpl;
struct dhcpclient_req *req = NULL;
struct udp_hdr *udp;
size_t msglen;
struct dhcp_msg *msg;
uint16_t ip_sum, uh_sum;
struct addr eth_dha;
/* IPv4 only */
/* Check if we manage a virtual machine with this ethernet address */
addr_pack(ð_dha, ADDR_TYPE_ETH, ETH_ADDR_BITS,
ð->eth_dst, ETH_ADDR_LEN);
if ((arp = arp_find(ð_dha)) == NULL || !(arp->flags & ARP_INTERNAL))
return;
tmpl = arp->owner;
req = tmpl->dhcp_req;
if (req == NULL) {
syslog(LOG_WARNING, "%s: received DHCP reply for template %s "
"without dhcp_req", __func__, addr_ntoa(ð_dha));
return;
}
if (!(req->state & (DHREQ_STATE_WAITANS | DHREQ_STATE_WAITACK)))
return;
udp = (struct udp_hdr *)((u_char *)ip + (ip->ip_hl << 2));
msg = (struct dhcp_msg *)((u_char *)udp + UDP_HDR_LEN);
msglen = ntohs(udp->uh_ulen) - UDP_HDR_LEN;
if (msglen != (iplen - (ip->ip_hl << 2) - UDP_HDR_LEN))
return;
ip_sum = ip->ip_sum;
uh_sum = udp->uh_sum;
ip_checksum(ip, iplen);
if (ip_sum != ip->ip_sum || uh_sum != udp->uh_sum) {
syslog(LOG_WARNING, "%s: bad checksum for template %s",
__func__, addr_ntoa(ð_dha));
return;
}
/* save the servers address */
memcpy(&req->server_ea, ð->eth_src, ETH_ADDR_LEN);
_dhcp_reply(tmpl, (u_char *)msg, msglen);
}
static int
_bcast(struct template *tmpl,
int (*_pack)(struct dhcpclient_req *, void *, size_t *))
{
struct eth_hdr *eth;
uint8_t buf[1024], *p;
size_t restlen = 1024, len, iplen;
struct udp_hdr *udph;
struct ip_hdr *iph;
struct dhcpclient_req *req = tmpl->dhcp_req;
struct interface *inter = tmpl->inter;
assert(req != NULL);
assert(inter != NULL);
memset(buf, 0, sizeof(buf));
p = &buf[0];
eth = (struct eth_hdr *)p;
eth_pack_hdr(eth, ETH_ADDR_BROADCAST, req->ea, ETH_TYPE_IP);
restlen -= ETH_HDR_LEN;
p += ETH_HDR_LEN;
iph = (struct ip_hdr *)p;
ip_pack_hdr(iph, 0, 0, 0, 0, 16,
IP_PROTO_UDP, IP_ADDR_ANY, IP_ADDR_BROADCAST);
p += IP_HDR_LEN;
restlen -= IP_HDR_LEN;
udph = (struct udp_hdr *)p;
udp_pack_hdr(udph, 68, 67, 0);
p += UDP_HDR_LEN;
restlen -= UDP_HDR_LEN;
(*_pack)(req, p, &restlen);
len = 1024 - restlen;
iplen = len - ETH_HDR_LEN;
iph->ip_len = htons(iplen);
udph->uh_ulen = htons(iplen - IP_HDR_LEN);
ip_checksum(buf + ETH_HDR_LEN, iplen);
if (eth_send(inter->if_eth, buf, len) < 0)
err(1, "eth_send");
return (0);
}
static int
_unicast(struct template *tmpl,
int (*_pack)(struct dhcpclient_req *, void *, size_t *))
{
struct eth_hdr *eth;
uint8_t buf[1024], *p;
size_t restlen = 1024, len, iplen;
struct udp_hdr *udph;
struct ip_hdr *iph;
struct dhcpclient_req *req = tmpl->dhcp_req;
struct interface *inter = tmpl->inter;
assert(req != NULL);
assert(inter != NULL);
memset(buf, 0, sizeof(buf));
p = &buf[0];
eth = (struct eth_hdr *)p;
eth_pack_hdr(eth, req->server_ea, req->ea, ETH_TYPE_IP);
restlen -= ETH_HDR_LEN;
p += ETH_HDR_LEN;
iph = (struct ip_hdr *)p;
ip_pack_hdr(iph, 0, 0, 0, 0, 16,
IP_PROTO_UDP,
req->nc.hostaddr.addr_ip, req->nc.gwaddr.addr_ip);
p += IP_HDR_LEN;
restlen -= IP_HDR_LEN;
udph = (struct udp_hdr *)p;
udp_pack_hdr(udph, 68, 67, 0);
p += UDP_HDR_LEN;
restlen -= UDP_HDR_LEN;
(*_pack)(req, p, &restlen);
len = 1024 - restlen;
iplen = len - ETH_HDR_LEN;
iph->ip_len = htons(iplen);
udph->uh_ulen = htons(iplen - IP_HDR_LEN);
ip_checksum(buf + ETH_HDR_LEN, iplen);
if (eth_send(inter->if_eth, buf, len) < 0)
err(1, "eth_send");
return (0);
}
/*
* We should cache dhcp packets.
*/
static int
_pack_request(struct dhcpclient_req *req, void *buf, size_t *restlen)
{
struct dhcp_msg *msg;
u_char *p;
const char *G_hostname = "someone";
size_t optlen, padlen = 0;
struct timeval tv, difftv;
struct netconf *nc = &req->nc;
gettimeofday(&tv, NULL);
timersub(&tv, &req->timer, &difftv);
optlen = (3) + (2 + strlen(G_hostname)) + (8) + (1);
optlen += 6 * (nc->defined & NC_HOSTADDR) +
6 * (req->servident.addr_type != 0);
/* optlen += (nc->defined & NC_DOMAIN) * strlen(nc->domain); */
if (*restlen < sizeof(*msg) + optlen)
return (-1);
msg = (struct dhcp_msg *)buf;
msg->dh_op = DH_BOOTREQUEST;
msg->dh_htype = DH_HTYPE_ETHERNET;
msg->dh_hlen = ETH_ADDR_LEN;
msg->dh_xid = req->xid;
msg->dh_secs = htons((uint16_t)difftv.tv_sec);
memcpy(msg->dh_chaddr, &req->ea, ETH_ADDR_LEN);
msg->dh_magiccookie = htonl(DH_MAGICCOOKIE);
p = (u_char *)buf + sizeof(*msg);
/* Options */
/* Message type */
*p++ = DH_MSGTYPE;
*p++ = 1;
*p++ = req->state & DHREQ_STATE_WAITANS ?
DH_MSGTYPE_DISCOVER : DH_MSGTYPE_REQUEST;
/* Hostname */
*p++ = DH_HOSTNAME;
*p++ = strlen(G_hostname);
memcpy(p, G_hostname, strlen(G_hostname));
p += strlen(G_hostname);
/* Requested Parameters */
*p++ = DH_PARAMREQ;
*p++ = 5; /* Number of parameters */
*p++ = 1; /* Subnet mask */
padlen += 4;
*p++ = 28; /* Broadcast address */
padlen += 4;
*p++ = 3; /* Router */
padlen += 4;
*p++ = 15; /* Domain name */
padlen += 256;
*p++ = 6; /* Domain name server */
padlen += 4;
/* *p++ = 12; /\* Host name *\/ */
if (nc->defined & NC_HOSTADDR) {
uint32_t ipaddr;
*p++ = DH_REQIP;
*p++ = 4;
ipaddr = /* htonl( */nc->hostaddr.addr_ip/* ) */;
memcpy(p, &ipaddr, IP_ADDR_LEN);
p += IP_ADDR_LEN;
}
if (req->servident.addr_type != 0) {
uint32_t ipaddr;
*p++ = DH_SERVIDENT;
*p++ = 4;
ipaddr = /* htonl( */req->servident.addr_ip/* ) */;
memcpy(p, &ipaddr, IP_ADDR_LEN);
p += IP_ADDR_LEN;
}
*p = 0xff; /* End options */
*restlen -= sizeof(*msg) + optlen;
if (*restlen >= padlen)
*restlen -= padlen; /* Fix for retarted DHCP servers. */
return (0);
}
static int
_pack_release(struct dhcpclient_req *req, void *buf, size_t *restlen)
{
struct dhcp_msg *msg;
u_char *p;
size_t optlen, padlen = 0;
struct netconf *nc = &req->nc;
optlen = (3) + (1); /* just message type */
if (*restlen < sizeof(*msg) + optlen)
return (-1);
msg = (struct dhcp_msg *)buf;
memset(msg, 0, sizeof(struct dhcp_msg));
msg->dh_op = DH_BOOTREQUEST;
msg->dh_htype = DH_HTYPE_ETHERNET;
msg->dh_hlen = ETH_ADDR_LEN;
msg->dh_xid = req->xid;
memcpy(msg->dh_chaddr, &req->ea, ETH_ADDR_LEN);
msg->dh_ciaddr = nc->hostaddr.addr_ip;
msg->dh_magiccookie = htonl(DH_MAGICCOOKIE);
p = (u_char *)buf + sizeof(*msg);
/* Options */
/* Message type */
*p++ = DH_MSGTYPE;
*p++ = 1;
*p++ = DH_MSGTYPE_RELEASE;
*p = 0xff; /* End options */
*restlen -= sizeof(*msg) + optlen;
if (*restlen >= padlen)
*restlen -= padlen; /* Fix for retarted DHCP servers. */
return (0);
}
syntax highlighted by Code2HTML, v. 0.9.1