From cced195e703c548e7375727ea91bbdcd76aae517 Mon Sep 17 00:00:00 2001 From: Roy Marples Date: Mon, 27 Nov 2006 20:23:22 +0000 Subject: [PATCH] Add dhcpcd-3 re-write --- ChangeLog | 21 ++ Makefile | 45 ++++ arp.c | 157 ++++++++++++ arp.h | 29 +++ client.c | 554 ++++++++++++++++++++++++++++++++++++++++++ client.h | 29 +++ common.c | 64 +++++ common.h | 28 +++ configure.c | 527 ++++++++++++++++++++++++++++++++++++++++ configure.h | 31 +++ dhcp.c | 648 +++++++++++++++++++++++++++++++++++++++++++++++++ dhcp.h | 185 ++++++++++++++ dhcpcd.8 | 332 +++++++++++++++++++++++++ dhcpcd.c | 362 +++++++++++++++++++++++++++ dhcpcd.h | 67 +++++ dhcpcd.sh | 46 ++++ interface.c | 688 ++++++++++++++++++++++++++++++++++++++++++++++++++++ interface.h | 81 +++++++ logger.c | 112 +++++++++ logger.h | 32 +++ pathnames.h | 42 ++++ signals.c | 91 +++++++ signals.h | 27 +++ socket.c | 528 ++++++++++++++++++++++++++++++++++++++++ socket.h | 39 +++ 25 files changed, 4765 insertions(+) create mode 100644 ChangeLog create mode 100644 Makefile create mode 100644 arp.c create mode 100644 arp.h create mode 100644 client.c create mode 100644 client.h create mode 100644 common.c create mode 100644 common.h create mode 100644 configure.c create mode 100644 configure.h create mode 100644 dhcp.c create mode 100644 dhcp.h create mode 100644 dhcpcd.8 create mode 100644 dhcpcd.c create mode 100644 dhcpcd.h create mode 100755 dhcpcd.sh create mode 100644 interface.c create mode 100644 interface.h create mode 100644 logger.c create mode 100644 logger.h create mode 100644 pathnames.h create mode 100644 signals.c create mode 100644 signals.h create mode 100644 socket.c create mode 100644 socket.h diff --git a/ChangeLog b/ChangeLog new file mode 100644 index 00000000..3acca3d3 --- /dev/null +++ b/ChangeLog @@ -0,0 +1,21 @@ +dhcpcd-3.0.0 +A complete rewrite by Roy Marples + +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. diff --git a/Makefile b/Makefile new file mode 100644 index 00000000..5e9bb5f0 --- /dev/null +++ b/Makefile @@ -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) diff --git a/arp.c b/arp.c new file mode 100644 index 00000000..f5c0830b --- /dev/null +++ b/arp.c @@ -0,0 +1,157 @@ +/* + * dhcpcd - DHCP client daemon - + * Copyright (C) 2006 Roy Marples + * + * 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 +#include +#include +#include +#include +#ifdef __linux +#include +#include +#endif +#include +#include +#include +#include +#include + +#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; +} + diff --git a/arp.h b/arp.h new file mode 100644 index 00000000..4dfd007d --- /dev/null +++ b/arp.h @@ -0,0 +1,29 @@ +/* + * dhcpcd - DHCP client daemon - + * Copyright (C) 2005 - 2006 Roy Marples + * + * 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 + +#include "interface.h" + +int arp_check (interface_t *iface, struct in_addr address); + +#endif diff --git a/client.c b/client.c new file mode 100644 index 00000000..0ed7fb5c --- /dev/null +++ b/client.c @@ -0,0 +1,554 @@ +/* + * dhcpcd - DHCP client daemon - + * Copyright (C) 2006 Roy Marples + * + * 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 +#include +#ifdef __linux__ +#include +#endif +#include +#include +#include +#include +#include +#include +#include +#include + +#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; +} + diff --git a/client.h b/client.h new file mode 100644 index 00000000..11832274 --- /dev/null +++ b/client.h @@ -0,0 +1,29 @@ +/* + * dhcpcd - DHCP client daemon - + * Copyright (C) 2006 Roy Marples + * + * 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 diff --git a/common.c b/common.c new file mode 100644 index 00000000..c3203b9c --- /dev/null +++ b/common.c @@ -0,0 +1,64 @@ +/* + * dhcpcd - DHCP client daemon - + * Copyright (C) 2006 Roy Marples + * + * 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 +#include +#include + +#include "logger.h" + +/* This requires us to link to rt on glibc, so we use sysinfo instead */ +#ifdef __linux__ +#include +long uptime (void) +{ + struct sysinfo info; + + sysinfo (&info); + return info.uptime; +} +#else +#include +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); +} + diff --git a/common.h b/common.h new file mode 100644 index 00000000..dee34d6d --- /dev/null +++ b/common.h @@ -0,0 +1,28 @@ +/* + * dhcpcd - DHCP client daemon - + * Copyright (C) 2006 Roy Marples + * + * 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 diff --git a/configure.c b/configure.c new file mode 100644 index 00000000..ff6cecf4 --- /dev/null +++ b/configure.c @@ -0,0 +1,527 @@ +/* + * dhcpcd - DHCP client daemon - + * Copyright (C) 2005 - 2006 Roy Marples + * + * 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 +#include +#include +#include + +#include + +#ifdef __linux__ +#include +#endif +#include +#include +#include +#include +#include +#include +#include + +#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; +} + diff --git a/configure.h b/configure.h new file mode 100644 index 00000000..ce8514bd --- /dev/null +++ b/configure.h @@ -0,0 +1,31 @@ +/* + * dhcpcd - DHCP client daemon - + * Copyright (C) 2005 - 2006 Roy Marples + * + * 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 diff --git a/dhcp.c b/dhcp.c new file mode 100644 index 00000000..486b964d --- /dev/null +++ b/dhcp.c @@ -0,0 +1,648 @@ +/* + * dhcpcd - DHCP client daemon - + * Copyright (C) 2005 - 2006 Roy Marples + * + * 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 +#include + +#include +#include + +#include +#include +#include +#include + +#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; +} + diff --git a/dhcp.h b/dhcp.h new file mode 100644 index 00000000..93af9cfc --- /dev/null +++ b/dhcp.h @@ -0,0 +1,185 @@ +/* + * dhcpcd - DHCP client daemon - + * Copyright (C) 2005 - 2006 Roy Marples + * + * 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 +#include +#include +#include +#include + +#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 diff --git a/dhcpcd.8 b/dhcpcd.8 new file mode 100644 index 00000000..936d27a4 --- /dev/null +++ b/dhcpcd.8 @@ -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-.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-.pid +file containing the process id of +.B dhcpcd. +The word +.I +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 diff --git a/dhcpcd.c b/dhcpcd.c new file mode 100644 index 00000000..9b03fb03 --- /dev/null +++ b/dhcpcd.c @@ -0,0 +1,362 @@ +/* + * dhcpcd - DHCP client daemon - + * Copyright (C) 2005 - 2006 Roy Marples + * + * 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 +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#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); +} diff --git a/dhcpcd.h b/dhcpcd.h new file mode 100644 index 00000000..f0ffb5a5 --- /dev/null +++ b/dhcpcd.h @@ -0,0 +1,67 @@ +/* + * dhcpcd - DHCP client daemon - + * Copyright (C) 2005 - 2006 Roy Marples + * + * 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 +#endif +#include +#include +#include + +#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 diff --git a/dhcpcd.sh b/dhcpcd.sh new file mode 100755 index 00000000..7df0c322 --- /dev/null +++ b/dhcpcd.sh @@ -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 diff --git a/interface.c b/interface.c new file mode 100644 index 00000000..c55a662c --- /dev/null +++ b/interface.c @@ -0,0 +1,688 @@ +/* + * dhcpcd - DHCP client daemon - + * Copyright (C) 2006 Roy Marples + * + * 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 +#include +#include +#include + +#include + +/* Netlink suff */ +#ifdef __linux__ +#include +#include +#include +#include +#else +#include +#include +#include +#include +#endif /* __linux__ */ + +#include +#include +#include +#include +#include +#include + +#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)); +} + diff --git a/interface.h b/interface.h new file mode 100644 index 00000000..f118dc1e --- /dev/null +++ b/interface.h @@ -0,0 +1,81 @@ +/* + * dhcpcd - DHCP client daemon - + * Copyright (C) 2006 Roy Marples + * + * 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 +#include +#include +#include +#include +#include +#include + +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 diff --git a/logger.c b/logger.c new file mode 100644 index 00000000..aea56fd3 --- /dev/null +++ b/logger.c @@ -0,0 +1,112 @@ +/* + * dhcpcd - DHCP client daemon - + * Copyright (C) 2006 Roy Marples + * + * 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 +#include +#include +#include +#include + +#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); +} + diff --git a/logger.h b/logger.h new file mode 100644 index 00000000..d98d5cf0 --- /dev/null +++ b/logger.h @@ -0,0 +1,32 @@ +/* + * dhcpcd - DHCP client daemon - + * Copyright (C) 2006 Roy Marples + * + * 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 + +int logtolevel (const char *priority); +void setloglevel (int level); +void setlogprefix (const char *prefix); +void logger (int level, const char *fmt, ...); + +#endif diff --git a/pathnames.h b/pathnames.h new file mode 100644 index 00000000..f4a1b14e --- /dev/null +++ b/pathnames.h @@ -0,0 +1,42 @@ +/* + * dhcpcd - DHCP client daemon - + * Copyright (C) 2005 - 2006 Roy Marples + * + * 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 diff --git a/signals.c b/signals.c new file mode 100644 index 00000000..b565b8d2 --- /dev/null +++ b/signals.c @@ -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 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 +#include +#include +#include +#include +#include +#include + +#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; +} + diff --git a/signals.h b/signals.h new file mode 100644 index 00000000..62795d85 --- /dev/null +++ b/signals.h @@ -0,0 +1,27 @@ +/* + * Shameless taken from udhcp as I think it's a good idea. + * Russ Dill 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 diff --git a/socket.c b/socket.c new file mode 100644 index 00000000..3996d3b2 --- /dev/null +++ b/socket.c @@ -0,0 +1,528 @@ +/* + * Copyright (C) 2006 Roy Marples + * 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 +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#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 + +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 + +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 diff --git a/socket.h b/socket.h new file mode 100644 index 00000000..023b862d --- /dev/null +++ b/socket.h @@ -0,0 +1,39 @@ +/* + * dhcpcd - DHCP client daemon - + * Copyright (C) 2006 Roy Marples + * 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 + +#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 -- 2.47.3