Mercurial > hg > dhcpcd
changeset 0:49aca2b065f8 draft
Add dhcpcd-3 re-write
| author | Roy Marples <roy@marples.name> |
|---|---|
| date | Mon, 27 Nov 2006 20:23:22 +0000 |
| parents | |
| children | 1703860620f3 |
| files | ChangeLog Makefile arp.c arp.h client.c client.h common.c common.h configure.c configure.h dhcp.c dhcp.h dhcpcd.8 dhcpcd.c dhcpcd.h dhcpcd.sh interface.c interface.h logger.c logger.h pathnames.h signals.c signals.h socket.c socket.h |
| diffstat | 25 files changed, 4765 insertions(+), 0 deletions(-) [+] |
line wrap: on
line diff
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/ChangeLog Mon Nov 27 20:23:22 2006 +0000 @@ -0,0 +1,21 @@ +dhcpcd-3.0.0 +A complete rewrite by Roy Marples <uberlord@gentoo.org> + +Functional changes since version 2: +We now support FreeBSD as well as Linux. +RFC 3004 User Class support has been added. +RFC 3442 Classless Static Routes support has been added +(thanks to apexman for helping). +Options -r, -e, -v, -w, -B, -C, -D, -L, -S have been dropped. +-a now means "do arp" instead of "don't arp". +-o has been dropped, but we never bring down the interface anymore. +IP address is now required for the -s option. +-G no longer takes an IP address as an replacement gateway. +The .cache file is no longer created or used. +Default script is now /etc/dhcpcd.sh instead of /etc/dhcpc/dhcpcd.exe. +The .info file has changed slightly to better support mulitple entries with +more than one entity, such as route now having network,genmask and gateway. +We no longer create and restore .sv files as it's up to the interface +manager to restore them correctly as >1 dhcp client may be running. + +For ChangeLog for prior versions can be found in their tarballs.
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Makefile Mon Nov 27 20:23:22 2006 +0000 @@ -0,0 +1,45 @@ +# Should work for both GNU make and BSD mke + +VERSION = 3.0.0_alpha3 + +CFLAGS ?= -Wall -O2 -pedantic -std=gnu99 + +DESTDIR = +SBINDIR = $(DESTDIR)/sbin +MANDIR = $(DESDIR)/usr/share/man + +SBIN_TARGETS = dhcpcd +MAN8_TARGETS = dhcpcd.8 +TARGET = $(SBIN_TARGETS) + +dhcpcd_H = version.h +dhcpcd_OBJS = arp.o client.o common.o configure.o dhcp.o dhcpcd.o \ + interface.o logger.o signals.o socket.o + +dhcpcd: $(dhcpcd_H) $(dhcpcd_OBJS) + $(CC) $(LDFLAGS) $(dhcpcd_OBJS) -o dhcpcd + +version.h: + echo '#define VERSION "$(VERSION)"' > version.h + +$(dhcpcd_OBJS): + $(CC) $(CFLAGS) -c $*.c + +all: $(TARGET) + +install: $(TARGET) + $(INSTALL) -m 0755 -d $(SBINDIR) + $(INSTALL) -m 0755 $(SBIN_TARGETS) $(SBINDIR) + $(INSTALL) -m 0755 -d $(MANDIR)/man8 + $(INSTALL) -m 0755 $(MAN8_TARGETS) $(MANDIR)/man8 + +clean: + rm -f $(TARGET) $(dhcpcd_H) *.o *~ + +dist: + $(INSTALL) -m 0755 -d /tmp/dhcpcd-$(VERSION) + cp -RPp . /tmp/dhcpcd-$(VERSION) + $(MAKE) -C /tmp/dhcpcd-$(VERSION) clean + rm -f /tmp/dhcpcd-$(VERSION)/*.bz2 + tar cvjpf dhcpcd-$(VERSION).tar.bz2 -C /tmp dhcpcd-$(VERSION) + rm -rf /tmp/dhcpcd-$(VERSION)
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/arp.c Mon Nov 27 20:23:22 2006 +0000 @@ -0,0 +1,157 @@ +/* + * dhcpcd - DHCP client daemon - + * Copyright (C) 2006 Roy Marples <uberlord@gentoo.org> + * + * dhcpcd is an RFC2131 compliant DHCP client daemon. + * + * This 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., 675 Mass Ave, Cambridge, MA 02139, USA. + */ + +/* OK, a lot of this was lifting from iputils as the existing code + for dhcpcd was kinda klunky and had some issues */ + +#define _BSD_SOURCE + +#include <sys/ioctl.h> +#include <sys/select.h> +#include <sys/socket.h> +#include <sys/types.h> +#include <netinet/in_systm.h> +#ifdef __linux +#include <netinet/ether.h> +#include <netpacket/packet.h> +#endif +#include <net/if.h> +#include <net/if_arp.h> +#include <arpa/inet.h> +#include <string.h> +#include <unistd.h> + +#include "common.h" +#include "interface.h" +#include "logger.h" +#include "socket.h" + +/* Longer is safer and slower - 2 seconds seems a happy medium */ +#define TIMEOUT 2 + +/* Linux does not seem to define these handy macros */ +#ifndef ar_sha +#define ar_sha(ap) (((unsigned char *) ((ap) + 1)) + 0) +#define ar_spa(ap) (((unsigned char *) ((ap) + 1)) + (ap)->ar_hln) +#define ar_tha(ap) (((unsigned char *) ((ap) + 1)) + (ap)->ar_hln + (ap)->ar_pln) +#define ar_tpa(ap) (((unsigned char *) ((ap) + 1)) + 2 * (ap)->ar_hln + (ap)->ar_pln) + +#define arphdr_len2(ar_hln, ar_pln) (sizeof (struct arphdr) + 2 * (ar_hln) + 2 * (ar_pln)) +#define arphdr_len(ap) (arphdr_len2 ((ap)->ar_hln, (ap)->ar_pln)) +#endif + +int arp_check (interface_t *iface, struct in_addr address) +{ + if (! iface->arpable) + { + logger (LOG_DEBUG, "arp_check: interface `%s' is not ARPable", + iface->name); + return 0; + } + + unsigned char buf[256]; + struct arphdr *ah = (struct arphdr *) buf; + + memset (&buf, 0, sizeof (buf)); + + ah->ar_hrd = htons (ARPHRD_ETHER); + ah->ar_pro = htons (ETHERTYPE_IP); + ah->ar_hln = ETHER_ADDR_LEN; + ah->ar_pln = sizeof (struct in_addr); + ah->ar_op = htons (ARPOP_REQUEST); + memcpy (ar_sha (ah), &iface->ethernet_address, ah->ar_hln); + memcpy (ar_tpa (ah), &address, ah->ar_pln); + + logger (LOG_INFO, "checking %s is available on attached networks", inet_ntoa + (address)); + + open_socket (iface, true); + send_packet (iface, ETHERTYPE_ARP, (unsigned char *) &buf, arphdr_len(ah)); + + unsigned char reply[4096]; + int bytes; + unsigned char buffer[iface->buffer_length]; + + struct timeval tv; + long timeout = 0; + fd_set rset; + + timeout = uptime() + TIMEOUT; + while (1) + { + tv.tv_sec = timeout - uptime (); + tv.tv_usec = 0; + + if (tv.tv_sec < 1) + break; /* Time out */ + + FD_ZERO (&rset); + FD_SET (iface->fd, &rset); + + if (select (iface->fd + 1, &rset, NULL, NULL, &tv) == 0) + break; + + if (! FD_ISSET (iface->fd, &rset)) + continue; + + memset (buffer, 0, sizeof (buffer)); + int buflen = sizeof (buffer); + int bufpos = -1; + + while (bufpos != 0) + { + memset (&reply, 0, sizeof (reply)); + if ((bytes = get_packet (iface, (unsigned char *) &reply, buffer, + &buflen, &bufpos)) < 0) + break; + + ah = (struct arphdr *) reply; + + /* Only these types are recognised */ + if (ah->ar_op != htons(ARPOP_REPLY) + || ah->ar_hrd != htons (ARPHRD_ETHER)) + continue; + + /* Protocol must be IP. */ + if (ah->ar_pro != htons (ETHERTYPE_IP)) + continue; + if (ah->ar_pln != sizeof (struct in_addr)) + continue; + + if (ah->ar_hln != ETHER_ADDR_LEN) + continue; + if (bytes < sizeof (*ah) + 2 * (4 + ah->ar_hln)) + continue; + + logger (LOG_ERR, "ARPOP_REPLY received from %s (%s)", + inet_ntoa (* (struct in_addr *) ar_spa (ah)), + ether_ntoa ((struct ether_addr *) ar_sha (ah))); + close (iface->fd); + iface->fd = -1; + return 1; + } + } + + close (iface->fd); + iface->fd = -1; + return 0; +} +
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/arp.h Mon Nov 27 20:23:22 2006 +0000 @@ -0,0 +1,29 @@ +/* + * dhcpcd - DHCP client daemon - + * Copyright (C) 2005 - 2006 Roy Marples <uberlord@gentoo.org> + * + * This 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., 675 Mass Ave, Cambridge, MA 02139, USA. + */ + +#ifndef ARP_H +#define ARP_H + +#include <netinet/in.h> + +#include "interface.h" + +int arp_check (interface_t *iface, struct in_addr address); + +#endif
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/client.c Mon Nov 27 20:23:22 2006 +0000 @@ -0,0 +1,554 @@ +/* + * dhcpcd - DHCP client daemon - + * Copyright (C) 2006 Roy Marples <uberlord@gentoo.org> + * + * dhcpcd is an RFC2131 compliant DHCP client daemon. + * + * This 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., 675 Mass Ave, Cambridge, MA 02139, USA. + */ + +#include <sys/select.h> +#include <arpa/inet.h> +#ifdef __linux__ +#include <netinet/ether.h> +#endif +#include <errno.h> +#include <fcntl.h> +#include <signal.h> +#include <stdbool.h> +#include <stdlib.h> +#include <stdio.h> +#include <string.h> +#include <unistd.h> + +#include "arp.h" +#include "common.h" +#include "configure.h" +#include "dhcp.h" +#include "dhcpcd.h" +#include "interface.h" +#include "logger.h" +#include "signals.h" +#include "socket.h" + +/* This is out mini timeout. + Basically we resend the last request every TIMEOUT_MINI seconds. */ +#define TIMEOUT_MINI 3 + +#define STATE_INIT 0 +#define STATE_REQUESTING 1 +#define STATE_BOUND 2 +#define STATE_RENEWING 3 +#define STATE_REBINDING 4 +#define STATE_REBOOT 5 +#define STATE_RENEW_REQUESTED 6 +#define STATE_RELEASED 7 + +#define SOCKET_CLOSED 0 +#define SOCKET_OPEN 1 + +#define SOCKET_MODE(_mode) \ + if (iface->fd >= 0) close (iface->fd); \ +iface->fd = -1; \ +if (_mode == SOCKET_OPEN) \ +if (open_socket (iface, false) < 0) { retval = -1; goto eexit; } \ +mode = _mode; + +#define SEND_MESSAGE(_type) \ + memcpy (&last_dhcp, &dhcp, sizeof (struct dhcp_t)); \ +last_type = _type; \ +send_message (iface, &dhcp, xid, _type, options); + +static int daemonise (char *pidfile) +{ + FILE *fp; + + if (daemon (0, 0) < 0) + { + logger (LOG_ERR, "unable to daemonise: %s", strerror (errno)); + return -1; + } + + if ((fp = fopen (pidfile, "w")) == NULL) + { + logger (LOG_ERR, "fopen `%s': %m", pidfile); + return -1; + } + + fprintf (fp, "%u\n", getpid ()); + fclose (fp); + + return 0; +} + +unsigned long random_xid (void) +{ + static int initialized; + + if (! initialized) + { + int fd; + unsigned long seed; + + fd = open ("/dev/urandom", 0); + if (fd < 0 || read (fd, &seed, sizeof(seed)) < 0) + { + logger (LOG_WARNING, "Could not load seed from /dev/urandom: %m"); + seed = time (0); + } + if (fd >= 0) + close(fd); + + srand(seed); + initialized++; + } + + return rand(); +} + +/* This state machine is based on the one from udhcpc + written by Russ Dill */ +int dhcp_run (options_t *options) +{ + interface_t *iface; + int mode = SOCKET_CLOSED; + int state = STATE_INIT; + struct timeval tv; + int xid = 0; + unsigned long timeout = 0; + fd_set rset; + int maxfd; + int retval; + dhcpmessage_t message; + dhcp_t dhcp; + dhcp_t last_dhcp; + int type; + int last_type = DHCP_REQUEST; + bool daemonised = false; + unsigned long start = 0; + int sig; + unsigned char *buffer = NULL; + int buffer_len = 0; + int buffer_pos = 0; + + if (! options || (iface = (read_interface (options->interface, + options->metric))) == NULL) + return -1; + + /* Remove all existing addresses. + After all, we ARE a DHCP client whose job it is to configure the + interface. We only do this on start, so persistent addresses can be added + afterwards by the user if needed. + */ + flush_addresses (iface->name); + + memset (&dhcp, 0, sizeof (dhcp_t)); + memset (&last_dhcp, 0, sizeof (dhcp_t)); + + dhcp.leasetime = options->leasetime; + strcpy (dhcp.classid, options->classid); + if (options->clientid[0]) + strcpy (dhcp.clientid, options->clientid); + else + sprintf (dhcp.clientid, "%s", ether_ntoa (&iface->ethernet_address)); + + if (options->requestaddress.s_addr != 0) + memcpy (&dhcp.address, &options->requestaddress, sizeof (struct in_addr)); + + signal_setup (); + + while (1) + { + int timeout_secs = timeout - uptime(); + tv.tv_sec = timeout - uptime (); + tv.tv_usec = 0; + + maxfd = signal_fd_set (&rset, iface->fd); + + if (timeout_secs > 0 || (options->timeout == 0 && + (state != STATE_INIT || xid))) + { + if (options->timeout == 0) + { + logger (LOG_DEBUG, "waiting on select for infinity"); + retval = select (maxfd + 1, &rset, NULL, NULL, NULL); + } + else + { + logger (LOG_DEBUG, "waiting on select for %d seconds", + timeout_secs); + /* If we're waiting for a reply, then we re-send the last + DHCP request periodically in-case of a bad line */ + if (iface->fd == -1) + { + tv.tv_sec = timeout_secs; + tv.tv_usec = 0; + retval = select (maxfd + 1, &rset, NULL, NULL, &tv); + } + else + { + while (timeout_secs > 0) + { + tv.tv_sec = TIMEOUT_MINI; + tv.tv_usec = 0; + retval = select (maxfd + 1, &rset, NULL, NULL, &tv); + if (retval != 0) + break; + send_message (iface, &last_dhcp, xid, last_type, options); + timeout_secs -= TIMEOUT_MINI; + } + } + } + } + else + retval = 0; + + /* We should always handle our signals first */ + if (retval > 0 && (sig = signal_read (&rset))) + { + switch (sig) + { + case SIGINT: + logger (LOG_INFO, "receieved SIGINT, stopping"); + retval = 0; + goto eexit; + + case SIGTERM: + logger (LOG_INFO, "receieved SIGTERM, stopping"); + retval = 0; + goto eexit; + + case SIGALRM: + + logger (LOG_INFO, "receieved SIGALRM, renewing lease"); + switch (state) + { + case STATE_BOUND: + SOCKET_MODE (SOCKET_OPEN); + case STATE_RENEWING: + case STATE_REBINDING: + state = STATE_RENEW_REQUESTED; + break; + case STATE_RENEW_REQUESTED: + case STATE_REQUESTING: + case STATE_RELEASED: + state = STATE_INIT; + break; + } + + timeout = 0; + xid = 0; + break; + + case SIGHUP: + if (state == STATE_BOUND || state == STATE_RENEWING + || state == STATE_REBINDING) + { + logger (LOG_INFO, "received SIGHUP, releasing lease"); + SOCKET_MODE (SOCKET_OPEN); + xid = random_xid (); + if ((open_socket (iface, false)) >= 0) + SEND_MESSAGE (DHCP_RELEASE); + SOCKET_MODE (SOCKET_CLOSED); + unlink (iface->infofile); + } + else + logger (LOG_ERR, + "receieved SIGUP, but no we have lease to release"); + retval = 0; + goto eexit; + + default: + logger (LOG_ERR, + "received signal %d, but don't know what to do with it", + sig); + } + } + else if (retval == 0) /* timed out */ + { + switch (state) + { + case STATE_INIT: + if (iface->previous_address.s_addr != 0) + { + logger (LOG_ERR, "lost lease"); + xid = 0; + SOCKET_MODE (SOCKET_CLOSED); + if (! options->persistent) + { + dhcp.address.s_addr = 0; + dhcp.netmask.s_addr = 0; + dhcp.broadcast.s_addr = 0; + configure (options, iface, &dhcp); + } + if (! daemonised) + { + retval = -1; + goto eexit; + } + break; + } + + if (xid == 0) + xid = random_xid (); + else + { + logger (LOG_ERR, "timed out"); + if (! daemonised) + { + retval = -1; + goto eexit; + } + } + + timeout = uptime () + options->timeout; + if (dhcp.address.s_addr == 0) + logger (LOG_INFO, "broadcasting for lease"); + else + logger (LOG_INFO, "broadcasting for lease of %s", + inet_ntoa (dhcp.address)); + + SOCKET_MODE (SOCKET_OPEN); + SEND_MESSAGE (DHCP_DISCOVER); + break; + case STATE_BOUND: + state = STATE_RENEWING; + case STATE_RENEWING: + logger (LOG_INFO, "renewing lease of %s", inet_ntoa + (dhcp.address)); + SOCKET_MODE (SOCKET_OPEN); + xid = random_xid (); + SEND_MESSAGE (DHCP_REQUEST); + timeout = uptime() + (dhcp.rebindtime - dhcp.renewaltime); + state = STATE_REBINDING; + break; + case STATE_REBINDING: + logger (LOG_ERR, "lost lease, attemping to rebind"); + xid = random_xid (); + SEND_MESSAGE (DHCP_DISCOVER); + timeout = uptime() + (dhcp.leasetime - dhcp.rebindtime); + state = STATE_INIT; + break; + case STATE_REQUESTING: + case STATE_RENEW_REQUESTED: + logger (LOG_ERR, "timed out"); + if (! daemonised) + { + retval = -1; + goto eexit; + } + + state = STATE_INIT; + timeout = uptime(); + xid = 0; + SOCKET_MODE (SOCKET_OPEN); + break; + + case STATE_RELEASED: + timeout = 0x7fffffff; + break; + } + } + else if (retval > 0 && mode != SOCKET_CLOSED && FD_ISSET(iface->fd, &rset)) + { + + /* Allocate our buffer space for BPF. + We cannot do this until we have opened our socket as we don't + know how much of a buffer we need until then. */ + if (! buffer) + buffer = xmalloc (iface->buffer_length); + buffer_len = iface->buffer_length; + buffer_pos = -1; + + /* We loop through until our buffer is empty. + The benefit is that if we get >1 DHCP packet in our buffer and + the first one fails for any reason, we can use the next. */ + + memset (&message, 0, sizeof (struct dhcpmessage_t)); + int valid = 0; + while (buffer_pos != 0) + { + if (get_packet (iface, (unsigned char *) &message, buffer, + &buffer_len, &buffer_pos) < 0) + break; + + if (xid != message.xid) + { + logger (LOG_ERR, "ignoring transaction %d as it's not ours (%d)", + message.xid, xid); + continue; + } + + logger (LOG_DEBUG, "got packet with transaction %d", message.xid); + if ((type = parse_dhcpmessage (&dhcp, &message)) < 0) + { + logger (LOG_ERR, "failed to parse message"); + continue; + } + + /* If we got here then the DHCP packet is valid and appears to + be for us, so let's clear the buffer as we don't care about + any more DHCP packets at this point. */ + valid = 1; + break; + } + + /* No packets for us, so wait until we get one */ + if (! valid) + continue; + + switch (state) + { + case STATE_INIT: + if (type == DHCP_OFFER) + { + logger (LOG_INFO, "offered lease of %s", + inet_ntoa (dhcp.address)); + + SEND_MESSAGE (DHCP_REQUEST); + state = STATE_REQUESTING; + } + break; + + case STATE_RENEW_REQUESTED: + case STATE_REQUESTING: + case STATE_RENEWING: + case STATE_REBINDING: + if (type == DHCP_ACK) + { + SOCKET_MODE (SOCKET_CLOSED); + if (options->doarp && iface->previous_address.s_addr != + dhcp.address.s_addr) + { + if (arp_check (iface, dhcp.address)) + { + SOCKET_MODE (SOCKET_OPEN); + SEND_MESSAGE (DHCP_DECLINE); + SOCKET_MODE (SOCKET_CLOSED); + dhcp.address.s_addr = 0; + if (daemonised) + configure (options, iface, &dhcp); + + xid = 0; + state = STATE_INIT; + /* RFC 2131 says that we should wait for 10 seconds + before doing anything else */ + sleep (10); + continue; + } + } + + if (! dhcp.leasetime) + { + dhcp.leasetime = DEFAULT_TIMEOUT; + logger(LOG_INFO, + "no lease time supplied, assuming %d seconds", + dhcp.leasetime); + } + + if (! dhcp.renewaltime) + { + dhcp.renewaltime = dhcp.leasetime / 2; + logger (LOG_INFO, + "no renewal time supplied, assuming %d seconds", + dhcp.renewaltime); + } + + if (! dhcp.rebindtime) + { + dhcp.rebindtime = (dhcp.leasetime * 0x7) >> 3; + logger (LOG_INFO, + "no rebind time supplied, assuming %d seconds", + dhcp.rebindtime); + } + + logger (LOG_INFO, "leased %s for %d seconds", + inet_ntoa (dhcp.address), dhcp.leasetime); + state = STATE_BOUND; + start = uptime (); + timeout = start + dhcp.renewaltime; + xid = 0; + + if (configure (options, iface, &dhcp) < 0 && ! daemonised) + { + retval = -1; + goto eexit; + } + + if (! daemonised) + { + if ((daemonise (options->pidfile)) < 0 ) + { + retval = -1; + goto eexit; + } + daemonised = true; + } + } + else if (type == DHCP_NAK) + logger (LOG_INFO, "received NAK: %s", dhcp.message); + else if (type == DHCP_OFFER) + logger (LOG_INFO, "got subsequent offer of %s, ignoring ", + inet_ntoa (dhcp.address)); + else + logger (LOG_ERR, + "no idea what to do with DHCP type %d at this point", + type); + break; + } + } + else if (retval == -1 && errno == EINTR) + { + /* Signal interupt will be handled above */ + } + else + { + /* An error occured */ + logger (LOG_ERR, "error on select: %s", strerror (errno)); + } + } + +eexit: + SOCKET_MODE (SOCKET_CLOSED); + + /* Remove our config if we need to */ + if (dhcp.address.s_addr != 0 && ! options->persistent && daemonised) + { + dhcp.address.s_addr = 0; + dhcp.netmask.s_addr = 0; + dhcp.broadcast.s_addr = 0; + configure (options, iface, &dhcp); + } + + free_dhcp (&dhcp); + + if (iface) + { + if (iface->previous_routes) + free_route (iface->previous_routes); + free (iface); + } + + if (buffer) + free (buffer); + + logger (LOG_INFO, "exiting"); + + /* Unlink our pidfile */ + unlink (options->pidfile); + + return retval; +} +
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/client.h Mon Nov 27 20:23:22 2006 +0000 @@ -0,0 +1,29 @@ +/* + * dhcpcd - DHCP client daemon - + * Copyright (C) 2006 Roy Marples <uberlord@gentoo.org> + * + * dhcpcd is an RFC2131 compliant DHCP client daemon. + * + * This 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., 675 Mass Ave, Cambridge, MA 02139, USA. + */ + +#ifndef CLIENT_H +#define CLIENT_H + +#include "dhcpcd.h" + +int dhcp_run (options_t *options); + +#endif
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/common.c Mon Nov 27 20:23:22 2006 +0000 @@ -0,0 +1,64 @@ +/* + * dhcpcd - DHCP client daemon - + * Copyright (C) 2006 Roy Marples <uberlord@gentoo.org> + * + * dhcpcd is an RFC2131 compliant DHCP client daemon. + * + * This 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., 675 Mass Ave, Cambridge, MA 02139, USA. + */ + +#include <sys/time.h> +#include <stdlib.h> +#include <string.h> + +#include "logger.h" + +/* This requires us to link to rt on glibc, so we use sysinfo instead */ +#ifdef __linux__ +#include <sys/sysinfo.h> +long uptime (void) +{ + struct sysinfo info; + + sysinfo (&info); + return info.uptime; +} +#else +#include <time.h> +long uptime (void) +{ + struct timespec tp; + + if (clock_gettime(CLOCK_MONOTONIC, &tp) == -1) + { + logger (LOG_ERR, "Unable to get uptime: %m"); + return -1; + } + + return tp.tv_sec; +} +#endif /* __linux__ */ + +void *xmalloc (size_t size) +{ + register void *value = malloc (size); + + if (value) + return value; + + logger (LOG_ERR, "memory exhausted"); + exit (1); +} +
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/common.h Mon Nov 27 20:23:22 2006 +0000 @@ -0,0 +1,28 @@ +/* + * dhcpcd - DHCP client daemon - + * Copyright (C) 2006 Roy Marples <uberlord@gentoo.org> + * + * dhcpcd is an RFC2131 compliant DHCP client daemon. + * + * This 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., 675 Mass Ave, Cambridge, MA 02139, USA. + */ + +#ifndef COMMON_H +#define COMMON_H + +long uptime (void); +void *xmalloc (size_t size); + +#endif
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/configure.c Mon Nov 27 20:23:22 2006 +0000 @@ -0,0 +1,527 @@ +/* + * dhcpcd - DHCP client daemon - + * Copyright (C) 2005 - 2006 Roy Marples <uberlord@gentoo.org> + * + * dhcpcd is an RFC2131 compliant DHCP client daemon. + * + * This 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., 675 Mass Ave, Cambridge, MA 02139, USA. + */ + +#include <sys/types.h> +#include <sys/ioctl.h> +#include <sys/socket.h> +#include <sys/stat.h> + +#include <arpa/inet.h> + +#ifdef __linux__ +#include <netinet/ether.h> +#endif +#include <netinet/in.h> +#include <string.h> +#include <errno.h> +#include <netdb.h> +#include <resolv.h> +#include <stdlib.h> +#include <unistd.h> + +#include "common.h" +#include "dhcp.h" +#include "interface.h" +#include "dhcpcd.h" +#include "pathnames.h" +#include "logger.h" +#include "socket.h" + +static char *cleanmetas (char *cstr) +{ + if (!cstr) + return ""; + + register char *c = cstr; + + do + if (*c == 39) + *c = ' '; + while (*c++); + + return cstr; +} + +void exec_script (char *script, char *infofile, char *arg) +{ + if (!script || !infofile || !arg) + return; + + struct stat buf; + if (stat (script, &buf)) + { + if (strcmp (script, DEFAULT_SCRIPT)) + logger (LOG_ERR, "`%s': %s", script, strerror (ENOENT)); + return; + } + + char *argc[4]; + + argc[0] = script; + argc[1] = infofile; + argc[2] = arg; + argc[3] = NULL; + logger (LOG_DEBUG, "exec \"%s %s %s\"", script, infofile, arg); + + /* We don't wait for the user script to finish - do we trust it? */ + /* Don't use vfork as we lose our memory when dhcpcd exits + causing the script to fail */ + pid_t pid; + if ((pid = fork ()) == 0) + { + if (execv (script, argc)) + logger (LOG_ERR, "error executing \"%s %s %s\": %m", + argc[0], argc[1], argc[2]); + exit (0); + } + else if (pid == -1) + logger (LOG_ERR, "fork: %s", strerror (errno)); +} + +static int make_resolv (char *ifname, dhcp_t *dhcp, int wait) +{ + FILE *f; + struct stat buf; + char resolvconf[PATH_MAX]; + address_t *address; + + if (!stat ("/sbin/resolvconf", &buf)) + { + logger (LOG_DEBUG, "sending DNS information to resolvconf"); + snprintf (resolvconf, PATH_MAX, "/sbin/resolvconf -a %s", ifname); + f = popen (resolvconf, "w"); + + if (!f) + logger (LOG_ERR, "popen: %m"); + } + else + { + if (! (f = fopen(RESOLVFILE, "w"))) + logger (LOG_ERR, "fopen `%s': %m", RESOLVFILE); + } + + if (f) + { + fprintf (f, "# Generated by dhcpcd for interface %s\n", ifname); + if (dhcp->dnssearch) + fprintf (f, "search %s\n", dhcp->dnssearch); + else if (dhcp->dnsdomain) { + fprintf (f, "search %s\n", dhcp->dnsdomain); + } + + for (address = dhcp->dnsservers; address; address = address->next) + fprintf (f, "nameserver %s\n", inet_ntoa (address->address)); + + if (resolvconf) + { + pclose (f); + logger (LOG_DEBUG, "resolvconf completed"); + } + else + fclose (f); + } + else + return -1; + + /* Refresh the local resolver */ + res_init (); + return 0; +} + +static void restore_resolv(char *ifname) +{ + struct stat buf; + + if (stat ("/sbin/resolvconf", &buf)) + return; + + logger (LOG_DEBUG, "removing information from resolvconf"); + + char *argc[4]; + + argc[0] = "/sbin/resolvconf"; + argc[1] = "-d"; + argc[2] = ifname; + argc[3] = NULL; + + /* Don't wait around here as we should only be called when + dhcpcd is closing down and something may do a kill -9 + if we take too long */ + /* Don't use vfork as we lose our memory when dhcpcd exits + causing the script to fail */ + pid_t pid; + if ((pid = fork ()) == 0) + { + if (execve (argc[0], argc, NULL)) + logger (LOG_ERR, "error executing \"%s %s %s\": %m", + argc[0], argc[1], argc[2]); + exit (0); + } + else if (pid == -1) + logger (LOG_ERR, "fork: %s", strerror (errno)); +} + +static int make_ntp (char *ifname, dhcp_t *dhcp) +{ + FILE *f; + address_t *address; + char *a; + + if (! (f = fopen(NTPFILE, "w"))) + { + logger (LOG_ERR, "fopen `%s': %m", NTPFILE); + return -1; + } + + fprintf (f, "# Generated by dhcpcd for interface %s\n", ifname); + fprintf (f, "restrict default noquery notrust nomodify\n"); + fprintf (f, "restrict 127.0.0.1\n"); + + for (address = dhcp->ntpservers; address; address = address->next) + { + a = inet_ntoa (address->address); + fprintf (f, "restrict %s nomodify notrap noquery\nserver %s\n", a, a); + } + + fprintf (f, "driftfile " NTPDRIFTFILE "\n"); + fprintf (f, "logfile " NTPLOGFILE "\n"); + fclose (f); + return 0; +} + +static int make_nis (char *ifname, dhcp_t *dhcp) +{ + FILE *f; + address_t *address; + char prefix[256] = {0}; + + if (! (f = fopen(NISFILE, "w"))) + { + logger (LOG_ERR, "fopen `%s': %m", NISFILE); + return -1; + } + + fprintf (f, "# Generated by dhcpcd for interface %s\n", ifname); + if (dhcp->nisdomain) + { + setdomainname (dhcp->nisdomain, strlen (dhcp->nisdomain)); + + if (dhcp->nisservers) + snprintf (prefix, sizeof (prefix), "domain %s server", dhcp->nisdomain); + else + fprintf (f, "domain %s broadcast\n", dhcp->nisdomain); + } + else + sprintf(prefix, "ypserver %c", '\0'); + + for (address = dhcp->nisservers; address; address = address->next) + fprintf (f, "%s%s\n", prefix, inet_ntoa (address->address)); + + fclose (f); + + return 0; +} + +static int write_info(interface_t *iface, dhcp_t *dhcp) +{ + FILE *f; + route_t *route; + address_t *address; + + if ((f = fopen (iface->infofile, "w")) == NULL) + { + logger (LOG_ERR, "fopen `%s': %m", iface->infofile); + return -1; + } + + fprintf (f, "IPADDR=%s\n", inet_ntoa (dhcp->address)); + fprintf (f, "NETMASK=%s\n", inet_ntoa (dhcp->netmask)); + fprintf (f, "BROADCAST=%s\n", inet_ntoa (dhcp->broadcast)); + if (dhcp->mtu > 0) + fprintf (f, "MTU=%d\n", dhcp->mtu); + + if (dhcp->routes) + { + fprintf (f, "ROUTES='"); + for (route = dhcp->routes; route; route = route->next) + { + fprintf (f, "%s", inet_ntoa (route->destination)); + fprintf (f, ",%s", inet_ntoa (route->netmask)); + fprintf (f, ",%s", inet_ntoa (route->gateway)); + if (route->next) + fprintf (f, " "); + } + fprintf (f, "'\n"); + } + + if (dhcp->hostname) + fprintf (f, "HOSTNAME='%s'\n",cleanmetas (dhcp->hostname)); + + if (dhcp->dnsdomain) + fprintf (f, "DNSDOMAIN='%s'\n", cleanmetas (dhcp->dnsdomain)); + + if (dhcp->dnssearch) + fprintf (f, "DNSSEARCH='%s'\n", cleanmetas (dhcp->dnssearch)); + + if (dhcp->dnsservers) + { + fprintf (f, "DNSSERVERS='"); + for (address = dhcp->dnsservers; address; address = address->next) + { + fprintf (f, "%s", inet_ntoa (address->address)); + if (address->next) + fprintf (f, " "); + } + fprintf (f, "'\n"); + } + + if (dhcp->fqdn) + { + fprintf (f, "FQDNFLAGS=%u\n", dhcp->fqdn->flags); + fprintf (f, "FQDNRCODE1=%u\n", dhcp->fqdn->r1); + fprintf (f, "FQDNRCODE2=%u\n", dhcp->fqdn->r2); + fprintf (f, "FQDNHOSTNAME='%s'\n", dhcp->fqdn->name); + } + + if (dhcp->ntpservers) + { + fprintf (f, "NTPSERVERS='"); + for (address = dhcp->ntpservers; address; address = address->next) + { + fprintf (f, "%s", inet_ntoa (address->address)); + if (address->next) + fprintf (f, " "); + } + fprintf (f, "'\n"); + } + + if (dhcp->nisdomain) + fprintf (f, "NISDOMAIN='%s'\n", cleanmetas (dhcp->nisdomain)); + + if (dhcp->nisservers) + { + fprintf (f, "NISSERVERS='"); + for (address = dhcp->nisservers; address; address = address->next) + { + fprintf (f, "%s", inet_ntoa (address->address)); + if (address->next) + fprintf (f, " "); + } + fprintf (f, "'\n"); + } + + if (dhcp->rootpath) + fprintf (f, "ROOTPATH='%s'\n", cleanmetas (dhcp->rootpath)); + + fprintf (f, "DHCPSID=%s\n", inet_ntoa (dhcp->serveraddress)); + fprintf (f, "DHCPCHADDR=%s\n", ether_ntoa (&iface->ethernet_address)); + fprintf (f, "DHCPSNAME='%s'\n", cleanmetas (dhcp->servername)); + fprintf (f, "LEASETIME=%u\n", dhcp->leasetime); + fprintf (f, "RENEWALTIME=%u\n", dhcp->renewaltime); + fprintf (f, "REBINDTIME=%u\n", dhcp->rebindtime); + fprintf (f, "INTERFACE='%s'\n", iface->name); + fprintf (f, "CLASSID='%s'\n", cleanmetas (dhcp->classid)); + fprintf (f, "CLIENTID='%s'\n", cleanmetas (dhcp->clientid)); + + fclose (f); + return 0; +} + +int configure (options_t *options, interface_t *iface, dhcp_t *dhcp) +{ + route_t *route = NULL; + route_t *new_route = NULL; + route_t *old_route = NULL; + struct hostent *he = NULL; + char *newhostname[HOSTNAME_MAX_LEN] = {0}; + char curhostname[HOSTNAME_MAX_LEN] = {0}; + char *dname = NULL; + int dnamel = 0; + + if (!options || !iface || !dhcp) + return -1; + + /* Remove old routes + Always do this as the interface may have >1 address not added by us + so the routes we added may still exist */ + if (iface->previous_routes) + { + for (route = iface->previous_routes; route; route = route->next) + if (route->destination.s_addr || options->dogateway) + { + int have = 0; + if (dhcp->address.s_addr != 0) + for (new_route = dhcp->routes; new_route; new_route = new_route->next) + if (new_route->destination.s_addr == route->destination.s_addr + && new_route->netmask.s_addr == route->netmask.s_addr + && new_route->gateway.s_addr == route->gateway.s_addr) + { + have = 1; + break; + } + if (! have) + del_route (iface->name, route->destination, route->netmask, + route->gateway, options->metric); + } + } + + /* If we don't have an address, then return */ + if (dhcp->address.s_addr == 0) + { + if (iface->previous_routes) + { + free_route (iface->previous_routes); + iface->previous_routes = NULL; + } + + if (iface->previous_address.s_addr != 0) + del_address (iface->name, iface->previous_address); + memset (&iface->previous_address, 0, sizeof (struct in_addr)); + + restore_resolv (iface->name); + + /* We currently don't have a resolvconf style programs for ntp/nis */ + exec_script (options->script, iface->infofile, "down"); + return 0; + } + + if (add_address (iface->name, dhcp->address, dhcp->netmask, + dhcp->broadcast) < 0 && errno != EEXIST) + return -1; + + /* Now delete the old address if different */ + if (iface->previous_address.s_addr != dhcp->address.s_addr + && iface->previous_address.s_addr != 0) + del_address (iface->name, iface->previous_address); + +#ifdef __linux__ + /* On linux, we need to change the subnet route to have our metric. */ + if (iface->previous_address.s_addr != dhcp->address.s_addr + && options->metric > 0) + { + struct in_addr td; + struct in_addr tg; + memset (&td, 0, sizeof (td)); + memset (&tg, 0, sizeof (tg)); + td.s_addr = dhcp->address.s_addr & dhcp->netmask.s_addr; + add_route (iface->name, td, dhcp->netmask, tg, options->metric); + del_route (iface->name, td, dhcp->netmask, tg, 0); + } +#endif + + /* Remember added routes */ + if (dhcp->routes) + { + route_t *new_routes = NULL; + + for (route = dhcp->routes; route; route = route->next) + { + int remember = add_route (iface->name, route->destination, + route->netmask, route->gateway, + options->metric); + /* If we failed to add the route, we may have already added it + ourselves. If so, remember it again. */ + if (remember < 0) + for (old_route = iface->previous_routes; old_route; + old_route = old_route->next) + if (old_route->destination.s_addr == route->destination.s_addr + && old_route->netmask.s_addr == route->netmask.s_addr + && old_route->gateway.s_addr == route->gateway.s_addr) + { + remember = 1; + break; + } + + if (remember >= 0) + { + if (! new_routes) + { + new_routes = xmalloc (sizeof (route_t)); + memset (new_routes, 0, sizeof (route_t)); + new_route = new_routes; + } + else + { + new_route->next = xmalloc (sizeof (route_t)); + new_route = new_route->next; + } + memcpy (new_route, route, sizeof (route_t)); + new_route -> next = NULL; + } + } + + if (iface->previous_routes) + free_route (iface->previous_routes); + + iface->previous_routes = new_routes; + } + + if (options->dodns && dhcp->dnsservers) + make_resolv(iface->name, dhcp, (options->dohostname && !dhcp->hostname)); + + if (options->dontp && dhcp->ntpservers) + make_ntp(iface->name, dhcp); + + if (options->donis && (dhcp->nisservers || dhcp->nisdomain)) + make_nis(iface->name, dhcp); + + /* Now we have made a resolv.conf we can obtain a hostname if we need one */ + if (options->dohostname && !dhcp->hostname) + { + he = gethostbyaddr (inet_ntoa (dhcp->address), + sizeof (struct in_addr), AF_INET); + if (he) + { + dname = he->h_name; + while (*dname > 32) + dname++; + dnamel = dname - he->h_name; + memcpy (newhostname, he->h_name, dnamel); + newhostname[dnamel] = 0; + } + } + + gethostname (curhostname, sizeof (curhostname)); + + if (options->dohostname || !strlen (curhostname) + || !strcmp (curhostname, "(none)") || !strcmp (curhostname, "localhost")) + { + if (dhcp->hostname) + strcpy ((char *) newhostname, dhcp->hostname); + + sethostname ((char *) newhostname, strlen ((char *) newhostname)); + logger (LOG_INFO, "setting hostname to %s", newhostname); + } + + write_info (iface, dhcp); + + if (iface->previous_address.s_addr != dhcp->address.s_addr) + { + memcpy (&iface->previous_address, + &dhcp->address, sizeof (struct in_addr)); + exec_script (options->script, iface->infofile, "new"); + } + else + exec_script (options->script, iface->infofile, "up"); + + return 0; +} +
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/configure.h Mon Nov 27 20:23:22 2006 +0000 @@ -0,0 +1,31 @@ +/* + * dhcpcd - DHCP client daemon - + * Copyright (C) 2005 - 2006 Roy Marples <uberlord@gentoo.org> + * + * dhcpcd is an RFC2131 compliant DHCP client daemon. + * + * This 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., 675 Mass Ave, Cambridge, MA 02139, USA. + */ + +#ifndef DHCPCONFIG_H +#define DHCPCONFIG_H + +#include "dhcpcd.h" +#include "interface.h" +#include "dhcp.h" + +int configure (options_t *options, interface_t *iface, dhcp_t *dhcp); + +#endif
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/dhcp.c Mon Nov 27 20:23:22 2006 +0000 @@ -0,0 +1,648 @@ +/* + * dhcpcd - DHCP client daemon - + * Copyright (C) 2005 - 2006 Roy Marples <uberlord@gentoo.org> + * + * dhcpcd is an RFC2131 compliant DHCP client daemon. + * + * This 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., 675 Mass Ave, Cambridge, MA 02139, USA. + */ + +#include <sys/socket.h> +#include <sys/types.h> + +#include <netinet/in.h> +#include <net/if_arp.h> + +#include <limits.h> +#include <math.h> +#include <stdlib.h> +#include <string.h> + +#include "common.h" +#include "dhcp.h" +#include "interface.h" +#include "logger.h" +#include "socket.h" + +static char *dhcp_message[] = { + [DHCP_DISCOVER] = "DHCP_DISCOVER", + [DHCP_OFFER] = "DHCP_OFFER", + [DHCP_REQUEST] = "DHCP_REQUEST", + [DHCP_DECLINE] = "DHCP_DECLINE", + [DHCP_ACK] = "DHCP_ACK", + [DHCP_NAK] = "DHCP_NAK", + [DHCP_RELEASE] = "DHCP_RELEASE", + [DHCP_INFORM] = "DHCP_INFORM", + [DHCP_INFORM + 1] = NULL +}; + +size_t send_message (interface_t *iface, dhcp_t *dhcp, + unsigned long xid, char type, options_t *options) +{ + dhcpmessage_t message; + unsigned char *p = (unsigned char *) &message.options; + unsigned char *n_params = NULL; + unsigned long l; + + if (!iface || !options || !dhcp) + return -1; + + memset (&message, 0, sizeof (dhcpmessage_t)); + + message.op = DHCP_BOOTREQUEST; + message.hwtype = ARPHRD_ETHER; + message.hwlen = ETHER_ADDR_LEN; + message.secs = htons (10); + message.xid = xid; + memcpy (&message.hwaddr, &iface->ethernet_address, ETHER_ADDR_LEN); + message.cookie = htonl(MAGIC_COOKIE); + + if (iface->previous_address.s_addr != 0 && + iface->previous_address.s_addr == dhcp->address.s_addr) + message.ciaddr = iface->previous_address.s_addr; + + *p++ = DHCP_MESSAGETYPE; + *p++ = 1; + *p++ = type; + + if (type == DHCP_REQUEST) + { + *p++ = DHCP_MAXMESSAGESIZE; + *p++ = 2; + uint16_t sz = htons (sizeof (struct udp_dhcp_packet)); + memcpy (p, &sz, 2); + p += 2; + } + + if (dhcp->address.s_addr != 0 && iface->previous_address.s_addr == 0) + { + *p++ = DHCP_ADDRESS; + *p++ = 4; + memcpy (p, &dhcp->address.s_addr, 4); + p += 4; + } + + if (dhcp->serveraddress.s_addr != 0 && dhcp->address.s_addr !=0 && + iface->previous_address.s_addr == 0) + { + *p++ = DHCP_SERVERIDENTIFIER; + *p++ = 4; + memcpy (p, &dhcp->serveraddress.s_addr, 4); + p += 4; + + /* Blank out the server address so we broadcast */ + if (type == DHCP_REQUEST) dhcp->serveraddress.s_addr = 0; + } + + if (type == DHCP_REQUEST || type == DHCP_DISCOVER) + { + if (dhcp->leasetime > 0) + { + *p++ = DHCP_LEASETIME; + *p++ = 4; + uint32_t ul = htonl (dhcp->leasetime); + memcpy (p, &ul, 4); + p += 4; + } + } + + *p++ = DHCP_PARAMETERREQUESTLIST; + n_params = p; + *p++ = 0; + + if (type == DHCP_REQUEST) + { + *p++ = DHCP_RENEWALTIME; + *p++ = DHCP_REBINDTIME; + *p++ = DHCP_NETMASK; + *p++ = DHCP_BROADCAST; + *p++ = DHCP_CSR; + /* RFC 3442 states classless static routes should be before routers + * and static routes as classless static routes override them both */ + *p++ = DHCP_ROUTERS; + *p++ = DHCP_STATICROUTE; + *p++ = DHCP_HOSTNAME; + *p++ = DHCP_DNSSEARCH; + *p++ = DHCP_DNSDOMAIN; + *p++ = DHCP_DNSSERVER; + *p++ = DHCP_NISDOMAIN; + *p++ = DHCP_NISSERVER; + *p++ = DHCP_NTPSERVER; + /* These parameters were requested by dhcpcd-2.0 and earlier + but we never did anything with them */ + /* *p++ = DHCP_DEFAULTIPTTL; + *p++ = DHCP_MASKDISCOVERY; + *p++ = DHCP_ROUTERDISCOVERY; */ + } + else + /* Always request one parameter so we don't get the server default + when we don't actally need any at this time */ + *p++ = DHCP_DNSSERVER; + + *n_params = p - n_params - 1; + + if (type == DHCP_REQUEST) + { + if (options->hostname) + { + if (options->fqdn == FQDN_DISABLE) + { + *p++ = DHCP_HOSTNAME; + *p++ = l = strlen (options->hostname); + memcpy (p, options->hostname, l); + p += l; + } + else + { + /* Draft IETF DHC-FQDN option (81) */ + *p++ = DHCP_FQDN; + *p++ = (l = strlen (options->hostname)) + 3; + /* Flags: 0000NEOS + * S: 1 => Client requests Server to update A RR in DNS as well as PTR + * O: 1 => Server indicates to client that DNS has been updated + * E: 1 => Name data is DNS format + * N: 1 => Client requests Server to not update DNS + */ + *p++ = options->fqdn & 0x9; + *p++ = 0; /* rcode1, response from DNS server for PTR RR */ + *p++ = 0; /* rcode2, response from DNS server for A RR if S=1 */ + memcpy (p, options->hostname, l); + p += l; + } + } + } + + if (options->userclass) + { + *p++ = DHCP_USERCLASS; + *p++ = l = strlen (options->userclass); + memcpy (p, options->userclass, l); + p += l; + } + + *p++ = DHCP_CLASSID; + *p++ = l = strlen (options->classid); + memcpy (p, options->classid, l); + p += l; + + *p++ = DHCP_CLIENTID; + if (options->clientid[0]) + { + l = strlen (options->clientid); + *p++ = l + 1; + *p++ = 0; /* string */ + memcpy (p, options, l); + p += l; + } + else + { + *p++ = ETHER_ADDR_LEN + 1; + *p++ = ARPHRD_ETHER; + memcpy (p, &iface->ethernet_address, ETHER_ADDR_LEN); + p += ETHER_ADDR_LEN; + } + + *p = DHCP_END; + + struct udp_dhcp_packet packet; + memset (&packet, 0, sizeof (struct udp_dhcp_packet)); + make_dhcp_packet (&packet, (unsigned char *) &message, + dhcp->address, dhcp->serveraddress); + + logger (LOG_DEBUG, "Sending %s with xid %d", dhcp_message[(int) type], xid); + return send_packet (iface, ETHERTYPE_IP, (unsigned char *) &packet, + sizeof (struct udp_dhcp_packet)); +} + +static unsigned long getnetmask (unsigned long ip_in) +{ + unsigned long t, p = ntohl (ip_in); + + if (IN_CLASSA (p)) + t = ~IN_CLASSA_NET; + else + { + if (IN_CLASSB (p)) + t = ~IN_CLASSB_NET; + else + { + if (IN_CLASSC (p)) + t = ~IN_CLASSC_NET; + else + t = 0; + } + } + while (t & p) t >>= 1; + return htonl (~t); +} + +/* Decode an RFC3397 DNS search order option into a space + seperated string. Returns length of string (including + terminating zero) or zero on error. out may be NULL + to just determine output length. */ +static unsigned int decode_search (u_char *p, int len, char *out) +{ + u_char *r, *q = p; + unsigned int count = 0, l, hops; + + while (q - p < len) + { + r = NULL; + hops = 0; + while ((l = *q++)) + { + unsigned int label_type = l & 0xc0; + if (label_type == 0x80 || label_type == 0x40) + return 0; + else if (label_type == 0xc0) /* pointer */ + { + l = (l & 0x3f) << 8; + l |= *q++; + + /* save source of first jump. */ + if (!r) + r = q; + + hops++; + if (hops > 255) + return 0; + + q = p + l; + if (q - p >= len) + return 0; + } + else + { + /* straightforward name segment, add with '.' */ + count += l + 1; + if (out) + { + memcpy (out, q, l); + out += l; + *out++ = '.'; + } + q += l; + } + } + + /* change last dot to space */ + if (out) + *(out - 1) = ' '; + + if (r) + q = r; + } + + /* change last space to zero terminator */ + if (out) + *(out - 1) = 0; + + return count; +} + +/* Add our classless static routes to the routes variable + * and return the last route set */ +static route_t *decodeCSR(unsigned char *p, int len) +{ + /* Minimum is 5 -first is CIDR and a router length of 4 */ + if (len < 5) + return NULL; + + unsigned char *q = p; + int cidr; + int ocets; + route_t *first = xmalloc (sizeof (route_t)); + route_t *route = first; + + while (q - p < len) + { + memset (route, 0, sizeof (route_t)); + + cidr = (int) *q++; + if (cidr == 0) + ocets = 0; + else if (cidr < 9) + ocets = 1; + else if (cidr < 17) + ocets = 2; + else if (cidr < 25) + ocets = 3; + else + ocets = 4; + + if (ocets > 0) + { + memcpy (&route->destination.s_addr, q, ocets); + q += ocets; + } + + /* Now enter the netmask */ + if (ocets > 0) + { + memset (&route->netmask.s_addr, 255, ocets - 1); + memset ((unsigned char *) &route->netmask.s_addr + (ocets - 1), + (256 - (1 << (32 - cidr) % 8)), 1); + } + + /* Finally, snag the router */ + memcpy (&route->gateway.s_addr, q, 4); + q += 4; + + /* We have another route */ + if (q - p < len) + { + route->next = xmalloc (sizeof (route_t)); + route = route->next; + } + } + + return first; +} + +void free_dhcp (dhcp_t *dhcp) +{ + if (!dhcp) + return; + + if (dhcp->routes) + free_route (dhcp->routes); + + if (dhcp->hostname) + free (dhcp->hostname); + + if (dhcp->dnsservers) + free_address (dhcp->dnsservers); + if (dhcp->dnsdomain) + free (dhcp->dnsdomain); + if (dhcp->dnssearch) + free (dhcp->dnssearch); + + if (dhcp->ntpservers) + free_address (dhcp->ntpservers); + + if (dhcp->nisdomain) + free (dhcp->nisdomain); + if (dhcp->nisservers) + free_address (dhcp->nisservers); + + if (dhcp->rootpath) + free (dhcp->rootpath); + + if (dhcp->fqdn) + { + if (dhcp->fqdn->name) + free (dhcp->fqdn->name); + free (dhcp->fqdn); + } +} + +static void dhcp_add_address(address_t *address, unsigned char *data, int length) +{ + int i; + address_t *p = address; + + for (i = 0; i < length; i += 4) + { + memset (p, 0, sizeof (address_t)); + memcpy (&p->address.s_addr, data + i, 4); + if (length - i > 4) + { + p->next = xmalloc (sizeof (address_t)); + p = p->next; + } + } +} + +int parse_dhcpmessage (dhcp_t *dhcp, dhcpmessage_t *message) +{ + unsigned char *p = message->options; + unsigned char option; + unsigned char length; + unsigned char *end = message->options + sizeof (message->options); + unsigned int len = 0; + int i; + int retval = -1; + route_t *first_route = xmalloc (sizeof (route_t)); + route_t *route = first_route; + route_t *last_route = NULL; + route_t *csr = NULL; + char classid[CLASS_ID_MAX_LEN]; + char clientid[CLIENT_ID_MAX_LEN]; + + memset (first_route, 0, sizeof (route_t)); + + /* The message back never has the class or client id's so we save them */ + strcpy (classid, dhcp->classid); + strcpy (clientid, dhcp->clientid); + + free_dhcp (dhcp); + memset (dhcp, 0, sizeof (dhcp_t)); + + dhcp->address.s_addr = message->yiaddr; + strcpy (dhcp->servername, message->servername); + + while (p < end) + { + option = *p++; + if (!option) + continue; + + length = *p++; + + if (p + length >= end) + { + retval = -1; + goto eexit; + } + + switch (option) + { + case DHCP_END: + goto eexit; + + case DHCP_MESSAGETYPE: + retval = (int) *p; + break; + + case DHCP_ADDRESS: + memcpy (&dhcp->address.s_addr, p, 4); + break; + case DHCP_NETMASK: + memcpy (&dhcp->netmask.s_addr, p, 4); + break; + case DHCP_BROADCAST: + memcpy (&dhcp->broadcast.s_addr, p, 4); + break; + case DHCP_SERVERIDENTIFIER: + memcpy (&dhcp->serveraddress.s_addr, p, 4); + break; + + case DHCP_LEASETIME: + dhcp->leasetime = ntohl (* (uint32_t *) p); + break; + case DHCP_RENEWALTIME: + dhcp->renewaltime = ntohl (* (uint32_t *) p); + break; + case DHCP_REBINDTIME: + dhcp->rebindtime = ntohl (* (uint32_t *) p); + break; + case DHCP_MTU: + dhcp->mtu = ntohs (* (uint16_t *) p); + /* Minimum legal mtu is 68 */ + if (dhcp->mtu > 0 && dhcp->mtu < 68) + dhcp->mtu = 68; + break; + + case DHCP_HOSTNAME: + if (dhcp->hostname) + free (dhcp->hostname); + dhcp->hostname = xmalloc (length + 1); + memcpy (dhcp->hostname, p, length); + dhcp->hostname[length] = '\0'; + break; + + case DHCP_DNSDOMAIN: + if (dhcp->dnsdomain) + free (dhcp->dnsdomain); + dhcp->dnsdomain = xmalloc (length + 1); + memcpy (dhcp->dnsdomain, p, length); + dhcp->dnsdomain[length] = '\0'; + break; + + case DHCP_MESSAGE: + if (dhcp->message) + free (dhcp->message); + dhcp->message = xmalloc (length + 1); + memcpy (dhcp->message, p, length); + dhcp->message[length] = '\0'; + break; + + case DHCP_ROOTPATH: + if (dhcp->rootpath) + free (dhcp->rootpath); + dhcp->rootpath = xmalloc (length + 1); + memcpy (dhcp->rootpath, p, length); + dhcp->rootpath[length] = '\0'; + break; + + case DHCP_NISDOMAIN: + if (dhcp->nisdomain) + free (dhcp->nisdomain); + dhcp->nisdomain = xmalloc (length + 1); + memcpy (dhcp->nisdomain, p, length); + dhcp->nisdomain[length] = '\0'; + break; + + case DHCP_DNSSERVER: + if (dhcp->dnsservers) + free_address (dhcp->dnsservers); + dhcp->dnsservers = xmalloc (sizeof (address_t)); + dhcp_add_address (dhcp->dnsservers, p, length); + break; + case DHCP_NTPSERVER: + if (dhcp->ntpservers) + free_address (dhcp->ntpservers); + dhcp->ntpservers = xmalloc (sizeof (address_t)); + dhcp_add_address (dhcp->ntpservers, p, length); + break; + case DHCP_NISSERVER: + if (dhcp->nisservers) + free_address (dhcp->nisservers); + dhcp->nisservers = xmalloc (sizeof (address_t)); + dhcp_add_address (dhcp->nisservers, p, length); + break; + + case DHCP_DNSSEARCH: + if (dhcp->dnssearch) + free (dhcp->dnssearch); + if ((len = decode_search (p, length, NULL))) + { + dhcp->dnssearch = xmalloc (len); + decode_search (p, length, dhcp->dnssearch); + } + break; + + case DHCP_CSR: + csr = decodeCSR (p, length); + break; + + case DHCP_STATICROUTE: + for (i = 0; i < length; i += 8) + { + memcpy (&route->destination.s_addr, p + i, 4); + memcpy (&route->gateway.s_addr, p + i + 4, 4); + route->netmask.s_addr = getnetmask (route->destination.s_addr); + last_route = route; + route->next = xmalloc (sizeof (route_t)); + route = route->next; + memset (route, 0, sizeof (route_t)); + } + break; + + case DHCP_ROUTERS: + for (i = 0; i < length; i += 4) + { + memcpy (&route->gateway.s_addr, p + i, 4); + last_route = route; + route->next = xmalloc (sizeof (route_t)); + route = route->next; + memset (route, 0, sizeof (route_t)); + } + break; + + default: + logger (LOG_DEBUG, "no facility to parse DHCP code %u", option); + break; + } + + p += length; + } + +eexit: + /* Fill in any missing fields */ + if (!dhcp->netmask.s_addr) + dhcp->netmask.s_addr = getnetmask (dhcp->address.s_addr); + if (!dhcp->broadcast.s_addr) + dhcp->broadcast.s_addr = dhcp->address.s_addr | ~dhcp->netmask.s_addr; + + /* If we have classess static routes then we discard + static routes and routers according to RFC 3442 */ + if (csr) + { + dhcp->routes = csr; + free_route (first_route); + } + else + { + dhcp->routes = first_route; + if (last_route) + { + free (last_route->next); + last_route->next = NULL; + } + else + { + free_route (dhcp->routes); + dhcp->routes = NULL; + } + } + + /* The message back never has the class or client id's so we restore them */ + strcpy (dhcp->classid, classid); + strcpy (dhcp->clientid, clientid); + + return retval; +} +
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/dhcp.h Mon Nov 27 20:23:22 2006 +0000 @@ -0,0 +1,185 @@ +/* + * dhcpcd - DHCP client daemon - + * Copyright (C) 2005 - 2006 Roy Marples <uberlord@gentoo.org> + * + * dhcpcd is an RFC2131 compliant DHCP client daemon. + * + * This 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., 675 Mass Ave, Cambridge, MA 02139, USA. + */ + +#ifndef DHCP_H +#define DHCP_H + +#include <sys/types.h> +#include <netinet/in_systm.h> +#include <netinet/in.h> +#include <netinet/ip.h> +#include <netinet/udp.h> + +#include "dhcpcd.h" +#include "interface.h" + +/* UDP port numbers for DHCP */ +#define DHCP_SERVER_PORT 67 +#define DHCP_CLIENT_PORT 68 + +#define MAGIC_COOKIE 0x63825363 + +/* DHCP message OP code */ +#define DHCP_BOOTREQUEST 1 +#define DHCP_BOOTREPLY 2 + +/* DHCP message type */ +#define DHCP_DISCOVER 1 +#define DHCP_OFFER 2 +#define DHCP_REQUEST 3 +#define DHCP_DECLINE 4 +#define DHCP_ACK 5 +#define DHCP_NAK 6 +#define DHCP_RELEASE 7 +#define DHCP_INFORM 8 + +/* DHCP options */ +enum DHCP_OPTIONS +{ + DHCP_PAD = 0, + DHCP_NETMASK = 1, + DHCP_TIMEROFFSET = 2, + DHCP_ROUTERS = 3, + DHCP_TIMESERVER = 4, + DHCP_NAMESERVER = 5, + DHCP_DNSSERVER = 6, + DHCP_LOGSERVER = 7, + DHCP_COOKIESERVER = 8, + DHCP_HOSTNAME = 12, + DHCP_DNSDOMAIN = 15, + DHCP_ROOTPATH = 17, + DHCP_DEFAULTIPTTL = 23, + DHCP_MTU = 26, + DHCP_BROADCAST = 28, + DHCP_MASKDISCOVERY = 29, + DHCP_ROUTERDISCOVERY = 31, + DHCP_STATICROUTE = 33, + DHCP_NISDOMAIN = 40, + DHCP_NISSERVER = 41, + DHCP_NTPSERVER = 42, + DHCP_ADDRESS = 50, + DHCP_LEASETIME = 51, + DHCP_MESSAGETYPE = 53, + DHCP_SERVERIDENTIFIER = 54, + DHCP_PARAMETERREQUESTLIST = 55, + DHCP_MESSAGE = 56, + DHCP_MAXMESSAGESIZE = 57, + DHCP_RENEWALTIME = 58, + DHCP_REBINDTIME = 59, + DHCP_CLASSID = 60, + DHCP_CLIENTID = 61, + DHCP_USERCLASS = 77, /* RFC 3004 */ + DHCP_FQDN = 81, + DHCP_DNSSEARCH = 119, /* RFC 3397 */ + DHCP_CSR = 121, /* RFC 3442 */ + DHCP_END = 255 +}; + +/* SetFQDNHostName values - lsnybble used in flags + * byte (see buildmsg.c), hsnybble to create order + * and to allow 0x00 to mean disable + */ +enum FQQN { + FQDN_DISABLE = 0x00, + FQDN_NONE = 0x18, + FQDN_PTR = 0x20, + FQDN_BOTH = 0x31 +}; + +typedef struct fqdn_t +{ + uint8_t flags; + uint8_t r1; + uint8_t r2; + char *name; +} fqdn_t; + +typedef struct dhcp_t +{ + char version[11]; + + struct in_addr serveraddress; + char serverhw[IF_NAMESIZE]; + char servername[64]; + + char classid[CLASS_ID_MAX_LEN]; + char clientid[CLIENT_ID_MAX_LEN]; + + struct in_addr address; + struct in_addr netmask; + struct in_addr broadcast; + unsigned short mtu; + + unsigned leasetime; + unsigned renewaltime; + unsigned rebindtime; + + route_t *routes; + + char *hostname; + fqdn_t *fqdn; + + address_t *dnsservers; + char *dnsdomain; + char *dnssearch; + + address_t *ntpservers; + + address_t *nisservers; + char *nisdomain; + + char *message; + char *rootpath; +} dhcp_t; + +typedef struct dhcpmessage_t +{ + char op; /* message type */ + char hwtype; /* hardware address type */ + char hwlen; /* hardware address length */ + char hwopcount; /* should be zero in client's message */ + int32_t xid; /* transaction id */ + int16_t secs; /* elapsed time in sec. from trying to boot */ + int16_t flags; + int32_t ciaddr; /* (previously allocated) client IP address */ + int32_t yiaddr; /* 'your' client IP address */ + int32_t siaddr; /* should be zero in client's messages */ + int32_t giaddr; /* should be zero in client's messages */ + unsigned char hwaddr[16]; /* client's hardware address */ + char servername[64]; /* server host name, null terminated string */ + char bootfile[128]; /* boot file name, null terminated string */ + uint32_t cookie; + unsigned char options[308]; /* message options - cookie */ +} dhcpmessage_t; + +struct udp_dhcp_packet +{ + struct ip ip; + struct udphdr udp; + dhcpmessage_t dhcp; +}; + +size_t send_message (interface_t *iface, dhcp_t *dhcp, + unsigned long xid, char type, options_t *options); +void free_dhcp (dhcp_t *dhcp); +int parse_dhcpmessage (dhcp_t *dhcp, dhcpmessage_t *message); + +#endif
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/dhcpcd.8 Mon Nov 27 20:23:22 2006 +0000 @@ -0,0 +1,332 @@ +.\" $Id$ +.\" +.TH dhcpcd 8 "15 August 2006" "dhcpcd 3.0" + +.SH NAME +dhcpcd \- DHCP client daemon + +.SH SYNOPSIS +.in +.5i +.ti -.5i +dhcpcd +\%[\-adknpGHNRY] +\%[\-c\ script] +\%[\-h\ hostname] +\%[\-i\ vendorClassID] +\%[\-l\ leasetime] +\%[\-m\ metric] +\%[\-s\ ipaddr] +\%[\-t\ timeout] +\%[\-u\ userClass] +\%[\-F\ none | ptr | both] +\%[\-I\ clientID] +\%[interface] +.in -.5i +.SH DESCRIPTION +.B dhcpcd +is an implementation of the DHCP client specified in +.B RFC2131. + +It gets the host information (IP address, netmask, broadcast address, +etc.) from a DHCP server and configures the network interface of the +machine on which it is running. It also tries to renew the lease time +according to +.B RFC2131. + +.SH OPTIONS +.TP +.BI interface +Specifies the network interface name (eth0, eth1, etc.). +.TP +.BI \-a +Do an +.B ARP +check on the IP address give to us by the DHCP server. We may need to do this +if a client on the same network segment has the same IP address, however we do +not do this by default as most DHCP servers test the IP briefly with an ICMP +Echo request before assigning the IP address. +.TP +.BI \-c \ script +.B dhcpcd +will try to execute +.I script +instead of the default script +.I /etc/dhcpcd.sh +every time it configures or brings down the interface. See the +description of +.I dhcpcd.sh +script in +.B FILES +section below. +.TP +.BI \-d +Echos debugging and information messages to the console. +.TP +.BI \-h \ hostname +specifies a string used for the hostname option field when +.B dhcpcd +sends DHCP messages. Some DHCP servers, notably those used by +@Home Networks, require the hostname option +field containing a specific string in the DHCP messages from clients. +When combined with the -F switch, specifies the string used for the +FQDN option field instead of the hostname option field. +.TP +.BI \-i \ vendorClassID +Specifies the vendor class identifier string. +.B dhcpcd +uses the default vendor class identifier string (system name, system release, +and machine type) if it is not specified. +.TP +.BI \-k +Sends +.B SIGHUP +signal to the +.B dhcpcd +process associated with the specified interface if one is currently running. If +.B dhcpcd +receives +.B SIGHUP +it will send +.B DCHP_RELEASE +message to the server and destroy dhcpcd cache. In a case +.B dhcpcd +receives +.B SIGTERM +which is normally used by +.B shutdown(8) +when rebooting the system +.B dhcpcd +will not send +.B DHCP_RELEASE +and will not destroy cache. When system boots +.B dhcpcd +will use cache to request the same IP address +from DHCP server which was assigned before the +system went down. (see also +.B -p +) +.TP +.BI \-l \ leasetime +Specifies (in seconds) the recommended lease time to the server. (Note +that the server can override this value if it sees fit). This value is +used in the +.B DHCP_DISCOVER +message. Use -1 for an infinite lease time. We don't request a specific +lease time by default. +.TP +.BI \-m \ metric +Routes will be added with the given metric. The default is 0. +On some systems such as FreeBSD the interface is given the metric. +.TP +.BI \-n +Sends +.B SIGALRM +signal to the +.B dhcpcd +process that is currently running which +forces +.B dhcpcd +to try to renew the lease. If dhcpcd is not running, the flag +is ignored and +.B dhcpcd +follows the normal startup procedure. +.TP +.BI \-p +Stops +.B dhcpcd +from removing the interface configuration when it is terminated with the +.B SIGTERM +signal. This is useful when a host is running with an NFS-mounted root +filesystem over an interface controlled by DHCP. It should not be used +except in those circumstances, since if +.B dhcp +is stopped it can no longer down an interface at the end of its +lease period when the lease is not renewed. +.TP +.BI \-s \ ipaddr +Sends DHCP_DISCOVER message to DHCP server requesting to lease ip address +ipaddr. +The ipaddr parameter must be in the form xxx.xxx.xxx.xxx. +.TP +.BI \-t \ timeout +Specifies (in seconds ) for how long +.B dhcpcd +will try to get an IP address. The default is 10 seconds. +.B dhcpcd +will not fork into background until it gets a valid IP address +in which case dhcpcd will return 0 to the parent process. +In a case +.B dhcpcd +times out before receiving a valid IP address from DHCP server +.B dhcpcd +will return exit code 1 to the parent process. Setting the timeout to +zero disables it: dhcp will keep trying forever to get a lease, and if +the lease is lost, it will try forever to get another. +.TP +.BI \-u \ userClass +Tags the DHCP message with the specified user class. DHCP servers can use +these fields to send back different information instead of grouping by +fixed hardware addresses. You can specify more than one user class, but the +total length must be less than 255 characters, -1 character for each user +class. +.TP +.BI \-H +Forces +.B dhcpcd +to set hostname of the host to the hostname option supplied by DHCP server. +By default +.B dhcpcd +will NOT set hostname of the host to the hostname option +received from DHCP server unless the current hostname is blank, (none) or +localhost. +.TP +.BI \-F \ none | ptr | both +Forces +.B dhcpcd +to request the DHCP server update the DNS using the FQDN option +instead of the Hostname option. The name used by this option +is specified with the \fB-h\fP switch, which must be present. If +the \fB-h\fP switch is not present, the FQDN option is ignored. +The name should be fully qualified, although servers usually +accept a simple name. +.I both +requests that the DHCP server update both the A and PTR +records in the DNS. +.I ptr +requests that the DHCP server updates only the PTR record in +the DNS. +.I none +requests that the DHCP server perform no updates. +.B dhcpcd +does not perform any DNS update, even when the server is +requested to perform no updates. This can be easily +implemented outside the client; all the necessary +information is recorded in the +.I /var/lib/dhcpcd/dhcpcd-<interface>.info +file. +.TP +.BI \-I \ clientID +Specifies the client identifier string. +.B dhcpcd +uses the default client identifier (MAC address of the network +interface) if it is not specified. +.TP +.BI \-N +Prevents +.B dhcpcd +from replacing existing +.I /etc/ntp.conf +file. +.TP +.BI \-R +Prevents +.B dhcpcd +from replacing existing using resolvconf or replacing +.I /etc/resolv.conf +file. +.TP +.BI \-Y +Prevents +.B dhcpcd +from replacing existing +.I /etc/yp.conf +file. +.TP +.BI \-G +Prevents +.B dhcpcd +from installing default routes provided by DHCP server. +.SH NOTES +.TP +.B dhcpcd +uses +.I LOCAL0 +syslog facility for all logging. To catch +.B dhcpcd +debugging output add the following line to +.I /etc/syslog.conf +file: + +local0.* /var/log/dhcpcd.log + +and then refresh syslogd daemon: + +kill -1 `cat /var/run/syslogd.pid` + +.SH FILES +.PD 0 +.TP +.BI /var/lib/dhcpcd +Directory used for storing files information files created by +.B dhcpcd +that can be used by shell scripts. +.PD 1 +.TP +.BI /etc/dhcpcd.sh +script file, which +.B dhcpcd +will try to execute whenever it configures or brings down the interface. The +path to this executable script can be changed with +.I \-c \ script +option. +.B dhcpcd +passes 3 parameters to +.I dhcpcd.sh +script: +.TP +.I dhcpcd.sh infofile [up | down | new] +The first parameter infofile is the path to a file containing all DHCP +information we have. The second parameter value +.I up | down | new +mean the interface has been brought up with the same IP address as before ("up"), or +with the new IP address ("new"), or the interface has been brought down ("down"). +.TP +.BI /etc/resolv.conf +file created by +.B dhcpcd +when the client receives DNS and domain name options. +If resolvconf is present on the system then we send the data to it instead +of overwriting resolv.conf +.TP +.BI /etc/yp.conf +file created by +.B dhcpcd +when the client receives NIS options. +.TP +.BI /etc/ntp.conf +file created by +.B dhcpcd +when the client receives NTP options. +.TP +.BI /var/run/dhcpcd-<interface>.pid +file containing the process id of +.B dhcpcd. +The word +.I <interface> +is actually replaced with the network interface name like +.I eth0 +to which +.B dhcpcd +is attached. + +.SH SEE ALSO +.BR dig (1), +.BR nslookup (8), +.BR nsupdate (8) +.LP +.I Dynamic Host Configuration Protocol, +RFC2132 +.LP +.I DHCP Options and BOOTP Vendor Extensions, +RFC2132 +.LP +.I Draft DHC FQDN Option specification, +draft-ietf-dhc-fqdn-option + +.SH BUGS +Probably many. +Please report them to http://bugs.gentoo.org. +.PD 0 + +.SH AUTHORS +Roy Marples <uberlord@gentoo.org>
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/dhcpcd.c Mon Nov 27 20:23:22 2006 +0000 @@ -0,0 +1,362 @@ +/* + * dhcpcd - DHCP client daemon - + * Copyright (C) 2005 - 2006 Roy Marples <uberlord@gentoo.org> + * + * dhcpcd is an RFC2131 compliant DHCP client daemon. + * + * This 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., 675 Mass Ave, Cambridge, MA 02139, USA. + */ + +#include <sys/stat.h> +#include <arpa/inet.h> +#include <errno.h> +#include <fcntl.h> +#include <getopt.h> +#include <paths.h> +#include <signal.h> +#include <stdbool.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <unistd.h> + +#include "client.h" +#include "dhcpcd.h" +#include "dhcp.h" +#include "interface.h" +#include "logger.h" +#include "pathnames.h" +#include "version.h" + +#define PACKAGE "dhcpcd" + +#define STRINGINT(_string, _int) { \ + char *_tmp; \ + errno = 0; \ + long _number = strtol (_string, &_tmp, 0); \ + if ((errno != 0 && _number == 0) || _string == _tmp || \ + (errno == ERANGE && (_number == LONG_MAX || _number == LONG_MIN))) \ + { \ + logger (LOG_ERR, "`%s' out of range", _string);; \ + exit (EXIT_FAILURE); \ + } \ + else \ + _int = (int) _number; \ +} + +static pid_t readpid(char *pidfile) +{ + FILE *fp; + pid_t pid; + + if ((fp = fopen (pidfile, "r")) == NULL) + { + errno = ENOENT; + return 0; + } + + fscanf (fp, "%d", &pid); + fclose (fp); + + return pid; +} + +static int kill_pid (char *pidfile, int sig) +{ + pid_t pid = readpid (pidfile); + int r = 0; + + if (!pid || (r = kill (pid, sig))) + { + logger (LOG_ERR, ""PACKAGE" not running"); + unlink (pidfile); + } + + return r; +} + +static void usage () +{ + printf ("usage: "PACKAGE" [-adknpGHNRY] [-c script] [-h hostame] [-i classID]\n" + " [-l leasetime] [-m metric] [-s ipaddress] [-t timeout]\n" + " [-u userclass] [-F [none | ptr | both]] [-I clientID]\n"); +} + +int main(int argc, char **argv) +{ + options_t options; + + /* Sanitize our fd's */ + int zero; + if ((zero = open (_PATH_DEVNULL, O_RDWR, 0)) >= 0) + { + while (zero < 3) + zero = dup (zero); + close(zero); + } + + openlog (PACKAGE, LOG_PID, LOG_LOCAL0); + + memset (&options, 0, sizeof (options_t)); + options.script = DEFAULT_SCRIPT; + snprintf (options.classid, CLASS_ID_MAX_LEN, "%s %s", PACKAGE, VERSION); + + options.doarp = false; + options.dodns = true; + options.dontp = true; + options.dogateway = true; + options.timeout = DEFAULT_TIMEOUT; + + int doversion = 0; + int dohelp = 0; + int userclasses = 0; + + const struct option longopts[] = + { + {"arp", no_argument, NULL, 'a'}, + {"script",required_argument, NULL, 'c'}, + {"debug", no_argument, NULL, 'd'}, + {"hostname", required_argument, NULL, 'h'}, + {"classid", required_argument, NULL, 'i'}, + {"release", no_argument, NULL, 'k'}, + {"leasetime", required_argument, NULL, 'l'}, + {"metric", required_argument, NULL, 'm'}, + {"renew", no_argument, NULL, 'n'}, + {"persistent", no_argument, NULL, 'p'}, + {"request", required_argument, NULL, 's'}, + {"timeout", required_argument, NULL, 't'}, + {"userclass", required_argument, NULL, 'u'}, + {"fqdn", optional_argument, NULL, 'F'}, + {"nogateway", no_argument, NULL, 'G'}, + {"sethostname", no_argument, NULL, 'H'}, + {"clientid", required_argument, NULL, 'I'}, + {"nontp", no_argument, NULL, 'N'}, + {"nodns", no_argument, NULL, 'R'}, + {"nonis", no_argument, NULL, 'Y'}, + {"help", no_argument, &dohelp, 1}, + {"version", no_argument, &doversion, 1}, + {NULL, 0, NULL, 0} + }; + + int ch; + int option_index = 0; + while ((ch = getopt_long(argc, argv, "ac:dh:i:kl:m:nps:t:u:F:GHI:NRY", longopts, + &option_index)) != -1) + switch (ch) + { + case 0: + if (longopts[option_index].flag) + break; + logger (LOG_ERR, "option `%s' should set a flag", + longopts[option_index].name); + exit (EXIT_FAILURE); + break; + + case 'a': + options.doarp = true; + break; + case 'c': + options.script = optarg; + break; + case 'd': + setloglevel(LOG_DEBUG); + break; + case 'h': + if (strlen (optarg) > HOSTNAME_MAX_LEN) + { + logger(LOG_ERR, "`%s' too long for HostName string, max is %d", + optarg, HOSTNAME_MAX_LEN); + exit (EXIT_FAILURE); + } + else + options.hostname = optarg; + break; + case 'i': + if (strlen(optarg) > CLASS_ID_MAX_LEN) + { + logger (LOG_ERR, "`%s' too long for ClassID string, max is %d", + optarg, CLASS_ID_MAX_LEN); + exit (EXIT_FAILURE); + } + else + sprintf(options.classid, "%s", optarg); + break; + case 'k': + options.signal = SIGHUP; + break; + case 'l': + STRINGINT (optarg, options.leasetime); + if (options.leasetime <= 0) + { + logger (LOG_ERR, "leasetime must be a positive value"); + exit (EXIT_FAILURE); + } + break; + case 'm': + STRINGINT(optarg, options.metric); + break; + case 'n': + options.signal = SIGALRM; + break; + case 'p': + options.persistent = true; + break; + case 's': + if (! inet_aton (optarg, &options.requestaddress)) + { + logger (LOG_ERR, "`%s' is not a valid IP address", optarg); + exit (EXIT_FAILURE); + } + break; + case 't': + STRINGINT (optarg, options.timeout); + if (options.timeout < 0) + { + logger (LOG_ERR, "timeout must be a positive value"); + exit (EXIT_FAILURE); + } + break; + case 'u': + { + int i; + int offset = 0; + for (i = 0; i < userclasses; i++) + offset += (int) options.userclass[offset] + 1; + if (offset + 1 + strlen (optarg) > USERCLASS_MAX_LEN) + { + logger (LOG_ERR, "userclass overrun, max is %d", + USERCLASS_MAX_LEN); + exit (EXIT_FAILURE); + } + userclasses++; + memcpy (options.userclass + offset + 1 , optarg, strlen (optarg)); + options.userclass[offset] = strlen (optarg); + } + break; + case 'F': + if (strcmp (optarg, "none") == 0) + options.fqdn = FQDN_NONE; + else if (strcmp (optarg, "ptr") == 0) + options.fqdn = FQDN_PTR; + else if (strcmp (optarg, "both") == 0) + options.fqdn = FQDN_BOTH; + else + { + logger (LOG_ERR, "invalid value `%s' for FQDN", optarg); + exit (EXIT_FAILURE); + } + break; + case 'G': + options.dogateway = false; + break; + case 'H': + options.dohostname = true; + break; + case 'I': + if (strlen (optarg) > CLIENT_ID_MAX_LEN) + { + logger (LOG_ERR, "`%s' is too long for ClientID, max is %d", + optarg, CLIENT_ID_MAX_LEN); + exit (EXIT_FAILURE); + } + else + sprintf(options.clientid, "%s", optarg); + break; + case 'N': + options.dontp = false; + break; + case 'R': + options.dodns = false; + break; + case 'Y': + options.donis = false; + break; + case '?': + usage (); + exit (EXIT_FAILURE); + default: + usage (); + exit (EXIT_FAILURE); + } + + if (doversion) + printf (""PACKAGE" "VERSION"\n"); + + if (dohelp) + usage (); + + if (optind < argc) + { + if (strlen (argv[optind]) > IF_NAMESIZE) + { + logger (LOG_ERR, "`%s' is too long for an interface name (max=%d)", + argv[optind], IF_NAMESIZE); + exit (EXIT_FAILURE); + } + options.interface = argv[optind]; + } + else + { + /* If only version was requested then exit now */ + if (doversion || dohelp) + exit (EXIT_SUCCESS); + + logger (LOG_ERR, "no interface specified", options.interface); + exit (EXIT_FAILURE); + } + + if (geteuid ()) + { + logger (LOG_ERR, "you need to be root to run "PACKAGE); + exit (EXIT_FAILURE); + } + + char prefix[IF_NAMESIZE + 3]; + snprintf (prefix, IF_NAMESIZE, "%s: ", options.interface); + setlogprefix (prefix); + snprintf (options.pidfile, sizeof (options.pidfile), PIDFILE, + options.interface); + + if (options.signal != 0) + exit (kill_pid (options.pidfile, options.signal)); + + umask (022); + + if (readpid (options.pidfile)) + { + logger (LOG_ERR, ""PACKAGE" already running (%s)", options.pidfile); + exit (EXIT_FAILURE); + } + + if (mkdir (CONFIGDIR, S_IRUSR |S_IWUSR |S_IXUSR | S_IRGRP | S_IXGRP + | S_IROTH | S_IXOTH) && errno != EEXIST ) + { + logger( LOG_ERR, "mkdir(\"%s\",0): %m\n", CONFIGDIR); + exit (EXIT_FAILURE); + } + + if (mkdir (ETCDIR, S_IRUSR | S_IWUSR | S_IXUSR | S_IRGRP | S_IXGRP + | S_IROTH | S_IXOTH) && errno != EEXIST ) + { + logger (LOG_ERR, "mkdir(\"%s\",0): %m\n", ETCDIR); + exit (EXIT_FAILURE); + } + + logger (LOG_INFO, PACKAGE " " VERSION " starting"); + if (dhcp_run (&options)) + exit (EXIT_FAILURE); + + exit (EXIT_SUCCESS); +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/dhcpcd.h Mon Nov 27 20:23:22 2006 +0000 @@ -0,0 +1,67 @@ +/* + * dhcpcd - DHCP client daemon - + * Copyright (C) 2005 - 2006 Roy Marples <uberlord@gentoo.org> + * + * This 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., 675 Mass Ave, Cambridge, MA 02139, USA. + */ + +#ifndef DHCPCD_H +#define DHCPCD_H + +#ifdef __linux__ +#include <linux/limits.h> +#endif +#include <netinet/in.h> +#include <limits.h> +#include <stdbool.h> + +#include "common.h" + +#define DEFAULT_TIMEOUT 10 +// #define DEFAULT_LEASETIME 0xffffffff /* infinite lease time */ +#define DEFAULT_LEASETIME 3600 /* 1 hour */ + +#define CLASS_ID_MAX_LEN 48 +#define CLIENT_ID_MAX_LEN 48 +#define HOSTNAME_MAX_LEN 64 +#define USERCLASS_MAX_LEN 255 + +typedef struct options_t { + char *interface; + char *hostname; + int fqdn; + char classid[CLASS_ID_MAX_LEN]; + char clientid[CLIENT_ID_MAX_LEN]; + char userclass[USERCLASS_MAX_LEN]; + unsigned leasetime; + time_t timeout; + int metric; + struct in_addr requestaddress; + + bool doarp; + bool dodns; + bool dontp; + bool donis; + bool dogateway; + bool dohostname; + bool dodomainname; + int signal; + bool persistent; + + char *script; + char pidfile[PATH_MAX]; +} options_t; + +#endif
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/dhcpcd.sh Mon Nov 27 20:23:22 2006 +0000 @@ -0,0 +1,46 @@ +#!/bin/sh +# +# This is a sample /etc/dhcpcd.sh script. +# /etc/dhcpcd.sh script is executed by dhcpcd daemon +# any time it configures or shuts down interface. +# The following parameters are passed to dhcpcd.exe script: +# $1 = HostInfoFilePath, e.g "/var/lib/dhcpcd/dhcpcd-eth0.info" +# $2 = "up" if interface has been configured with the same +# IP address as before reboot; +# $2 = "down" if interface has been shut down; +# $2 = "new" if interface has been configured with new IP address; +# +# Sanity checks + +if [ $# -lt 2 ]; then + logger -s -p local0.err -t dhcpcd.sh "wrong usage" + exit 1 +fi + +hostinfo="$1" +state="$2" + +# Reading HostInfo file for configuration parameters +. "${hostinfo}" + +case "${state}" in + up) + logger -s -p local0.info -t dhcpcd.sh \ + "interface ${INTERFACE} has been configured with old IP=${IPADDR}" + # Put your code here for when the interface has been brought up with an + # old IP address here + ;; + + new) + logger -s -p local0.info -t dhcpcd.sh \ + "interface ${INTERFACE} has been configured with new IP=${IPADDR}" + # Put your code here for when the interface has been brought up with a + # new IP address + ;; + + down) logger -s -p local0.info -t dhcpcd.sh \ + "interface ${INTERFACE} has been brought down" + # Put your code here for the when the interface has been shut down + ;; +esac +exit 0
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/interface.c Mon Nov 27 20:23:22 2006 +0000 @@ -0,0 +1,688 @@ +/* + * dhcpcd - DHCP client daemon - + * Copyright (C) 2006 Roy Marples <uberlord@gentoo.org> + * + * dhcpcd is an RFC2131 compliant DHCP client daemon. + * + * This 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., 675 Mass Ave, Cambridge, MA 02139, USA. + */ + +#include <sys/types.h> +#include <sys/socket.h> +#include <sys/stat.h> +#include <sys/ioctl.h> + +#include <arpa/inet.h> + +/* Netlink suff */ +#ifdef __linux__ +#include <linux/netlink.h> +#include <linux/rtnetlink.h> +#include <netinet/ether.h> +#include <netpacket/packet.h> +#else +#include <net/if_dl.h> +#include <net/if_types.h> +#include <net/route.h> +#include <netinet/in.h> +#endif /* __linux__ */ + +#include <errno.h> +#include <ifaddrs.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <unistd.h> + +#include "common.h" +#include "interface.h" +#include "logger.h" +#include "pathnames.h" + +#ifndef IFF_NOTRAILERS +#define IFF_NOTRAILERS 0 +#endif + +void free_address (address_t *addresses) +{ + if (!addresses) + return; + + address_t *p = addresses; + address_t *n = NULL; + + while (p) + { + n = p->next; + free (p); + p = n; + } +} + +void free_route (route_t *routes) +{ + if (!routes) + return; + + route_t *p = routes; + route_t *n = NULL; + + while (p) + { + n = p->next; + free (p); + p = n; + } +} + +interface_t *read_interface (const char *ifname, int metric) +{ + if (! ifname) + return NULL; + + int s; + struct ifreq ifr; + interface_t *iface; + unsigned char hwaddr[ETHER_ADDR_LEN]; + + struct ifaddrs *ifap; + struct ifaddrs *p; + unsigned int flags; + + if (getifaddrs (&ifap) != 0) + return NULL; + + for (p = ifap; p; p = p->ifa_next) + { + if (strcmp (p->ifa_name, ifname) != 0) + continue; +#ifdef __linux__ + struct sockaddr_ll *sll = (struct sockaddr_ll*) p->ifa_addr; + if (p->ifa_addr->sa_family != AF_PACKET + || sll->sll_hatype != ARPHRD_ETHER) +#else + struct sockaddr_dl *sdl = (struct sockaddr_dl *) p->ifa_addr; + if (p->ifa_addr->sa_family != AF_LINK || sdl->sdl_type != IFT_ETHER) +#endif + { + logger (LOG_ERR, "not Ethernet"); + freeifaddrs (ifap); + return NULL; + } + + flags = p->ifa_flags; +#ifdef __linux__ + memcpy (hwaddr, sll->sll_addr, ETHER_ADDR_LEN); +#else + memcpy (hwaddr, sdl->sdl_data + sdl->sdl_nlen, ETHER_ADDR_LEN); +#endif + break; + } + freeifaddrs (ifap); + + if (!p) + { + logger (LOG_ERR, "could not find interface %s", ifname); + return NULL; + } + + memset (&ifr, 0, sizeof (struct ifreq)); + strncpy (ifr.ifr_name, ifname, sizeof (ifr.ifr_name)); + if ((s = socket(AF_INET, SOCK_DGRAM, 0)) < 0) + { + logger (LOG_ERR, "socket: %s", strerror (errno)); + return NULL; + } + + if (ioctl(s, SIOCGIFFLAGS, &ifr) < 0) + { + logger (LOG_ERR, "ioctl SIOCGIFFLAGS: %s", strerror (errno)); + close (s); + return NULL; + } + + ifr.ifr_flags |= IFF_UP | IFF_BROADCAST | IFF_NOTRAILERS | IFF_RUNNING; + if (ioctl(s, SIOCSIFFLAGS, &ifr) < 0) + { + logger (LOG_ERR, "ioctl SIOCSIFFLAGS: %s", strerror (errno)); + close (s); + return NULL; + } + +#ifndef __linux__ + ifr.ifr_metric = metric; + if (ioctl(s, SIOCSIFMETRIC, &ifr) < 0) + { + logger (LOG_ERR, "ioctl SIOCSIFMETRIC: %s", strerror (errno)); + close (s); + return NULL; + } +#endif + + close (s); + + iface = xmalloc (sizeof (interface_t)); + memset (iface, 0, sizeof (interface_t)); + strncpy (iface->name, ifname, IF_NAMESIZE); + snprintf (iface->infofile, PATH_MAX, INFOFILE, ifname); + memcpy (&iface->ethernet_address, &hwaddr, ETHER_ADDR_LEN); + + iface->arpable = ! (ifr.ifr_flags & (IFF_NOARP | IFF_LOOPBACK)); + + logger (LOG_INFO, "ethernet address = %s", + ether_ntoa (&iface->ethernet_address)); + + /* 0 is a valid fd, so init to -1 */ + iface->fd = -1; + + return iface; +} + +#ifdef __FreeBSD__ +static int do_address (const char *ifname, struct in_addr address, + struct in_addr netmask, struct in_addr broadcast, int del) +{ + if (! ifname) + return -1; + + int s; + if ((s = socket(AF_INET, SOCK_DGRAM, 0)) < 0) + { + logger (LOG_ERR, "socket: %s", strerror (errno)); + return -1; + } + + struct ifaliasreq ifa; + memset (&ifa, 0, sizeof (ifa)); + strcpy (ifa.ifra_name, ifname); + +#define ADDADDR(_var, _addr) \ + { \ + struct sockaddr_in *_sin = (struct sockaddr_in *) &_var; \ + _sin->sin_family = AF_INET; \ + _sin->sin_len = sizeof (struct sockaddr_in); \ + memcpy (&_sin->sin_addr, &_addr, sizeof (struct in_addr)); \ + } + + ADDADDR (ifa.ifra_addr, address); + if (! del) + { + ADDADDR (ifa.ifra_mask, netmask); + ADDADDR (ifa.ifra_broadaddr, broadcast); + } + +#undef ADDADDR + + if (ioctl (s, del ? SIOCDIFADDR : SIOCAIFADDR, &ifa) == -1) + { + logger (LOG_ERR, "ioctl %s: %s", del ? "SIOCDIFADDR" : "SIOCAIFADDR", + strerror (errno)); + close (s); + return -1; + } + + close (s); + return 0; +} + +static int do_route (const char *ifname, + struct in_addr destination, + struct in_addr netmask, + struct in_addr gateway, + int metric, + int change, int del) +{ + if (! ifname) + return -1; + + char *destd = strdup (inet_ntoa (destination)); + char *gend = strdup (inet_ntoa (netmask)); + logger (LOG_INFO, "%s route to %s (%s) via %s", + change ? "changing" : del ? "removing" : "adding", + destd, gend, inet_ntoa(gateway)); + if (destd) + free (destd); + if (gend) + free (gend); + + int s; + if ((s = socket(PF_ROUTE, SOCK_RAW, 0)) < 0) + { + logger (LOG_ERR, "socket: %s", strerror (errno)); + return -1; + } + + struct rtm + { + struct rt_msghdr hdr; + struct sockaddr_in destination; + struct sockaddr_in gateway; + struct sockaddr_in netmask; + } rtm; + memset (&rtm, 0, sizeof (struct rtm)); + + rtm.hdr.rtm_version = RTM_VERSION; + static int seq; + rtm.hdr.rtm_seq = ++seq; + rtm.hdr.rtm_type = change ? RTM_CHANGE : del ? RTM_DELETE : RTM_ADD; + + rtm.hdr.rtm_flags = RTF_UP | RTF_GATEWAY | RTF_STATIC; + if (netmask.s_addr == 0xffffffff) + rtm.hdr.rtm_flags |= RTF_HOST; + + rtm.hdr.rtm_addrs = RTA_DST | RTA_GATEWAY | RTA_NETMASK; + +#define ADDADDR(_var, _addr) \ + _var.sin_family = AF_INET; \ + _var.sin_len = sizeof (struct sockaddr_in); \ + memcpy (&_var.sin_addr, &_addr, sizeof (struct in_addr)); + + ADDADDR (rtm.destination, destination); + ADDADDR (rtm.gateway, gateway); + ADDADDR (rtm.netmask, netmask); + +#undef ADDADDR + + rtm.hdr.rtm_msglen = sizeof (rtm); + + if (write(s, &rtm, sizeof (rtm)) < 0) + { + /* Don't report error about routes already existing */ + if (errno != EEXIST) + logger (LOG_ERR, "write: %s", strerror (errno)); + close (s); + return -1; + } + + close (s); + return 0; +} + +#elif __linux__ +/* This netlink stuff is overly compex IMO. + The BSD implementation is much cleaner and a lot less code. + send_netlink handles the actual transmission so we can work out + if there was an error or not. + + As always throughout this code, credit is due :) + This blatently taken from libnetlink.c from the iproute2 package + which is the only good source of netlink code. + */ +static int send_netlink(struct nlmsghdr *hdr) +{ + int s; + if ((s = socket(AF_NETLINK, SOCK_RAW, NETLINK_ROUTE)) < 0) + { + logger (LOG_ERR, "socket: %s", strerror (errno)); + return -1; + } + + int mypid = getpid (); + struct sockaddr_nl nl; + memset (&nl, 0, sizeof (struct sockaddr_nl)); + nl.nl_family = AF_NETLINK; + if (bind (s, (struct sockaddr *) &nl, sizeof (nl)) < 0) + { + logger (LOG_ERR, "bind: %s", strerror (errno)); + close (s); + return -1; + } + + struct iovec iov; + memset (&iov, 0, sizeof (struct iovec)); + iov.iov_base = hdr; + iov.iov_len = hdr->nlmsg_len; + + struct msghdr msg; + memset (&msg, 0, sizeof (struct msghdr)); + msg.msg_name = &nl; + msg.msg_namelen = sizeof (nl); + msg.msg_iov = &iov; + msg.msg_iovlen = 1; + + /* Request a reply */ + hdr->nlmsg_flags |= NLM_F_ACK; + static int seq; + hdr->nlmsg_seq = ++seq; + + if (sendmsg (s, &msg, 0) < 0) + { + logger (LOG_ERR, "write: %s", strerror (errno)); + close (s); + return -1; + } + + char buffer[16384]; + memset (&buffer, 0, sizeof (buffer)); + iov.iov_base = buffer; + + struct nlmsghdr *h; + while (1) + { + iov.iov_len = sizeof (buffer); + int bytes = recvmsg(s, &msg, 0); + + if (bytes < 0) + { + if (errno != EINTR) + logger (LOG_ERR, "overrun"); + continue; + } + + if (bytes == 0) + { + logger (LOG_ERR, "EOF on netlink"); + goto eexit; + } + + if (msg.msg_namelen != sizeof (nl)) + { + logger (LOG_ERR, "sender address length == %d", msg.msg_namelen); + goto eexit; + } + + for (h = (struct nlmsghdr *) buffer; bytes >= sizeof (*h); ) + { + int len = h->nlmsg_len; + int l = len - sizeof (*h); + + if (l < 0 || len > bytes) + { + if (msg.msg_flags & MSG_TRUNC) + logger (LOG_ERR, "truncated message"); + else + logger (LOG_ERR, "malformed message"); + goto eexit; + } + + if (nl.nl_pid != 0 || + h->nlmsg_pid != mypid || + h->nlmsg_seq != seq) + /* Message isn't for us, so skip it */ + goto next; + + /* We get an NLMSG_ERROR back with a code of zero for success */ + if (h->nlmsg_type == NLMSG_ERROR) + { + struct nlmsgerr *err = (struct nlmsgerr *) NLMSG_DATA (h); + if (l < sizeof (struct nlmsgerr)) + logger (LOG_ERR, "truncated error message"); + else + { + errno = -err->error; + if (errno == 0) + { + close (s); + return 0; + } + + /* Don't report on something already existing */ + if (errno != EEXIST) + logger (LOG_ERR, "RTNETLINK answers: %s", strerror (errno)); + } + goto eexit; + } + + logger (LOG_ERR, "unexpected reply"); +next: + bytes -= NLMSG_ALIGN (len); + h = (struct nlmsghdr *) ((char *) h + NLMSG_ALIGN (len)); + } + + if (msg.msg_flags & MSG_TRUNC) + { + logger (LOG_ERR, "message truncated"); + continue; + } + + if (bytes) + { + logger (LOG_ERR, "remnant of size %d", bytes); + goto eexit; + } + } + +eexit: + close (s); + return -1; +} + +#define NLMSG_TAIL(nmsg) \ + ((struct rtattr *) (((unsigned char *) (nmsg)) \ + + NLMSG_ALIGN((nmsg)->nlmsg_len))) +static int add_attr_l(struct nlmsghdr *n, int maxlen, int type, const void *data, + int alen) +{ + int len = RTA_LENGTH(alen); + struct rtattr *rta; + + if (NLMSG_ALIGN (n->nlmsg_len) + RTA_ALIGN (len) > maxlen) + { + logger (LOG_ERR, "add_attr_l: message exceeded bound of %d\n", maxlen); + return -1; + } + + rta = NLMSG_TAIL (n); + rta->rta_type = type; + rta->rta_len = len; + memcpy (RTA_DATA (rta), data, alen); + n->nlmsg_len = NLMSG_ALIGN (n->nlmsg_len) + RTA_ALIGN (len); + + return 0; +} + +static int add_attr_32(struct nlmsghdr *n, int maxlen, int type, uint32_t data) +{ + int len = RTA_LENGTH (sizeof (uint32_t)); + struct rtattr *rta; + if (NLMSG_ALIGN (n->nlmsg_len) + len > maxlen) + { + logger (LOG_ERR, "add_attr32: message exceeded bound of %d\n", maxlen); + return -1; + } + + rta = NLMSG_TAIL (n); + rta->rta_type = type; + rta->rta_len = len; + memcpy (RTA_DATA (rta), &data, sizeof (uint32_t)); + n->nlmsg_len = NLMSG_ALIGN (n->nlmsg_len) + len; + + return 0; +} + + +static int do_address(const char *ifname, + struct in_addr address, struct in_addr netmask, + struct in_addr broadcast, int del) +{ + if (!ifname) + return -1; + + struct + { + struct nlmsghdr hdr; + struct ifaddrmsg ifa; + char buffer[256]; + } + nlm; + + memset (&nlm, 0, sizeof (nlm)); + + nlm.hdr.nlmsg_len = NLMSG_LENGTH (sizeof (struct ifaddrmsg)); + nlm.hdr.nlmsg_flags = NLM_F_REQUEST; + nlm.hdr.nlmsg_type = del ? RTM_DELADDR : RTM_NEWADDR; + nlm.ifa.ifa_index = if_nametoindex (ifname); + nlm.ifa.ifa_family = AF_INET; + + /* Store the netmask in the prefix */ + uint32_t mask = htonl (netmask.s_addr); + while (mask) + { + nlm.ifa.ifa_prefixlen++; + mask <<= 1; + } + + add_attr_l (&nlm.hdr, sizeof (nlm), IFA_LOCAL, &address.s_addr, + sizeof (address.s_addr)); + if (! del) + add_attr_l (&nlm.hdr, sizeof (nlm), IFA_BROADCAST, &broadcast.s_addr, + sizeof (broadcast.s_addr)); + + return send_netlink (&nlm.hdr); +} + +static int do_route (const char *ifname, + struct in_addr destination, + struct in_addr netmask, + struct in_addr gateway, + int metric, int change, int del) +{ + if (! ifname) + return -1; + + char *dstd = strdup (inet_ntoa (destination)); + char *gend = strdup (inet_ntoa (netmask)); + logger (LOG_INFO, "%s route to %s (%s) via %s, metric %d", + change ? "changing" : del ? "removing" : "adding", + dstd, gend, inet_ntoa (gateway), metric); + if (dstd) + free (dstd); + if (gend) + free (gend); + + struct + { + struct nlmsghdr hdr; + struct rtmsg rt; + char buffer[256]; + } + nlm; + memset (&nlm, 0, sizeof (nlm)); + + nlm.hdr.nlmsg_len = NLMSG_LENGTH (sizeof (struct rtmsg)); + if (change) + nlm.hdr.nlmsg_flags = NLM_F_REPLACE; + else if (! del) + nlm.hdr.nlmsg_flags = NLM_F_CREATE | NLM_F_EXCL; + nlm.hdr.nlmsg_flags |= NLM_F_REQUEST; + nlm.hdr.nlmsg_type = del ? RTM_DELROUTE : RTM_NEWROUTE; + nlm.rt.rtm_family = AF_INET; + nlm.rt.rtm_table = RT_TABLE_MAIN; + + if (del) + nlm.rt.rtm_scope = RT_SCOPE_NOWHERE; + else + { + nlm.hdr.nlmsg_flags |= NLM_F_CREATE | NLM_F_EXCL; + nlm.rt.rtm_protocol = RTPROT_BOOT; + if (gateway.s_addr == 0) + nlm.rt.rtm_scope = RT_SCOPE_LINK; + else + nlm.rt.rtm_scope = RT_SCOPE_UNIVERSE; + nlm.rt.rtm_type = RTN_UNICAST; + } + + /* Store the netmask in the prefix */ + uint32_t mask = htonl (netmask.s_addr); + while (mask) + { + nlm.rt.rtm_dst_len++; + mask <<= 1; + } + + add_attr_l (&nlm.hdr, sizeof (nlm), RTA_DST, &destination.s_addr, + sizeof (destination.s_addr)); + if (gateway.s_addr != 0) + add_attr_l (&nlm.hdr, sizeof (nlm), RTA_GATEWAY, &gateway.s_addr, + sizeof (gateway.s_addr)); + + add_attr_32 (&nlm.hdr, sizeof (nlm), RTA_OIF, if_nametoindex (ifname)); + add_attr_32 (&nlm.hdr, sizeof (nlm), RTA_PRIORITY, metric); + + return send_netlink (&nlm.hdr); +} + +#else +#error "Platform not supported!" +#error "We currently support BPF and Linux sockets." +#error "Other platforms may work using BPF. If yours does, please let me know" +#error "so I can add it to our list." +#endif + + +int add_address (const char *ifname, struct in_addr address, + struct in_addr netmask, struct in_addr broadcast) +{ + char *daddress = strdup (inet_ntoa (address)); + logger (LOG_INFO, "adding IP address %s netmask %s", + daddress, inet_ntoa (netmask)); + free (daddress); + + return (do_address (ifname, address, netmask, broadcast, 0)); +} + +int del_address (const char *ifname, struct in_addr address) +{ + logger (LOG_INFO, "deleting IP address %s", inet_ntoa (address)); + + struct in_addr t; + memset (&t, 0, sizeof (t)); + return (do_address (ifname, address, t, t, 1)); +} + +/* This should work on all platforms */ +int flush_addresses (const char *ifname) +{ + if (! ifname) + return -1; + + struct ifaddrs *ifap; + struct ifaddrs *p; + + if (getifaddrs (&ifap) != 0) + return -1; + + for (p = ifap; p; p = p->ifa_next) + { + if (strcmp (p->ifa_name, ifname) != 0) + continue; + + struct sockaddr_in *sin = (struct sockaddr_in*) p->ifa_addr; + if (sin->sin_family == AF_INET) + del_address (ifname, sin->sin_addr); + } + freeifaddrs (ifap); + + return 0; +} + +int add_route (const char *ifname, struct in_addr destination, + struct in_addr netmask, struct in_addr gateway, int metric) +{ + return (do_route (ifname, destination, netmask, gateway, metric, 0, 0)); +} + +int change_route (const char *ifname, struct in_addr destination, + struct in_addr netmask, struct in_addr gateway, int metric) +{ + return (do_route (ifname, destination, netmask, gateway, metric, 1, 0)); +} + +int del_route (const char *ifname, struct in_addr destination, + struct in_addr netmask, struct in_addr gateway, int metric) +{ + return (do_route (ifname, destination, netmask, gateway, metric, 0, 1)); +} +
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/interface.h Mon Nov 27 20:23:22 2006 +0000 @@ -0,0 +1,81 @@ +/* + * dhcpcd - DHCP client daemon - + * Copyright (C) 2006 Roy Marples <uberlord@gentoo.org> + * + * dhcpcd is an RFC2131 compliant DHCP client daemon. + * + * This 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., 675 Mass Ave, Cambridge, MA 02139, USA. + */ + +#ifndef INTERFACE_H +#define INTERFACE_H + +#include <sys/types.h> +#include <sys/socket.h> +#include <net/if.h> +#include <netinet/if_ether.h> +#include <netinet/in.h> +#include <limits.h> +#include <stdbool.h> + +typedef struct route_t +{ + struct in_addr destination; + struct in_addr netmask; + struct in_addr gateway; + struct route_t *next; +} route_t; + +typedef struct address_t +{ + struct in_addr address; + struct address_t *next; +} address_t; + +typedef struct interface_t +{ + char name[IF_NAMESIZE]; + struct ether_addr ethernet_address; + bool arpable; + + int fd; + int buffer_length; + +#ifdef __linux__ + int socket_protocol; +#endif + + char infofile[PATH_MAX]; + + struct in_addr previous_address; + route_t *previous_routes; +} interface_t; + +void free_address (address_t *addresses); +void free_route (route_t *routes); +interface_t *read_interface (const char *ifname, int metric); + +int add_address (const char *ifname, struct in_addr address, + struct in_addr netmask, struct in_addr broadcast); +int del_address (const char *ifname, struct in_addr address); +int flush_addresses (const char *ifname); + +int add_route (const char *ifname, struct in_addr destination, + struct in_addr netmask, struct in_addr gateway, int metric); +int change_route (const char *ifname, struct in_addr destination, + struct in_addr netmask, struct in_addr gateway, int metric); +int del_route (const char *ifname, struct in_addr destination, + struct in_addr netmask, struct in_addr gateway, int metric); +#endif
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/logger.c Mon Nov 27 20:23:22 2006 +0000 @@ -0,0 +1,112 @@ +/* + * dhcpcd - DHCP client daemon - + * Copyright (C) 2006 Roy Marples <uberlord@gentoo.org> + * + * dhcpcd is an RFC2131 compliant DHCP client daemon. + * + * This 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., 675 Mass Ave, Cambridge, MA 02139, USA. + */ + +#include <stdarg.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <syslog.h> + +#include "common.h" +#include "logger.h" + +static int loglevel = LOG_WARNING; +static char logprefix[12] = {0}; + +static char *syslog_level_msg[] = { + [LOG_EMERG] = "EMERGENCY!", + [LOG_ALERT] = "ALERT!", + [LOG_CRIT] = "Critical!", + [LOG_WARNING] = "Warning", + [LOG_ERR] = "Error", + [LOG_INFO] = "Info", + [LOG_DEBUG] = "Debug", + [LOG_DEBUG + 1] = NULL +}; + +static char *syslog_level[] = { + [LOG_EMERG] = "LOG_EMERG", + [LOG_ALERT] = "LOG_ALERT", + [LOG_CRIT] = "LOG_CRIT", + [LOG_ERR] = "LOG_ERR", + [LOG_WARNING] = "LOG_WARNING", + [LOG_NOTICE] = "LOG_NOTICE", + [LOG_INFO] = "LOG_INFO", + [LOG_DEBUG] = "LOG_DEBUG", + [LOG_DEBUG + 1] = NULL +}; + +int logtolevel (const char *priority) +{ + int i = 0; + + while (syslog_level[i]) + { + if (!strcmp (priority, syslog_level[i])) + return i; + i++; + } + return -1; +} + +void setloglevel (int level) +{ + loglevel = level; +} + +void setlogprefix (const char *prefix) +{ + snprintf (logprefix, sizeof (logprefix), "%s", prefix); +} + +void logger(int level, const char *fmt, ...) +{ + va_list p; + va_list p2; + FILE *f = stderr; + + va_start (p, fmt); + va_copy (p2, p); + + if (level <= LOG_ERR || level <= loglevel) + { + if (level == LOG_DEBUG || level == LOG_INFO) + f = stdout; + fprintf (f, "%s, %s", syslog_level_msg[level], logprefix); + vfprintf (f, fmt, p); + fputc ('\n', f); + } + + if (level < LOG_DEBUG || level <= loglevel) + { + int len = strlen (logprefix); + char *fmt2 = xmalloc (strlen (fmt) + len + 1); + char *p = fmt2; + memcpy (p, &logprefix, len); + p += len; + strcpy (p, fmt); + vsyslog (level, fmt2, p2); + free (fmt2); + } + + va_end (p); +} +
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/logger.h Mon Nov 27 20:23:22 2006 +0000 @@ -0,0 +1,32 @@ +/* + * dhcpcd - DHCP client daemon - + * Copyright (C) 2006 Roy Marples <uberlord@gentoo.org> + * + * dhcpcd is an RFC2131 compliant DHCP client daemon. + * + * This 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., 675 Mass Ave, Cambridge, MA 02139, USA. + */ + +#ifndef LOGGER_H +#define LOGGER_H + +#include <syslog.h> + +int logtolevel (const char *priority); +void setloglevel (int level); +void setlogprefix (const char *prefix); +void logger (int level, const char *fmt, ...); + +#endif
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/pathnames.h Mon Nov 27 20:23:22 2006 +0000 @@ -0,0 +1,42 @@ +/* + * dhcpcd - DHCP client daemon - + * Copyright (C) 2005 - 2006 Roy Marples <uberlord@gentoo.org> + * + * dhcpcd is an RFC2131 compliant DHCP client daemon. + * + * This 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., 675 Mass Ave, Cambridge, MA 02139, USA. + */ + +#ifndef PATHNAMES_H +#define PATHNAMES_H + +#define PACKAGE "dhcpcd" + +#define ETCDIR "/etc" +#define RESOLVFILE ETCDIR "/resolv.conf" +#define NISFILE ETCDIR "/yp.conf" +#define NTPFILE ETCDIR "/ntp.conf" +#define NTPDRIFTFILE ETCDIR "/ntp.drift" +#define DEFAULT_SCRIPT ETCDIR "/" PACKAGE ".sh" + +#define STATEDIR "/var" +#define PIDFILE STATEDIR "/run/" PACKAGE "-%s.pid" + +#define CONFIGDIR STATEDIR "/lib/" PACKAGE +#define INFOFILE CONFIGDIR "/" PACKAGE "-%s.info" + +#define NTPLOGFILE "/var/log/ntp.log" + +#endif
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/signals.c Mon Nov 27 20:23:22 2006 +0000 @@ -0,0 +1,91 @@ +/* + * Shameless taken from udhcp as I think it's a good idea. + * Signal pipe infrastructure. A reliable way of delivering signals. + * + * Russ Dill <Russ.Dill@asu.edu> December 2003 + * + * 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., 675 Mass Ave, Cambridge, MA 02139, USA. + */ + +#include <sys/socket.h> +#include <sys/select.h> +#include <errno.h> +#include <fcntl.h> +#include <signal.h> +#include <string.h> +#include <unistd.h> + +#include "logger.h" + +static int signal_pipe[2]; + +static void signal_handler (int sig) +{ + if (send (signal_pipe[1], &sig, sizeof (sig), MSG_DONTWAIT) < 0) + logger (LOG_ERR, "Could not send signal: %s", strerror (errno)); +} + + +/* Call this before doing anything else. Sets up the socket pair + * and installs the signal handler */ +void signal_setup(void) +{ + int i; + int flags; + + socketpair (AF_UNIX, SOCK_STREAM, 0, signal_pipe); + + /* Stop any scripts from inheriting us */ + for (i = 0; i < 2; i++) + if ((flags = fcntl (signal_pipe[i], F_GETFD, 0)) < 0 || + fcntl (signal_pipe[i], F_SETFD, flags | FD_CLOEXEC) < 0) + logger (LOG_ERR ,"fcntl: %s", strerror (errno)); + + signal (SIGHUP, signal_handler); + signal (SIGALRM, signal_handler); + signal (SIGTERM, signal_handler); + signal (SIGINT, signal_handler); +} + + +/* Quick little function to setup the rfds. Will return the + * max_fd for use with select. Limited in that you can only pass + * one extra fd */ +int signal_fd_set (fd_set *rfds, int extra_fd) +{ + FD_ZERO (rfds); + FD_SET (signal_pipe[0], rfds); + if (extra_fd >= 0) + FD_SET (extra_fd, rfds); + return signal_pipe[0] > extra_fd ? signal_pipe[0] : extra_fd; +} + + +/* Read a signal from the signal pipe. Returns 0 if there is + * no signal, -1 on error (and sets errno appropriately), and + * your signal on success */ +int signal_read (fd_set *rfds) +{ + int sig; + + if (!FD_ISSET (signal_pipe[0], rfds)) + return 0; + + if (read (signal_pipe[0], &sig, sizeof (sig)) < 0) + return -1; + + return sig; +} +
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/signals.h Mon Nov 27 20:23:22 2006 +0000 @@ -0,0 +1,27 @@ +/* + * Shameless taken from udhcp as I think it's a good idea. + * Russ Dill <Russ.Dill@asu.edu> December 2003 + * + * 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., 675 Mass Ave, Cambridge, MA 02139, USA. + */ + +#ifndef SIGNALS_H +#define SIGNALS_H + +void signal_setup (void); +int signal_fd_set (fd_set *rfds, int extra_fd); +int signal_read (fd_set *rfds); + +#endif
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/socket.c Mon Nov 27 20:23:22 2006 +0000 @@ -0,0 +1,528 @@ +/* + * Copyright (C) 2006 Roy Marples <uberlord@gentoo.org> + * although a lot was lifted from udhcp + * + * dhcpcd is an RFC2131 compliant DHCP client daemon. + * + * This 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., 675 Mass Ave, Cambridge, MA 02139, USA. + */ + +/* We use BSD structure so our code is more portable */ +#define _BSD_SOURCE + +#include <sys/ioctl.h> +#include <sys/socket.h> +#include <sys/types.h> +#include <sys/uio.h> +#include <arpa/inet.h> +#include <netinet/in_systm.h> +#include <netinet/ip.h> +#include <netinet/udp.h> +#include <netinet/if_ether.h> +#include <net/ethernet.h> +#include <net/if.h> +#include <errno.h> +#include <fcntl.h> +#include <stdio.h> +#include <string.h> +#include <unistd.h> + +#include "dhcp.h" +#include "interface.h" +#include "logger.h" + +/* A suitably large buffer for all transactions. + BPF buffer size is set by the kernel, so no define. */ +#ifdef __linux__ +#define BUFFER_LENGTH 4096 +#endif + +static uint16_t checksum (unsigned char *addr, uint16_t len) +{ + register uint32_t sum = 0; + register uint16_t *w = (uint16_t *) addr; + register uint16_t nleft = len; + + while (nleft > 1) + { + sum += *w++; + nleft -= 2; + } + + if (nleft == 1) + { + uint8_t a = 0; + memcpy (&a, w, 1); + sum += ntohs (a) << 8; + // sum += a; + } + + sum = (sum >> 16) + (sum & 0xffff); + sum += (sum >> 16); + + return ~sum; +} + +void make_dhcp_packet(struct udp_dhcp_packet *packet, + unsigned char *data, + struct in_addr source, struct in_addr dest) +{ + struct ip *ip = &packet->ip; + struct udphdr *udp = &packet->udp; + + /* OK, this is important :) + We copy the data to our packet and then create a small part of the + ip structure and an invalid ip_len (basically udp length). + We then fill the udp structure and put the checksum + of the whole packet into the udp checksum. + Finally we complete the ip structure and ip checksum. + If we don't do the ordering like so then the udp checksum will be + broken, so find another way of doing it! */ + + memcpy (&packet->dhcp, data, sizeof (dhcpmessage_t)); + + ip->ip_p = IPPROTO_UDP; + ip->ip_src.s_addr = htonl (source.s_addr); + if (dest.s_addr == 0) + ip->ip_dst.s_addr = htonl (INADDR_BROADCAST); + else + ip->ip_dst.s_addr = htonl (dest.s_addr); + + udp->uh_sport = htons (DHCP_CLIENT_PORT); + udp->uh_dport = htons (DHCP_SERVER_PORT); + udp->uh_ulen = htons (sizeof (struct udphdr) + sizeof (struct dhcpmessage_t)); + ip->ip_len = udp->uh_ulen; + udp->uh_sum = checksum ((unsigned char *) packet, + sizeof (struct udp_dhcp_packet)); + + ip->ip_v = IPVERSION; + ip->ip_hl = 5; + ip->ip_id = 0; + ip->ip_tos = IPTOS_LOWDELAY; + ip->ip_len = htons (sizeof (struct ip) + sizeof (struct udphdr) + + sizeof (struct dhcpmessage_t)); + ip->ip_id = 0; + ip->ip_off = 0; + ip->ip_ttl = IPDEFTTL; + + ip->ip_sum = checksum ((unsigned char *) ip, sizeof (struct ip)); +} + +static int valid_dhcp_packet (unsigned char * data) +{ + struct udp_dhcp_packet *packet = (struct udp_dhcp_packet *) data; + uint16_t bytes = ntohs (packet->ip.ip_len); + uint16_t ipsum = packet->ip.ip_sum; + uint16_t iplen = packet->ip.ip_len; + uint16_t udpsum = packet->udp.uh_sum; + struct in_addr source; + struct in_addr dest; + int retval = 0; + + packet->ip.ip_sum = 0; + if (ipsum != checksum ((unsigned char *) &packet->ip, sizeof (struct ip))) + { + logger (LOG_DEBUG, "bad IP header checksum, ignoring"); + retval = -1; + goto eexit; + } + + memcpy (&source, &packet->ip.ip_src, sizeof (struct in_addr)); + memcpy (&dest, &packet->ip.ip_dst, sizeof (struct in_addr)); + memset (&packet->ip, 0, sizeof (struct ip)); + packet->udp.uh_sum = 0; + + packet->ip.ip_p = IPPROTO_UDP; + memcpy (&packet->ip.ip_src, &source, sizeof (struct in_addr)); + memcpy (&packet->ip.ip_dst, &dest, sizeof (struct in_addr)); + packet->ip.ip_len = packet->udp.uh_ulen; + if (udpsum && udpsum != checksum ((unsigned char *) packet, bytes)) + { + logger (LOG_ERR, "bad UDP checksum, ignoring"); + retval = -1; + } + +eexit: + packet->ip.ip_sum = ipsum; + packet->ip.ip_len = iplen; + packet->udp.uh_sum = udpsum; + + return retval; +} + +#ifdef __FreeBSD__ + +/* Credit where credit is due :) + The below BPF filter is taken from ISC DHCP */ + +# include <net/bpf.h> + +static struct bpf_insn dhcp_bpf_filter [] = { + /* Make sure this is an IP packet... */ + BPF_STMT (BPF_LD + BPF_H + BPF_ABS, 12), + BPF_JUMP (BPF_JMP + BPF_JEQ + BPF_K, ETHERTYPE_IP, 0, 8), + + /* Make sure it's a UDP packet... */ + BPF_STMT (BPF_LD + BPF_B + BPF_ABS, 23), + BPF_JUMP (BPF_JMP + BPF_JEQ + BPF_K, IPPROTO_UDP, 0, 6), + + /* Make sure this isn't a fragment... */ + BPF_STMT (BPF_LD + BPF_H + BPF_ABS, 20), + BPF_JUMP (BPF_JMP + BPF_JSET + BPF_K, 0x1fff, 4, 0), + + /* Get the IP header length... */ + BPF_STMT (BPF_LDX + BPF_B + BPF_MSH, 14), + + /* Make sure it's to the right port... */ + BPF_STMT (BPF_LD + BPF_H + BPF_IND, 16), + BPF_JUMP (BPF_JMP + BPF_JEQ + BPF_K, DHCP_CLIENT_PORT, 0, 1), + + /* If we passed all the tests, ask for the whole packet. */ + BPF_STMT (BPF_RET+BPF_K, (u_int) - 1), + + /* Otherwise, drop it. */ + BPF_STMT (BPF_RET+BPF_K, 0), +}; + +static struct bpf_insn arp_bpf_filter [] = { + /* Make sure this is an ARP packet... */ + BPF_STMT (BPF_LD + BPF_H + BPF_ABS, 12), + BPF_JUMP (BPF_JMP + BPF_JEQ + BPF_K, ETHERTYPE_ARP, 0, 3), + + /* Make sure this is an ARP REPLY... */ + BPF_STMT (BPF_LD + BPF_H + BPF_ABS, 20), + BPF_JUMP (BPF_JMP + BPF_JEQ + BPF_K, ARPOP_REPLY, 0, 1), + + /* If we passed all the tests, ask for the whole packet. */ + BPF_STMT (BPF_RET+BPF_K, (u_int) - 1), + + /* Otherwise, drop it. */ + BPF_STMT (BPF_RET+BPF_K, 0), +}; + +int open_socket (interface_t *iface, bool arp) +{ + int n = 0; + int fd = 0; + char device[PATH_MAX]; + + do + { + snprintf (device, PATH_MAX, "/dev/bpf%d", n++); + fd = open (device, O_RDWR); + } while (fd < 0 && errno == EBUSY); + + if (fd < 0) + { + logger (LOG_ERR, "unable to open a BPF device"); + return -1; + } + + int flags; + if ((flags = fcntl (fd, F_GETFD, 0)) < 0 + || fcntl (fd, F_SETFD, flags | FD_CLOEXEC) < 0) + { + logger (LOG_ERR, "fcntl: %s", strerror (errno)); + close (fd); + return -1; + } + + struct ifreq ifr; + strncpy (ifr.ifr_name, iface->name, sizeof (ifr.ifr_name)); + if (ioctl (fd, BIOCSETIF, &ifr) < 0) + { + logger (LOG_ERR, "cannot attach interface `%s' to bpf device `%s': %s", + iface->name, strerror (errno)); + close (fd); + return -1; + } + + /* Get the required BPF buffer length from the kernel. */ + int buf = 0; + if (ioctl (fd, BIOCGBLEN, &buf) < 0) + { + logger (LOG_ERR, "ioctl BIOCGBLEN: %s", strerror (errno)); + close (fd); + return -1; + } + iface->buffer_length = buf; + + int flag = 1; + if (ioctl (fd, BIOCIMMEDIATE, &flag) < 0) + { + logger (LOG_ERR, "ioctl BIOCIMMEDIATE: %s", strerror (errno)); + close (fd); + return -1; + } + + /* Install the DHCP filter */ + struct bpf_program p; + if (arp) + { + p.bf_insns = arp_bpf_filter; + p.bf_len = sizeof (arp_bpf_filter) / sizeof (struct bpf_insn); + } + else + { + p.bf_insns = dhcp_bpf_filter; + p.bf_len = sizeof (dhcp_bpf_filter) / sizeof (struct bpf_insn); + } + if (ioctl (fd, BIOCSETF, &p) < 0) + { + logger (LOG_ERR, "ioctl BIOCSETF: %s", strerror (errno)); + close (fd); + return -1; + } + + if (iface->fd > -1) + close (iface->fd); + iface->fd = fd; + + return fd; +} + +int send_packet (interface_t *iface, int type, unsigned char *data, + unsigned int len) +{ + /* We only support ethernet atm */ + struct ether_header hw; + memset (&hw, 0, sizeof (struct ether_header)); + memset (&hw.ether_dhost, 0xff, ETHER_ADDR_LEN); + hw.ether_type = htons (type); + + int retval = -1; + struct iovec iov[2]; + + iov[0].iov_base = &hw; + iov[0].iov_len = sizeof (struct ether_header); + iov[1].iov_base = data; + iov[1].iov_len = len; + + if ((retval = writev(iface->fd, iov, 2)) == -1) + logger (LOG_ERR, "writev: %s", strerror (errno)); + + return retval; +} + +/* BPF requires that we read the entire buffer. + So we pass the buffer in the API so we can loop on >1 dhcp packet. */ +int get_packet (interface_t *iface, unsigned char *data, + unsigned char *buffer, int *buffer_len, int *buffer_pos) +{ + unsigned char *buf = buffer; + struct bpf_hdr *packet; + struct ether_header *hw; + unsigned char *hdr; + + if (*buffer_pos < 1) + { + memset (buf, 0, iface->buffer_length); + *buffer_len = read (iface->fd, buf, iface->buffer_length); + *buffer_pos = 0; + if (*buffer_len < 1) + { + logger (LOG_ERR, "read: %s", strerror (errno)); + return -1; + } + } + else + buf += *buffer_pos; + + packet = (struct bpf_hdr *) buf; + while (packet) + { + /* Ensure that the entire packet is in our buffer */ + if (*buffer_pos + packet->bh_hdrlen + packet->bh_caplen > *buffer_len) + break; + + hw = (struct ether_header *) ((char *) packet + packet->bh_hdrlen); + hdr = (unsigned char *) ((char *) hw + sizeof (struct ether_header)); + + /* If it's an ARP reply, then just send it back */ + int len = -1; + if (hw->ether_type == htons (ETHERTYPE_ARP)) + { + len = packet->bh_caplen - sizeof (struct ether_header); + memcpy (data, hdr, len); + } + else + { + if (valid_dhcp_packet (hdr) >= 0) + { + struct udp_dhcp_packet *dhcp = (struct udp_dhcp_packet *) hdr; + len = ntohs (dhcp->ip.ip_len) - sizeof (struct ip) - + sizeof (struct udphdr); + memcpy (data, &dhcp->dhcp, len); + } + } + + /* Update the buffer_pos pointer */ + packet += BPF_WORDALIGN (packet->bh_hdrlen + packet->bh_caplen); + if (packet - (struct bpf_hdr *) buffer < *buffer_len) + *buffer_pos = (packet - (struct bpf_hdr *) buffer); + else + *buffer_pos = 0; + + if (len != -1) + return len; + + if (*buffer_pos == 0) + break; + } + + /* No valid packets left, so return */ + *buffer_pos = 0; + return -1; +} + +#elif __linux__ + +#include <netpacket/packet.h> + +int open_socket (interface_t *iface, bool arp) +{ + int fd; + int flags; + struct sockaddr_ll sll; + + if ((fd = socket (PF_PACKET, SOCK_DGRAM, htons (ETH_P_IP))) == -1) + { + logger (LOG_ERR, "socket: %s", strerror (errno)); + return -1; + } + + if ((flags = fcntl (fd, F_GETFD, 0)) < 0 + || fcntl (fd, F_SETFD, flags | FD_CLOEXEC) < 0) + { + logger (LOG_ERR, "fcntl: %s", strerror (errno)); + close (fd); + return -1; + } + + memset (&sll, 0, sizeof (struct sockaddr_ll)); + sll.sll_family = AF_PACKET; + if (arp) + sll.sll_protocol = htons (ETH_P_ARP); + else + sll.sll_protocol = htons (ETH_P_IP); + sll.sll_ifindex = if_nametoindex (iface->name); + sll.sll_halen = ETHER_ADDR_LEN; + memset(sll.sll_addr, 0xff, sizeof (sll.sll_addr)); + + if (bind(fd, (struct sockaddr *) &sll, sizeof (struct sockaddr_ll)) == -1) + { + logger (LOG_ERR, "bind: %s", strerror (errno)); + close (fd); + return -1; + } + + if (iface->fd > -1) + close (iface->fd); + iface->fd = fd; + iface->socket_protocol = ntohs (sll.sll_protocol); + + iface->buffer_length = BUFFER_LENGTH; + + return fd; +} + +int send_packet (interface_t *iface, int type, unsigned char *data, int len) +{ + struct sockaddr_ll sll; + int retval; + + if (! iface) + return -1; + + memset (&sll, 0, sizeof (struct sockaddr_ll)); + sll.sll_family = AF_PACKET; + sll.sll_protocol = htons (type); + sll.sll_ifindex = if_nametoindex (iface->name); + sll.sll_halen = ETHER_ADDR_LEN; + memset(sll.sll_addr, 0xff, sizeof (sll.sll_addr)); + + if ((retval = sendto (iface->fd, data, len, 0, (struct sockaddr *) &sll, + sizeof (struct sockaddr_ll))) < 0) + + logger (LOG_ERR, "sendto: %s", strerror (errno)); + return retval; +} + +/* Linux has no need for the buffer as we can read as much as we want. + We only have the buffer listed to keep the same API. */ +size_t get_packet (interface_t *iface, unsigned char *data, + unsigned char *buffer, int *buffer_len, int *buffer_pos) +{ + long bytes; + + /* We don't use the given buffer, but we need to rewind the position */ + *buffer_pos = 0; + + memset (buffer, 0, iface->buffer_length); + bytes = read (iface->fd, buffer, iface->buffer_length); + if (bytes < 0) + { + logger (LOG_ERR, "read: %s", strerror (errno)); + return -1; + } + + *buffer_len = bytes; + /* If it's an ARP reply, then just send it back */ + if (iface->socket_protocol == ETH_P_ARP) + { + memcpy (data, buffer, bytes); + return bytes; + } + + if (bytes < (sizeof (struct ip) + sizeof (struct udphdr))) + { + logger (LOG_DEBUG, "message too short, ignoring"); + return -1; + } + + struct udp_dhcp_packet *dhcp = (struct udp_dhcp_packet *) buffer; + if (bytes < ntohs (dhcp->ip.ip_len)) + { + logger (LOG_DEBUG, "truncated packet, ignoring"); + return -1; + } + + bytes = ntohs (dhcp->ip.ip_len); + + /* This is like our BPF filter above */ + if (dhcp->ip.ip_p != IPPROTO_UDP || dhcp->ip.ip_v != IPVERSION || + dhcp->ip.ip_hl != sizeof (dhcp->ip) >> 2 || + dhcp->udp.uh_dport != htons (DHCP_CLIENT_PORT) || + bytes > (int) sizeof (struct udp_dhcp_packet) || + ntohs (dhcp->udp.uh_ulen) != (uint16_t) (bytes - sizeof (dhcp->ip))) + { + return -1; + } + + if (valid_dhcp_packet (buffer) < 0) + return -1; + + memcpy(data, &dhcp->dhcp, bytes - (sizeof (dhcp->ip) + + sizeof (dhcp->udp))); + + return bytes - (sizeof (dhcp->ip) + sizeof (dhcp->udp)); +} + +#else +#error "Platform not supported!" +#error "We currently support BPF and Linux sockets." +#error "Other platforms may work using BPF. If yours does, please let me know" +#error "so I can add it to our list." +#endif
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/socket.h Mon Nov 27 20:23:22 2006 +0000 @@ -0,0 +1,39 @@ +/* + * dhcpcd - DHCP client daemon - + * Copyright (C) 2006 Roy Marples <uberlord@gentoo.org> + * although a lot was lifted from udhcp + * + * dhcpcd is an RFC2131 compliant DHCP client daemon. + * + * This 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., 675 Mass Ave, Cambridge, MA 02139, USA. + */ + +#ifndef SOCKET_H +#define SOCKET_H + +#include <stdbool.h> + +#include "dhcp.h" +#include "interface.h" + +void make_dhcp_packet(struct udp_dhcp_packet *packet, + unsigned char *data, + struct in_addr source, struct in_addr dest); + +int open_socket (interface_t *iface, bool arp); +int send_packet (interface_t *iface, int type, unsigned char *data, unsigned int len); +int get_packet (interface_t *iface, unsigned char *data, + unsigned char *buffer, int *buffer_len, int *buffer_pos); +#endif
