/* * Copyright (c) 2004 Niels Provos * 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 * 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 #include #ifdef HAVE_CONFIG_H #include "config.h" #endif #include #include #include #include #include #include #include #include #include #include #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); }