diff options
-rw-r--r-- | Android.mk | 11 | ||||
-rw-r--r-- | Makefile | 2 | ||||
-rw-r--r-- | README | 12 | ||||
-rw-r--r-- | bpf-filter.h | 14 | ||||
-rw-r--r-- | bpf.c | 19 | ||||
-rw-r--r-- | client.c | 1328 | ||||
-rw-r--r-- | common.c | 97 | ||||
-rw-r--r-- | common.h | 11 | ||||
-rw-r--r-- | config.h | 31 | ||||
-rw-r--r-- | configure.c | 224 | ||||
-rw-r--r-- | configure.h | 4 | ||||
-rw-r--r-- | dhcp.c | 207 | ||||
-rw-r--r-- | dhcp.h | 89 | ||||
-rw-r--r-- | dhcpcd-hooks/01-test | 2 | ||||
-rw-r--r-- | dhcpcd-hooks/20-resolv.conf | 87 | ||||
-rw-r--r-- | dhcpcd-hooks/29-lookup-hostname | 3 | ||||
-rw-r--r-- | dhcpcd-hooks/30-hostname | 15 | ||||
-rw-r--r-- | dhcpcd-hooks/50-ntp.conf | 87 | ||||
-rw-r--r-- | dhcpcd-hooks/50-yp.conf | 1 | ||||
-rw-r--r-- | dhcpcd-hooks/90-NetworkManager | 11 | ||||
-rw-r--r-- | dhcpcd-hooks/95-configured | 6 | ||||
-rw-r--r-- | dhcpcd-hooks/Makefile | 4 | ||||
-rwxr-xr-x | dhcpcd-run-hooks | 29 | ||||
-rw-r--r-- | dhcpcd-run-hooks.8 | 20 | ||||
-rw-r--r-- | dhcpcd-run-hooks.8.in | 20 | ||||
-rw-r--r-- | dhcpcd-run-hooks.in | 110 | ||||
-rw-r--r-- | dhcpcd.8 | 140 | ||||
-rw-r--r-- | dhcpcd.8.in | 140 | ||||
-rw-r--r-- | dhcpcd.c | 427 | ||||
-rw-r--r-- | dhcpcd.conf.5 | 36 | ||||
-rw-r--r-- | dhcpcd.conf.5.in | 36 | ||||
-rw-r--r-- | dhcpcd.h | 25 | ||||
-rw-r--r-- | if-bsd.c | 54 | ||||
-rw-r--r-- | if-linux.c | 241 | ||||
-rw-r--r-- | logger.c | 29 | ||||
-rw-r--r-- | logger.h | 1 | ||||
-rw-r--r-- | lpf.c | 21 | ||||
-rw-r--r-- | mk/os-Darwin.mk | 5 | ||||
-rw-r--r-- | mk/os-Linux.mk | 20 | ||||
-rw-r--r-- | mk/os.mk | 2 | ||||
-rw-r--r-- | mk/prog.mk | 11 | ||||
-rw-r--r-- | net.c | 168 | ||||
-rw-r--r-- | net.h | 20 | ||||
-rw-r--r-- | showlease.c | 349 | ||||
-rw-r--r-- | signals.c | 22 | ||||
-rw-r--r-- | signals.h | 5 |
46 files changed, 2623 insertions, 1573 deletions
@@ -10,13 +10,20 @@ include $(CLEAR_VARS) LOCAL_SRC_FILES := common.c dhcp.c dhcpcd.c logger.c net.c \ signals.c configure.c client.c if-linux.c lpf.c LOCAL_C_INCLUDES := $(KERNEL_HEADERS) -LOCAL_CFLAGS := -DDISABLE_ARP -LOCAL_SHARED_LIBRARIES := libc +LOCAL_SHARED_LIBRARIES := libc libcutils LOCAL_MODULE = dhcpcd LOCAL_MODULE_TAGS := user development include $(BUILD_EXECUTABLE) include $(CLEAR_VARS) +LOCAL_SRC_FILES := showlease.c +LOCAL_C_INCLUDES := $(KERNEL_HEADERS) +LOCAL_SHARED_LIBRARIES := libc +LOCAL_MODULE = showlease +LOCAL_MODULE_TAGS := user development +include $(BUILD_EXECUTABLE) + +include $(CLEAR_VARS) LOCAL_MODULE := dhcpcd.conf LOCAL_MODULE_TAGS := user development LOCAL_MODULE_CLASS := ETC @@ -46,6 +46,6 @@ SED_SYS= -e 's:@SYSCONFDIR@:${SYSCONFDIR}:g' ${SED} ${SED_HOOKDIR} ${SED_SCRIPT} ${SED_SYS} $< > $@ MK= mk -include ${MK}/os.mk include ${MK}/sys.mk +include ${MK}/os.mk include ${MK}/prog.mk @@ -15,12 +15,10 @@ If you're cross compiling you may need to set the below knobs to avoid automatic tests. OS=BSD | Linux -If size is your thing, you can remove all non-essential userland options -by adding -DMINIMAL to your CPPFLAGS. This currently shaves off around 6k. -You can save a futher 600 bytes or so by using the small make target. - -If you're building for a NOMMU system where fork() does not work, you should -add -DTHERE_IS_NO_FORK to your CPPFLAGS. +If you're building for an MMU-less system where fork() does not work, you +should add -DTHERE_IS_NO_FORK to your CPPFLAGS. +This also puts the --no-background flag on and stops the --background flag +from working. You can change the default dir with these knobs. For example, to satisfy FHS compliance you would do this:- @@ -66,4 +64,4 @@ ChangeLog --------- We no longer supply a ChangeLog. However, you're more than welcome to read the git commit comments at -http://git.marples.name/?p=dhcpcd/.git;a=summary +http://git.marples.name/?p=dhcpcd.git;a=summary diff --git a/bpf-filter.h b/bpf-filter.h index adcc8bb..881f678 100644 --- a/bpf-filter.h +++ b/bpf-filter.h @@ -36,14 +36,14 @@ static const struct bpf_insn const arp_bpf_filter [] = { BPF_STMT(BPF_LD + BPF_H + BPF_ABS, 12), BPF_JUMP(BPF_JMP + BPF_JEQ + BPF_K, ETHERTYPE_ARP, 0, 3), #endif - - /* Make sure this is an ARP REPLY... */ + /* Make sure this is an ARP REQUEST... */ + BPF_STMT(BPF_LD + BPF_H + BPF_ABS, 20 + BPF_ETHCOOK), + BPF_JUMP(BPF_JMP + BPF_JEQ + BPF_K, ARPOP_REQUEST, 2, 0), + /* or ARP REPLY... */ BPF_STMT(BPF_LD + BPF_H + BPF_ABS, 20 + BPF_ETHCOOK), 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, BPF_WHOLEPACKET), - /* Otherwise, drop it. */ BPF_STMT(BPF_RET + BPF_K, 0), }; @@ -81,25 +81,19 @@ static const struct bpf_insn const dhcp_bpf_filter [] = { BPF_STMT(BPF_LD + BPF_H + BPF_ABS, 12), BPF_JUMP(BPF_JMP + BPF_JEQ + BPF_K, ETHERTYPE_IP, 0, 8), #endif - /* Make sure it's a UDP packet... */ BPF_STMT(BPF_LD + BPF_B + BPF_ABS, 23 + BPF_ETHCOOK), 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_ETHCOOK), 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 + BPF_ETHCOOK), - /* Make sure it's to the right port... */ BPF_STMT(BPF_LD + BPF_H + BPF_IND, 16 + BPF_ETHCOOK), 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, BPF_WHOLEPACKET), - /* Otherwise, drop it. */ BPF_STMT(BPF_RET + BPF_K, 0), }; @@ -108,15 +108,13 @@ open_socket(struct interface *iface, int protocol) /* Install the DHCP filter */ if (protocol == ETHERTYPE_ARP) { -#ifdef ENABLE_ARP pf.bf_insns = UNCONST(arp_bpf_filter); pf.bf_len = arp_bpf_filter_len; fdp = &iface->arp_fd; -#endif } else { pf.bf_insns = UNCONST(dhcp_bpf_filter); pf.bf_len = dhcp_bpf_filter_len; - fdp = &iface->fd; + fdp = &iface->raw_fd; } if (ioctl(fd, BIOCSETF, &pf) == -1) goto eexit; @@ -142,6 +140,7 @@ send_raw_packet(const struct interface *iface, int protocol, { struct iovec iov[2]; struct ether_header hw; + int fd; memset(&hw, 0, ETHER_HDR_LEN); memset(&hw.ether_dhost, 0xff, ETHER_ADDR_LEN); @@ -150,7 +149,11 @@ send_raw_packet(const struct interface *iface, int protocol, iov[0].iov_len = ETHER_HDR_LEN; iov[1].iov_base = UNCONST(data); iov[1].iov_len = len; - return writev(iface->fd, iov, 2); + if (protocol == ETHERTYPE_ARP) + fd = iface->arp_fd; + else + fd = iface->raw_fd; + return writev(fd, iov, 2); } /* BPF requires that we read the entire buffer. @@ -164,12 +167,10 @@ get_raw_packet(struct interface *iface, int protocol, ssize_t bytes; const unsigned char *payload; - if (protocol == ETHERTYPE_ARP) { -#ifdef ENABLE_ARP + if (protocol == ETHERTYPE_ARP) fd = iface->arp_fd; -#endif - } else - fd = iface->fd; + else + fd = iface->raw_fd; for (;;) { if (iface->buffer_len == 0) { @@ -53,12 +53,7 @@ #include "logger.h" #include "signals.h" -#ifdef ENABLE_IPV4LL -# ifndef ENABLE_ARP - # error "IPv4LL requires ENABLE_ARP to work" -# endif -# define IPV4LL_LEASETIME 2 -#endif +#define IPV4LL_LEASETIME 2 /* Some platforms don't define INFTIM */ #ifndef INFTIM @@ -73,17 +68,18 @@ #define STATE_REBINDING 5 #define STATE_REBOOT 6 #define STATE_RENEW_REQUESTED 7 -#define STATE_PROBING 8 -#define STATE_ANNOUNCING 9 +#define STATE_INIT_IPV4LL 8 +#define STATE_PROBING 9 +#define STATE_ANNOUNCING 10 -/* Constants taken from RFC 2131. - * We multiply some numbers by 1000 so they are suitable for use in poll(). */ +/* Constants taken from RFC 2131. */ #define T1 0.5 #define T2 0.875 -#define DHCP_BASE 4 * 1000 -#define DHCP_RAND_MIN -1 * 1000 -#define DHCP_RAND_MAX 1 * 1000 -#define DHCP_MAX 64 * 1000 +#define DHCP_BASE 4 +#define DHCP_MAX 64 +#define DHCP_RAND_MIN -1 +#define DHCP_RAND_MAX 1 +#define DHCP_ARP_FAIL 10 /* We should define a maximum for the NAK exponential backoff */ #define NAKOFF_MAX 60 @@ -91,24 +87,44 @@ #define SOCKET_CLOSED 0 #define SOCKET_OPEN 1 -/* Indexes for pollfds */ -#define POLLFD_SIGNAL 0 -#define POLLFD_IFACE 1 -#define POLLFD_ARP 2 - -/* These are really for IPV4LL, RFC 3927. - * We multiply some numbers by 1000 so they are suitable for use in poll(). */ -#define PROBE_WAIT 1 * 1000 +/* These are for IPV4LL, RFC 3927. */ +#define PROBE_WAIT 1 #define PROBE_NUM 3 -#define PROBE_MIN 1 * 1000 -#define PROBE_MAX 2 * 1000 -#define ANNOUNCE_WAIT 2 * 1000 -#define ANNOUNCE_NUM 2 -#define ANNOUNCE_INTERVAL 2 * 1000 +#define PROBE_MIN 1 +#define PROBE_MAX 2 +#define ANNOUNCE_WAIT 2 +/* BSD systems always do a grauitous ARP when assigning an address, + * so we can do one less announce. */ +#ifdef BSD +# define ANNOUNCE_NUM 1 +#else +# define ANNOUNCE_NUM 2 +#endif +#define ANNOUNCE_INTERVAL 2 #define MAX_CONFLICTS 10 #define RATE_LIMIT_INTERVAL 60 #define DEFEND_INTERVAL 10 + +/* number of usecs in a second. */ +#define USECS_SECOND 1000000 +/* As we use timevals, we should use the usec part for + * greater randomisation. */ +#define DHCP_RAND_MIN_U DHCP_RAND_MIN * USECS_SECOND +#define DHCP_RAND_MAX_U DHCP_RAND_MAX * USECS_SECOND +#define PROBE_MIN_U PROBE_MIN * USECS_SECOND +#define PROBE_MAX_U PROBE_MAX * USECS_SECOND + +#define timernorm(tvp) \ + do { \ + while ((tvp)->tv_usec >= 1000000) { \ + (tvp)->tv_sec++; \ + (tvp)->tv_usec -= 1000000; \ + } \ + } while (0 /* CONSTCOND */); + +#define timerneg(tvp) ((tvp)->tv_sec < 0 || (tvp)->tv_usec < 0) + struct if_state { int options; struct interface *interface; @@ -116,25 +132,28 @@ struct if_state { struct dhcp_message *new; struct dhcp_message *old; struct dhcp_lease lease; - struct timeval start; + struct timeval timeout; struct timeval stop; + struct timeval exit; int state; int messages; - long timeout; time_t nakoff; uint32_t xid; int socket; int *pid_fd; int signal_fd; -#ifdef ENABLE_ARP + int carrier; int probes; int claims; int conflicts; time_t defend; struct in_addr fail; -#endif }; +#define LINK_UP 1 +#define LINK_UNKNOWN 0 +#define LINK_DOWN -1 + struct dhcp_op { uint8_t value; const char *name; @@ -163,19 +182,17 @@ get_dhcp_op(uint8_t type) return NULL; } +#ifdef THERE_IS_NO_FORK +#define daemonise(a,b) 0 +#else static int daemonise(struct if_state *state, const struct options *options) { pid_t pid; sigset_t full; sigset_t old; -#ifdef THERE_IS_NO_FORK - char **argv; - int i; -#else char buf = '\0'; int sidpipe[2]; -#endif if (state->options & DHCPCD_DAEMONISED || !(options->options & DHCPCD_DAEMONISE)) @@ -184,7 +201,6 @@ daemonise(struct if_state *state, const struct options *options) sigfillset(&full); sigprocmask(SIG_SETMASK, &full, &old); -#ifndef THERE_IS_NO_FORK /* Setup a signal pipe so parent knows when to exit. */ if (pipe(sidpipe) == -1) { logger(LOG_ERR,"pipe: %s", strerror(errno)); @@ -214,36 +230,6 @@ daemonise(struct if_state *state, const struct options *options) close(sidpipe[0]); break; } -#else - logger(LOG_INFO, "forking to background"); - - /* We need to add --daemonise to our options */ - argv = xmalloc(sizeof(char *) * (dhcpcd_argc + 4)); - argv[0] = dhcpcd; - for (i = 1; i < dhcpcd_argc; i++) - argv[i] = dhcpcd_argv[i]; - argv[i] = (char *)"--daemonised"; - if (dhcpcd_skiproutes) { - argv[++i] = (char *)"--skiproutes"; - argv[++i] = dhcpcd_skiproutes; - } - argv[i + 1] = NULL; - - switch (pid = vfork()) { - case -1: - logger(LOG_ERR, "vfork: %s", strerror(errno)); - _exit(EXIT_FAILURE); - case 0: - signal_reset(); - sigprocmask(SIG_SETMASK, &old, NULL); - execvp(dhcpcd, argv); - /* Must not use stdio here. */ - write(STDERR_FILENO, "exec failed\n", 12); - _exit(EXIT_FAILURE); - } - - free(argv); -#endif /* Done with the fd now */ if (pid != 0) { @@ -253,18 +239,16 @@ daemonise(struct if_state *state, const struct options *options) } sigprocmask(SIG_SETMASK, &old, NULL); - - state->state = STATE_BOUND; if (pid == 0) { state->options |= DHCPCD_DAEMONISED; + timerclear(&state->exit); return 0; } - state->options |= DHCPCD_PERSISTENT | DHCPCD_FORKED; return -1; } +#endif -#ifndef MINIMAL #define THIRTY_YEARS_IN_SECONDS 946707779 static size_t get_duid(unsigned char *duid, const struct interface *iface) @@ -333,9 +317,7 @@ get_duid(unsigned char *duid, const struct interface *iface) } return len; } -#endif -#ifdef ENABLE_IPV4LL static struct dhcp_message* ipv4ll_get_dhcp(uint32_t old_addr) { @@ -346,42 +328,58 @@ ipv4ll_get_dhcp(uint32_t old_addr) dhcp = xzalloc(sizeof(*dhcp)); /* Put some LL options in */ p = dhcp->options; - *p++ = DHCP_SUBNETMASK; - *p += sizeof(u32); - u32 = LINKLOCAL_MASK; + *p++ = DHO_SUBNETMASK; + *p++ = sizeof(u32); + u32 = htonl(LINKLOCAL_MASK); memcpy(p, &u32, sizeof(u32)); p += sizeof(u32); - *p++ = DHCP_BROADCAST; - *p += sizeof(u32); - u32 = LINKLOCAL_BRDC; + *p++ = DHO_BROADCAST; + *p++ = sizeof(u32); + u32 = htonl(LINKLOCAL_BRDC); memcpy(p, &u32, sizeof(u32)); p += sizeof(u32); - *p++ = DHCP_END; + *p++ = DHO_END; for (;;) { dhcp->yiaddr = htonl(LINKLOCAL_ADDR | (((uint32_t)abs((int)arc4random()) % 0xFD00) + 0x0100)); - if (dhcp->yiaddr != old_addr) + if (dhcp->yiaddr != old_addr && + IN_LINKLOCAL(ntohl(dhcp->yiaddr))) break; } return dhcp; } -#endif + +static double +timeval_to_double(struct timeval *tv) +{ + return tv->tv_sec * 1.0 + tv->tv_usec * 1.0e-6; +} static void get_lease(struct dhcp_lease *lease, const struct dhcp_message *dhcp) { + time_t t; + lease->frominfo = 0; lease->addr.s_addr = dhcp->yiaddr; - if (get_option_addr(&lease->net.s_addr, dhcp, DHCP_SUBNETMASK) == -1) + if (get_option_addr(&lease->net.s_addr, dhcp, DHO_SUBNETMASK) == -1) lease->net.s_addr = get_netmask(dhcp->yiaddr); - if (get_option_uint32(&lease->leasetime, dhcp, DHCP_LEASETIME) != 0) + if (get_option_uint32(&lease->leasetime, dhcp, DHO_LEASETIME) == 0) { + /* Ensure that we can use the lease */ + t = 0; + if (t + (time_t)lease->leasetime < t) { + logger(LOG_WARNING, "lease of %u would overflow, " + "treating as infinite", lease->leasetime); + lease->leasetime = ~0U; /* Infinite lease */ + } + } else lease->leasetime = DEFAULT_LEASETIME; - if (get_option_uint32(&lease->renewaltime, dhcp, DHCP_RENEWALTIME) != 0) + if (get_option_uint32(&lease->renewaltime, dhcp, DHO_RENEWALTIME) != 0) lease->renewaltime = 0; - if (get_option_uint32(&lease->rebindtime, dhcp, DHCP_REBINDTIME) != 0) + if (get_option_uint32(&lease->rebindtime, dhcp, DHO_REBINDTIME) != 0) lease->rebindtime = 0; } @@ -390,21 +388,21 @@ get_old_lease(struct if_state *state) { struct interface *iface = state->interface; struct dhcp_lease *lease = &state->lease; - struct dhcp_message *dhcp; + struct dhcp_message *dhcp = NULL; struct timeval tv; unsigned int offset = 0; struct stat sb; + if (stat(iface->leasefile, &sb) == -1) { + if (errno != ENOENT) + logger(LOG_ERR, "stat: %s", strerror(errno)); + goto eexit; + } if (!IN_LINKLOCAL(ntohl(iface->addr.s_addr))) logger(LOG_INFO, "trying to use old lease in `%s'", iface->leasefile); if ((dhcp = read_lease(iface)) == NULL) { - if (errno != ENOENT) - logger(LOG_INFO, "read_lease: %s", strerror(errno)); - goto eexit; - } - if (stat(iface->leasefile, &sb) == -1) { - logger(LOG_ERR, "stat: %s", strerror(errno)); + logger(LOG_INFO, "read_lease: %s", strerror(errno)); goto eexit; } get_lease(&state->lease, dhcp); @@ -416,10 +414,8 @@ get_old_lease(struct if_state *state) dhcp->servername[0] = '\0'; if (!IN_LINKLOCAL(ntohl(dhcp->yiaddr))) { -#ifndef THERE_IS_NO_FORK if (!(state->options & DHCPCD_LASTLEASE)) goto eexit; -#endif /* Ensure that we can still use the lease */ if (gettimeofday(&tv, NULL) == -1) { @@ -429,7 +425,7 @@ get_old_lease(struct if_state *state) offset = tv.tv_sec - lease->leasedfrom; if (lease->leasedfrom && - tv.tv_sec - lease->leasedfrom > lease->leasetime) + tv.tv_sec - lease->leasedfrom > (time_t)lease->leasetime) { logger(LOG_ERR, "lease expired %u seconds ago", offset + lease->leasetime); @@ -445,8 +441,8 @@ get_old_lease(struct if_state *state) if (lease->leasedfrom == 0) offset = 0; - state->timeout = lease->renewaltime - offset; iface->start_uptime = uptime(); + state->timeout.tv_sec = lease->renewaltime - offset; free(state->old); state->old = state->new; state->new = NULL; @@ -465,24 +461,25 @@ client_setup(struct if_state *state, const struct options *options) struct interface *iface = state->interface; struct dhcp_lease *lease = &state->lease; struct in_addr addr; -#ifndef MINIMAL + struct timeval tv; size_t len = 0; unsigned char *duid = NULL; uint32_t ul; -#endif state->state = STATE_INIT; state->nakoff = 1; state->options = options->options; + timerclear(&tv); if (options->request_address.s_addr == 0 && (options->options & DHCPCD_INFORM || options->options & DHCPCD_REQUEST || - options->options & DHCPCD_DAEMONISED)) + (options->options & DHCPCD_DAEMONISED && + !(options->options & DHCPCD_BACKGROUND)))) { if (get_old_lease(state) != 0) return -1; - state->timeout = 0; + timerclear(&state->timeout); if (!(options->options & DHCPCD_DAEMONISED) && IN_LINKLOCAL(ntohl(lease->addr.s_addr))) @@ -490,21 +487,21 @@ client_setup(struct if_state *state, const struct options *options) logger(LOG_ERR, "cannot request a link local address"); return -1; } -#ifdef THERE_IS_NO_FORK - if (options->options & DHCPCD_DAEMONISED) { - state->state = STATE_BOUND; - state->timeout = state->lease.renewaltime; - iface->addr.s_addr = lease->addr.s_addr; - iface->net.s_addr = lease->net.s_addr; - get_option_addr(&lease->server.s_addr, - state->offer, DHCP_SERVERID); - } -#endif } else { lease->addr.s_addr = options->request_address.s_addr; lease->net.s_addr = options->request_netmask.s_addr; } + if (options->options & DHCPCD_REQUEST && + state->options & DHCPCD_ARP && + !state->offer) + { + state->offer = xzalloc(sizeof(*state->offer)); + state->offer->yiaddr = options->request_address.s_addr; + state->state = STATE_PROBING; + state->xid = arc4random(); + } + /* If INFORMing, ensure the interface has the address */ if (state->options & DHCPCD_INFORM && has_address(iface->name, &lease->addr, &lease->net) < 1) @@ -522,7 +519,6 @@ client_setup(struct if_state *state, const struct options *options) iface->net.s_addr = lease->net.s_addr; } -#ifndef MINIMAL if (*options->clientid) { iface->clientid = xmalloc(options->clientid[0] + 1); memcpy(iface->clientid, @@ -536,7 +532,7 @@ client_setup(struct if_state *state, const struct options *options) } if (len > 0) { - logger(LOG_INFO, "DUID = %s", + logger(LOG_DEBUG, "DUID = %s", hwaddr_ntoa(duid, len)); iface->clientid = xmalloc(len + 6); @@ -568,34 +564,64 @@ client_setup(struct if_state *state, const struct options *options) memcpy(iface->clientid + 2, iface->hwaddr, iface->hwlen); } } -#endif + if (state->options & DHCPCD_LINK) { + open_link_socket(iface); + switch (carrier_status(iface->name)) { + case 0: + state->carrier = LINK_DOWN; + break; + case 1: + state->carrier = LINK_UP; + break; + default: + state->carrier = LINK_UNKNOWN; + } + } + + if (options->timeout > 0 && + !(state->options & DHCPCD_DAEMONISED)) + { + if (state->options & DHCPCD_IPV4LL) { + state->stop.tv_sec = options->timeout; + if (!(state->options & DHCPCD_BACKGROUND)) + state->exit.tv_sec = state->stop.tv_sec + 10; + } else if (!(state->options & DHCPCD_BACKGROUND)) + state->exit.tv_sec = options->timeout; + } return 0; } static int do_socket(struct if_state *state, int mode) { - if (state->interface->fd >= 0) { - close(state->interface->fd); - state->interface->fd = -1; + if (state->interface->raw_fd != -1) { + close(state->interface->raw_fd); + state->interface->raw_fd = -1; } - if (mode == SOCKET_CLOSED && state->interface->udp_fd >= 0) { - close(state->interface->udp_fd); - state->interface->udp_fd = -1; + if (mode == SOCKET_CLOSED) { + if (state->interface->udp_fd != -1) { + close(state->interface->udp_fd); + state->interface->udp_fd = -1; + } + if (state->interface->arp_fd != -1) { + close(state->interface->arp_fd); + state->interface->arp_fd = -1; + } } - /* We need to bind to a port, otherwise we generate ICMP messages - * that cannot connect the port when we have an address. - * We don't actually use this fd at all, instead using our packet - * filter socket. */ + /* Always have the UDP socket open to avoid the kernel sending + * ICMP unreachable messages. */ + /* For systems without SO_BINDTODEVICE, (ie BSD ones) we may get an + * error or EADDRINUSE when binding to INADDR_ANY as another dhcpcd + * instance could be running. + * Oddly enough, we don't care about this as the socket is there + * just to please the kernel - we don't care for reading from it. */ if (mode == SOCKET_OPEN && state->interface->udp_fd == -1 && - state->lease.addr.s_addr != 0) - if (open_udp_socket(state->interface) == -1) { - logger(LOG_ERR, "open_udp_socket: %s", strerror(errno)); - return -1; - } + open_udp_socket(state->interface) == -1 && + (errno != EADDRINUSE || state->interface->addr.s_addr != 0)) + logger(LOG_ERR, "open_udp_socket: %s", strerror(errno)); if (mode == SOCKET_OPEN) if (open_socket(state->interface, ETHERTYPE_IP) == -1) { @@ -611,107 +637,211 @@ send_message(struct if_state *state, int type, const struct options *options) { struct dhcp_message *dhcp; uint8_t *udp; - ssize_t len; - ssize_t r; - struct in_addr from; - struct in_addr to; + ssize_t len, r; + struct in_addr from, to; + in_addr_t a = 0; - logger(LOG_DEBUG, "sending %s with xid 0x%x", - get_dhcp_op(type), state->xid); + if (state->carrier == LINK_DOWN) + return 0; + if (type == DHCP_RELEASE) + logger(LOG_DEBUG, "sending %s with xid 0x%x", + get_dhcp_op(type), state->xid); + else + logger(LOG_DEBUG, + "sending %s with xid 0x%x, next in %0.2f seconds", + get_dhcp_op(type), state->xid, + timeval_to_double(&state->timeout)); state->messages++; + if (state->messages < 0) + state->messages = INT_MAX; + /* If we couldn't open a UDP port for our IP address + * then we cannot renew. + * This could happen if our IP was pulled out from underneath us. */ + if (state->interface->udp_fd == -1) { + a = state->interface->addr.s_addr; + state->interface->addr.s_addr = 0; + } len = make_message(&dhcp, state->interface, &state->lease, state->xid, type, options); + if (state->interface->udp_fd == -1) + state->interface->addr.s_addr = a; from.s_addr = dhcp->ciaddr; if (from.s_addr) to.s_addr = state->lease.server.s_addr; else to.s_addr = 0; - if (to.s_addr) { + if (to.s_addr && to.s_addr != INADDR_BROADCAST) { r = send_packet(state->interface, to, (uint8_t *)dhcp, len); if (r == -1) logger(LOG_ERR, "send_packet: %s", strerror(errno)); } else { len = make_udp_packet(&udp, (uint8_t *)dhcp, len, from, to); - free(dhcp); r = send_raw_packet(state->interface, ETHERTYPE_IP, udp, len); + free(udp); if (r == -1) logger(LOG_ERR, "send_raw_packet: %s", strerror(errno)); - free(udp); + } + free(dhcp); + /* Failed to send the packet? Return to the init state */ + if (r == -1) { + state->state = STATE_INIT; + timerclear(&state->timeout); + timerclear(&state->stop); + do_socket(state, SOCKET_CLOSED); } return r; } static void -drop_config(struct if_state *state, const char *reason, const struct options *options) +drop_config(struct if_state *state, const char *reason, + const struct options *options) { - configure(state->interface, reason, NULL, state->new, - &state->lease, options, 0); - free(state->old); - state->old = NULL; - free(state->new); - state->new = NULL; - + if (state->new || strcmp(reason, "FAIL") == 0) { + configure(state->interface, reason, NULL, state->new, + &state->lease, options, 0); + free(state->old); + state->old = NULL; + free(state->new); + state->new = NULL; + } state->lease.addr.s_addr = 0; } +static void +reduce_timers(struct if_state *state, const struct timeval *tv) +{ + if (timerisset(&state->exit)) { + timersub(&state->exit, tv, &state->exit); + if (!timerisset(&state->exit)) + state->exit.tv_sec = -1; + } + if (timerisset(&state->stop)) { + timersub(&state->stop, tv, &state->stop); + if (!timerisset(&state->stop)) + state->stop.tv_sec = -1; + } + if (timerisset(&state->timeout)) { + timersub(&state->timeout, tv, &state->timeout); + if (!timerisset(&state->timeout)) + state->timeout.tv_sec = -1; + } +} + +static struct timeval * +get_lowest_timer(struct if_state *state) +{ + struct timeval *ref = NULL; + + if (timerisset(&state->exit)) + ref = &state->exit; + if (timerisset(&state->stop)) { + if (!ref || timercmp(&state->stop, ref, <)) + ref = &state->stop; + } + if (timerisset(&state->timeout)) { + if (!ref || timercmp(&state->timeout, ref, <)) + ref = &state->timeout; + } + return ref; +} + static int -wait_for_packet(struct if_state *state) +wait_for_fd(struct if_state *state, int *fd) { - struct pollfd fds[3]; /* iface, arp, signal */ - int retval, timeout, nfds = 0; - time_t start; - struct timeval now, d; + struct pollfd fds[4]; /* signal, link, raw, arp */ + struct interface *iface = state->interface; + int i, r, nfds = 0, msecs = -1; + struct timeval start, stop, diff, *ref; + static int lastinf = 0; + + /* Ensure that we haven't already timed out */ + ref = get_lowest_timer(state); + if (ref && timerneg(ref)) + return 0; /* We always listen to signals */ fds[nfds].fd = state->signal_fd; fds[nfds].events = POLLIN; nfds++; + /* And links */ + if (iface->link_fd != -1) { + fds[nfds].fd = iface->link_fd; + fds[nfds].events = POLLIN; + nfds++; + } - if (state->lease.leasetime == ~0U && state->state == STATE_BOUND) { - logger(LOG_DEBUG, "waiting for infinity"); - timeout = INFTIM; - } else { - timeout = state->timeout; - if (timerisset(&state->stop)) { - get_time(&now); - if (timercmp(&state->stop, &now, >)) { - timersub(&state->stop, &now, &d); - retval = d.tv_sec * 1000 + (d.tv_usec + 999) / 1000; - if (retval < timeout) - timeout = retval; - } + if (state->lease.leasetime == ~0U && + state->state == STATE_BOUND) + { + if (!lastinf) { + logger(LOG_DEBUG, "waiting for infinity"); + lastinf = 1; } - if (timeout <= 0) - return 0; - if (state->interface->fd != -1) { - fds[nfds].fd = state->interface->fd; + ref = NULL; + } else if (state->carrier == LINK_DOWN && !ref) { + if (!lastinf) { + logger(LOG_DEBUG, "waiting for carrier"); + lastinf = 1; + } + if (timerisset(&state->exit)) + ref = &state->exit; + else + ref = NULL; + } else { + if (iface->raw_fd != -1) { + fds[nfds].fd = iface->raw_fd; fds[nfds].events = POLLIN; nfds++; } -#ifdef ENABLE_ARP - if (state->interface->arp_fd != -1) { - fds[nfds].fd = state->interface->arp_fd; + if (iface->arp_fd != -1) { + fds[nfds].fd = iface->arp_fd; fds[nfds].events = POLLIN; nfds++; } -#endif - logger(LOG_DEBUG, "waiting for %0.3f seconds", - (float)timeout / 1000); } - start = uptime(); - retval = poll(fds, nfds, timeout); - if (timeout != INFTIM) { - state->timeout -= uptime() - start; - if (state->timeout < 0) - state->timeout = 0; - } - if (retval == -1) { - if (errno == EINTR) + /* Wait and then reduce the timers. + * If we reduce a timer to zero, set it negative to indicate timeout. + * We cannot reliably use select as there is no guarantee we will + * actually wait the whole time if greater than 31 days according + * to POSIX. So we loop on poll if needed as it's limitation of + * INT_MAX milliseconds is known. */ + for (;;) { + get_monotonic(&start); + if (ref) { + lastinf = 0; + if (ref->tv_sec > INT_MAX / 1000 || + (ref->tv_sec == INT_MAX / 1000 && + (ref->tv_usec + 999) / 1000 > INT_MAX % 1000)) + msecs = INT_MAX; + else + msecs = ref->tv_sec * 1000 + + (ref->tv_usec + 999) / 1000; + } else + msecs = -1; + r = poll(fds, nfds, msecs); + get_monotonic(&stop); + timersub(&stop, &start, &diff); + reduce_timers(state, &diff); + if (r == -1) { + if (errno != EINTR) + logger(LOG_ERR, "poll: %s", strerror(errno)); + return -1; + } + if (r) + break; + /* We should not have an infinite timeout if we get here */ + if (timerneg(ref)) return 0; - logger(LOG_ERR, "poll: %s", strerror(errno)); } - return retval; + + /* We configured our array in the order we should deal with them */ + for (i = 0; i < nfds; i++) + if (fds[i].revents & POLLIN) { + *fd = fds[i].fd; + return r; + } + return r; } static int @@ -730,37 +860,18 @@ handle_signal(int sig, struct if_state *state, const struct options *options) if (!(state->options & DHCPCD_PERSISTENT)) drop_config(state, "STOP", options); return -1; - case SIGALRM: - logger (LOG_INFO, "received SIGALRM, renewing lease"); - switch (state->state) { - case STATE_BOUND: - case STATE_RENEWING: - case STATE_REBINDING: - case STATE_ANNOUNCING: - state->state = STATE_RENEW_REQUESTED; - break; - case STATE_RENEW_REQUESTED: - case STATE_REQUESTING: - state->state = STATE_INIT; - break; - } + logger(LOG_INFO, "received SIGALRM, renewing lease"); + do_socket(state, SOCKET_CLOSED); + state->state = STATE_RENEW_REQUESTED; + timerclear(&state->timeout); timerclear(&state->stop); - state->timeout = 0; - return 0; - + return 1; case SIGHUP: - if (state->state != STATE_BOUND && - state->state != STATE_RENEWING && - state->state != STATE_REBINDING) + logger(LOG_INFO, "received SIGHUP, releasing lease"); + if (lease->addr.s_addr && + !IN_LINKLOCAL(ntohl(lease->addr.s_addr))) { - logger(LOG_ERR, - "received SIGHUP, but no lease to release"); - return -1; - } - - logger (LOG_INFO, "received SIGHUP, releasing lease"); - if (!IN_LINKLOCAL(ntohl(lease->addr.s_addr))) { do_socket(state, SOCKET_OPEN); state->xid = arc4random(); send_message(state, DHCP_RELEASE, options); @@ -768,14 +879,13 @@ handle_signal(int sig, struct if_state *state, const struct options *options) } drop_config(state, "RELEASE", options); return -1; - default: logger (LOG_ERR, "received signal %d, but don't know what to do with it", sig); } - return -1; + return 0; } static int bind_dhcp(struct if_state *state, const struct options *options) @@ -783,17 +893,19 @@ static int bind_dhcp(struct if_state *state, const struct options *options) struct interface *iface = state->interface; struct dhcp_lease *lease = &state->lease; const char *reason = NULL; - struct timeval tv; + struct timeval start, stop, diff; int retval; free(state->old); state->old = state->new; state->new = state->offer; state->offer = NULL; -#ifdef ENABLE_ARP + state->messages = 0; state->conflicts = 0; state->defend = 0; -#endif + timerclear(&state->exit); + if (clock_monotonic) + get_monotonic(&lease->boundtime); if (options->options & DHCPCD_INFORM) { if (options->request_address.s_addr != 0) @@ -804,17 +916,18 @@ static int bind_dhcp(struct if_state *state, const struct options *options) inet_ntoa(lease->addr)); state->state = STATE_BOUND; state->lease.leasetime = ~0U; + timerclear(&state->stop); reason = "INFORM"; } else if (IN_LINKLOCAL(htonl(state->new->yiaddr))) { get_lease(lease, state->new); logger(LOG_INFO, "using IPv4LL address %s", inet_ntoa(lease->addr)); state->state = STATE_INIT; - state->timeout = 0; + timerclear(&state->timeout); reason = "IPV4LL"; } else { - if (gettimeofday(&tv, NULL) == 0) - lease->leasedfrom = tv.tv_sec; + if (gettimeofday(&start, NULL) == 0) + lease->leasedfrom = start.tv_sec; get_lease(lease, state->new); if (lease->frominfo) @@ -822,55 +935,40 @@ static int bind_dhcp(struct if_state *state, const struct options *options) if (lease->leasetime == ~0U) { lease->renewaltime = lease->rebindtime = lease->leasetime; - state->timeout = 1; /* So we wait for infinity */ logger(LOG_INFO, "leased %s for infinity", inet_ntoa(lease->addr)); state->state = STATE_BOUND; + timerclear(&state->stop); } else { - logger(LOG_INFO, "leased %s for %u seconds", - inet_ntoa(lease->addr), lease->leasetime); - if (lease->rebindtime >= lease->leasetime) { - lease->rebindtime = (lease->leasetime * T2); + lease->rebindtime = lease->leasetime * T2; logger(LOG_ERR, "rebind time greater than lease " "time, forcing to %u seconds", lease->rebindtime); } - if (lease->renewaltime > lease->rebindtime) { - lease->renewaltime = (lease->leasetime * T1); + lease->renewaltime = lease->leasetime * T1; logger(LOG_ERR, "renewal time greater than rebind time, " "forcing to %u seconds", lease->renewaltime); } - - if (!lease->renewaltime) { - lease->renewaltime = (lease->leasetime * T1); - logger(LOG_INFO, - "no renewal time supplied, assuming %d seconds", - lease->renewaltime); - } else - logger(LOG_DEBUG, "renew in %u seconds", - lease->renewaltime); - - if (!lease->rebindtime) { - lease->rebindtime = (lease->leasetime * T2); - logger(LOG_INFO, - "no rebind time supplied, assuming %d seconds", - lease->rebindtime); - } else - logger(LOG_DEBUG, "rebind in %u seconds", - lease->rebindtime); - - state->timeout = lease->renewaltime * 1000; + if (!lease->renewaltime) + lease->renewaltime = lease->leasetime * T1; + if (!lease->rebindtime) + lease->rebindtime = lease->leasetime * T2; + logger(LOG_INFO, + "leased %s for %u seconds", + inet_ntoa(lease->addr), lease->leasetime); + state->stop.tv_sec = lease->renewaltime; + state->stop.tv_usec = 0; } state->state = STATE_BOUND; } state->xid = 0; - timerclear(&state->stop); + timerclear(&state->timeout); if (!reason) { if (state->old) { if (state->old->yiaddr == state->new->yiaddr && @@ -881,8 +979,18 @@ static int bind_dhcp(struct if_state *state, const struct options *options) } else reason = "BOUND"; } + /* If we have a monotonic clock we can safely substract the + * script execution time from our timers. + * Otherwise we can't as the script may update the real time. */ + if (clock_monotonic) + get_monotonic(&start); retval = configure(iface, reason, state->new, state->old, &state->lease, options, 1); + if (clock_monotonic) { + get_monotonic(&stop); + timersub(&stop, &start, &diff); + reduce_timers(state, &diff); + } if (retval != 0) return -1; return daemonise(state, options); @@ -895,18 +1003,15 @@ handle_timeout_fail(struct if_state *state, const struct options *options) struct interface *iface = state->interface; int gotlease = -1; const char *reason = NULL; - struct timeval tv; - timerclear(&tv); - /* Clear our timers and counters as we've failed. - * We'll either abort or move to another state with new timers */ timerclear(&state->stop); - state->messages = 0; - state->timeout = 0; + timerclear(&state->exit); + if (state->state != STATE_DISCOVERING) + state->messages = 0; switch (state->state) { - case STATE_DISCOVERING: - /* FALLTHROUGH */ + case STATE_INIT: /* FALLTHROUGH */ + case STATE_DISCOVERING: /* FALLTHROUGH */ case STATE_REQUESTING: if (IN_LINKLOCAL(ntohl(iface->addr.s_addr))) { if (!(state->options & DHCPCD_DAEMONISED)) @@ -915,7 +1020,8 @@ handle_timeout_fail(struct if_state *state, const struct options *options) if (iface->addr.s_addr != 0 && !(state->options & DHCPCD_INFORM)) logger(LOG_ERR, "lost lease"); - else + else if (state->carrier != LINK_DOWN || + !(state->options & DHCPCD_DAEMONISED)) logger(LOG_ERR, "timed out"); } do_socket(state, SOCKET_CLOSED); @@ -923,64 +1029,81 @@ handle_timeout_fail(struct if_state *state, const struct options *options) state->options & DHCPCD_TEST) return -1; - if (state->options & DHCPCD_IPV4LL || - state->options & DHCPCD_LASTLEASE) + if (state->carrier != LINK_DOWN && + (state->options & DHCPCD_IPV4LL || + state->options & DHCPCD_LASTLEASE)) gotlease = get_old_lease(state); -#ifdef ENABLE_IPV4LL - if (state->options & DHCPCD_IPV4LL && gotlease != 0) { + if (state->carrier != LINK_DOWN && + state->options & DHCPCD_IPV4LL && + gotlease != 0) + { logger(LOG_INFO, "probing for an IPV4LL address"); free(state->offer); state->offer = ipv4ll_get_dhcp(0); gotlease = 0; } -#endif -#ifdef ENABLE_ARP if (gotlease == 0 && state->offer->yiaddr != iface->addr.s_addr) { state->state = STATE_PROBING; state->claims = 0; state->probes = 0; - state->conflicts = 0; - return 0; + if (iface->addr.s_addr) + state->conflicts = 0; + return 1; } -#endif if (gotlease == 0) return bind_dhcp(state, options); - reason = "FAIL"; + if (iface->addr.s_addr) + reason = "EXPIRE"; + else + reason = "FAIL"; drop_config(state, reason, options); if (!(state->options & DHCPCD_DAEMONISED) && (state->options & DHCPCD_DAEMONISE)) return -1; - state->state = STATE_INIT; + state->state = STATE_RENEW_REQUESTED; + return 1; + case STATE_BOUND: + logger(LOG_INFO, "renewing lease of %s",inet_ntoa(lease->addr)); + if (state->carrier != LINK_DOWN) + do_socket(state, SOCKET_OPEN); + state->xid = arc4random(); + state->state = STATE_RENEWING; + state->stop.tv_sec = lease->rebindtime - lease->renewaltime; break; case STATE_RENEWING: logger(LOG_ERR, "failed to renew, attempting to rebind"); - lease->addr.s_addr = 0; state->state = STATE_REBINDING; - tv.tv_sec = lease->rebindtime - lease->renewaltime; + if (lease->server.s_addr == 0) + state->stop.tv_sec = options->timeout; + else + state->stop.tv_sec = lease->rebindtime - \ + lease->renewaltime; + lease->server.s_addr = 0; break; case STATE_REBINDING: - logger(LOG_ERR, "failed to rebind, attempting to discover"); + logger(LOG_ERR, "failed to rebind"); reason = "EXPIRE"; drop_config(state, reason, options); state->state = STATE_INIT; break; + case STATE_PROBING: /* FALLTHROUGH */ + case STATE_ANNOUNCING: + /* We should have lost carrier here and exit timer went */ + logger(LOG_ERR, "timed out"); + return -1; default: logger(LOG_DEBUG, "handle_timeout_failed: invalid state %d", state->state); } - get_time(&state->start); - if (timerisset(&tv)) - timeradd(&state->start, &tv, &state->stop); - /* This effectively falls through into the handle_timeout funtion */ - return 0; + return 1; } static int @@ -988,14 +1111,32 @@ handle_timeout(struct if_state *state, const struct options *options) { struct dhcp_lease *lease = &state->lease; struct interface *iface = state->interface; - int i; - struct timeval tv; + int i = 0; struct in_addr addr; + struct timeval tv; + + timerclear(&state->timeout); + if (timerneg(&state->exit)) + return handle_timeout_fail(state, options); -#ifdef ENABLE_ARP + if (state->state == STATE_RENEW_REQUESTED && + IN_LINKLOCAL(ntohl(lease->addr.s_addr))) + { + state->state = STATE_PROBING; + free(state->offer); + state->offer = read_lease(state->interface); + state->probes = 0; + state->claims = 0; + } switch (state->state) { + case STATE_INIT_IPV4LL: + state->state = STATE_PROBING; + free(state->offer); + state->offer = ipv4ll_get_dhcp(0); + state->probes = 0; + state->claims = 0; + /* FALLTHROUGH */ case STATE_PROBING: - timerclear(&state->stop); if (iface->arp_fd == -1) open_socket(iface, ETHERTYPE_ARP); if (state->probes < PROBE_NUM) { @@ -1006,79 +1147,130 @@ handle_timeout(struct if_state *state, const struct options *options) inet_ntoa(addr)); } state->probes++; - logger(LOG_DEBUG, "sending ARP probe #%d", - state->probes); - if (state->probes < PROBE_NUM) - state->timeout = (arc4random() % - (PROBE_MAX - PROBE_MIN)) + PROBE_MIN; - else - state->timeout = ANNOUNCE_WAIT; - send_arp(iface, ARPOP_REQUEST, 0, state->offer->yiaddr); + if (state->probes < PROBE_NUM) { + state->timeout.tv_sec = PROBE_MIN; + state->timeout.tv_usec = arc4random() % + (PROBE_MAX_U - PROBE_MIN_U); + timernorm(&state->timeout); + } else { + state->timeout.tv_sec = ANNOUNCE_WAIT; + state->timeout.tv_usec = 0; + } + logger(LOG_DEBUG, + "sending ARP probe (%d of %d), next in %0.2f seconds", + state->probes, PROBE_NUM, + timeval_to_double(&state->timeout)); + if (send_arp(iface, ARPOP_REQUEST, 0, + state->offer->yiaddr) == -1) + { + logger(LOG_ERR, "send_arp: %s", strerror(errno)); + return -1; + } return 0; } else { /* We've waited for ANNOUNCE_WAIT after the final probe * so the address is now ours */ - i = bind_dhcp(state, options); - state->state = STATE_ANNOUNCING; - state->timeout = ANNOUNCE_INTERVAL; - return i; + if (IN_LINKLOCAL(htonl(state->offer->yiaddr))) { + i = bind_dhcp(state, options); + state->state = STATE_ANNOUNCING; + state->timeout.tv_sec = ANNOUNCE_INTERVAL; + state->timeout.tv_usec = 0; + return i; + } + state->state = STATE_REQUESTING; } + break; case STATE_ANNOUNCING: - timerclear(&state->stop); + if (iface->arp_fd == -1) + open_socket(iface, ETHERTYPE_ARP); if (state->claims < ANNOUNCE_NUM) { state->claims++; - logger(LOG_DEBUG, "sending ARP announce #%d", - state->claims); - send_arp(iface, ARPOP_REQUEST, - state->new->yiaddr, state->new->yiaddr); - if (state->claims < ANNOUNCE_NUM) - state->timeout = ANNOUNCE_INTERVAL; - else if (IN_LINKLOCAL(htonl(lease->addr.s_addr))) { - state->state = STATE_INIT; - state->timeout = 0; - } else { - state->state = STATE_BOUND; - state->timeout = lease->renewaltime * 1000 - - (ANNOUNCE_INTERVAL * ANNOUNCE_NUM); - close(iface->arp_fd); - iface->arp_fd = -1; + if (state->claims < ANNOUNCE_NUM) { + state->timeout.tv_sec = ANNOUNCE_INTERVAL; + state->timeout.tv_usec = 0; + logger(LOG_DEBUG, + "sending ARP announce (%d of %d)," + " next in %0.2f seconds", + state->claims, ANNOUNCE_NUM, + timeval_to_double(&state->timeout)); + } else + logger(LOG_DEBUG, + "sending ARP announce (%d of %d)", + state->claims, ANNOUNCE_NUM); + i = send_arp(iface, ARPOP_REQUEST, + state->new->yiaddr, state->new->yiaddr); + if (i == -1) { + logger(LOG_ERR, "send_arp: %s", strerror(errno)); + return -1; + } + } + if (state->claims < ANNOUNCE_NUM) + return 0; + if (IN_LINKLOCAL(htonl(state->new->yiaddr))) { + /* We should pretend to be at the end + * of the DHCP negotation cycle */ + state->state = STATE_INIT; + state->messages = DHCP_MAX / DHCP_BASE; + state->probes = 0; + state->claims = 0; + timerclear(&state->stop); + goto dhcp_timeout; + } else { + state->state = STATE_BOUND; + close(iface->arp_fd); + iface->arp_fd = -1; + if (lease->leasetime != ~0U) { + state->stop.tv_sec = lease->renewaltime; + state->stop.tv_usec = 0; + if (clock_monotonic) { + get_monotonic(&tv); + timersub(&tv, &lease->boundtime, &tv); + timersub(&state->stop, &tv, &state->stop); + } else { + state->stop.tv_sec -= + (ANNOUNCE_INTERVAL * ANNOUNCE_NUM); + } + logger(LOG_DEBUG, "renew in %ld seconds", + (long int)state->stop.tv_sec); } } return 0; } -#endif - if (timerisset(&state->stop)) { - get_time(&tv); - if (timercmp(&tv, &state->stop, >)) - return handle_timeout_fail(state, options); - } - timerclear(&tv); + if (timerneg(&state->stop)) + return handle_timeout_fail(state, options); switch (state->state) { - case STATE_INIT: /* FALLTHROUGH */ case STATE_BOUND: /* FALLTHROUGH */ case STATE_RENEW_REQUESTED: + timerclear(&state->stop); + /* FALLTHROUGH */ + case STATE_INIT: do_socket(state, SOCKET_OPEN); state->xid = arc4random(); - state->messages = 0; - state->nakoff = 1; iface->start_uptime = uptime(); - get_time(&state->start); - timerclear(&state->stop); + break; } switch(state->state) { - case STATE_INIT: - /* 21Jul08 - was && DHCPCD_DAEMONISED */ - if (!(state->state & DHCPCD_DAEMONISED) && - options->timeout && - !IN_LINKLOCAL(htonl(iface->addr.s_addr))) - { - get_time(&state->start); - tv.tv_sec = options->timeout; - timeradd(&state->start, &tv, &state->stop); + case STATE_RENEW_REQUESTED: + /* If a renew was requested (ie, didn't timeout) we actually + * enter the REBIND state so that we broadcast to all servers. + * We need to do this for when we change networks. */ + lease->server.s_addr = 0; + state->messages = 0; + if (lease->addr.s_addr && !(state->options & DHCPCD_INFORM)) { + logger(LOG_INFO, "rebinding lease of %s", + inet_ntoa(lease->addr)); + state->state = STATE_REBINDING; + state->stop.tv_sec = options->timeout; + state->stop.tv_usec = 0; + break; } + /* FALLTHROUGH */ + case STATE_INIT: + if (state->carrier == LINK_DOWN) + return 0; if (lease->addr.s_addr == 0 || IN_LINKLOCAL(ntohl(iface->addr.s_addr))) { @@ -1093,20 +1285,33 @@ handle_timeout(struct if_state *state, const struct options *options) inet_ntoa(lease->addr)); state->state = STATE_REQUESTING; } + if (!lease->addr.s_addr && !timerisset(&state->stop)) { + state->stop.tv_sec = DHCP_MAX + DHCP_RAND_MIN; + state->stop.tv_usec = arc4random() % (DHCP_RAND_MAX_U - DHCP_RAND_MIN_U); + timernorm(&state->stop); + } break; - case STATE_RENEW_REQUESTED: - case STATE_BOUND: - if (IN_LINKLOCAL(ntohl(lease->addr.s_addr))) { - lease->addr.s_addr = 0; - state->state = STATE_INIT; - state->timeout = 0; + } + +dhcp_timeout: + if (state->carrier == LINK_DOWN) { + timerclear(&state->timeout); + return 0; + } + state->timeout.tv_sec = DHCP_BASE; + for (i = 0; i < state->messages; i++) { + state->timeout.tv_sec *= 2; + if (state->timeout.tv_sec > DHCP_MAX) { + state->timeout.tv_sec = DHCP_MAX; break; } - logger(LOG_INFO, "renewing lease of %s",inet_ntoa(lease->addr)); - state->state = STATE_RENEWING; - break; } + state->timeout.tv_sec += DHCP_RAND_MIN; + state->timeout.tv_usec = arc4random() % + (DHCP_RAND_MAX_U - DHCP_RAND_MIN_U); + timernorm(&state->timeout); + /* We send the message here so that the timeout is reported */ switch (state->state) { case STATE_DISCOVERING: send_message(state, DHCP_DISCOVER, options); @@ -1119,111 +1324,155 @@ handle_timeout(struct if_state *state, const struct options *options) /* FALLTHROUGH */ case STATE_RENEWING: /* FALLTHROUGH */ case STATE_REBINDING: + if (iface->raw_fd == -1) + do_socket(state, SOCKET_OPEN); send_message(state, DHCP_REQUEST, options); break; } - state->timeout = DHCP_BASE; - for (i = 1; i < state->messages; i++) { - state->timeout *= 2; - if (state->timeout > DHCP_MAX) { - state->timeout = DHCP_MAX; - break; - } - } - state->timeout += (arc4random() % (DHCP_RAND_MAX - DHCP_RAND_MIN)) + - DHCP_RAND_MIN; return 0; } +static void +log_dhcp(int lvl, const char *msg, const struct dhcp_message *dhcp) +{ + char *a; + struct in_addr addr; + int r; + + if (strcmp(msg, "NAK:") == 0) + a = get_option_string(dhcp, DHO_MESSAGE); + else { + addr.s_addr = dhcp->yiaddr; + a = xstrdup(inet_ntoa(addr)); + } + r = get_option_addr(&addr.s_addr, dhcp, DHO_SERVERID); + if (dhcp->servername[0] && r == 0) + logger(lvl, "%s %s from %s `%s'", msg, a, + inet_ntoa(addr), dhcp->servername); + else if (r == 0) + logger(lvl, "%s %s from %s", msg, a, inet_ntoa(addr)); + else + logger(lvl, "%s %s", msg, a); + free(a); +} + static int handle_dhcp(struct if_state *state, struct dhcp_message **dhcpp, const struct options *options) { - struct timespec ts; struct dhcp_message *dhcp = *dhcpp; struct interface *iface = state->interface; struct dhcp_lease *lease = &state->lease; - char *addr; - struct in_addr saddr; - uint8_t type; + uint8_t type, tmp; + struct in_addr addr; + size_t i; int r; - if (get_option_uint8(&type, dhcp, DHCP_MESSAGETYPE) == -1) { - logger(LOG_ERR, "no DHCP type in message"); - return -1; - } - /* reset the message counter */ state->messages = 0; + /* We have to have DHCP type to work */ + if (get_option_uint8(&type, dhcp, DHO_MESSAGETYPE) == -1) { + log_dhcp(LOG_ERR, "no DHCP type in", dhcp); + return 0; + } + + /* Ensure that it's not from a blacklisted server. + * We should expand this to check IP and/or hardware address + * at the packet level. */ + if (options->blacklist_len != 0 && + get_option_addr(&addr.s_addr, dhcp, DHO_SERVERID) == 0) + { + for (i = 0; i < options->blacklist_len; i++) { + if (options->blacklist[i] != addr.s_addr) + continue; + if (dhcp->servername[0]) + logger(LOG_WARNING, + "ignoring blacklisted server %s `%s'", + inet_ntoa(addr), dhcp->servername); + else + logger(LOG_WARNING, + "ignoring blacklisted server %s", + inet_ntoa(addr)); + return 0; + } + } + /* We should restart on a NAK */ if (type == DHCP_NAK) { - addr = get_option_string(dhcp, DHCP_MESSAGE); - logger(LOG_WARNING, "received NAK: %s", addr); - free(addr); + log_dhcp(LOG_WARNING, "NAK:", dhcp); + drop_config(state, "EXPIRE", options); + do_socket(state, SOCKET_CLOSED); state->state = STATE_INIT; - state->timeout = 0; - lease->addr.s_addr = 0; - timerclear(&state->stop); - /* If we constantly get NAKS then we should slowly back off */ - if (state->nakoff > 0) { - logger(LOG_DEBUG, "sleeping for %lu seconds", - (unsigned long)state->nakoff); - ts.tv_sec = state->nakoff; - ts.tv_nsec = 0; + if (state->nakoff == 0) { + state->nakoff = 1; + timerclear(&state->timeout); + } else { + state->timeout.tv_sec = state->nakoff; + state->timeout.tv_usec = 0; state->nakoff *= 2; if (state->nakoff > NAKOFF_MAX) state->nakoff = NAKOFF_MAX; - nanosleep(&ts, NULL); - } - + } return 0; } /* No NAK, so reset the backoff */ state->nakoff = 1; + /* Ensure that all required options are present */ + for (i = 1; i < 255; i++) { + if (has_option_mask(options->requiremask, i) && + get_option_uint8(&tmp, dhcp, i) != 0) + { + log_dhcp(LOG_WARNING, "reject", dhcp); + return 0; + } + } + if (type == DHCP_OFFER && state->state == STATE_DISCOVERING) { lease->addr.s_addr = dhcp->yiaddr; - addr = xstrdup(inet_ntoa(lease->addr)); - r = get_option_addr(&lease->server.s_addr, dhcp, DHCP_SERVERID); - if (dhcp->servername[0] && r == 0) - logger(LOG_INFO, "offered %s from %s `%s'", - addr, inet_ntoa(lease->server), - dhcp->servername); - else if (r == 0) - logger(LOG_INFO, "offered %s from %s", - addr, inet_ntoa(lease->server)); - else - logger(LOG_INFO, "offered %s", addr); - free(addr); - + get_option_addr(&lease->server.s_addr, dhcp, DHO_SERVERID); + log_dhcp(LOG_INFO, "offered", dhcp); if (state->options & DHCPCD_TEST) { - exec_script(options, iface->name, "TEST", dhcp, NULL); - free(dhcp); - return 0; + run_script(options, iface->name, "TEST", dhcp, NULL); + /* Fake the fact we forked so we return 0 to userland */ + state->options |= DHCPCD_FORKED; + return -1; + } + free(state->offer); + state->offer = dhcp; + *dhcpp = NULL; + timerclear(&state->timeout); + if (state->options & DHCPCD_ARP && + iface->addr.s_addr != state->offer->yiaddr) + { + /* If the interface already has the address configured + * then we can't ARP for duplicate detection. */ + addr.s_addr = state->offer->yiaddr; + if (!has_address(iface->name, &addr, NULL)) { + state->state = STATE_PROBING; + state->claims = 0; + state->probes = 0; + state->conflicts = 0; + timerclear(&state->stop); + return 1; + } } - - free(dhcp); state->state = STATE_REQUESTING; - state->timeout = 0; - return 0; + return 1; } if (type == DHCP_OFFER) { - saddr.s_addr = dhcp->yiaddr; - logger(LOG_INFO, "got subsequent offer of %s, ignoring ", - inet_ntoa(saddr)); - free(dhcp); + log_dhcp(LOG_INFO, "ignoring offer of", dhcp); return 0; } /* We should only be dealing with acks */ if (type != DHCP_ACK) { - logger(LOG_ERR, "%d not an ACK or OFFER", type); - free(dhcp); + log_dhcp(LOG_ERR, "not ACK or OFFER", dhcp); return 0; } @@ -1233,35 +1482,30 @@ handle_dhcp(struct if_state *state, struct dhcp_message **dhcpp, case STATE_RENEWING: case STATE_REBINDING: if (!(state->options & DHCPCD_INFORM)) { - saddr.s_addr = dhcp->yiaddr; - logger(LOG_INFO, "lease of %s acknowledged", - inet_ntoa(saddr)); + get_option_addr(&lease->server.s_addr, + dhcp, DHO_SERVERID); + log_dhcp(LOG_INFO, "acknowledged", dhcp); } + free(state->offer); + state->offer = dhcp; + *dhcpp = NULL; break; default: logger(LOG_ERR, "wrong state %d", state->state); } do_socket(state, SOCKET_CLOSED); - free(state->offer); - state->offer = dhcp; - *dhcpp = NULL; - -#ifdef ENABLE_ARP - if (state->options & DHCPCD_ARP && - iface->addr.s_addr != state->offer->yiaddr) - { - state->state = STATE_PROBING; - state->timeout = 0; - state->claims = 0; - state->probes = 0; - state->conflicts = 0; - timerclear(&state->stop); - return 0; + r = bind_dhcp(state, options); + if (!(state->options & DHCPCD_ARP)) { + if (!(state->options & DHCPCD_INFORM)) + logger(LOG_DEBUG, "renew in %ld seconds", + (long int)state->stop.tv_sec); + return r; } -#endif - - return bind_dhcp(state, options); + state->state = STATE_ANNOUNCING; + if (state->options & DHCPCD_FORKED) + return r; + return 1; } static int @@ -1269,7 +1513,7 @@ handle_dhcp_packet(struct if_state *state, const struct options *options) { uint8_t *packet; struct interface *iface = state->interface; - struct dhcp_message *dhcp; + struct dhcp_message *dhcp = NULL; const uint8_t *pp; uint8_t *p; ssize_t bytes; @@ -1279,7 +1523,6 @@ handle_dhcp_packet(struct if_state *state, const struct options *options) * 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. */ packet = xmalloc(udp_dhcp_len); - dhcp = xmalloc(sizeof(*dhcp)); for(;;) { bytes = get_raw_packet(iface, ETHERTYPE_IP, packet, udp_dhcp_len); @@ -1296,11 +1539,14 @@ handle_dhcp_packet(struct if_state *state, const struct options *options) logger(LOG_ERR, "packet greater than DHCP size"); continue; } + if (!dhcp) + dhcp = xmalloc(sizeof(*dhcp)); memcpy(dhcp, pp, bytes); if (dhcp->cookie != htonl(MAGIC_COOKIE)) { logger(LOG_DEBUG, "bogus cookie, ignoring"); continue; } + /* Ensure it's the right transaction */ if (state->xid != dhcp->xid) { logger(LOG_DEBUG, "ignoring packet with xid 0x%x as" @@ -1308,25 +1554,28 @@ handle_dhcp_packet(struct if_state *state, const struct options *options) dhcp->xid, state->xid); continue; } + /* Ensure packet is for us */ + if (iface->hwlen <= sizeof(dhcp->chaddr) && + memcmp(dhcp->chaddr, iface->hwaddr, iface->hwlen)) + { + logger(LOG_DEBUG, "xid 0x%x is not for our hwaddr %s", + dhcp->xid, + hwaddr_ntoa(dhcp->chaddr, sizeof(dhcp->chaddr))); + continue; + } /* We should ensure that the packet is terminated correctly * if we have space for the terminator */ if ((size_t)bytes < sizeof(struct dhcp_message)) { p = (uint8_t *)dhcp + bytes - 1; - while (p > dhcp->options && *p == DHCP_PAD) + while (p > dhcp->options && *p == DHO_PAD) p--; - if (*p != DHCP_END) - *++p = DHCP_END; + if (*p != DHO_END) + *++p = DHO_END; } - free(packet); - if (handle_dhcp(state, &dhcp, options) == 0) { - /* Fake the fact we forked so we return 0 to userland */ - if (state->options & DHCPCD_TEST) - state->options |= DHCPCD_FORKED; - else - return 0; - } - if (state->options & DHCPCD_FORKED) - return -1; + retval = handle_dhcp(state, &dhcp, options); + if (retval == 0 && state->options & DHCPCD_TEST) + state->options |= DHCPCD_FORKED; + break; } free(packet); @@ -1334,7 +1583,6 @@ handle_dhcp_packet(struct if_state *state, const struct options *options) return retval; } -#ifdef ENABLE_ARP static int handle_arp_packet(struct if_state *state) { @@ -1347,7 +1595,6 @@ handle_arp_packet(struct if_state *state) struct interface *iface = state->interface; state->fail.s_addr = 0; - for(;;) { bytes = get_raw_packet(iface, ETHERTYPE_ARP, arp_reply, sizeof(arp_reply)); @@ -1373,6 +1620,10 @@ handle_arp_packet(struct if_state *state) /* Ensure we got all the data */ if ((hw_t + reply.ar_hln + reply.ar_pln) - arp_reply > bytes) continue; + /* Ignore messages from ourself */ + if (reply.ar_hln == iface->hwlen && + memcmp(hw_s, iface->hwaddr, iface->hwlen) == 0) + continue; /* Copy out the IP addresses */ memcpy(&reply_s, hw_s + reply.ar_hln, reply.ar_pln); memcpy(&reply_t, hw_t + reply.ar_hln, reply.ar_pln); @@ -1380,19 +1631,13 @@ handle_arp_packet(struct if_state *state) /* Check for conflict */ if (state->offer && (reply_s == state->offer->yiaddr || - (reply_t == state->offer->yiaddr && - reply.ar_op == htons(ARPOP_REQUEST) && - (iface->hwlen != reply.ar_hln || - memcmp(hw_s, iface->hwaddr, iface->hwlen) != 0)))) + (reply_s == 0 && reply_t == state->offer->yiaddr))) state->fail.s_addr = state->offer->yiaddr; /* Handle IPv4LL conflicts */ if (IN_LINKLOCAL(htonl(iface->addr.s_addr)) && (reply_s == iface->addr.s_addr || - (reply_t == iface->addr.s_addr && - reply.ar_op == htons(ARPOP_REQUEST) && - (iface->hwlen != reply.ar_hln || - memcmp(hw_s, iface->hwaddr, iface->hwlen) != 0)))) + (reply_s == 0 && reply_t == iface->addr.s_addr))) state->fail.s_addr = iface->addr.s_addr; if (state->fail.s_addr) { @@ -1409,78 +1654,118 @@ handle_arp_packet(struct if_state *state) static int handle_arp_fail(struct if_state *state, const struct options *options) { - struct timespec ts; time_t up; + int cookie = state->offer->cookie; - if (IN_LINKLOCAL(htonl(state->fail.s_addr))) { - if (state->fail.s_addr == state->interface->addr.s_addr) { + if (!IN_LINKLOCAL(htonl(state->fail.s_addr))) { + state->state = STATE_INIT; + free(state->offer); + state->offer = NULL; + state->lease.addr.s_addr = 0; + if (!cookie) + return 1; + state->timeout.tv_sec = DHCP_ARP_FAIL; + state->timeout.tv_usec = 0; + do_socket(state, SOCKET_OPEN); + send_message(state, DHCP_DECLINE, options); + do_socket(state, SOCKET_CLOSED); + return 0; + } + + if (state->fail.s_addr == state->interface->addr.s_addr) { + if (state->state == STATE_PROBING) + /* This should only happen when SIGALRM or + * link when down/up and we have a conflict. */ + drop_config(state, "EXPIRE", options); + else { up = uptime(); if (state->defend + DEFEND_INTERVAL > up) { - drop_config(state, "FAIL", options); - state->state = STATE_PROBING; - state->timeout = 0; - state->claims = 0; - state->probes = 0; - state->conflicts = 0; - timerclear(&state->stop); - } else + drop_config(state, "EXPIRE", options); + state->conflicts = -1; + /* drop through to set conflicts to 0 */ + } else { state->defend = up; - return 0; + return 0; + } } + } + do_socket(state, SOCKET_CLOSED); + state->conflicts++; + timerclear(&state->stop); + if (state->conflicts > MAX_CONFLICTS) { + logger(LOG_ERR, "failed to obtain an IPv4LL address"); + state->state = STATE_INIT; + timerclear(&state->timeout); + if (!(state->options & DHCPCD_DAEMONISED) && + (state->options & DHCPCD_DAEMONISE)) + return -1; + return 1; + } + state->state = STATE_INIT_IPV4LL; + state->timeout.tv_sec = PROBE_WAIT; + state->timeout.tv_usec = 0; + return 0; +} - timerclear(&state->stop); - state->conflicts++; - state->timeout = 0; - state->claims = 0; - state->probes = 0; - state->state = STATE_PROBING; - free(state->offer); - if (state->conflicts > MAX_CONFLICTS) { - /* RFC 3927 says we should rate limit */ - logger(LOG_INFO, "sleeping for %d seconds", - RATE_LIMIT_INTERVAL); - ts.tv_sec = RATE_LIMIT_INTERVAL; - ts.tv_nsec = 0; - nanosleep(&ts, NULL); - } - state->offer = ipv4ll_get_dhcp(0); - return 0; +static int +handle_link(struct if_state *state) +{ + int retval; + + retval = link_changed(state->interface); + if (retval == -1) { + logger(LOG_ERR, "link_changed: %s", strerror(errno)); + return -1; } + if (retval == 0) + return 0; - do_socket(state, SOCKET_OPEN); - send_message(state, DHCP_DECLINE, options); - state->timeout = 0; - state->state = STATE_INIT; - /* RFC 2131 says that we should wait for 10 seconds - * before doing anything else */ - logger(LOG_INFO, "sleeping for 10 seconds"); - ts.tv_sec = 10; - ts.tv_nsec = 0; - nanosleep(&ts, NULL); + timerclear(&state->timeout); + switch (carrier_status(state->interface->name)) { + case -1: + logger(LOG_ERR, "carrier_status: %s", strerror(errno)); + return -1; + case 0: + if (state->carrier != LINK_DOWN) { + logger(LOG_INFO, "carrier lost"); + state->carrier = LINK_DOWN; + do_socket(state, SOCKET_CLOSED); + if (state->state != STATE_BOUND) + timerclear(&state->stop); + } + break; + default: + if (state->carrier != LINK_UP) { + logger(LOG_INFO, "carrier acquired"); + state->state = STATE_RENEW_REQUESTED; + state->carrier = LINK_UP; + timerclear(&state->stop); + return 1; + } + break; + } return 0; } -#endif int dhcp_run(const struct options *options, int *pid_fd) { struct interface *iface; struct if_state *state = NULL; - int retval = -1; - int sig; + int fd = -1, r = 0, sig; iface = read_interface(options->interface, options->metric); if (!iface) { logger(LOG_ERR, "read_interface: %s", strerror(errno)); goto eexit; } - - logger(LOG_INFO, "hardware address = %s", + logger(LOG_DEBUG, "hardware address = %s", hwaddr_ntoa(iface->hwaddr, iface->hwlen)); - state = xzalloc(sizeof(*state)); state->pid_fd = pid_fd; state->interface = iface; + if (!(options->options & DHCPCD_TEST)) + run_script(options, iface->name, "PREINIT", NULL, NULL); if (client_setup(state, options) == -1) goto eexit; @@ -1488,42 +1773,51 @@ dhcp_run(const struct options *options, int *pid_fd) goto eexit; if (signal_setup() == -1) goto eexit; - state->signal_fd = signal_fd(); + if (state->options & DHCPCD_BACKGROUND && + !(state->options & DHCPCD_DAEMONISED)) + if (daemonise(state, options) == -1) + goto eexit; + + if (state->carrier == LINK_DOWN) + logger(LOG_INFO, "waiting for carrier"); + for (;;) { - retval = wait_for_packet(state); - - /* We should always handle our signals first */ - if ((sig = (signal_read(state->signal_fd))) != -1) { - retval = handle_signal(sig, state, options); - } else if (retval == 0) - retval = handle_timeout(state, options); - else if (retval == -1) { - if (errno == EINTR) - /* The interupt will be handled above */ - retval = 0; - } else if (retval > 0) { - if (fd_hasdata(state->interface->fd) == 1) - retval = handle_dhcp_packet(state, options); -#ifdef ENABLE_ARP - else if (fd_hasdata(state->interface->arp_fd) == 1) { - retval = handle_arp_packet(state); - if (retval == -1) - retval = handle_arp_fail(state, options); - } -#endif - else - retval = 0; + if (r == 0) + r = handle_timeout(state, options); + else if (r > 0) { + if (fd == state->signal_fd) { + if ((sig = signal_read()) != -1) + r = handle_signal(sig, state, options); + } else if (fd == iface->link_fd) + r = handle_link(state); + else if (fd == iface->raw_fd) + r = handle_dhcp_packet(state, options); + else if (fd == iface->arp_fd) { + if ((r = handle_arp_packet(state)) == -1) + r = handle_arp_fail(state, options); + } else + r = 0; } - - if (retval != 0) + if (r == -1) break; + if (r == 0) { + fd = -1; + r = wait_for_fd(state, &fd); + if (r == -1 && errno == EINTR) { + r = 1; + fd = state->signal_fd; + } + } else + r = 0; } eexit: if (iface) { do_socket(state, SOCKET_CLOSED); + if (iface->link_fd != -1) + close(iface->link_fd); free_routes(iface->routes); free(iface->clientid); free(iface->buffer); @@ -1532,7 +1826,7 @@ eexit: if (state) { if (state->options & DHCPCD_FORKED) - retval = 0; + r = 0; if (state->options & DHCPCD_DAEMONISED) unlink(options->pidfile); free(state->offer); @@ -1541,5 +1835,5 @@ eexit: free(state); } - return retval; + return r; } @@ -25,6 +25,11 @@ * SUCH DAMAGE. */ +#ifdef __APPLE__ +# include <mach/mach_time.h> +# include <mach/kern_return.h> +#endif + #include <sys/param.h> #include <sys/time.h> @@ -33,7 +38,6 @@ #ifdef BSD # include <paths.h> #endif -#include <poll.h> #include <stdint.h> #include <stdio.h> #include <stdlib.h> @@ -48,6 +52,8 @@ # define _PATH_DEVNULL "/dev/null" #endif +int clock_monotonic = 0; + /* Handy routine to read very long lines in text files. * This means we read the whole line and avoid any nasty buffer overflows. */ ssize_t @@ -151,25 +157,6 @@ close_fds(void) } int -fd_hasdata(int fd) -{ - struct pollfd fds; - int retval; - - if (fd == -1) - return -1; - fds.fd = fd; - fds.events = POLLIN; - fds.revents = 0; - retval = poll(&fds, 1, 0); - if (retval == -1) - return -1; - if (retval > 0 && fds.revents & POLLIN) - return retval; - return 0; -} - -int set_cloexec(int fd) { int flags; @@ -202,41 +189,77 @@ set_nonblock(int fd) * Which is why we use CLOCK_MONOTONIC, but it is not available on all * platforms. */ +#define NO_MONOTONIC "host does not support a monotonic clock - timing can skew" int -get_time(struct timeval *tp) +get_monotonic(struct timeval *tp) { + static int posix_clock_set = 0; #if defined(_POSIX_MONOTONIC_CLOCK) && defined(CLOCK_MONOTONIC) struct timespec ts; static clockid_t posix_clock; - static int posix_clock_set = 0; - if (!posix_clock_set) { - if (sysconf(_SC_MONOTONIC_CLOCK) >= 0) + if (posix_clock_set == 0) { + if (clock_gettime(CLOCK_MONOTONIC, &ts) == 0) { posix_clock = CLOCK_MONOTONIC; - else - posix_clock = CLOCK_REALTIME; + clock_monotonic = 1; + } posix_clock_set = 1; } - if (clock_gettime(posix_clock, &ts) == -1) - return -1; + if (clock_monotonic) { + if (clock_gettime(posix_clock, &ts) == 0) { + tp->tv_sec = ts.tv_sec; + tp->tv_usec = ts.tv_nsec / 1000; + return 0; + } + } +#elif defined(__APPLE__) +#define NSEC_PER_SEC 1000000000 + /* We can use mach kernel functions here. + * This is crap though - why can't they implement clock_gettime?*/ + static struct mach_timebase_info info = { 0, 0 }; + static double factor = 0.0; + uint64_t nano; + long rem; + + if (posix_clock_set == 0) { + if (mach_timebase_info(&info) == KERN_SUCCESS) { + factor = (double)info.numer / (double)info.denom; + clock_monotonic = 1; + } + posix_clock_set = 1; + } + if (clock_monotonic) { + nano = mach_absolute_time(); + if ((info.denom != 1 || info.numer != 1) && factor != 0.0) + nano *= factor; + tp->tv_sec = nano / NSEC_PER_SEC; + rem = nano % NSEC_PER_SEC; + if (rem < 0) { + tp->tv_sec--; + rem += NSEC_PER_SEC; + } + tp->tv_usec = rem / 1000; + return 0; + } +#endif - tp->tv_sec = ts.tv_sec; - tp->tv_usec = ts.tv_nsec / 1000; - return 0; -#else + /* Something above failed, so fall back to gettimeofday */ + if (!posix_clock_set) { + logger(LOG_WARNING, NO_MONOTONIC); + posix_clock_set = 1; + } return gettimeofday(tp, NULL); -#endif } time_t uptime(void) { - struct timeval tp; + struct timeval tv; - if (get_time(&tp) == -1) + if (get_monotonic(&tv) == -1) return -1; - return tp.tv_sec; + return tv.tv_sec; } int @@ -247,7 +270,7 @@ writepid(int fd, pid_t pid) if (ftruncate(fd, (off_t)0) == -1) return -1; - snprintf(spid, sizeof(spid), "%u", pid); + snprintf(spid, sizeof(spid), "%u\n", pid); len = pwrite(fd, spid, strlen(spid), (off_t)0); if (len != (ssize_t)strlen(spid)) return -1; @@ -63,19 +63,20 @@ size_t strlcpy(char *, const char *, size_t); #endif #ifndef HAVE_CLOSEFROM -#define HAVE_CLOSEFROM 1 +# if defined(__NetBSD__) || defined(__OpenBSD__) +# define HAVE_CLOSEFROM 1 +# endif #endif -#if defined(__linux__) || defined(__FreeBSD__) -# undef HAVE_CLOSEFROM +#ifndef HAVE_CLOSEFROM int closefrom(int); #endif int close_fds(void); int set_cloexec(int); int set_nonblock(int); -int fd_hasdata(int); ssize_t get_line(char **, size_t *, FILE *); -int get_time(struct timeval *); +extern int clock_monotonic; +int get_monotonic(struct timeval *); time_t uptime(void); int writepid(int, pid_t); void *xrealloc(void *, size_t); @@ -28,34 +28,7 @@ #define CONFIG_H #define PACKAGE "dhcpcd" -#define VERSION "4.0.0-beta9" - -/* You can enable/disable various chunks of optional code here. - * You would only do this to try and shrink the end binary if dhcpcd - * was running on a low memory device */ - -/* Disable everything we possibly can. */ -#ifdef MINIMAL -# ifndef DISABLE_ARP -# define DISABLE_ARP -# endif -# ifndef DISABLE_IPV4LL -# define DISABLE_IPV4LL -# endif -#endif - -/* Enable ARP by default. */ -#ifndef DISABLE_ARP -# define ENABLE_ARP -#endif - -/* IPV4LL, aka ZeroConf, aka APIPA, aka RFC 3927. - * Needs ARP. */ -#ifndef DISABLE_IPV4LL -# ifdef ENABLE_ARP -# define ENABLE_IPV4LL -# endif -#endif +#define VERSION "4.0.1" /* * By default we don't add a local link route if we got a routeable address. @@ -64,7 +37,7 @@ * Ideally the host network scripts should add the link local route for us. * If not, you can define this to get dhcpcd to always add the link local route. */ -// #define ENABLE_IPV4LL_ALWAYSROUTE +// #define IPV4LL_ALWAYSROUTE /* Some systems do not have a working fork. */ /* #define THERE_IS_NO_FORK */ diff --git a/configure.c b/configure.c index 8c024f8..dad3bce 100644 --- a/configure.c +++ b/configure.c @@ -48,22 +48,50 @@ #define DEFAULT_PATH "PATH=/usr/bin:/usr/sbin:/bin:/sbin" + +static int +exec_script(char *const *argv, char *const *env) +{ + pid_t pid; + sigset_t full; + sigset_t old; + + /* OK, we need to block signals */ + sigfillset(&full); + sigprocmask(SIG_SETMASK, &full, &old); + signal_reset(); + + switch (pid = vfork()) { + case -1: + logger(LOG_ERR, "vfork: %s", strerror(errno)); + break; + case 0: + sigprocmask(SIG_SETMASK, &old, NULL); + execve(argv[0], argv, env); + logger(LOG_ERR, "%s: %s", argv[0], strerror(errno)); + _exit(127); + /* NOTREACHED */ + } + + /* Restore our signals */ + signal_setup(); + sigprocmask(SIG_SETMASK, &old, NULL); + return pid; +} + int -exec_script(const struct options *options, const char *iface, - const char *reason, - const struct dhcp_message *dhcpn, const struct dhcp_message *dhcpo) +run_script(const struct options *options, const char *iface, + const char *reason, + const struct dhcp_message *dhcpn, const struct dhcp_message *dhcpo) { char *const argv[2] = { UNCONST(options->script), NULL }; char **env = NULL, **ep; char *path; ssize_t e, elen; - int ret = 0; pid_t pid; int status = 0; - sigset_t full; - sigset_t old; - logger(LOG_DEBUG, "executing `%s'", options->script); + logger(LOG_DEBUG, "executing `%s', reason %s", options->script, reason); /* Make our env */ elen = 5; @@ -115,50 +143,17 @@ exec_script(const struct options *options, const char *iface, } env[elen] = '\0'; - /* OK, we need to block signals */ - sigfillset(&full); - sigprocmask(SIG_SETMASK, &full, &old); - -#ifdef THERE_IS_NO_FORK - signal_reset(); - pid = vfork(); -#else - pid = fork(); -#endif - - switch (pid) { - case -1: -#ifdef THERE_IS_NO_FORK - logger(LOG_ERR, "vfork: %s", strerror(errno)); -#else - logger(LOG_ERR, "fork: %s", strerror(errno)); -#endif - ret = -1; - break; - case 0: -#ifndef THERE_IS_NO_FORK - signal_reset(); -#endif - sigprocmask(SIG_SETMASK, &old, NULL); - execve(options->script, argv, env); - logger(LOG_ERR, "%s: %s", options->script, strerror(errno)); - _exit(111); - /* NOTREACHED */ - } - -#ifdef THERE_IS_NO_FORK - signal_setup(); -#endif - - /* Restore our signals */ - sigprocmask(SIG_SETMASK, &old, NULL); - - /* Wait for the script to finish */ - while (waitpid(pid, &status, 0) == -1) { - if (errno != EINTR) { - logger(LOG_ERR, "waitpid: %s", strerror(errno)); - status = -1; - break; + pid = exec_script(argv, env); + if (pid == -1) + status = -1; + else if (pid != 0) { + /* Wait for the script to finish */ + while (waitpid(pid, &status, 0) == -1) { + if (errno != EINTR) { + logger(LOG_ERR, "waitpid: %s", strerror(errno)); + status = -1; + break; + } } } @@ -167,7 +162,6 @@ exec_script(const struct options *options, const char *iface, while (*ep) free(*ep++); free(env); - return status; } @@ -193,14 +187,13 @@ delete_route(const char *iface, struct rt *rt, int metric) int retval; addr = xstrdup(inet_ntoa(rt->dest)); - logger(LOG_DEBUG, "removing route %s/%d via %s", - addr, inet_ntocidr(rt->net), inet_ntoa(rt->gate)); + logger(LOG_DEBUG, "deleting route %s/%d via %s", + addr, inet_ntocidr(rt->net), inet_ntoa(rt->gate)); free(addr); retval = del_route(iface, &rt->dest, &rt->net, &rt->gate, metric); - if (retval != 0) + if (retval != 0 && errno != ENOENT && errno != ESRCH) logger(LOG_ERR," del_route: %s", strerror(errno)); return retval; - } static int @@ -245,22 +238,9 @@ configure_routes(struct interface *iface, const struct dhcp_message *dhcp, int retval = 0; char *addr; -#ifdef THERE_IS_NO_FORK - char *skipp; - size_t skiplen; - int skip = 0; - - free(dhcpcd_skiproutes); - /* We can never have more than 255 routes. So we need space - * for 255 3 digit numbers and commas */ - skiplen = 255 * 4 + 1; - skipp = dhcpcd_skiproutes = xmalloc(sizeof(char) * skiplen); - *skipp = '\0'; -#endif - ort = get_option_routes(dhcp); -#ifdef ENABLE_IPV4LL_ALWAYSROUTE +#ifdef IPV4LL_ALWAYSROUTE if (options->options & DHCPCD_IPV4LL && IN_PRIVATE(ntohl(dhcp->yiaddr))) { @@ -287,43 +267,6 @@ configure_routes(struct interface *iface, const struct dhcp_message *dhcp, } #endif -#ifdef THERE_IS_NO_FORK - if (dhcpcd_skiproutes) { - int i = -1; - char *sk, *skp, *token; - free_routes(iface->routes); - for (rt = ort; rt; rt = rt->next) { - i++; - /* Check that we did add this route or not */ - sk = skp = xstrdup(dhcpcd_skiproutes); - while ((token = strsep(&skp, ","))) { - if (isdigit((unsigned char)*token) && - atoi(token) == i) - break; - } - free(sk); - if (token) - continue; - if (nr) { - rtn->next = xmalloc(sizeof(*rtn)); - rtn = rtn->next; - } else { - nr = rtn = xmalloc(sizeof(*rtn)); - } - rtn->dest.s_addr = rt->dest.s_addr; - rtn->net.s_addr = rt->net.s_addr; - rtn->gate.s_addr = rt->gate.s_addr; - rtn->next = NULL; - } - iface->routes = nr; - nr = NULL; - - /* We no longer need this */ - free(dhcpcd_skiproutes); - dhcpcd_skiproutes = NULL; - } -#endif - /* Now remove old routes we no longer use. * We should do this in reverse order. */ iface->routes = reverse_routes(iface->routes); @@ -370,37 +313,25 @@ configure_routes(struct interface *iface, const struct dhcp_message *dhcp, rtn->gate.s_addr = rt->gate.s_addr; rtn->next = NULL; } -#ifdef THERE_IS_NO_FORK - /* If we have daemonised yet we need to record which routes - * we failed to add so we can skip them */ - else if (!(options->options & DHCPCD_DAEMONISED)) { - /* We can never have more than 255 / 4 routes, - * so 3 chars is plently */ - printf("foo\n"); - if (*skipp) - *skipp++ = ','; - skipp += snprintf(skipp, - dhcpcd_skiproutes + skiplen - skipp, - "%d", skip); - } - skip++; -#endif } free_routes(ort); free_routes(iface->routes); iface->routes = nr; + return retval; +} -#ifdef THERE_IS_NO_FORK - if (dhcpcd_skiproutes) { - if (*dhcpcd_skiproutes) - *skipp = '\0'; - else { - free(dhcpcd_skiproutes); - dhcpcd_skiproutes = NULL; - } - } -#endif - +static int +delete_address(struct interface *iface) +{ + int retval; + logger(LOG_DEBUG, "deleting IP address %s/%d", + inet_ntoa(iface->addr), + inet_ntocidr(iface->net)); + retval = del_address(iface->name, &iface->addr, &iface->net); + if (retval == -1 && errno != EADDRNOTAVAIL) + logger(LOG_ERR, "del_address: %s", strerror(errno)); + iface->addr.s_addr = 0; + iface->net.s_addr = 0; return retval; } @@ -419,14 +350,16 @@ configure(struct interface *iface, const char *reason, #endif /* Grab our IP config */ - if (dhcp == NULL || dhcp->yiaddr == 0) + if (dhcp == NULL) up = 0; else { addr.s_addr = dhcp->yiaddr; + if (addr.s_addr == 0) + addr.s_addr = lease->addr.s_addr; /* Ensure we have all the needed values */ - if (get_option_addr(&net.s_addr, dhcp, DHCP_SUBNETMASK) == -1) + if (get_option_addr(&net.s_addr, dhcp, DHO_SUBNETMASK) == -1) net.s_addr = get_netmask(addr.s_addr); - if (get_option_addr(&brd.s_addr, dhcp, DHCP_BROADCAST) == -1) + if (get_option_addr(&brd.s_addr, dhcp, DHO_BROADCAST) == -1) brd.s_addr = addr.s_addr | ~net.s_addr; } @@ -435,19 +368,10 @@ configure(struct interface *iface, const char *reason, /* Only reset things if we had set them before */ if (iface->addr.s_addr != 0) { delete_routes(iface, options->metric); - logger(LOG_DEBUG, "deleting IP address %s/%d", - inet_ntoa(iface->addr), - inet_ntocidr(iface->net)); - if (del_address(iface->name, &iface->addr, - &iface->net) == -1 && - errno != ENOENT) - logger(LOG_ERR, "del_address: %s", - strerror(errno)); - iface->addr.s_addr = 0; - iface->net.s_addr = 0; + delete_address(iface); } - exec_script(options, iface->name, reason, NULL, old); + run_script(options, iface->name, reason, NULL, old); return 0; } @@ -466,8 +390,8 @@ configure(struct interface *iface, const char *reason, /* Now delete the old address if different */ if (iface->addr.s_addr != addr.s_addr && - iface->addr.s_addr != 0) - del_address(iface->name, &iface->addr, &iface->net); + iface->addr.s_addr != 0) + delete_address(iface); #ifdef __linux__ /* On linux, we need to change the subnet route to have our metric. */ @@ -491,6 +415,6 @@ configure(struct interface *iface, const char *reason, if (write_lease(iface, dhcp) == -1) logger(LOG_ERR, "write_lease: %s", strerror(errno)); - exec_script(options, iface->name, reason, dhcp, old); + run_script(options, iface->name, reason, dhcp, old); return 0; } diff --git a/configure.h b/configure.h index c4c731c..fe065db 100644 --- a/configure.h +++ b/configure.h @@ -32,8 +32,8 @@ #include "dhcp.h" #include "net.h" -int exec_script(const struct options *, const char *, const char *, - const struct dhcp_message *, const struct dhcp_message *); +int run_script(const struct options *, const char *, const char *, + const struct dhcp_message *, const struct dhcp_message *); int configure(struct interface *, const char *, const struct dhcp_message *, const struct dhcp_message *, const struct dhcp_lease *, const struct options *, int); @@ -137,6 +137,7 @@ static const struct dhcp_opt const dhcp_opts[] = { { 75, IPV4 | ARRAY, "streettalk_server" }, { 76, IPV4 | ARRAY, "streettalk_directory_assistance_server" }, { 77, STRING, "user_class" }, + { 81, STRING | RFC3397, "fqdn_name" }, { 85, IPV4 | ARRAY, "nds_servers" }, { 86, STRING, "nds_tree_name" }, { 87, STRING, "nds_context" }, @@ -151,7 +152,7 @@ static const struct dhcp_opt const dhcp_opts[] = { { 118, IPV4, "subnet_selection" }, { 119, STRING | RFC3397, "domain_search" }, { 121, RFC3442 | REQUEST, "classless_static_routes" }, - { 249, RFC3442, "ms-classless_static_routes" }, + { 249, RFC3442, "ms_classless_static_routes" }, { 0, 0, NULL } }; @@ -165,11 +166,11 @@ print_options(void) printf("%03d %s\n", opt->option, opt->var); } -int make_reqmask(uint8_t *mask, char **opts, int add) +int make_option_mask(uint8_t *mask, char **opts, int add) { - char *token; - char *p = *opts; + char *token, *p = *opts, *t; const struct dhcp_opt *opt; + int match, n; while ((token = strsep(&p, ", "))) { if (*token == '\0') @@ -177,13 +178,23 @@ int make_reqmask(uint8_t *mask, char **opts, int add) for (opt = dhcp_opts; opt->option; opt++) { if (!opt->var) continue; - if (strcmp(opt->var, token) == 0) { + match = 0; + if (strcmp(opt->var, token) == 0) + match = 1; + else { + errno = 0; + n = strtol(token, &t, 0); + if (errno == 0 && !*t) + if (opt->option == n) + match = 1; + } + if (match) { if (add == 1) - add_reqmask(mask, - opt->option); + add_option_mask(mask, + opt->option); else - del_reqmask(mask, - opt->option); + del_option_mask(mask, + opt->option); break; } } @@ -268,9 +279,9 @@ get_option(const struct dhcp_message *dhcp, uint8_t opt, int *len, int *type) bl += ol; } switch (o) { - case DHCP_PAD: + case DHO_PAD: continue; - case DHCP_END: + case DHO_END: if (overl & 1) { /* bit 1 set means parse boot file */ overl &= ~1; @@ -284,7 +295,7 @@ get_option(const struct dhcp_message *dhcp, uint8_t opt, int *len, int *type) } else goto exit; break; - case DHCP_OPTIONSOVERLOADED: + case DHO_OPTIONSOVERLOADED: /* Ensure we only get this option once */ if (!overl) overl = p[1]; @@ -295,7 +306,7 @@ get_option(const struct dhcp_message *dhcp, uint8_t opt, int *len, int *type) } exit: - if (valid_length(o, bl, type) == -1) { + if (valid_length(opt, bl, type) == -1) { errno = EINVAL; return NULL; } @@ -372,7 +383,9 @@ decode_rfc3397(char *out, ssize_t len, int pl, const uint8_t *p) while (q - p < pl) { r = NULL; hops = 0; - while ((l = *q++)) { + /* We check we are inside our length again incase + * the data is NOT terminated correctly. */ + while ((l = *q++) && q - p < pl) { ltype = l & 0xc0; if (ltype == 0x80 || ltype == 0x40) return 0; @@ -583,7 +596,7 @@ decode_rfc3361(int dl, const uint8_t *data) char * get_option_string(const struct dhcp_message *dhcp, uint8_t option) { - int type; + int type = 0; int len; const uint8_t *p; char *s; @@ -654,10 +667,10 @@ get_option_routes(const struct dhcp_message *dhcp) int len; /* If we have CSR's then we MUST use these only */ - p = get_option(dhcp, DHCP_CSR, &len, NULL); + p = get_option(dhcp, DHO_CSR, &len, NULL); /* Check for crappy MS option */ if (!p) - p = get_option(dhcp, DHCP_MSCSR, &len, NULL); + p = get_option(dhcp, DHO_MSCSR, &len, NULL); if (p) { routes = decode_rfc3442_rt(len, p); if (routes) @@ -665,7 +678,7 @@ get_option_routes(const struct dhcp_message *dhcp) } /* OK, get our static routes first. */ - p = get_option(dhcp, DHCP_STATICROUTE, &len, NULL); + p = get_option(dhcp, DHO_STATICROUTE, &len, NULL); if (p) { e = p + len; while (p < e) { @@ -684,7 +697,7 @@ get_option_routes(const struct dhcp_message *dhcp) } /* Now grab our routers */ - p = get_option(dhcp, DHCP_ROUTER, &len, NULL); + p = get_option(dhcp, DHO_ROUTER, &len, NULL); if (p) { e = p + len; while (p < e) { @@ -701,26 +714,51 @@ get_option_routes(const struct dhcp_message *dhcp) return routes; } +static size_t +encode_rfc1035(const char *src, uint8_t *dst, size_t len) +{ + const char *c = src; + uint8_t *p = dst; + uint8_t *lp = p++; + + if (len == 0) + return 0; + while (c < src + len) { + if (*c == '\0') + break; + if (*c == '.') { + /* Skip the trailing . */ + if (c == src + len - 1) + break; + *lp = p - lp - 1; + if (*lp == '\0') + return p - dst; + lp = p++; + } else + *p++ = (uint8_t) *c; + c++; + } + *lp = p - lp - 1; + *p++ = '\0'; + return p - dst; +} + ssize_t make_message(struct dhcp_message **message, const struct interface *iface, const struct dhcp_lease *lease, uint32_t xid, uint8_t type, const struct options *options) { struct dhcp_message *dhcp; - uint8_t *m, *p; + uint8_t *m, *lp, *p; uint8_t *n_params = NULL; time_t up = uptime() - iface->start_uptime; uint32_t ul; uint16_t sz; const struct dhcp_opt *opt; -#ifndef MINIMAL - uint8_t *d; - const char *c; -#endif dhcp = xzalloc(sizeof (*dhcp)); m = (uint8_t *)dhcp; - p = (uint8_t *)&dhcp->options; + p = dhcp->options; if ((type == DHCP_INFORM || type == DHCP_RELEASE || @@ -760,12 +798,12 @@ make_message(struct dhcp_message **message, dhcp->xid = xid; dhcp->cookie = htonl(MAGIC_COOKIE); - *p++ = DHCP_MESSAGETYPE; + *p++ = DHO_MESSAGETYPE; *p++ = 1; *p++ = type; if (type == DHCP_REQUEST) { - *p++ = DHCP_MAXMESSAGESIZE; + *p++ = DHO_MAXMESSAGESIZE; *p++ = 2; sz = get_mtu(iface->name); if (sz < MTU_MIN) { @@ -777,27 +815,26 @@ make_message(struct dhcp_message **message, p += 2; } -#ifndef MINIMAL if (iface->clientid) { - *p++ = DHCP_CLIENTID; + *p++ = DHO_CLIENTID; memcpy(p, iface->clientid, iface->clientid[0] + 1); p += iface->clientid[0] + 1; } if (type != DHCP_DECLINE && type != DHCP_RELEASE) { if (options->userclass[0]) { - *p++ = DHCP_USERCLASS; + *p++ = DHO_USERCLASS; memcpy(p, options->userclass, options->userclass[0] + 1); p += options->userclass[0] + 1; } - if (options->classid[0]) { - *p++ = DHCP_CLASSID; - memcpy(p, options->classid, options->classid[0] + 1); - p += options->classid[0] + 1; + if (options->vendorclassid[0]) { + *p++ = DHO_VENDORCLASSID; + memcpy(p, options->vendorclassid, + options->vendorclassid[0] + 1); + p += options->vendorclassid[0] + 1; } } -#endif if (type == DHCP_DISCOVER || type == DHCP_REQUEST) { #define PUTADDR(_type, _val) \ @@ -811,14 +848,14 @@ make_message(struct dhcp_message **message, lease->addr.s_addr != iface->addr.s_addr && !IN_LINKLOCAL(ntohl(lease->addr.s_addr))) { - PUTADDR(DHCP_IPADDRESS, lease->addr); + PUTADDR(DHO_IPADDRESS, lease->addr); if (lease->server.s_addr) - PUTADDR(DHCP_SERVERID, lease->server); + PUTADDR(DHO_SERVERID, lease->server); } #undef PUTADDR if (options->leasetime != 0) { - *p++ = DHCP_LEASETIME; + *p++ = DHO_LEASETIME; *p++ = 4; ul = htonl(options->leasetime); memcpy(p, &ul, 4); @@ -830,61 +867,52 @@ make_message(struct dhcp_message **message, type == DHCP_INFORM || type == DHCP_REQUEST) { -#ifndef MINIMAL if (options->hostname[0]) { - if (options->fqdn == FQDN_DISABLE) { - *p++ = DHCP_HOSTNAME; - memcpy(p, options->hostname, options->hostname[0] + 1); - p += options->hostname[0] + 1; - } else { - /* Draft IETF DHC-FQDN option (81) */ - *p++ = DHCP_FQDN; - *p++ = options->hostname[0] + 4; - /* - * 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) | 0x4; - *p++ = 0; /* from server for PTR RR */ - *p++ = 0; /* from server for A RR if S=1 */ - c = options->hostname + 1; - d = p++; - while (*c) { - if (*c == '.') { - *d = p - d - 1; - d = p++; - } else - *p++ = (uint8_t) *c; - c++; - } - *p ++ = 0; - } + *p++ = DHO_HOSTNAME; + memcpy(p, options->hostname, options->hostname[0] + 1); + p += options->hostname[0] + 1; + } + if (options->fqdn != FQDN_DISABLE) { + /* IETF DHC-FQDN option (81), RFC4702 */ + *p++ = DHO_FQDN; + lp = p; + *p++ = 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 & 0x09) | 0x04; + *p++ = 0; /* from server for PTR RR */ + *p++ = 0; /* from server for A RR if S=1 */ + ul = encode_rfc1035(options->hostname + 1, p, + options->hostname[0]); + *lp += ul; + p += ul; } /* vendor is already encoded correctly, so just add it */ if (options->vendor[0]) { - *p++ = DHCP_VENDOR; + *p++ = DHO_VENDOR; memcpy(p, options->vendor, options->vendor[0] + 1); p += options->vendor[0] + 1; } -#endif - *p++ = DHCP_PARAMETERREQUESTLIST; + *p++ = DHO_PARAMETERREQUESTLIST; n_params = p; *p++ = 0; for (opt = dhcp_opts; opt->option; opt++) { if (!(opt->type & REQUEST || - has_reqmask(options->reqmask, opt->option))) + has_option_mask(options->requestmask, opt->option))) continue; switch (opt->option) { - case DHCP_RENEWALTIME: /* FALLTHROUGH */ - case DHCP_REBINDTIME: + case DHO_RENEWALTIME: /* FALLTHROUGH */ + case DHO_REBINDTIME: if (type == DHCP_INFORM) continue; break; @@ -893,14 +921,14 @@ make_message(struct dhcp_message **message, } *n_params = p - n_params - 1; } - *p++ = DHCP_END; + *p++ = DHO_END; #ifdef BOOTP_MESSAGE_LENTH_MIN /* Some crappy DHCP servers think they have to obey the BOOTP minimum * message length. * They are wrong, but we should still cater for them. */ while (p - m < BOOTP_MESSAGE_LENTH_MIN) - *p++ = DHCP_PAD; + *p++ = DHO_PAD; #endif *message = dhcp; @@ -931,12 +959,12 @@ write_lease(const struct interface *iface, const struct dhcp_message *dhcp) /* Only write as much as we need */ while (p < e) { o = *p; - if (o == DHCP_END) { + if (o == DHO_END) { bytes = p - (const uint8_t *)dhcp; break; } p++; - if (o != DHCP_PAD) { + if (o != DHO_PAD) { l = *p++; p += l; } @@ -1149,13 +1177,13 @@ configure_env(char **env, const char *prefix, const struct dhcp_message *dhcp, char cidr[4]; uint8_t overl = 0; - get_option_uint8(&overl, dhcp, DHCP_OPTIONSOVERLOADED); + get_option_uint8(&overl, dhcp, DHO_OPTIONSOVERLOADED); if (!env) { for (opt = dhcp_opts; opt->option; opt++) { if (!opt->var) continue; - if (has_reqmask(options->nomask, opt->option)) + if (has_option_mask(options->nomask, opt->option)) continue; if (get_option_raw(dhcp, opt->option)) e++; @@ -1175,14 +1203,14 @@ configure_env(char **env, const char *prefix, const struct dhcp_message *dhcp, * message but are not necessarily in the options */ addr.s_addr = dhcp->yiaddr; setvar(&ep, prefix, "ip_address", inet_ntoa(addr)); - if (get_option_addr(&net.s_addr, dhcp, DHCP_SUBNETMASK) == -1) { + if (get_option_addr(&net.s_addr, dhcp, DHO_SUBNETMASK) == -1) { net.s_addr = get_netmask(addr.s_addr); setvar(&ep, prefix, "subnet_mask", inet_ntoa(net)); } i = inet_ntocidr(net); snprintf(cidr, sizeof(cidr), "%d", inet_ntocidr(net)); setvar(&ep, prefix, "subnet_cidr", cidr); - if (get_option_addr(&brd.s_addr, dhcp, DHCP_BROADCAST) == -1) { + if (get_option_addr(&brd.s_addr, dhcp, DHO_BROADCAST) == -1) { brd.s_addr = addr.s_addr | ~net.s_addr; setvar(&ep, prefix, "broadcast_address", inet_ntoa(net)); } @@ -1198,12 +1226,17 @@ configure_env(char **env, const char *prefix, const struct dhcp_message *dhcp, for (opt = dhcp_opts; opt->option; opt++) { if (!opt->var) continue; - if (has_reqmask(options->nomask, opt->option)) + if (has_option_mask(options->nomask, opt->option)) continue; val = NULL; p = get_option(dhcp, opt->option, &pl, NULL); if (!p) continue; + /* We only want the FQDN name */ + if (opt->option == DHO_FQDN) { + p += 3; + pl -= 3; + } len = print_option(NULL, 0, opt->type, pl, p); if (len < 0) return -1; @@ -62,60 +62,52 @@ #define DHCP_INFORM 8 /* DHCP options */ -enum DHCP_OPTIONS +enum DHO { - DHCP_PAD = 0, - DHCP_SUBNETMASK = 1, - DHCP_ROUTER = 3, - DHCP_DNSSERVER = 6, - DHCP_HOSTNAME = 12, - DHCP_DNSDOMAIN = 15, - DHCP_MTU = 26, - DHCP_BROADCAST = 28, - DHCP_STATICROUTE = 33, - DHCP_NISDOMAIN = 40, - DHCP_NISSERVER = 41, - DHCP_NTPSERVER = 42, - DHCP_VENDOR = 43, - DHCP_IPADDRESS = 50, - DHCP_LEASETIME = 51, - DHCP_OPTIONSOVERLOADED = 52, - DHCP_MESSAGETYPE = 53, - DHCP_SERVERID = 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_MSCSR = 249, /* MS code for RFC 3442 */ - DHCP_END = 255 + DHO_PAD = 0, + DHO_SUBNETMASK = 1, + DHO_ROUTER = 3, + DHO_DNSSERVER = 6, + DHO_HOSTNAME = 12, + DHO_DNSDOMAIN = 15, + DHO_MTU = 26, + DHO_BROADCAST = 28, + DHO_STATICROUTE = 33, + DHO_NISDOMAIN = 40, + DHO_NISSERVER = 41, + DHO_NTPSERVER = 42, + DHO_VENDOR = 43, + DHO_IPADDRESS = 50, + DHO_LEASETIME = 51, + DHO_OPTIONSOVERLOADED = 52, + DHO_MESSAGETYPE = 53, + DHO_SERVERID = 54, + DHO_PARAMETERREQUESTLIST = 55, + DHO_MESSAGE = 56, + DHO_MAXMESSAGESIZE = 57, + DHO_RENEWALTIME = 58, + DHO_REBINDTIME = 59, + DHO_VENDORCLASSID = 60, + DHO_CLIENTID = 61, + DHO_USERCLASS = 77, /* RFC 3004 */ + DHO_FQDN = 81, + DHO_DNSSEARCH = 119, /* RFC 3397 */ + DHO_CSR = 121, /* RFC 3442 */ + DHO_MSCSR = 249, /* MS code for RFC 3442 */ + DHO_END = 255 }; -/* SetFQDNHostName values - lsnybble used in flags - * byte (see buildmsg.c), hsnybble to create order +/* FQDN values - lsnybble used in flags + * hsnybble to create order * and to allow 0x00 to mean disable */ -enum FQQN { +enum FQDN { FQDN_DISABLE = 0x00, FQDN_NONE = 0x18, FQDN_PTR = 0x20, FQDN_BOTH = 0x31 }; -struct fqdn -{ - uint8_t flags; - uint8_t r1; - uint8_t r2; - char *name; -}; - /* Sizes for DHCP options */ #define DHCP_CHADDR_LEN 16 #define SERVERNAME_LEN 64 @@ -157,14 +149,15 @@ struct dhcp_lease { uint32_t renewaltime; uint32_t rebindtime; struct in_addr server; - uint32_t leasedfrom; + time_t leasedfrom; + struct timeval boundtime; uint8_t frominfo; }; -#define add_reqmask(var, val) (var[val >> 3] |= 1 << (val & 7)) -#define del_reqmask(var, val) (var[val >> 3] &= ~(1 << (val & 7))) -#define has_reqmask(var, val) (var[val >> 3] & (1 << (val & 7))) -int make_reqmask(uint8_t *, char **, int); +#define add_option_mask(var, val) (var[val >> 3] |= 1 << (val & 7)) +#define del_option_mask(var, val) (var[val >> 3] &= ~(1 << (val & 7))) +#define has_option_mask(var, val) (var[val >> 3] & (1 << (val & 7))) +int make_option_mask(uint8_t *, char **, int); void print_options(void); char *get_option_string(const struct dhcp_message *, uint8_t); int get_option_addr(uint32_t *, const struct dhcp_message *, uint8_t); diff --git a/dhcpcd-hooks/01-test b/dhcpcd-hooks/01-test index d3ca40d..609b3a1 100644 --- a/dhcpcd-hooks/01-test +++ b/dhcpcd-hooks/01-test @@ -3,5 +3,5 @@ case ${reason} in TEST) set | grep "^\(interface\|metric\|pid\|reason\|skip_hooks\)=" | sort set | grep "^\(new_\|old_\)" | sort - ;; + ;; esac diff --git a/dhcpcd-hooks/20-resolv.conf b/dhcpcd-hooks/20-resolv.conf index 437c116..e757ddf 100644 --- a/dhcpcd-hooks/20-resolv.conf +++ b/dhcpcd-hooks/20-resolv.conf @@ -1,14 +1,71 @@ # Generate /etc/resolv.conf # Support resolvconf(8) if available +# We can merge other dhcpcd resolv.conf files into one like resolvconf, +# but resolvconf is preferred as other applications like VPN clients +# can readily hook into it. +# Also, resolvconf can configure local nameservers such as bind +# or dnsmasq. This is important as the libc resolver isn't that powerful. -make_resolv_conf() +resolv_conf_dir="${state_dir}/resolv.conf" + +build_resolv_conf() +{ + local cf="/etc/resolv.conf.${interface}" + local interfaces= header= search= srvs= servers= x= + + # Build a list of interfaces + interfaces=$(list_interfaces "${resolv_conf_dir}") + + # Build the resolv.conf + if [ -n "${interfaces}" ]; then + # Build the header + for x in ${interfaces}; do + header="${header}${header:+, }${x}" + done + + # Build the search list + search=$(cd "${resolv_conf_dir}"; \ + key_get_value "search " ${interfaces}) + [ -n "${search}" ] && search="search $(uniqify ${search})\n" + + # Build the nameserver list + srvs=$(cd "${resolv_conf_dir}"; \ + key_get_value "nameserver " ${interfaces}) + for x in $(uniqify ${srvs}); do + servers="${servers}nameserver ${x}\n" + done + fi + header="${signature_base}${header:+ ${from} }${header}" + + # Assemble resolv.conf using our head and tail files + [ -f "${cf}" ] && rm -f "${cf}" + echo "${header}" > "${cf}" + if [ -f /etc/resolv.conf.head ]; then + cat /etc/resolv.conf.head >> "${cf}" + else + echo "# /etc/resolv.conf.head can replace this line" >> "${cf}" + fi + printf "${search}${servers}" >> "${cf}" + if [ -f /etc/resolv.conf.tail ]; then + cat /etc/resolv.conf.tail >> "${cf}" + else + echo "# /etc/resolv.conf.tail can replace this line" >> "${cf}" + fi + mv -f "${cf}" /etc/resolv.conf +} + +add_resolv_conf() { + local x= conf="${signature}\n" + + # If we don't have any configuration, remove it if [ -z "${new_domain_name_servers}" -a \ -z "${new_domain_name}" -a \ -z "${new_domain_search}" ]; then - return 0 + remove_resolv_conf + return $? fi - local x= conf="${signature}\n" + if [ -n "${new_domain_search}" ]; then conf="${conf}search ${new_domain_search}\n" elif [ -n "${new_domain_name}" ]; then @@ -19,22 +76,32 @@ make_resolv_conf() done if type resolvconf >/dev/null 2>&1; then printf "${conf}" | resolvconf -a "${interface}" - else - save_conf /etc/resolv.conf - printf "${conf}" > /etc/resolv.conf + return $? + fi + + if [ -e "${resolv_conf_dir}/${interface}" ]; then + rm -f "${resolv_conf_dir}/${interface}" + fi + if [ ! -d "${resolv_conf_dir}" ]; then + mkdir -p "${resolv_conf_dir}" fi + printf "${conf}" > "${resolv_conf_dir}/${interface}" + build_resolv_conf } -restore_resolv_conf() +remove_resolv_conf() { if type resolvconf >/dev/null 2>&1; then resolvconf -d "${interface}" -f else - restore_conf /etc/resolv.conf || return 0 + if [ -e "${resolv_conf_dir}/${interface}" ]; then + rm -f "${resolv_conf_dir}/${interface}" + fi + build_resolv_conf fi } case "${reason}" in -BOUND|INFORM|REBIND|REBOOT|RENEW|TIMEOUT) make_resolv_conf;; -EXPIRE|FAIL|IPV4LL|RELEASE|STOP) restore_resolv_conf;; +BOUND|INFORM|REBIND|REBOOT|RENEW|TIMEOUT) add_resolv_conf;; +PREINIT|EXPIRE|FAIL|IPV4LL|RELEASE|STOP) remove_resolv_conf;; esac diff --git a/dhcpcd-hooks/29-lookup-hostname b/dhcpcd-hooks/29-lookup-hostname index b9ce458..3dfade3 100644 --- a/dhcpcd-hooks/29-lookup-hostname +++ b/dhcpcd-hooks/29-lookup-hostname @@ -2,6 +2,7 @@ lookup_hostname() { + [ -z "${new_ip_address}" ] && return 1 local h= # Silly ISC programs love to send error text to stdout if type dig >/dev/null 2>&1; then @@ -23,7 +24,7 @@ lookup_hostname() set_hostname() { - if [ -z "${new_host_name}" ]; then + if [ -z "${new_host_name}" -a -z "${new_fqdn_name}" ]; then export new_host_name="$(lookup_hostname)" fi } diff --git a/dhcpcd-hooks/30-hostname b/dhcpcd-hooks/30-hostname index 500ec0f..b2e5fc8 100644 --- a/dhcpcd-hooks/30-hostname +++ b/dhcpcd-hooks/30-hostname @@ -3,16 +3,23 @@ need_hostname() { case "$(hostname)" in - ""|"(none)"|localhost) [ -n "${new_host_name}" ];; - "${old_host_name}") true;; - *) false;; + ""|"(none)"|localhost|localhost.localdomain) + [ -n "${new_host_name}" -o -n "${new_fqdn_name}" ];; + "${old_host_name}"|"${old_fqdn_name}") + true;; + *) + false;; esac } set_hostname() { if need_hostname; then - hostname "${new_host_name}" + if [ -n "${new_host_name}" ]; then + hostname "${new_host_name}" + else + hostname "${new_fqdn_name}" + fi fi } diff --git a/dhcpcd-hooks/50-ntp.conf b/dhcpcd-hooks/50-ntp.conf index 3772215..536f14e 100644 --- a/dhcpcd-hooks/50-ntp.conf +++ b/dhcpcd-hooks/50-ntp.conf @@ -1,4 +1,6 @@ # Sample dhcpcd hook script for ntp +# Like our resolv.conf hook script, we store a database of ntp.conf files +# and merge into /etc/ntp.conf # Detect OpenRC or BSD rc # Distributions may want to just have their command here instead of this @@ -10,42 +12,71 @@ elif [ -x /usr/local/etc/rc.d/ntpd ]; then ntpd_restart_cmd="/usr/local/etc/rc.d/ntpd restart" fi -make_ntp_conf() +ntp_conf_dir="${state_dir}/ntp.conf" + +build_ntp_conf() { - [ -z "${new_ntp_servers}" ] && return 0 - local cf=/etc/ntp.conf."${interface}" x= - echo "${signature}" > "${cf}" - echo "restrict default noquery notrust nomodify" >> "${cf}" - echo "restrict 127.0.0.1" >> "${cf}" - for x in ${new_ntp_servers}; do - echo "restrict ${x} nomodify notrap noquery" >> "${cf}" - echo "server ${x}" >> "${cf}" - done - if [ ! -e /etc/ntp.conf ]; then - false - elif type cmp >/dev/null 2>&1; then - cmp -s /etc/ntp.conf "${cf}" - elif type diff >/dev/null 2>&1; then - diff -q /etc/ntp.conf "${cf}" >/dev/null - else - false + local cf="/etc/ntp.conf.${interface}" + local interfaces= header= srvs= servers= x= + + # Build a list of interfaces + interfaces=$(list_interfaces "${ntp_conf_dir}") + + if [ -n "${interfaces}" ]; then + # Build the header + for x in ${interfaces}; do + header="${header}${header:+, }${x}" + done + + # Build a server list + srvs=$(cd "${ntp_conf_dir}"; + key_get_value "server " ${interfaces}) + if [ -n "${srvs}" ]; then + for x in $(uniqify ${srvs}); do + servers="${servers}server ${x}\n" + done + fi fi - if [ $? = 0 ]; then - rm -f "${cf}" - else - save_conf /etc/ntp.conf - mv -f "${cf}" /etc/ntp.conf + + # Merge our config into ntp.conf + [ -e "${cf}" ] && rm -f "${cf}" + remove_markers "${signature_base}" "${signature_base_end}" \ + /etc/ntp.conf > "${cf}" + if [ -n "${servers}" ]; then + echo "${signature_base}${header:+ ${from} }${header}" >> "${cf}" + printf "${search}${servers}" >> "${cf}" + echo "${signature_base_end}${header:+ ${from} }${header}" >> "${cf}" + fi + + # If we changed anything, restart ntpd + if change_file /etc/ntp.conf "${cf}"; then [ -n "${ntpd_restart_cmd}" ] && ${ntpd_restart_cmd} fi } -restore_ntp_conf() +add_ntp_conf() { - restore_conf /etc/ntp.conf || return 0 - [ -n "${ntpd_restart_cmd}" ] && ${ntpd_restart_cmd} + local cf="${ntp_conf_dir}/${interface}" x= + + [ -e "${cf}" ] && rm "${cf}" + [ -d "${ntp_conf_dir}" ] || mkdir -p "${ntp_conf_dir}" + if [ -n "${new_ntp_servers}" ]; then + for x in ${new_ntp_servers}; do + echo "server ${x}" >> "${cf}" + done + fi + build_ntp_conf +} + +remove_ntp_conf() +{ + if [ -e "${ntp_conf_dir}/${interface}" ]; then + rm "${ntp_conf_dir}/${interface}" + fi + build_ntp_conf } case "${reason}" in -BOUND|INFORM|REBIND|REBOOT|RENEW|TIMEOUT) make_ntp_conf;; -EXPIRE|FAIL|IPV4LL|RELEASE|STOP) restore_ntp_conf;; +BOUND|INFORM|REBIND|REBOOT|RENEW|TIMEOUT) add_ntp_conf add;; +PREINIT|EXPIRE|FAIL|IPV4LL|RELEASE|STOP) remove_ntp_conf del;; esac diff --git a/dhcpcd-hooks/50-yp.conf b/dhcpcd-hooks/50-yp.conf index 603f267..a2296eb 100644 --- a/dhcpcd-hooks/50-yp.conf +++ b/dhcpcd-hooks/50-yp.conf @@ -10,6 +10,7 @@ make_yp_conf() { [ -z "${new_nis_domain}" -a -z "${new_nis_servers}" ] && return 0 local cf=/etc/yp.conf."${interface}" prefix= x= pid= + rm -f "${cf}" echo "${signature}" > "${cf}" if [ -n "${new_nis_domain}" ]; then domainname "${new_nis_domain}" diff --git a/dhcpcd-hooks/90-NetworkManager b/dhcpcd-hooks/90-NetworkManager index f05ccd7..c4d69fe 100644 --- a/dhcpcd-hooks/90-NetworkManager +++ b/dhcpcd-hooks/90-NetworkManager @@ -1,7 +1,8 @@ -# Hook for NetworkManager, relies on D-Bus +# Hook for NetworkManager-0.7.0 +# NOTE: NetworkManager will override the script dhcpcd calls, so this hook +# only makes sense if NetworkManager is patched NOT to override the +# script dhcpcd would call. -if type dbus-send >/dev/null 2>&1; then - dbus-send --system --dest=com.redhat.dhcp \ - --type=method_call /com/redhat/dhcp/"${interface}" \ - com.redhat.dhcp.set 'string:'"`env`" +if [ -x /usr/libexec/nm-dhcp-client.action ]; then + /usr/libexec/nm-dhcp-client.action fi diff --git a/dhcpcd-hooks/95-configured b/dhcpcd-hooks/95-configured index 1ff07cf..93f1c43 100644 --- a/dhcpcd-hooks/95-configured +++ b/dhcpcd-hooks/95-configured @@ -16,7 +16,11 @@ BOUND|INFORM|REBIND|REBOOT|RENEW|TIMEOUT) setprop dhcp.${interface}.result "ok" ;; -EXPIRE|FAIL|IPV4LL|RELEASE|STOP) +EXPIRE|FAIL|IPV4LL|STOP) setprop dhcp.${interface}.result "failed" ;; + +RELEASE) + setprop dhcp.${interface}.result "released" + ;; esac diff --git a/dhcpcd-hooks/Makefile b/dhcpcd-hooks/Makefile index 06660ac..cfb19f7 100644 --- a/dhcpcd-hooks/Makefile +++ b/dhcpcd-hooks/Makefile @@ -1,9 +1,11 @@ -LIBEXECDIR= ${PREFIX}/libexec +LIBEXECDIR?= ${PREFIX}/libexec HOOKDIR= ${LIBEXECDIR}/dhcpcd-hooks SYSTEMSCRIPTS= 01-test 10-mtu 20-resolv.conf 30-hostname FILES= ${SYSTEMSCRIPTS} ${HOOKSCRIPTS} FILESDIR= ${HOOKDIR} +all: + MK= ../mk include ${MK}/os.mk include ${MK}/sys.mk diff --git a/dhcpcd-run-hooks b/dhcpcd-run-hooks index 83534be..db9c4f8 100755 --- a/dhcpcd-run-hooks +++ b/dhcpcd-run-hooks @@ -1,29 +1,22 @@ #!/system/bin/sh # dhcpcd client configuration script -# Handy functions for our hooks to use -signature="# Generated by dhcpcd for ${interface}" -save_conf() -{ - if ls "$1" >/dev/null 2>&1; then - rm -f "$1"-pre."${interface}" - mv -f "$1" "$1"-pre."${interface}" - fi -} -restore_conf() -{ - ls "$1"-pre."${interface}" >/dev/null 2>&1 || return 1 - rm -f "$1" - mv -f "$1"-pre."${interface}" "$1" -} +# Handy variables and functions for our hooks to use +from="from" +signature_base="# Generated by dhcpcd" +signature="${signature_base} ${from} ${interface}" +signature_base_end="# End of dhcpcd" +signature_end="${signature_base_end} ${from} ${interface}" +state_dir="/data/misc/dhcpcd" # We source each script into this one so that scripts run earlier can # remove variables from the environment so later scripts don't see them. -# Thus, the user can create their dhcpcd.hook script to configure +# Thus, the user can create their dhcpcd.enter/exit-hook script to configure # /etc/resolv.conf how they want and stop the system scripts ever updating it. for hook in \ - /system/etc/dhcpcd/dhcpcd.hook \ - /system/etc/dhcpcd/dhcpcd-hooks/* + /system/etc/dhcpcd/dhcpcd.enter-hook \ + /system/etc/dhcpcd/dhcpcd-hooks/* \ + /system/etc/dhcpcd/dhcpcd.exit-hook do for skip in ${skip_hooks}; do case "${hook}" in diff --git a/dhcpcd-run-hooks.8 b/dhcpcd-run-hooks.8 index 2ba9792..dca5378 100644 --- a/dhcpcd-run-hooks.8 +++ b/dhcpcd-run-hooks.8 @@ -22,7 +22,7 @@ .\" OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF .\" SUCH DAMAGE. .\" -.Dd May 21, 2008 +.Dd August 14, 2008 .Dt DHCPCD.SH 8 SMM .Sh NAME .Nm dhcpcd-run-hooks @@ -31,11 +31,13 @@ .Nm is used by .Xr dhcpcd 8 -to run any system or user defined hook scripts. +to run any system and user defined hook scripts. System hook scripts are found in .Pa /system/etc/dhcpcd/dhcpcd-hooks -and the user defined hook is -.Pa /system/etc/dhcpcd/dhcpcd.hook . +and the user defined hooks are +.Pa /system/etc/dhcpcd/dhcpcd.enter-hook . +and +.Pa /system/etc/dhcpcd/dhcpcd.exit-hook . The default install supplies hook scripts for configuring .Pa /etc/resolv.conf and the hostname. @@ -67,6 +69,11 @@ Here's a list of reasons why .Nm could be invoked: .Bl -tag -width indent +.It Dv PREINIT +dhcpcd is starting up and any pre-initialisation should be done. +.It Dv INFORM +dhcpcd informed a DHCP server about it's address and obtained other +configuration details. .It Dv BOUND dhcpcd obtained a new lease from a DHCP server. .It Dv RENEW @@ -93,10 +100,11 @@ script to process them. When .Nm runs, it loads -.Pa /system/etc/dhcpcd/dhcpcd.hook +.Pa /system/etc/dhcpcd/dhcpcd.enter-hook and any scripts found in .Pa /system/etc/dhcpcd/dhcpcd-hooks -in a lexical order. +in a lexical order and then finally +.Pa /system/etc/dhcpcd/dhcpcd.exit-hook .Sh SEE ALSO .Xr dhcpcd 8 .Sh AUTHORS diff --git a/dhcpcd-run-hooks.8.in b/dhcpcd-run-hooks.8.in index a545203..72669f5 100644 --- a/dhcpcd-run-hooks.8.in +++ b/dhcpcd-run-hooks.8.in @@ -22,7 +22,7 @@ .\" OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF .\" SUCH DAMAGE. .\" -.Dd May 21, 2008 +.Dd August 14, 2008 .Dt DHCPCD.SH 8 SMM .Sh NAME .Nm dhcpcd-run-hooks @@ -31,11 +31,13 @@ .Nm is used by .Xr dhcpcd 8 -to run any system or user defined hook scripts. +to run any system and user defined hook scripts. System hook scripts are found in .Pa @HOOKDIR@ -and the user defined hook is -.Pa @SYSCONFDIR@/dhcpcd.hook . +and the user defined hooks are +.Pa @SYSCONFDIR@/dhcpcd.enter-hook . +and +.Pa @SYSCONFDIR@/dhcpcd.exit-hook . The default install supplies hook scripts for configuring .Pa /etc/resolv.conf and the hostname. @@ -67,6 +69,11 @@ Here's a list of reasons why .Nm could be invoked: .Bl -tag -width indent +.It Dv PREINIT +dhcpcd is starting up and any pre-initialisation should be done. +.It Dv INFORM +dhcpcd informed a DHCP server about it's address and obtained other +configuration details. .It Dv BOUND dhcpcd obtained a new lease from a DHCP server. .It Dv RENEW @@ -93,10 +100,11 @@ script to process them. When .Nm runs, it loads -.Pa @SYSCONFDIR@/dhcpcd.hook +.Pa @SYSCONFDIR@/dhcpcd.enter-hook and any scripts found in .Pa @HOOKDIR@ -in a lexical order. +in a lexical order and then finally +.Pa @SYSCONFDIR@/dhcpcd.exit-hook .Sh SEE ALSO .Xr dhcpcd 8 .Sh AUTHORS diff --git a/dhcpcd-run-hooks.in b/dhcpcd-run-hooks.in index 7fd8b09..1e5d5b3 100644 --- a/dhcpcd-run-hooks.in +++ b/dhcpcd-run-hooks.in @@ -1,8 +1,104 @@ #!/bin/sh # dhcpcd client configuration script -# Handy functions for our hooks to use -signature="# Generated by dhcpcd for ${interface}" +# Handy variables and functions for our hooks to use +from="from" +signature_base="# Generated by dhcpcd" +signature="${signature_base} ${from} ${interface}" +signature_base_end="# End of dhcpcd" +signature_end="${signature_base_end} ${from} ${interface}" +state_dir="/var/run/dhcpcd" + +# Ensure that all arguments are unique +uniqify() +{ + local result= + + while [ -n "$1" ]; do + case " ${result} " in + *" $1 "*);; + *) result="${result}${result:+ }$1";; + esac + shift + done + echo "${result}" +} + +# List interface config files in a dir +# We may wish to control the order at some point rather than just lexical +list_interfaces() +{ + local x= interfaces= + for x in "$1"/*; do + [ -e "${x}" ] || continue + interfaces="${interfaces}${interfaces:+ }${x##*/}" + done + echo "${interfaces}" +} + +# We normally use sed to extract values using a key from a list of files +# but sed may not always be available at the time. +key_get_value() +{ + local key="$1" value= x= line= + + shift + if type sed >/dev/null 2>&1; then + sed -n "s/^${key}//p" $@ + else + for x; do + while read line; do + case "${line}" in + "${key}"*) echo "${line##${key}}";; + esac + done < "${x}" + done + fi +} + +# We normally use sed to remove markers from a configuration file +# but sed may not always be available at the time. +remove_markers() +{ + local m1="$1" m2="$2" x= line= in_marker=0 + + shift; shift + if type sed >/dev/null 2>&1; then + sed "/^${m1}/,/^${m2}/d" $@ + else + for x; do + while read line; do + case "${line}" in + "${m1}"*) in_marker=1;; + "${m2}"*) in_marker=0;; + *) [ ${in_marker} = 0 ] && echo "${line}";; + esac + done < "${x}" + done + fi +} + +# Compare two files +# It different, replace first with second otherwise remove second +change_file() +{ + if type cmp >/dev/null 2>&1; then + cmp -s "$1" "$2" + elif type diff >/dev/null 2>&1; then + diff -q "$1" "$2" >/dev/null + else + # Hopefully we're only working on small text files ... + [ "$(cat "$1")" = "$(cat "$2")" ] + fi + if [ $? -eq 0 ]; then + rm -f "$2" + return 1 + fi + mv -f "$2" "$1" + return 0 +} + +# Save a config file save_conf() { if [ -f "$1" ]; then @@ -10,6 +106,8 @@ save_conf() mv -f "$1" "$1"-pre."${interface}" fi } + +# Restore a config file restore_conf() { [ -f "$1"-pre."${interface}" ] || return 1 @@ -17,13 +115,15 @@ restore_conf() mv -f "$1"-pre."${interface}" "$1" } + # We source each script into this one so that scripts run earlier can # remove variables from the environment so later scripts don't see them. -# Thus, the user can create their dhcpcd.hook script to configure +# Thus, the user can create their dhcpcd.enter/exit-hook script to configure # /etc/resolv.conf how they want and stop the system scripts ever updating it. for hook in \ - @SYSCONFDIR@/dhcpcd.hook \ - @HOOKDIR@/* + @SYSCONFDIR@/dhcpcd.enter-hook \ + @HOOKDIR@/* \ + @SYSCONFDIR@/dhcpcd.exit-hook do for skip in ${skip_hooks}; do case "${hook}" in @@ -22,18 +22,18 @@ .\" OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF .\" SUCH DAMAGE. .\" -.Dd Jul 08, 2008 +.Dd August 20, 2008 .Dt DHCPCD 8 SMM .Sh NAME .Nm dhcpcd .Nd an RFC 2131 compliant DHCP client .Sh SYNOPSIS .Nm -.Op Fl dknpqADEGLSTXV +.Op Fl bdknpqABDEGKLSTV .Op Fl c , -script Ar script .Op Fl f , -config Ar file .Op Fl h , -hostname Ar hostname -.Op Fl i , -classid Ar classid +.Op Fl i , -vendorclassid Ar vendorclassid .Op Fl l , -leasetime Ar seconds .Op Fl m , -metric Ar metric .Op Fl o , -option Ar option @@ -46,6 +46,8 @@ .Op Fl F , -fqdn Ar FQDN .Op Fl I , -clientid Ar clientid .Op Fl O , -nooption Ar option +.Op Fl Q , -require Ar option +.Op Fl X , -blacklist Ar address .Ar interface .Nm .Fl k , -release @@ -56,9 +58,7 @@ .Sh DESCRIPTION .Nm is an implementation of the DHCP client specified in -.Rs -.%T "RFC 2131" -.Re +.Li RFC 2131 . .Nm gets the host information .Po @@ -69,40 +69,42 @@ from a DHCP server and configures the network of the machine on which it is running. .Nm -will then write DNS information to +then runs the configuration script which writes DNS information to .Xr resolvconf 8 , if available, otherwise directly to .Pa /etc/resolv.conf . If the hostname is currenly blank, (null) or localhost then .Nm -will set the hostname to the one supplied by the DHCP server. +sets the hostname to the one supplied by the DHCP server. .Nm then daemonises and waits for the lease renewal time to lapse. Then it attempts to renew its lease and reconfigure if the new lease changes. .Ss Local Link configuration If .Nm -failed to obtain a lease, it will probe for a valid IPv4LL address +failed to obtain a lease, it probes for a valid IPv4LL address .Po -aka Zeroconf, aka APIPA +aka ZeroConf, aka APIPA .Pc . -Once obtained it will restart the process of looking for a DHCP server to get a +Once obtained it restarts the process of looking for a DHCP server to get a proper address. .Pp When using IPv4LL, .Nm -will always succeed and return a 0 exit code. To disable this behaviour, you -can use the +nearly always succeeds and returns an exit code of 0. +In the rare case it fails, it normally means that there is a reverse ARP proxy +installed which always defeats IPv4LL probing. +To disable this behaviour, you can use the .Fl L , -noipv4ll option. .Ss Hooking into DHCP events .Nm -will run +runs .Pa /system/etc/dhcpcd/dhcpcd-run-hooks , or the script specified by the .Fl c , -script option. -This script will run each script found in +This script runs each script found in .Pa /system/etc/dhcpcd/dhcpcd-hooks in a lexical order. The default installation supplies the scripts @@ -124,6 +126,10 @@ You can fine tune the behaviour of .Nm with the following options: .Bl -tag -width indent +.It Fl b , -background +Background immediately. +This is useful for startup scripts which don't disable link messages for +carrier status. .It Fl c , -script Ar script Use this .Ar script @@ -142,16 +148,16 @@ always processes the config file before any command line options. .It Fl h , -hostname Ar hostname By default, .Nm -will send the current hostname to the DHCP server so it can register in DNS. +sends the current hostname to the DHCP server so it can register in DNS. You can use this option to specify the .Ar hostname sent, or an empty string to stop any .Ar hostname from being sent. -.It Fl i , -classid Ar classid +.It Fl i , -vendorclassid Ar vendorclassid Override the -.Ar classid +.Ar vendorclassid field sent. The default is dhcpcd <version>. If not set then none is sent. @@ -163,6 +169,8 @@ process running on the to release its lease, deconfigure the .Ar interface and then exit. +.Nm +then waits until this process has exited. .It Fl l , -leasetime Ar seconds Request a specific lease time in .Ar seconds . @@ -184,14 +192,27 @@ Request the DHCP .Ar option variable for use in .Pa /system/etc/dhcpcd/dhcpcd-run-hooks . -.It Fl n , -renew +.It Fl n , -rebind Notifies an existing .Nm process running on the .Ar interface -to renew it's lease. If +to rebind it's lease. +.Nm +will not re-configure itself or use any other command line arguments. +.Nm +will timeout the rebind after 30 seconds at which point the lease will be +expired and +.Nm +will enter the discovery state to obtain a new lease. +Use the +.Fl t , -timeout +option to change this. +If .Nm is not running, then it starts up as normal. +This option used to be renew, but rebind is more accurate as we need to +broadcast the request instead of unicasting. .It Fl p , -persistent .Nm normally deconfigures the @@ -201,28 +222,31 @@ Sometimes, this isn't desirable if for example you have root mounted over NFS. You can use this option to stop this from happening. .It Fl r , -request Op Ar address .Nm -normally sends a DHCP Broadcast to find servers to offer an address. +normally sends a DHCP DISCOVER to find servers to offer an address. .Nm -will then request the address used. -You can use this option to skip the broadcast step and just request an +then requests the address used. +You can use this option to skip the BROADCAST step and just request the .Ar address . The downside is if you request an .Ar address the DHCP server does not know about or the DHCP server is not authorative, it will remain silent. -In this situation, we go back to the init state and broadcast again. +In this situation, we go back to the init state and DISCOVER again. If no .Ar address is given then the first address currently assigned to the .Ar interface is used. .It Fl s , -inform Op Ar address Ns Op Ar /cidr -Behaves exactly like +Behaves like .Fl r , -request as above, but sends a DHCP INFORM instead of a REQUEST. This does not get a lease as such, just notifies the DHCP server of the .Ar address in use. +You should also include the optional +.Ar cidr +network number in-case the address is not already configured on the interface. .Nm remains running and pretends it has an infinite lease. .Nm @@ -243,7 +267,7 @@ to wait forever to get a lease. .It Fl u , -userclass Ar class Tags the DHCP message with the userclass .Ar class . -DHCP servers use this give memebers of the class DHCP options other than the +DHCP servers use this give members of the class DHCP options other than the default, without having to know things like hardware address or hostname. .It Fl v , -vendor Ar code , Ns Ar value Add an enscapulated vendor option. @@ -252,30 +276,31 @@ should be between 1 and 254 inclusive. Examples. .Pp Set the vendor option 01 with an IP address. -.D1 dhcpcd -v 01,192.168.0.2 eth0 +.D1 dhcpcd \-v 01,192.168.0.2 eth0 Set the vendor option 02 with a hex code. -.D1 dhcpcd -v 02,01:02:03:04:05 eth0 +.D1 dhcpcd \-v 02,01:02:03:04:05 eth0 Do the above and set a third option with a string and not an IP address. -.D1 dhcpcd -v 01,192.168.0.2 -v 02,01:02:03:04:05 -v 03,\e"192.168.0.2\e" eth0 +.D1 dhcpcd \-v 01,192.168.0.2 \-v 02,01:02:03:04:05 \-v 03,\e"192.168.0.2\e" eth0 .It Fl x , -exit -This causes an existing +This will signal an existing .Nm process running on the .Ar interface to deconfigure the .Ar interface and exit. +.Nm +then waits until this process has exited. .It Fl D , -duid Generate an -.Rs -.%T "RFC 4361" -.Re +.Li RFC 4361 compliant clientid. This requires persistent storage and not all DHCP servers work with it so it's not enabled by default. -The DUID generated will be held in +.Nm +generates the DUID and stores in it .Pa /system/etc/dhcpcd/dhcpcd.duid -and should not be copied to other hosts. +This file should not be copied to other hosts. .It Fl E , -lastlease If .Nm @@ -289,12 +314,15 @@ Requests that the DHCP server updates DNS using FQDN instead of just a hostname. Valid values for .Ar fqdn -are none, ptr and both. +are disable, none, ptr and both. The current hostname or the hostname specified using the .Fl h , -hostname option must be a FQDN. .Nm itself never does any DNS updates. +.Nm +encodes the FQDN hostname as specified in +.Li RFC1035 . .It Fl I , -clientid Ar clientid Change the default clientid sent from the interface hardware address. If the string is of the format 01:02:03 then it is encoded as hex. @@ -311,12 +339,14 @@ Here are some options that deal with turning these bits off. Quiet .Nm on the command line, only warnings and errors will be displayed. -The messagea are still logged though. +The messages are still logged though. .It Fl A , -noarp Don't request or claim the address by ARP. This also disables IPv4LL. -.It Fl G , -nogateway -Don't set any default routes. +.It Fl B , -nobackground +Don't run in the background when we acquire a lease. +This is mainly useful for running under the control of another process, such +as a debugger or a network manager. .It Fl C , -nohook Ar script Don't run this hook script. Matches full name, or prefixed with 2 numbers optionally ending with @@ -324,19 +354,23 @@ Matches full name, or prefixed with 2 numbers optionally ending with .Pp So to stop dhcpcd from touching your DNS or MTU settings you would do:- .D1 dhcpcd -C resolv.conf -C mtu eth0 -.It Fl X , -nodaemonise -Don't daemonise when we acquire a lease. -This disables the -.Fl t, -timeout -option. -This is mainly useful for running under the control of another process, such -as a debugger or a network manager. +.It Fl G , -nogateway +Don't set any default routes. +.It Fl K , -nolink +Don't receive link messages for carrier status. +You should only have to use this with buggy device drivers or running +.Nm +through a network manager. .It Fl L , -noipv4ll -Don't use IPv4LL at all. +Don't use IPv4LL (aka APIPA, aka Bonjour, aka ZeroConf). .It Fl O , -nooption Ar option Don't request the specified option. If no option given, then don't request any options other than those to configure the interface and routing. +.It Fl Q , -require Ar option +Requires the +.Ar option +to be present in all DHCP messages, otherwise the message is ignored. .It Fl T, -test On receipt of OFFER messages just call .Pa /system/etc/dhcpcd/dhcpcd-run-hooks @@ -347,6 +381,13 @@ files. .It Fl V, -variables Display a list of option codes and the associated variable for use in .Xr dhcpcd-run-hooks 8 . +.It Fl X, -blacklist Ar address +Ignores all DHCP messages which have this +.Ar address +as the server ID. +This may be expanded in future releases to ignore all packets +matching either the IP or hardware +.Ar address . .El .Sh NOTES .Nm @@ -369,6 +410,11 @@ option described above. .It Pa /data/misc/dhcp/dhcpcd\- Ns Ar interface Ns .lease The actual DHCP message send by the server. We use this when reading the last lease and use the files mtime as when it was issued. +.It Pa /var/run/dhcpcd\- Ns Ar interface Ns .pid +Stores the PID of +.Nm +running on the +.Ar interface . .El .Sh SEE ALSO .Xr dhcpcd.conf 5 , diff --git a/dhcpcd.8.in b/dhcpcd.8.in index c008a5a..6c82d3f 100644 --- a/dhcpcd.8.in +++ b/dhcpcd.8.in @@ -22,18 +22,18 @@ .\" OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF .\" SUCH DAMAGE. .\" -.Dd Jul 08, 2008 +.Dd August 20, 2008 .Dt DHCPCD 8 SMM .Sh NAME .Nm dhcpcd .Nd an RFC 2131 compliant DHCP client .Sh SYNOPSIS .Nm -.Op Fl dknpqADEGLSTXV +.Op Fl bdknpqABDEGKLSTV .Op Fl c , -script Ar script .Op Fl f , -config Ar file .Op Fl h , -hostname Ar hostname -.Op Fl i , -classid Ar classid +.Op Fl i , -vendorclassid Ar vendorclassid .Op Fl l , -leasetime Ar seconds .Op Fl m , -metric Ar metric .Op Fl o , -option Ar option @@ -46,6 +46,8 @@ .Op Fl F , -fqdn Ar FQDN .Op Fl I , -clientid Ar clientid .Op Fl O , -nooption Ar option +.Op Fl Q , -require Ar option +.Op Fl X , -blacklist Ar address .Ar interface .Nm .Fl k , -release @@ -56,9 +58,7 @@ .Sh DESCRIPTION .Nm is an implementation of the DHCP client specified in -.Rs -.%T "RFC 2131" -.Re +.Li RFC 2131 . .Nm gets the host information .Po @@ -69,40 +69,42 @@ from a DHCP server and configures the network of the machine on which it is running. .Nm -will then write DNS information to +then runs the configuration script which writes DNS information to .Xr resolvconf 8 , if available, otherwise directly to .Pa /etc/resolv.conf . If the hostname is currenly blank, (null) or localhost then .Nm -will set the hostname to the one supplied by the DHCP server. +sets the hostname to the one supplied by the DHCP server. .Nm then daemonises and waits for the lease renewal time to lapse. Then it attempts to renew its lease and reconfigure if the new lease changes. .Ss Local Link configuration If .Nm -failed to obtain a lease, it will probe for a valid IPv4LL address +failed to obtain a lease, it probes for a valid IPv4LL address .Po -aka Zeroconf, aka APIPA +aka ZeroConf, aka APIPA .Pc . -Once obtained it will restart the process of looking for a DHCP server to get a +Once obtained it restarts the process of looking for a DHCP server to get a proper address. .Pp When using IPv4LL, .Nm -will always succeed and return a 0 exit code. To disable this behaviour, you -can use the +nearly always succeeds and returns an exit code of 0. +In the rare case it fails, it normally means that there is a reverse ARP proxy +installed which always defeats IPv4LL probing. +To disable this behaviour, you can use the .Fl L , -noipv4ll option. .Ss Hooking into DHCP events .Nm -will run +runs .Pa @SCRIPT@ , or the script specified by the .Fl c , -script option. -This script will run each script found in +This script runs each script found in .Pa @HOOKDIR@ in a lexical order. The default installation supplies the scripts @@ -124,6 +126,10 @@ You can fine tune the behaviour of .Nm with the following options: .Bl -tag -width indent +.It Fl b , -background +Background immediately. +This is useful for startup scripts which don't disable link messages for +carrier status. .It Fl c , -script Ar script Use this .Ar script @@ -142,16 +148,16 @@ always processes the config file before any command line options. .It Fl h , -hostname Ar hostname By default, .Nm -will send the current hostname to the DHCP server so it can register in DNS. +sends the current hostname to the DHCP server so it can register in DNS. You can use this option to specify the .Ar hostname sent, or an empty string to stop any .Ar hostname from being sent. -.It Fl i , -classid Ar classid +.It Fl i , -vendorclassid Ar vendorclassid Override the -.Ar classid +.Ar vendorclassid field sent. The default is dhcpcd <version>. If not set then none is sent. @@ -163,6 +169,8 @@ process running on the to release its lease, deconfigure the .Ar interface and then exit. +.Nm +then waits until this process has exited. .It Fl l , -leasetime Ar seconds Request a specific lease time in .Ar seconds . @@ -184,14 +192,27 @@ Request the DHCP .Ar option variable for use in .Pa @SCRIPT@ . -.It Fl n , -renew +.It Fl n , -rebind Notifies an existing .Nm process running on the .Ar interface -to renew it's lease. If +to rebind it's lease. +.Nm +will not re-configure itself or use any other command line arguments. +.Nm +will timeout the rebind after 30 seconds at which point the lease will be +expired and +.Nm +will enter the discovery state to obtain a new lease. +Use the +.Fl t , -timeout +option to change this. +If .Nm is not running, then it starts up as normal. +This option used to be renew, but rebind is more accurate as we need to +broadcast the request instead of unicasting. .It Fl p , -persistent .Nm normally deconfigures the @@ -201,28 +222,31 @@ Sometimes, this isn't desirable if for example you have root mounted over NFS. You can use this option to stop this from happening. .It Fl r , -request Op Ar address .Nm -normally sends a DHCP Broadcast to find servers to offer an address. +normally sends a DHCP DISCOVER to find servers to offer an address. .Nm -will then request the address used. -You can use this option to skip the broadcast step and just request an +then requests the address used. +You can use this option to skip the BROADCAST step and just request the .Ar address . The downside is if you request an .Ar address the DHCP server does not know about or the DHCP server is not authorative, it will remain silent. -In this situation, we go back to the init state and broadcast again. +In this situation, we go back to the init state and DISCOVER again. If no .Ar address is given then the first address currently assigned to the .Ar interface is used. .It Fl s , -inform Op Ar address Ns Op Ar /cidr -Behaves exactly like +Behaves like .Fl r , -request as above, but sends a DHCP INFORM instead of a REQUEST. This does not get a lease as such, just notifies the DHCP server of the .Ar address in use. +You should also include the optional +.Ar cidr +network number in-case the address is not already configured on the interface. .Nm remains running and pretends it has an infinite lease. .Nm @@ -243,7 +267,7 @@ to wait forever to get a lease. .It Fl u , -userclass Ar class Tags the DHCP message with the userclass .Ar class . -DHCP servers use this give memebers of the class DHCP options other than the +DHCP servers use this give members of the class DHCP options other than the default, without having to know things like hardware address or hostname. .It Fl v , -vendor Ar code , Ns Ar value Add an enscapulated vendor option. @@ -252,30 +276,31 @@ should be between 1 and 254 inclusive. Examples. .Pp Set the vendor option 01 with an IP address. -.D1 dhcpcd -v 01,192.168.0.2 eth0 +.D1 dhcpcd \-v 01,192.168.0.2 eth0 Set the vendor option 02 with a hex code. -.D1 dhcpcd -v 02,01:02:03:04:05 eth0 +.D1 dhcpcd \-v 02,01:02:03:04:05 eth0 Do the above and set a third option with a string and not an IP address. -.D1 dhcpcd -v 01,192.168.0.2 -v 02,01:02:03:04:05 -v 03,\e"192.168.0.2\e" eth0 +.D1 dhcpcd \-v 01,192.168.0.2 \-v 02,01:02:03:04:05 \-v 03,\e"192.168.0.2\e" eth0 .It Fl x , -exit -This causes an existing +This will signal an existing .Nm process running on the .Ar interface to deconfigure the .Ar interface and exit. +.Nm +then waits until this process has exited. .It Fl D , -duid Generate an -.Rs -.%T "RFC 4361" -.Re +.Li RFC 4361 compliant clientid. This requires persistent storage and not all DHCP servers work with it so it's not enabled by default. -The DUID generated will be held in +.Nm +generates the DUID and stores in it .Pa @SYSCONFDIR@/dhcpcd.duid -and should not be copied to other hosts. +This file should not be copied to other hosts. .It Fl E , -lastlease If .Nm @@ -289,12 +314,15 @@ Requests that the DHCP server updates DNS using FQDN instead of just a hostname. Valid values for .Ar fqdn -are none, ptr and both. +are disable, none, ptr and both. The current hostname or the hostname specified using the .Fl h , -hostname option must be a FQDN. .Nm itself never does any DNS updates. +.Nm +encodes the FQDN hostname as specified in +.Li RFC1035 . .It Fl I , -clientid Ar clientid Change the default clientid sent from the interface hardware address. If the string is of the format 01:02:03 then it is encoded as hex. @@ -311,12 +339,14 @@ Here are some options that deal with turning these bits off. Quiet .Nm on the command line, only warnings and errors will be displayed. -The messagea are still logged though. +The messages are still logged though. .It Fl A , -noarp Don't request or claim the address by ARP. This also disables IPv4LL. -.It Fl G , -nogateway -Don't set any default routes. +.It Fl B , -nobackground +Don't run in the background when we acquire a lease. +This is mainly useful for running under the control of another process, such +as a debugger or a network manager. .It Fl C , -nohook Ar script Don't run this hook script. Matches full name, or prefixed with 2 numbers optionally ending with @@ -324,19 +354,23 @@ Matches full name, or prefixed with 2 numbers optionally ending with .Pp So to stop dhcpcd from touching your DNS or MTU settings you would do:- .D1 dhcpcd -C resolv.conf -C mtu eth0 -.It Fl X , -nodaemonise -Don't daemonise when we acquire a lease. -This disables the -.Fl t, -timeout -option. -This is mainly useful for running under the control of another process, such -as a debugger or a network manager. +.It Fl G , -nogateway +Don't set any default routes. +.It Fl K , -nolink +Don't receive link messages for carrier status. +You should only have to use this with buggy device drivers or running +.Nm +through a network manager. .It Fl L , -noipv4ll -Don't use IPv4LL at all. +Don't use IPv4LL (aka APIPA, aka Bonjour, aka ZeroConf). .It Fl O , -nooption Ar option Don't request the specified option. If no option given, then don't request any options other than those to configure the interface and routing. +.It Fl Q , -require Ar option +Requires the +.Ar option +to be present in all DHCP messages, otherwise the message is ignored. .It Fl T, -test On receipt of OFFER messages just call .Pa @SCRIPT@ @@ -347,6 +381,13 @@ files. .It Fl V, -variables Display a list of option codes and the associated variable for use in .Xr dhcpcd-run-hooks 8 . +.It Fl X, -blacklist Ar address +Ignores all DHCP messages which have this +.Ar address +as the server ID. +This may be expanded in future releases to ignore all packets +matching either the IP or hardware +.Ar address . .El .Sh NOTES .Nm @@ -369,6 +410,11 @@ option described above. .It Pa @DBDIR@/dhcpcd\- Ns Ar interface Ns .lease The actual DHCP message send by the server. We use this when reading the last lease and use the files mtime as when it was issued. +.It Pa /var/run/dhcpcd\- Ns Ar interface Ns .pid +Stores the PID of +.Nm +running on the +.Ar interface . .El .Sh SEE ALSO .Xr dhcpcd.conf 5 , @@ -42,6 +42,7 @@ const char copyright[] = "Copyright (c) 2006-2008 Roy Marples"; #include <stdlib.h> #include <string.h> #include <unistd.h> +#include <time.h> #include "config.h" #include "client.h" @@ -53,71 +54,66 @@ const char copyright[] = "Copyright (c) 2006-2008 Roy Marples"; #ifdef ANDROID #include <linux/capability.h> #include <linux/prctl.h> +#include <cutils/properties.h> #include <private/android_filesystem_config.h> #endif /* Don't set any optional arguments here so we retain POSIX * compatibility with getopt */ -#define OPTS "c:df:h:i:kl:m:no:pqr:s:t:u:v:xAC:DEF:GI:LO:TVX" +#define OPTS "bc:df:h:i:kl:m:no:pqr:s:t:u:v:xABC:DEF:GI:KLO:Q:TVX:" static int doversion = 0; static int dohelp = 0; static const struct option longopts[] = { - {"script", required_argument, NULL, 'c'}, - {"debug", no_argument, NULL, 'd'}, - {"config", required_argument, NULL, 'f'}, - {"hostname", optional_argument, NULL, 'h'}, - {"classid", optional_argument, NULL, 'i'}, - {"release", no_argument, NULL, 'k'}, - {"leasetime", required_argument, NULL, 'l'}, - {"metric", required_argument, NULL, 'm'}, - {"renew", no_argument, NULL, 'n'}, - {"option", required_argument, NULL, 'o'}, - {"persistent", no_argument, NULL, 'p'}, - {"quiet", no_argument, NULL, 'q'}, - {"inform", optional_argument, NULL, 's'}, - {"request", optional_argument, NULL, 'r'}, - {"timeout", required_argument, NULL, 't'}, - {"userclass", required_argument, NULL, 'u'}, - {"vendor", required_argument, NULL, 'v'}, - {"exit", no_argument, NULL, 'x'}, - {"noarp", no_argument, NULL, 'A'}, - {"nohook", required_argument, NULL, 'C'}, - {"duid", no_argument, NULL, 'D'}, - {"lastlease", no_argument, NULL, 'E'}, - {"fqdn", optional_argument, NULL, 'F'}, - {"nogateway", no_argument, NULL, 'G'}, - {"clientid", optional_argument, NULL, 'I'}, - {"noipv4ll", no_argument, NULL, 'L'}, - {"nooption", optional_argument, NULL, 'O'}, - {"test", no_argument, NULL, 'T'}, - {"variables", no_argument, NULL, 'V'}, - {"nodaemonise", no_argument, NULL, 'X'}, - {"help", no_argument, &dohelp, 1}, - {"version", no_argument, &doversion, 1}, -#ifdef THERE_IS_NO_FORK - {"daemonised", no_argument, NULL, 'z'}, - {"skiproutes", required_argument, NULL, 'Z'}, -#endif + {"background", no_argument, NULL, 'b'}, + {"script", required_argument, NULL, 'c'}, + {"debug", no_argument, NULL, 'd'}, + {"config", required_argument, NULL, 'f'}, + {"hostname", optional_argument, NULL, 'h'}, + {"vendorclassid", optional_argument, NULL, 'i'}, + {"release", no_argument, NULL, 'k'}, + {"leasetime", required_argument, NULL, 'l'}, + {"metric", required_argument, NULL, 'm'}, + {"rebind", no_argument, NULL, 'n'}, + {"option", required_argument, NULL, 'o'}, + {"persistent", no_argument, NULL, 'p'}, + {"quiet", no_argument, NULL, 'q'}, + {"request", optional_argument, NULL, 'r'}, + {"inform", optional_argument, NULL, 's'}, + {"timeout", required_argument, NULL, 't'}, + {"userclass", required_argument, NULL, 'u'}, + {"vendor", required_argument, NULL, 'v'}, + {"exit", no_argument, NULL, 'x'}, + {"noarp", no_argument, NULL, 'A'}, + {"nobackground", no_argument, NULL, 'B'}, + {"nohook", required_argument, NULL, 'C'}, + {"duid", no_argument, NULL, 'D'}, + {"lastlease", no_argument, NULL, 'E'}, + {"fqdn", optional_argument, NULL, 'F'}, + {"nogateway", no_argument, NULL, 'G'}, + {"clientid", optional_argument, NULL, 'I'}, + {"nolink", no_argument, NULL, 'K'}, + {"noipv4ll", no_argument, NULL, 'L'}, + {"nooption", optional_argument, NULL, 'O'}, + {"require", required_argument, NULL, 'Q'}, + {"test", no_argument, NULL, 'T'}, + {"variables", no_argument, NULL, 'V'}, + {"blacklist", required_argument, NULL, 'X'}, + {"help", no_argument, &dohelp, 1}, + {"version", no_argument, &doversion, 1}, #ifdef CMDLINE_COMPAT - {"nohostname", no_argument, NULL, 'H'}, - {"nomtu", no_argument, NULL, 'M'}, - {"nontp", no_argument, NULL, 'N'}, - {"nodns", no_argument, NULL, 'R'}, - {"msscr", no_argument, NULL, 'S'}, - {"nonis", no_argument, NULL, 'Y'}, + {"classid", optional_argument, NULL, 'i'}, + {"renew", no_argument, NULL, 'n'}, + {"nohostname", no_argument, NULL, 'H'}, + {"nomtu", no_argument, NULL, 'M'}, + {"nontp", no_argument, NULL, 'N'}, + {"nodns", no_argument, NULL, 'R'}, + {"msscr", no_argument, NULL, 'S'}, + {"nonis", no_argument, NULL, 'Y'}, #endif {NULL, 0, NULL, '\0'} }; -#ifdef THERE_IS_NO_FORK -char dhcpcd[PATH_MAX]; -char **dhcpcd_argv = NULL; -int dhcpcd_argc = 0; -char *dhcpcd_skiproutes = NULL; -#define EXTRA_OPTS "zZ:" -#endif - #ifdef CMDLINE_COMPAT # define EXTRA_OPTS "HMNRSY" #endif @@ -164,12 +160,10 @@ read_pid(const char *pidfile) static void usage(void) { -#ifndef MINIMAL printf("usage: "PACKAGE" [-dknpqxADEGHKLOTV] [-c script] [-f file ] [-h hostname]\n" " [-i classID ] [-l leasetime] [-m metric] [-o option] [-r ipaddr]\n" " [-s ipaddr] [-t timeout] [-u userclass] [-F none|ptr|both]\n" - " [-I clientID] [-C hookscript] <interface>\n"); -#endif + " [-I clientID] [-C hookscript] [-Q option] [-X ipaddr] <interface>\n"); } static char * @@ -214,7 +208,6 @@ add_environ(struct options *options, const char *value, int uniq) return newlist[i]; } -#ifndef MINIMAL #define parse_string(buf, len, arg) parse_string_hwaddr(buf, len, arg, 0) static ssize_t parse_string_hwaddr(char *sbuf, ssize_t slen, char *str, int clid) @@ -312,7 +305,6 @@ parse_string_hwaddr(char *sbuf, ssize_t slen, char *str, int clid) } return l; } -#endif static int parse_option(int opt, char *oarg, struct options *options) @@ -320,44 +312,44 @@ parse_option(int opt, char *oarg, struct options *options) int i; char *p; ssize_t s; -#ifndef MINIMAL struct in_addr addr; -#endif switch(opt) { + case 'b': + options->options |= DHCPCD_BACKGROUND; + break; case 'c': strlcpy(options->script, oarg, sizeof(options->script)); break; case 'h': -#ifndef MINIMAL if (oarg) s = parse_string(options->hostname + 1, - MAXHOSTNAMELEN, oarg); + HOSTNAME_MAX_LEN, oarg); else s = 0; if (s == -1) { logger(LOG_ERR, "hostname: %s", strerror(errno)); return -1; } + if (s != 0 && options->hostname[1] == '.') { + logger(LOG_ERR, "hostname cannot begin with a ."); + return -1; + } options->hostname[0] = (uint8_t)s; -#endif break; case 'i': -#ifndef MINIMAL if (oarg) - s = parse_string((char *)options->classid + 1, - CLASSID_MAX_LEN, oarg); + s = parse_string((char *)options->vendorclassid + 1, + VENDORCLASSID_MAX_LEN, oarg); else s = 0; if (s == -1) { - logger(LOG_ERR, "classid: %s", strerror(errno)); + logger(LOG_ERR, "vendorclassid: %s", strerror(errno)); return -1; } - *options->classid = (uint8_t)s; -#endif + *options->vendorclassid = (uint8_t)s; break; case 'l': -#ifndef MINIMAL if (*oarg == '-') { logger(LOG_ERR, "leasetime must be a positive value"); @@ -369,7 +361,6 @@ parse_option(int opt, char *oarg, struct options *options) logger(LOG_ERR, "`%s' out of range", oarg); return -1; } -#endif break; case 'm': options->metric = atoint(oarg); @@ -379,7 +370,7 @@ parse_option(int opt, char *oarg, struct options *options) } break; case 'o': - if (make_reqmask(options->reqmask, &oarg, 1) != 0) { + if (make_option_mask(options->requestmask, &oarg, 1) != 0) { logger(LOG_ERR, "unknown option `%s'", oarg); return -1; } @@ -416,7 +407,7 @@ parse_option(int opt, char *oarg, struct options *options) case 'r': if (!(options->options & DHCPCD_INFORM)) options->options |= DHCPCD_REQUEST; - if (*oarg && !inet_aton(oarg, &options->request_address)) { + if (oarg && !inet_aton(oarg, &options->request_address)) { logger(LOG_ERR, "`%s' is not a valid IP address", oarg); return -1; @@ -430,7 +421,6 @@ parse_option(int opt, char *oarg, struct options *options) } break; case 'u': -#ifndef MINIMAL s = USERCLASS_MAX_LEN - options->userclass[0] - 1; s = parse_string((char *)options->userclass + options->userclass[0] + 2, s, oarg); @@ -442,10 +432,8 @@ parse_option(int opt, char *oarg, struct options *options) options->userclass[options->userclass[0] + 1] = s; options->userclass[0] += s + 1; } -#endif break; case 'v': -#ifndef MINIMAL p = strchr(oarg, ','); if (!p || !p[1]) { logger(LOG_ERR, "invalid vendor format"); @@ -480,13 +468,15 @@ parse_option(int opt, char *oarg, struct options *options) options->vendor[options->vendor[0] + 2] = s; options->vendor[0] += s + 2; } -#endif break; case 'A': options->options &= ~DHCPCD_ARP; /* IPv4LL requires ARP */ options->options &= ~DHCPCD_IPV4LL; break; + case 'B': + options->options &= ~DHCPCD_DAEMONISE; + break; case 'C': /* Commas to spaces for shell */ while ((p = strchr(oarg, ','))) @@ -504,7 +494,6 @@ parse_option(int opt, char *oarg, struct options *options) options->options |= DHCPCD_LASTLEASE; break; case 'F': -#ifndef MINIMAL if (!oarg) { options->fqdn = FQDN_BOTH; break; @@ -515,20 +504,20 @@ parse_option(int opt, char *oarg, struct options *options) options->fqdn = FQDN_PTR; else if (strcmp(oarg, "both") == 0) options->fqdn = FQDN_BOTH; + else if (strcmp(oarg, "disable") == 0) + options->fqdn = FQDN_DISABLE; else { logger(LOG_ERR, "invalid value `%s' for FQDN", oarg); return -1; } -#endif break; case 'G': options->options &= ~DHCPCD_GATEWAY; break; case 'I': -#ifndef MINIMAL /* Strings have a type of 0 */; - options->classid[1] = 0; + options->clientid[1] = 0; if (oarg) s = parse_string_hwaddr((char *)options->clientid + 1, CLIENTID_MAX_LEN, oarg, 1); @@ -543,21 +532,40 @@ parse_option(int opt, char *oarg, struct options *options) options->options &= ~DHCPCD_DUID; options->options &= ~DHCPCD_CLIENTID; } -#endif + break; + case 'K': + options->options &= ~DHCPCD_LINK; break; case 'L': options->options &= ~DHCPCD_IPV4LL; break; case 'O': - if (make_reqmask(options->reqmask, &optarg, -1) != 0 || - make_reqmask(options->nomask, &optarg, 1) != 0) + if (make_option_mask(options->requestmask, &oarg, -1) != 0 || + make_option_mask(options->requiremask, &oarg, -1) != 0 || + make_option_mask(options->nomask, &oarg, 1) != 0) + { + logger(LOG_ERR, "unknown option `%s'", oarg); + return -1; + } + break; + case 'Q': + if (make_option_mask(options->requiremask, &oarg, 1) != 0 || + make_option_mask(options->requestmask, &oarg, 1) != 0) { - logger(LOG_ERR, "unknown option `%s'", optarg); + logger(LOG_ERR, "unknown option `%s'", oarg); return -1; } break; case 'X': - options->options &= ~DHCPCD_DAEMONISE; + if (!inet_aton(oarg, &addr)) { + logger(LOG_ERR, "`%s' is not a valid IP address", + oarg); + return -1; + } + options->blacklist = xrealloc(options->blacklist, + sizeof(in_addr_t) * (options->blacklist_len + 1)); + options->blacklist[options->blacklist_len] = addr.s_addr; + options->blacklist_len++; break; default: return 0; @@ -630,38 +638,34 @@ main(int argc, char **argv) FILE *f; char *cf = NULL; char *intf = NULL; + struct timespec ts; #ifdef ANDROID switchUser(); #endif - closefrom(3); + /* Saves calling fflush(stream) in the logger */ + setlinebuf(stdout); openlog(PACKAGE, LOG_PID, LOG_LOCAL0); + setlogprefix(PACKAGE ": "); options = xzalloc(sizeof(*options)); - options->options |= DHCPCD_GATEWAY | DHCPCD_DAEMONISE; + options->options |= DHCPCD_CLIENTID | DHCPCD_GATEWAY | DHCPCD_DAEMONISE; + options->options |= DHCPCD_ARP | DHCPCD_IPV4LL | DHCPCD_LINK; options->timeout = DEFAULT_TIMEOUT; strlcpy(options->script, SCRIPT, sizeof(options->script)); -#ifndef MINIMAL - options->options |= DHCPCD_CLIENTID; - options->classid[0] = snprintf((char *)options->classid + 1, CLASSID_MAX_LEN, - "%s %s", PACKAGE, VERSION); -#endif -#ifdef ENABLE_ARP - options->options |= DHCPCD_ARP; - #ifdef ENABLE_IPV4LL - options->options |= DHCPCD_IPV4LL; - #endif -#endif + options->vendorclassid[0] = snprintf((char *)options->vendorclassid + 1, + VENDORCLASSID_MAX_LEN, + "%s %s", PACKAGE, VERSION); #ifdef CMDLINE_COMPAT - add_reqmask(options->reqmask, DHCP_DNSSERVER); - add_reqmask(options->reqmask, DHCP_DNSDOMAIN); - add_reqmask(options->reqmask, DHCP_DNSSEARCH); - add_reqmask(options->reqmask, DHCP_NISSERVER); - add_reqmask(options->reqmask, DHCP_NISDOMAIN); - add_reqmask(options->reqmask, DHCP_NTPSERVER); + add_option_mask(options->requestmask, DHO_DNSSERVER); + add_option_mask(options->requestmask, DHO_DNSDOMAIN); + add_option_mask(options->requestmask, DHO_DNSSEARCH); + add_option_mask(options->requestmask, DHO_NISSERVER); + add_option_mask(options->requestmask, DHO_NISDOMAIN); + add_option_mask(options->requestmask, DHO_NTPSERVER); /* If the duid file exists, then enable duid by default * This means we don't break existing clients that easily :) */ @@ -671,23 +675,11 @@ main(int argc, char **argv) } #endif -#ifdef THERE_IS_NO_FORK - dhcpcd_argv = argv; - dhcpcd_argc = argc; - if (!realpath(argv[0], dhcpcd)) { - fprintf(stderr, "unable to resolve the path `%s': %s", - argv[0], strerror(errno)); - goto abort; - } -#endif - -#ifndef MINIMAL gethostname(options->hostname + 1, sizeof(options->hostname)); if (strcmp(options->hostname + 1, "(none)") == 0 || strcmp(options->hostname + 1, "localhost") == 0) options->hostname[1] = '\0'; *options->hostname = strlen(options->hostname + 1); -#endif while ((opt = getopt_long(argc, argv, OPTS EXTRA_OPTS, longopts, &option_index)) != -1) @@ -711,23 +703,8 @@ main(int argc, char **argv) } } - if (doversion) { + if (doversion) printf(""PACKAGE" "VERSION"\n%s\n", copyright); - printf("Compile time options:" -#ifdef ENABLE_ARP - " ARP" -#endif -#ifdef ENABLE_IPV4LL - " IPV4LL" -#endif -#ifdef MINIMAL - " MINIMAL" -#endif -#ifdef THERE_IS_NO_FORK - " THERE_IS_NO_FORK" -#endif - "\n"); - } if (dohelp) usage(); @@ -822,15 +799,6 @@ main(int argc, char **argv) break; case 'f': break; -#ifdef THERE_IS_NO_FORK - case 'z': - options->options |= DHCPCD_DAEMONISED; - close_fds(); - break; - case 'Z': - dhcpcd_skiproutes = xstrdup(optarg); - break; -#endif case 'k': sig = SIGHUP; break; @@ -846,22 +814,22 @@ main(int argc, char **argv) #ifdef CMDLINE_COMPAT case 'H': /* FALLTHROUGH */ case 'M': - del_reqmask(options->reqmask, DHCP_MTU); + del_option_mask(options->requestmask, DHO_MTU); break; case 'N': - del_reqmask(options->reqmask, DHCP_NTPSERVER); + del_option_mask(options->requestmask, DHO_NTPSERVER); break; case 'R': - del_reqmask(options->reqmask, DHCP_DNSSERVER); - del_reqmask(options->reqmask, DHCP_DNSDOMAIN); - del_reqmask(options->reqmask, DHCP_DNSSEARCH); + del_option_mask(options->requestmask, DHO_DNSSERVER); + del_option_mask(options->requestmask, DHO_DNSDOMAIN); + del_option_mask(options->requestmask, DHO_DNSSEARCH); break; case 'S': - add_reqmask(options->reqmask, DHCP_MSCSR); + add_option_mask(options->requestmask, DHO_MSCSR); break; case 'Y': - del_reqmask(options->reqmask, DHCP_NISSERVER); - del_reqmask(options->reqmask, DHCP_NISDOMAIN); + del_option_mask(options->requestmask, DHO_NISSERVER); + del_option_mask(options->requestmask, DHO_NISDOMAIN); break; #endif default: @@ -874,60 +842,17 @@ main(int argc, char **argv) } } -#ifndef MINIMAL - if ((p = strchr(options->hostname, '.'))) { - if (options->fqdn == FQDN_DISABLE) - *p = '\0'; - } else { - if (options->fqdn != FQDN_DISABLE) { - logger(LOG_WARNING, "hostname `%s' is not a FQDN", - options->hostname); - options->fqdn = FQDN_DISABLE; - } - } - if (options->fqdn != FQDN_DISABLE) - del_reqmask(options->reqmask, DHCP_HOSTNAME); +#ifdef THERE_IS_NO_FORK + options->options &= ~DHCPCD_DAEMONISE; #endif - if (options->request_address.s_addr == 0 && - (options->options & DHCPCD_INFORM || - options->options & DHCPCD_REQUEST)) - { - if (get_address(options->interface, - &options->request_address, - &options->request_netmask) != 1) - { - logger(LOG_ERR, "no existing address"); - goto abort; - } - } - - if (!(options->options & DHCPCD_DAEMONISE)) - options->timeout = 0; - - if (IN_LINKLOCAL(ntohl(options->request_address.s_addr))) { - logger(LOG_ERR, - "you are not allowed to request a link local address"); - goto abort; - } - -/* android runs us as user "dhcp" */ #ifndef ANDROID + /* android runs us as user "dhcp" */ if (geteuid()) logger(LOG_WARNING, PACKAGE " will not work correctly unless" " run as root"); #endif - prefix = xmalloc(sizeof(char) * (IF_NAMESIZE + 3)); - snprintf(prefix, IF_NAMESIZE, "%s: ", options->interface); - setlogprefix(prefix); - snprintf(options->pidfile, sizeof(options->pidfile), PIDFILE, - options->interface); - free(prefix); - - chdir("/"); - umask(022); - if (options->options & DHCPCD_TEST) { if (options->options & DHCPCD_REQUEST || options->options & DHCPCD_INFORM) { @@ -948,32 +873,100 @@ main(int argc, char **argv) } } + prefix = xmalloc(sizeof(char) * (IF_NAMESIZE + 3)); + snprintf(prefix, IF_NAMESIZE, "%s: ", options->interface); + setlogprefix(prefix); + snprintf(options->pidfile, sizeof(options->pidfile), PIDFILE, + options->interface); + free(prefix); + + if (options->request_address.s_addr == 0 && + (options->options & DHCPCD_INFORM || + options->options & DHCPCD_REQUEST)) + { + errno = 0; + if (get_address(options->interface, + &options->request_address, + &options->request_netmask) != 1) + { + if (errno) + logger(LOG_ERR, "get_address: %s", + strerror(errno)); + else + logger(LOG_ERR, "no existing address"); + goto abort; + } + } + if (IN_LINKLOCAL(ntohl(options->request_address.s_addr))) { + logger(LOG_ERR, + "you are not allowed to request a link local address"); + goto abort; + } + + chdir("/"); + umask(022); + if (sig != 0 && !(options->options & DHCPCD_DAEMONISED)) { +#ifdef ANDROID + char pidpropname[PROPERTY_KEY_MAX]; + char pidpropval[PROPERTY_VALUE_MAX]; + + i = -1; + if (snprintf(pidpropname, + sizeof(pidpropname), + "dhcp.%s.pid", options->interface) >= PROPERTY_KEY_MAX) { + goto abort; + } + property_get(pidpropname, pidpropval, NULL); + if (strlen(pidpropval) == 0) { + goto abort; + } + pid = atoi(pidpropval); +#else i = -1; pid = read_pid(options->pidfile); +#endif if (pid != 0) logger(LOG_INFO, "sending signal %d to pid %d", sig, pid); - if (!pid || (i = kill(pid, sig))) - logger(sig == SIGALRM ? LOG_INFO : LOG_ERR, - ""PACKAGE" not running"); - - if (pid != 0 && (sig != SIGALRM || i != 0)) + if (!pid || (i = kill(pid, sig))) { + if (sig != SIGALRM) + logger(LOG_ERR, ""PACKAGE" not running"); unlink(options->pidfile); - + } if (i == 0) { - retval = EXIT_SUCCESS; + if (sig == SIGALRM) { + retval = EXIT_SUCCESS; + goto abort; + } + /* Spin until it exits */ + logger(LOG_INFO, "waiting for pid %d to exit", pid); + ts.tv_sec = 0; + ts.tv_nsec = 100000000; /* 10th of a second */ + for(i = 0; i < 100; i++) { + nanosleep(&ts, NULL); + if (read_pid(options->pidfile) == 0) { + retval = EXIT_SUCCESS; + break; + } + } + if (retval != EXIT_SUCCESS) + logger(LOG_ERR, "pid %d failed to exit", pid); goto abort; } - if (sig != SIGALRM) goto abort; } -#ifndef ANDROID + if (!(options->options & DHCPCD_TEST) && !(options->options & DHCPCD_DAEMONISED)) { +#ifdef ANDROID + char pidpropname[PROPERTY_KEY_MAX]; + char pidpropval[PROPERTY_VALUE_MAX]; +#endif +#ifndef ANDROID if ((pid = read_pid(options->pidfile)) > 0 && kill(pid, 0) == 0) { @@ -982,7 +975,7 @@ main(int argc, char **argv) pid, options->pidfile); goto abort; } - +#endif pid_fd = open(options->pidfile, O_WRONLY | O_CREAT | O_NONBLOCK, 0664); if (pid_fd == -1) { @@ -1001,17 +994,27 @@ main(int argc, char **argv) if (set_cloexec(pid_fd) == -1) goto abort; +#ifdef ANDROID + if (snprintf(pidpropname, + sizeof(pidpropname), + "dhcp.%s.pid", options->interface) >= PROPERTY_KEY_MAX) { + goto abort; + } + if (snprintf(pidpropval, sizeof(pidpropval), "%d", getpid()) >= PROPERTY_VALUE_MAX) { + goto abort; + } + property_set(pidpropname, pidpropval); +#else writepid(pid_fd, getpid()); +#endif logger(LOG_INFO, PACKAGE " " VERSION " starting"); } -#endif /* ANDROID */ -#ifndef MINIMAL + /* Terminate the encapsulated options */ if (options->vendor[0]) { options->vendor[0]++; - options->vendor[options->vendor[0]] = DHCP_END; + options->vendor[options->vendor[0]] = DHO_END; } -#endif if (dhcp_run(options, &pid_fd) == 0) retval = EXIT_SUCCESS; @@ -1028,14 +1031,8 @@ abort: free(options->environ[len++]); free(options->environ); } + free(options->blacklist); free(options); - -#ifdef THERE_IS_NO_FORK - /* There may have been an error before the dhcp_run function - * clears this, so just do it here to be safe */ - free(dhcpcd_skiproutes); -#endif - exit(retval); /* NOTREACHED */ } diff --git a/dhcpcd.conf.5 b/dhcpcd.conf.5 index 8fd4b90..217ecba 100644 --- a/dhcpcd.conf.5 +++ b/dhcpcd.conf.5 @@ -22,7 +22,7 @@ .\" OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF .\" SUCH DAMAGE. .\" -.Dd Jun 30, 2008 +.Dd August 18, 2008 .Dt DHCPCD.CONF 5 SMM .Sh NAME .Nm dhcpcd.conf @@ -43,9 +43,10 @@ Blank lines and lines starting with # are ignored. .Pp Here's a list of available options: .Bl -tag -width indent -.It Ic classid Ar string -Change the default classid sent from dhcpcd-version. -If not set then none is sent. +.It Ic background +Background immediately. +This is useful for startup scripts which don't disable link messages for +carrier status. .It Ic clientid Ar string Change the default clientid sent from the interface hardware address. If the string is of the format 01:02:03 then it is encoded as hex. @@ -71,10 +72,13 @@ if a FQDN (ie, contains a .) then it will be encoded as such. none disables FQDN encoding, ptr just asks the DHCP server to update the PTR record of the host in DNS whereas both also updates the A record. The current hostname or the hostname specified using the -.Fl h , -hostname +.Ic hostname option must be a FQDN. .Nm dhcpcd itself never does any DNS updates. +.Nm dhcpcd +encodes the FQDN hostname as specified in +.Li RFC1035 . .It Ic interface Ar interface Subsequent options are only parsed for this .Ar interface . @@ -96,14 +100,25 @@ See .Rs .%T "RFC 3927" .Re -.It Ic option Ar dhcp-option +.It Ic nolink +Don't receive link messages about carrier status. +You should only set this for buggy interface drivers. +.It Ic option Ar option Requests the -.Ar dhcp-option +.Ar option from the server. It can be a variable to be used in .Xr dhcpcd-run-hooks 8 or the numerical value. -You can specify more seperated by commas, spaces or more option lines. +You can specify more options seperated by commas, spaces or more option lines. +.It Ic require Ar option +Requires the +.Ar option +to be present in all DHCP messages, otherwise the message is ignored. +It can be a variable to be used in +.Xr dhcpcd-run-hooks 8 +or the numerical value. +You can specify more options seperated by commas, spaces or more require lines. .It Ic script Ar script Use .Ar script @@ -115,7 +130,7 @@ be too long or too short and can be changed here. .It Ic userclass Ar string Tag the DHCP messages with the userclass. You can specify more than one. -.It vendor Ar code , Ns Ar value +.It Ic vendor Ar code , Ns Ar value Add an enscapulated vendor option. .Ar code should be between 1 and 254 inclusive. @@ -127,6 +142,9 @@ Set the vendor option 02 with a hex code. .D1 vendor 02,01:02:03:04:05 Set the vendor option 03 with an IP address as a string. .D1 vendor 03,\e"192.168.0.2\e" +.It Ic vendorclassid Ar string +Change the default vendorclassid sent from dhcpcd-version. +If not set then none is sent. .El .Sh SEE ALSO .Xr dhcpcd-run-hooks 8 , diff --git a/dhcpcd.conf.5.in b/dhcpcd.conf.5.in index 5ba825f..899aea3 100644 --- a/dhcpcd.conf.5.in +++ b/dhcpcd.conf.5.in @@ -22,7 +22,7 @@ .\" OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF .\" SUCH DAMAGE. .\" -.Dd Jun 30, 2008 +.Dd August 18, 2008 .Dt DHCPCD.CONF 5 SMM .Sh NAME .Nm dhcpcd.conf @@ -43,9 +43,10 @@ Blank lines and lines starting with # are ignored. .Pp Here's a list of available options: .Bl -tag -width indent -.It Ic classid Ar string -Change the default classid sent from dhcpcd-version. -If not set then none is sent. +.It Ic background +Background immediately. +This is useful for startup scripts which don't disable link messages for +carrier status. .It Ic clientid Ar string Change the default clientid sent from the interface hardware address. If the string is of the format 01:02:03 then it is encoded as hex. @@ -71,10 +72,13 @@ if a FQDN (ie, contains a .) then it will be encoded as such. none disables FQDN encoding, ptr just asks the DHCP server to update the PTR record of the host in DNS whereas both also updates the A record. The current hostname or the hostname specified using the -.Fl h , -hostname +.Ic hostname option must be a FQDN. .Nm dhcpcd itself never does any DNS updates. +.Nm dhcpcd +encodes the FQDN hostname as specified in +.Li RFC1035 . .It Ic interface Ar interface Subsequent options are only parsed for this .Ar interface . @@ -96,14 +100,25 @@ See .Rs .%T "RFC 3927" .Re -.It Ic option Ar dhcp-option +.It Ic nolink +Don't receive link messages about carrier status. +You should only set this for buggy interface drivers. +.It Ic option Ar option Requests the -.Ar dhcp-option +.Ar option from the server. It can be a variable to be used in .Xr dhcpcd-run-hooks 8 or the numerical value. -You can specify more seperated by commas, spaces or more option lines. +You can specify more options seperated by commas, spaces or more option lines. +.It Ic require Ar option +Requires the +.Ar option +to be present in all DHCP messages, otherwise the message is ignored. +It can be a variable to be used in +.Xr dhcpcd-run-hooks 8 +or the numerical value. +You can specify more options seperated by commas, spaces or more require lines. .It Ic script Ar script Use .Ar script @@ -115,7 +130,7 @@ be too long or too short and can be changed here. .It Ic userclass Ar string Tag the DHCP messages with the userclass. You can specify more than one. -.It vendor Ar code , Ns Ar value +.It Ic vendor Ar code , Ns Ar value Add an enscapulated vendor option. .Ar code should be between 1 and 254 inclusive. @@ -127,6 +142,9 @@ Set the vendor option 02 with a hex code. .D1 vendor 02,01:02:03:04:05 Set the vendor option 03 with an IP address as a string. .D1 vendor 03,\e"192.168.0.2\e" +.It Ic vendorclassid Ar string +Change the default vendorclassid sent from dhcpcd-version. +If not set then none is sent. .El .Sh SEE ALSO .Xr dhcpcd-run-hooks 8 , @@ -41,18 +41,12 @@ #define DEFAULT_TIMEOUT 30 #define DEFAULT_LEASETIME 3600 /* 1 hour */ -#define CLASSID_MAX_LEN 48 +#define HOSTNAME_MAX_LEN 250 /* 255 - 3 (FQDN) - 2 (DNS enc) */ +#define VENDORCLASSID_MAX_LEN 48 #define CLIENTID_MAX_LEN 48 #define USERCLASS_MAX_LEN 255 #define VENDOR_MAX_LEN 255 -#ifdef THERE_IS_NO_FORK -extern char dhcpcd[PATH_MAX]; -extern char **dhcpcd_argv; -extern int dhcpcd_argc; -extern char *dhcpcd_skiproutes; -#endif - #define DHCPCD_ARP (1 << 0) #define DHCPCD_DOMAIN (1 << 2) #define DHCPCD_GATEWAY (1 << 3) @@ -68,11 +62,14 @@ extern char *dhcpcd_skiproutes; #define DHCPCD_FORKED (1 << 17) #define DHCPCD_HOSTNAME (1 << 18) #define DHCPCD_CLIENTID (1 << 19) +#define DHCPCD_LINK (1 << 20) +#define DHCPCD_BACKGROUND (1 << 21) struct options { char interface[IF_NAMESIZE]; int metric; - uint8_t reqmask[256 / 8]; + uint8_t requestmask[256 / 8]; + uint8_t requiremask[256 / 8]; uint8_t nomask[256 / 8]; uint32_t leasetime; time_t timeout; @@ -85,14 +82,14 @@ struct options { char script[PATH_MAX]; char pidfile[PATH_MAX]; -#ifndef MINIMAL - char hostname[MAXHOSTNAMELEN]; + char hostname[HOSTNAME_MAX_LEN + 1]; int fqdn; - uint8_t classid[CLASSID_MAX_LEN + 1]; + uint8_t vendorclassid[VENDORCLASSID_MAX_LEN + 1]; char clientid[CLIENTID_MAX_LEN + 1]; uint8_t userclass[USERCLASS_MAX_LEN + 1]; uint8_t vendor[VENDOR_MAX_LEN + 1]; -#endif -}; + size_t blacklist_len; + in_addr_t *blacklist; +}; #endif @@ -185,3 +185,57 @@ if_route(const char *ifname, const struct in_addr *destination, close(s); return retval; } + +int +open_link_socket(struct interface *iface) +{ + int fd; + + fd = socket(PF_ROUTE, SOCK_RAW, 0); + if (fd == -1) + return -1; + set_cloexec(fd); + if (iface->link_fd != -1) + close(iface->link_fd); + iface->link_fd = fd; + return 0; +} + +#define BUFFER_LEN 2048 +int +link_changed(struct interface *iface) +{ + char buffer[2048], *p; + ssize_t bytes; + struct rt_msghdr *rtm; + struct if_msghdr *ifm; + int i; + + if ((i = if_nametoindex(iface->name)) == -1) + return -1; + for (;;) { + bytes = recv(iface->link_fd, buffer, BUFFER_LEN, MSG_DONTWAIT); + if (bytes == -1) { + if (errno == EAGAIN) + return 0; + if (errno == EINTR) + continue; + return -1; + } + for (p = buffer; bytes > 0; + bytes -= ((struct rt_msghdr *)p)->rtm_msglen, + p += ((struct rt_msghdr *)p)->rtm_msglen) + { + rtm = (struct rt_msghdr *)p; + if (rtm->rtm_type != RTM_IFINFO) + continue; + ifm = (struct if_msghdr *)p; + if (ifm->ifm_index != i) + continue; + + /* Link changed */ + return 1; + } + } + return 0; +} @@ -46,128 +46,178 @@ #include <string.h> #include <unistd.h> +/* Support older kernels */ +#ifndef IFLA_WIRELESS +# define IFLA_WIRELSSS (IFLFA_MASTER + 1) +#endif + #include "config.h" #include "common.h" #include "dhcp.h" #include "net.h" -/* 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. */ #define BUFFERLEN 256 -static int -send_netlink(struct nlmsghdr *hdr) + +int +open_link_socket(struct interface *iface) { - int s; - pid_t mypid = getpid (); + int fd; struct sockaddr_nl nl; - struct iovec iov; - struct msghdr msg; - static unsigned int seq; - char *buffer = NULL; - ssize_t bytes; - union - { - char *buffer; - struct nlmsghdr *nlm; - } h; - int len, l; - struct nlmsgerr *err; - if ((s = socket(AF_NETLINK, SOCK_RAW, NETLINK_ROUTE)) == -1) + if ((fd = socket(AF_NETLINK, SOCK_RAW, NETLINK_ROUTE)) == -1) return -1; - memset(&nl, 0, sizeof(nl)); nl.nl_family = AF_NETLINK; - if (bind(s, (struct sockaddr *)&nl, sizeof(nl)) == -1) - goto eexit; - - memset(&iov, 0, sizeof(iov)); - iov.iov_base = hdr; - iov.iov_len = hdr->nlmsg_len; - - memset(&msg, 0, sizeof(msg)); - 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; - hdr->nlmsg_seq = ++seq; + nl.nl_groups = RTMGRP_LINK; + if (bind(fd, (struct sockaddr *)&nl, sizeof(nl)) == -1) + return -1; + set_cloexec(fd); + if (iface->link_fd != -1) + close(iface->link_fd); + iface->link_fd = fd; + return 0; +} - if (sendmsg(s, &msg, 0) == -1) - goto eexit; +static int +get_netlink(int fd, int flags, + int (*callback)(struct nlmsghdr *, const char *), + const char *ifname) +{ + char *buffer = NULL; + ssize_t bytes; + struct nlmsghdr *nlm; + int r = -1; buffer = xzalloc(sizeof(char) * BUFFERLEN); - iov.iov_base = buffer; - for (;;) { - iov.iov_len = BUFFERLEN; - bytes = recvmsg(s, &msg, 0); - + bytes = recv(fd, buffer, BUFFERLEN, flags); if (bytes == -1) { + if (errno == EAGAIN) { + r = 0; + goto eexit; + } if (errno == EINTR) continue; goto eexit; } - - if (bytes == 0) { - errno = ENODATA; - goto eexit; + for (nlm = (struct nlmsghdr *)buffer; + NLMSG_OK(nlm, (size_t)bytes); + nlm = NLMSG_NEXT(nlm, bytes)) + { + r = callback(nlm, ifname); + if (r != 0) + goto eexit; } + } - if (msg.msg_namelen != sizeof(nl)) { - errno = EBADMSG; - goto eexit; - } +eexit: + free(buffer); + return r; +} - for (h.buffer = buffer; bytes >= (signed) sizeof(*h.nlm); ) { - len = h.nlm->nlmsg_len; - l = len - sizeof(*h.nlm); - err = (struct nlmsgerr *)NLMSG_DATA(h.nlm); +static int +err_netlink(struct nlmsghdr *nlm, _unused const char *ifname) +{ + struct nlmsgerr *err; + int l; - if (l < 0 || len > bytes) { - errno = EBADMSG; - goto eexit; - } + if (nlm->nlmsg_type != NLMSG_ERROR) + return 0; + l = nlm->nlmsg_len - sizeof(*nlm); + if ((size_t)l < sizeof(*err)) { + errno = EBADMSG; + return -1; + } + err = (struct nlmsgerr *)NLMSG_DATA(nlm); + if (err->error == 0) + return l; + errno = -err->error; + return -1; +} - /* Ensure it's our message */ - if (nl.nl_pid != 0 || - (pid_t)h.nlm->nlmsg_pid != mypid || - h.nlm->nlmsg_seq != seq) - { - /* Next Message */ - bytes -= NLMSG_ALIGN(len); - h.buffer += NLMSG_ALIGN(len); - continue; - } +static int +link_netlink(struct nlmsghdr *nlm, const char *ifname) +{ + int len; + struct rtattr *rta; + struct ifinfomsg *ifi; + char ifn[IF_NAMESIZE + 1]; + + if (nlm->nlmsg_type != RTM_NEWLINK && nlm->nlmsg_type != RTM_DELLINK) + return 0; + len = nlm->nlmsg_len - sizeof(*nlm); + if ((size_t)len < sizeof(*ifi)) { + errno = EBADMSG; + return -1; + } + ifi = NLMSG_DATA(nlm); + if (ifi->ifi_flags & IFF_LOOPBACK) + return 0; + rta = (struct rtattr *) ((char *)ifi + NLMSG_ALIGN(sizeof(*ifi))); + len = NLMSG_PAYLOAD(nlm, sizeof(*ifi)); + *ifn = '\0'; + while (RTA_OK(rta, len)) { + switch (rta->rta_type) { + case IFLA_WIRELESS: + /* Ignore wireless messages */ + if (nlm->nlmsg_type == RTM_NEWLINK && + ifi->ifi_change == 0) + return 0; + break; + case IFLA_IFNAME: + strlcpy(ifn, RTA_DATA(rta), sizeof(ifn)); + break; + } + rta = RTA_NEXT(rta, len); + } - /* We get an NLMSG_ERROR back with a code of zero for success */ - if (h.nlm->nlmsg_type != NLMSG_ERROR) - continue; + if (strncmp(ifname, ifn, sizeof(ifn)) == 0) + return 1; + return 0; +} - if ((unsigned)l < sizeof(*err)) { - errno = EBADMSG; - goto eexit; - } +int +link_changed(struct interface *iface) +{ + return get_netlink(iface->link_fd, MSG_DONTWAIT, + &link_netlink, iface->name); +} - if (err->error == 0) { - close(s); - free(buffer); - return l; - } +static int +send_netlink(struct nlmsghdr *hdr) +{ + int fd, r; + struct sockaddr_nl nl; + struct iovec iov; + struct msghdr msg; + static unsigned int seq; - errno = -err->error; - goto eexit; - } + if ((fd = socket(AF_NETLINK, SOCK_RAW, NETLINK_ROUTE)) == -1) + return -1; + memset(&nl, 0, sizeof(nl)); + nl.nl_family = AF_NETLINK; + if (bind(fd, (struct sockaddr *)&nl, sizeof(nl)) == -1) { + close(fd); + return -1; } + memset(&iov, 0, sizeof(iov)); + iov.iov_base = hdr; + iov.iov_len = hdr->nlmsg_len; + memset(&msg, 0, sizeof(msg)); + 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; + hdr->nlmsg_seq = ++seq; -eexit: - close(s); - free(buffer); - return -1; + if (sendmsg(fd, &msg, 0) != -1) + r = get_netlink(fd, 0, &err_netlink, NULL); + else + r = -1; + close(fd); + return r; } #define NLMSG_TAIL(nmsg) \ @@ -285,15 +335,16 @@ if_route(const char *ifname, nlm->hdr.nlmsg_len = NLMSG_LENGTH(sizeof(struct rtmsg)); nlm->hdr.nlmsg_type = RTM_NEWROUTE; if (action == 0) - nlm->hdr.nlmsg_flags = NLM_F_REPLACE; + nlm->hdr.nlmsg_flags = NLM_F_REPLACE; else if (action > 0) /* - * commented out NLM_F_EXCL here and - * below. We sometimes keep one interface up while - * we are configuring the other one, and this flag + * ers@google: + * commented out NLM_F_EXCL here and below. We + * sometimes keep one interface up while we are + * configuring the other one, and this flag * causes route addition to fail. */ - nlm->hdr.nlmsg_flags = NLM_F_CREATE /* | NLM_F_EXCL*/; + nlm->hdr.nlmsg_flags = NLM_F_CREATE /* | NLM_F_EXCL */; else nlm->hdr.nlmsg_type = RTM_DELROUTE; nlm->hdr.nlmsg_flags |= NLM_F_REQUEST; @@ -38,31 +38,6 @@ static int loglevel = LOG_INFO; static char logprefix[12] = {0}; -struct logname { - int level; - const char *name; -}; -static const struct logname const lognames[] = { - { LOG_DEBUG, "debug" }, - { LOG_INFO, "info" }, - { LOG_WARNING, "warning" }, - { LOG_ERR, "error" }, - { -1, NULL } -}; - -int -logtolevel(const char *priority) -{ - const struct logname *lt; - - if (isdigit((unsigned char)*priority)) - return atoi(priority); - for (lt = lognames; lt->name; lt++) - if (!strcasecmp(priority, lt->name)) - return lt->level; - return -1; -} - void setloglevel(int level) { @@ -90,10 +65,6 @@ logger(int level, const char *fmt, ...) fprintf(f, "%s", logprefix); vfprintf(f, fmt, p); fputc('\n', f); - - /* stdout, stderr may be re-directed to some kind of buffer. - * So we always flush to ensure it's written. */ - fflush(f); } if (level < LOG_DEBUG || level <= loglevel) { @@ -36,7 +36,6 @@ #include <syslog.h> -int logtolevel(const char *); void setloglevel(int); void setlogprefix(const char *); void logger(int, const char *, ...) _PRINTF_LIKE (2, 3); @@ -89,13 +89,10 @@ open_socket(struct interface *iface, int protocol) } /* Install the DHCP filter */ memset(&pf, 0, sizeof(pf)); -#ifdef ENABLE_ARP if (protocol == ETHERTYPE_ARP) { pf.filter = UNCONST(arp_bpf_filter); pf.len = arp_bpf_filter_len; - } else -#endif - { + } else { pf.filter = UNCONST(dhcp_bpf_filter); pf.len = dhcp_bpf_filter_len; } @@ -107,12 +104,10 @@ open_socket(struct interface *iface, int protocol) goto eexit; if (bind(s, &su.sa, sizeof(su)) == -1) goto eexit; -#ifdef ENABLE_ARP if (protocol == ETHERTYPE_ARP) fd = &iface->arp_fd; else -#endif - fd = &iface->fd; + fd = &iface->raw_fd; if (*fd != -1) close(*fd); *fd = s; @@ -148,12 +143,10 @@ send_raw_packet(const struct interface *iface, int protocol, &ipv4_bcast_addr, sizeof(ipv4_bcast_addr)); else memset(&su.sll.sll_addr, 0xff, iface->hwlen); -#ifdef ENABLE_ARP if (protocol == ETHERTYPE_ARP) fd = iface->arp_fd; else -#endif - fd = iface->fd; + fd = iface->raw_fd; return sendto(fd, data, len, 0, &su.sa, sizeof(su)); } @@ -164,12 +157,10 @@ get_raw_packet(struct interface *iface, int protocol, void *data, ssize_t len) ssize_t bytes; int fd = -1; - if (protocol == ETHERTYPE_ARP) { -#ifdef ENABLE_ARP + if (protocol == ETHERTYPE_ARP) fd = iface->arp_fd; -#endif - } else - fd = iface->fd; + else + fd = iface->raw_fd; bytes = read(fd, data, len); if (bytes == -1) return errno == EAGAIN ? 0 : -1; diff --git a/mk/os-Darwin.mk b/mk/os-Darwin.mk new file mode 100644 index 0000000..f2c3104 --- /dev/null +++ b/mk/os-Darwin.mk @@ -0,0 +1,5 @@ +# Setup OS specific variables +# Copyright 2008 Roy Marples <roy@marples.name> + +include ${MK}/os-BSD.mk +LINK_RPATH= --rpath diff --git a/mk/os-Linux.mk b/mk/os-Linux.mk index 16f0f60..2d316b1 100644 --- a/mk/os-Linux.mk +++ b/mk/os-Linux.mk @@ -6,23 +6,3 @@ SRC_IF= if-linux.c CPPFLAGS+= -D_BSD_SOURCE -D_XOPEN_SOURCE=600 LIBRT= -lrt - -# Work out if our fork() works or not. -# If cross-compiling, you'll need to set HAVE_FORK to yes or no depending -# on your target arch. -_HAVE_FORK_SH= if test "${HAVE_FORK}" = "yes"; then \ - echo ""; \ - elif test -n "${HAVE_FORK}"; then \ - echo "-DTHERE_IS_NO_FORK"; \ - else \ - printf '\#include <stdlib.h>\n\#include <unistd.h>\nint main (void) { pid_t pid = fork(); if (pid == -1) exit (-1); exit (0); }\n' > .fork.c; \ - ${CC} .fork.c -o .fork >/dev/null 2>&1; \ - if ./.fork; then \ - echo ""; \ - else \ - echo "-DTHERE_IS_NO_FORK"; \ - fi; \ - rm -f .fork.c .fork; \ - fi; -_HAVE_FORK!= ${_HAVE_FORK_SH} -CPPFLAGS+= ${_HAVE_FORK}$(shell ${_HAVE_FORK_SH}) @@ -1,7 +1,7 @@ # Setup OS specific variables # Copyright 2008 Roy Marples <roy@marples.name> -_OS_SH= case `uname -s` in Linux) echo "Linux";; *) echo "BSD";; esac +_OS_SH= case `uname -s` in Linux) echo "Linux";; Darwin) echo "Darwin";; *) echo "BSD";; esac _OS!= ${_OS_SH} OS= ${_OS}$(shell ${_OS_SH}) include ${MK}/os-${OS}.mk @@ -7,18 +7,19 @@ include ${MK}/cc.mk OBJS+= ${SRCS:.c=.o} -# This is for NetBSD which has a different libc in /lib which we need -# to link to if installing in / +# If building for /, ensure we use the libc in / if different from +# the default one in /usr/lib +LINK_RPATH?= -Wl,-rpath _RPATH_SH= if test "${PREFIX}" = "" -o "${PREIX}" = "/"; then \ - echo "-Wl,-rpath=${PREFIX}/${LIBNAME}"; \ + echo "${LINK_RPATH}=${PREFIX}/${LIBNAME}"; \ else \ echo ""; \ fi _RPATH!= ${_RPATH_SH} LDFLAGS+= ${_RPATH}$(shell ${_RPATH_SH}) -# This is for NetBSD which has different dynamic linker in /lib which we need -# to use to if installing in / +# If building for /, ensure we use the linker in /libexec if different from +# the default one in /usr/libexec _DYNLINK_SH= if test "${PREFIX}" = "" -o "${PREFIX}" = "/" && test -e /libexec/ld.elf_so; then \ echo "-Wl,-dynamic-linker=/libexec/ld.elf_so"; \ else \ @@ -43,6 +43,9 @@ #define __FAVOR_BSD /* Nasty glibc hack so we can use BSD semantics for UDP */ #include <netinet/udp.h> #undef __FAVOR_BSD +#ifdef SIOCGIFMEDIA +#include <net/if_media.h> +#endif #include <arpa/inet.h> #ifdef AF_LINK # include <net/if_dl.h> @@ -50,7 +53,6 @@ #include <ctype.h> #include <errno.h> -#include <poll.h> #include <stddef.h> #include <stdio.h> #include <stdlib.h> @@ -79,7 +81,7 @@ inet_ntocidr(struct in_addr address) } int -inet_cidrtoaddr (int cidr, struct in_addr *addr) +inet_cidrtoaddr(int cidr, struct in_addr *addr) { int ocets; @@ -181,7 +183,7 @@ do_interface(const char *ifname, { int s; struct ifconf ifc; - int retval = 0; + int retval = 0, found = 0; int len = 10 * sizeof(struct ifreq); int lastlen = 0; char *p; @@ -192,9 +194,8 @@ do_interface(const char *ifname, struct sockaddr_in address; struct ifreq *ifr; struct sockaddr_in netmask; - #ifdef AF_LINK - struct sockaddr_dl sdl; + struct sockaddr_dl *sdl; #endif if ((s = socket(AF_INET, SOCK_DGRAM, 0)) == -1) @@ -239,12 +240,15 @@ do_interface(const char *ifname, if (strcmp(ifname, ifr->ifr_name) != 0) continue; + found = 1; + #ifdef AF_LINK if (hwaddr && hwlen && ifr->ifr_addr.sa_family == AF_LINK) { - memcpy(&sdl, &ifr->ifr_addr, sizeof(sdl)); - *hwlen = sdl.sdl_alen; - memcpy(hwaddr, sdl.sdl_data + sdl.sdl_nlen, - (size_t)sdl.sdl_alen); + sdl = xmalloc(ifr->ifr_addr.sa_len); + memcpy(sdl, &ifr->ifr_addr, ifr->ifr_addr.sa_len); + *hwlen = sdl->sdl_alen; + memcpy(hwaddr, LLADDR(sdl), *hwlen); + free(sdl); retval = 1; break; } @@ -273,11 +277,90 @@ do_interface(const char *ifname, } + if (!found) + errno = ENXIO; close(s); free(ifc.ifc_buf); return retval; } +int +up_interface(const char *ifname) +{ + int s; + struct ifreq ifr; + int retval = -1; +#ifdef __linux__ + char *p; +#endif + + if ((s = socket(AF_INET, SOCK_DGRAM, 0)) == -1) + return -1; + memset(&ifr, 0, sizeof(ifr)); + strlcpy(ifr.ifr_name, ifname, sizeof(ifr.ifr_name)); +#ifdef __linux__ + /* We can only bring the real interface up */ + if ((p = strchr(ifr.ifr_name, ':'))) + *p = '\0'; +#endif + if (ioctl(s, SIOCGIFFLAGS, &ifr) == 0) { + if ((ifr.ifr_flags & IFF_UP)) + retval = 0; + else { + ifr.ifr_flags |= IFF_UP; + if (ioctl(s, SIOCSIFFLAGS, &ifr) == 0) + retval = 0; + } + } + close(s); + return retval; +} + +int +carrier_status(const char *ifname) +{ + int s; + struct ifreq ifr; + int retval = -1; +#ifdef SIOCGIFMEDIA + struct ifmediareq ifmr; +#endif +#ifdef __linux__ + char *p; +#endif + + if ((s = socket(AF_INET, SOCK_DGRAM, 0)) == -1) + return -1; + memset(&ifr, 0, sizeof(ifr)); + strlcpy(ifr.ifr_name, ifname, sizeof(ifr.ifr_name)); +#ifdef __linux__ + /* We can only test the real interface up */ + if ((p = strchr(ifr.ifr_name, ':'))) + *p = '\0'; +#endif + if ((retval = ioctl(s, SIOCGIFFLAGS, &ifr)) == 0) { + if (ifr.ifr_flags & IFF_UP && ifr.ifr_flags & IFF_RUNNING) + retval = 1; + else + retval = 0; + } + +#ifdef SIOCGIFMEDIA + if (retval == 1) { + memset(&ifmr, 0, sizeof(ifmr)); + strncpy(ifmr.ifm_name, ifr.ifr_name, sizeof(ifmr.ifm_name)); + if (ioctl(s, SIOCGIFMEDIA, &ifmr) != -1 && + ifmr.ifm_status & IFM_AVALID) + { + if (!(ifmr.ifm_status & IFM_ACTIVE)) + retval = 0; + } + } +#endif + close(s); + return retval; +} + struct interface * read_interface(const char *ifname, _unused int metric) { @@ -287,9 +370,6 @@ read_interface(const char *ifname, _unused int metric) unsigned char *hwaddr = NULL; size_t hwlen = 0; sa_family_t family = 0; -#ifdef __linux__ - char *p; -#endif memset(&ifr, 0, sizeof(ifr)); strlcpy(ifr.ifr_name, ifname, sizeof(ifr.ifr_name)); @@ -342,20 +422,8 @@ read_interface(const char *ifname, _unused int metric) goto eexit; } - /* Bring the interface up if it's down */ - strlcpy(ifr.ifr_name, ifname, sizeof(ifr.ifr_name)); -#ifdef __linux__ - /* We can only bring the real interface up */ - if ((p = strchr(ifr.ifr_name, ':'))) - *p = '\0'; -#endif - if (ioctl(s, SIOCGIFFLAGS, &ifr) == -1) + if (up_interface(ifname) != 0) goto eexit; - if (!(ifr.ifr_flags & IFF_UP)) { - ifr.ifr_flags |= IFF_UP; - if (ioctl(s, SIOCSIFFLAGS, &ifr) != 0) - goto eexit; - } iface = xzalloc(sizeof(*iface)); strlcpy(iface->name, ifname, IF_NAMESIZE); @@ -367,11 +435,10 @@ read_interface(const char *ifname, _unused int metric) iface->arpable = !(ifr.ifr_flags & (IFF_NOARP | IFF_LOOPBACK)); /* 0 is a valid fd, so init to -1 */ - iface->fd = -1; + iface->raw_fd = -1; iface->udp_fd = -1; -#ifdef ENABLE_ARP iface->arp_fd = -1; -#endif + iface->link_fd = -1; eexit: close(s); @@ -419,21 +486,32 @@ open_udp_socket(struct interface *iface) struct sockaddr sa; struct sockaddr_in sin; } su; - int n = 1; + int n; +#ifdef SO_BINDTODEVICE + struct ifreq ifr; +#endif if ((s = socket(PF_INET, SOCK_DGRAM, IPPROTO_UDP)) == -1) return -1; - memset(&su, 0, sizeof(su)); - su.sin.sin_family = AF_INET; - su.sin.sin_port = htons(DHCP_CLIENT_PORT); - su.sin.sin_addr.s_addr = iface->addr.s_addr; + n = 1; if (setsockopt(s, SOL_SOCKET, SO_REUSEADDR, &n, sizeof(n)) == -1) goto eexit; - /* As we don't actually use this socket for anything, set - * the receiver buffer to 1 */ +#ifdef SO_BINDTODEVICE + memset(&ifr, 0, sizeof(ifr)); + strlcpy(ifr.ifr_name, iface->name, sizeof(ifr.ifr_name)); + if (setsockopt(s, SOL_SOCKET, SO_BINDTODEVICE, &ifr, sizeof(ifr)) == -1) + goto eexit; +#endif + /* As we don't use this socket for receiving, set the + * receive buffer to 1 */ + n = 1; if (setsockopt(s, SOL_SOCKET, SO_RCVBUF, &n, sizeof(n)) == -1) goto eexit; + memset(&su, 0, sizeof(su)); + su.sin.sin_family = AF_INET; + su.sin.sin_port = htons(DHCP_CLIENT_PORT); + su.sin.sin_addr.s_addr = iface->addr.s_addr; if (bind(s, &su.sa, sizeof(su)) == -1) goto eexit; @@ -596,38 +674,34 @@ valid_udp_packet(const uint8_t *data) return retval; } -#ifdef ENABLE_ARP int send_arp(const struct interface *iface, int op, in_addr_t sip, in_addr_t tip) { struct arphdr *arp; size_t arpsize; - unsigned char *p; + uint8_t *p; int retval; - arpsize = sizeof(*arp) + 2 * iface->hwlen + 2 *sizeof(sip); - + arpsize = sizeof(*arp) + 2 * iface->hwlen + 2 * sizeof(sip); arp = xmalloc(arpsize); arp->ar_hrd = htons(iface->family); arp->ar_pro = htons(ETHERTYPE_IP); arp->ar_hln = iface->hwlen; arp->ar_pln = sizeof(sip); arp->ar_op = htons(op); - p = (unsigned char *)arp; + p = (uint8_t *)arp; p += sizeof(*arp); memcpy(p, iface->hwaddr, iface->hwlen); p += iface->hwlen; memcpy(p, &sip, sizeof(sip)); p += sizeof(sip); - /* ARP requests should ignore this, but we fill with 0xff - * for broadcast. */ - memset(p, 0xff, iface->hwlen); - p += iface->hwlen; + /* ARP requests should ignore this */ + retval = iface->hwlen; + while (retval--) + *p++ = '\0'; memcpy(p, &tip, sizeof(tip)); - + p += sizeof(tip); retval = send_raw_packet(iface, ETHERTYPE_ARP, arp, arpsize); free(arp); return retval; } -#endif - @@ -71,8 +71,8 @@ #endif #define LINKLOCAL_ADDR 0xa9fe0000 -#define LINKLOCAL_MASK 0xffff0000 -#define LINKLOCAL_BRDC 0xa9feffff +#define LINKLOCAL_MASK IN_CLASSB_NET +#define LINKLOCAL_BRDC (LINKLOCAL_ADDR | ~LINKLOCAL_MASK) #ifndef IN_LINKLOCAL # define IN_LINKLOCAL(addr) ((addr & IN_CLASSB_NET) == LINKLOCAL_ADDR) @@ -101,13 +101,12 @@ struct interface size_t hwlen; int arpable; - int fd; + int raw_fd; int udp_fd; + int arp_fd; + int link_fd; size_t buffer_size, buffer_len, buffer_pos; unsigned char *buffer; -#ifdef ENABLE_ARP - int arp_fd; -#endif struct in_addr addr; struct in_addr net; @@ -131,6 +130,7 @@ int do_mtu(const char *, short int); int inet_ntocidr(struct in_addr); int inet_cidrtoaddr(int, struct in_addr *); +int up_interface(const char *); int do_interface(const char *, unsigned char *, size_t *, struct in_addr *, struct in_addr *, int); int if_address(const char *, const struct in_addr *, const struct in_addr *, @@ -148,6 +148,8 @@ int if_route(const char *, const struct in_addr *, const struct in_addr *, const struct in_addr *, int, int); #define add_route(ifname, dest, mask, gate, metric) \ if_route(ifname, dest, mask, gate, metric, 1) +#define change_route(ifname, dest, mask, gate, metric) \ + if_route(ifname, dest, mask, gate, metric, 0) #define del_route(ifname, dest, mask, gate, metric) \ if_route(ifname, dest, mask, gate, metric, -1) void free_routes(struct rt *); @@ -166,7 +168,9 @@ ssize_t send_raw_packet(const struct interface *, int, const void *, ssize_t); ssize_t get_raw_packet(struct interface *, int, void *, ssize_t); -#ifdef ENABLE_ARP int send_arp(const struct interface *, int, in_addr_t, in_addr_t); -#endif + +int open_link_socket(struct interface *); +int link_changed(struct interface *); +int carrier_status(const char *); #endif diff --git a/showlease.c b/showlease.c new file mode 100644 index 0000000..9bee4ab --- /dev/null +++ b/showlease.c @@ -0,0 +1,349 @@ +#include <stdio.h> +#include <stdlib.h> +#include <errno.h> +#include <fcntl.h> + +#include "dhcp.h" +#include "config.h" + +#define REQUEST (1 << 0) +#define UINT8 (1 << 1) +#define UINT16 (1 << 2) +#define SINT16 (1 << 3) +#define UINT32 (1 << 4) +#define SINT32 (1 << 5) +#define IPV4 (1 << 6) +#define STRING (1 << 7) +#define PAIR (1 << 8) +#define ARRAY (1 << 9) +#define RFC3361 (1 << 10) +#define RFC3397 (1 << 11) +#define RFC3442 (1 << 12) + +struct dhcp_opt { + uint8_t option; + int type; + const char *var; +}; + +static const struct dhcp_opt const dhcp_opts[] = { + { 1, IPV4 | REQUEST, "subnet_mask" }, + { 2, UINT32, "time_offset" }, + { 3, IPV4 | ARRAY | REQUEST, "routers" }, + { 4, IPV4 | ARRAY, "time_servers" }, + { 5, IPV4 | ARRAY, "ien116_name_servers" }, + { 6, IPV4 | ARRAY, "domain_name_servers" }, + { 7, IPV4 | ARRAY, "log_servers" }, + { 8, IPV4 | ARRAY, "cookie_servers" }, + { 9, IPV4 | ARRAY, "lpr_servers" }, + { 10, IPV4 | ARRAY, "impress_servers" }, + { 11, IPV4 | ARRAY, "resource_location_servers" }, + { 12, STRING, "host_name" }, + { 13, UINT16, "boot_size" }, + { 14, STRING, "merit_dump" }, + { 15, STRING, "domain_name" }, + { 16, IPV4, "swap_server" }, + { 17, STRING, "root_path" }, + { 18, STRING, "extensions_path" }, + { 19, UINT8, "ip_forwarding" }, + { 20, UINT8, "non_local_source_routing" }, + { 21, IPV4 | ARRAY, "policy_filter" }, + { 22, SINT16, "max_dgram_reassembly" }, + { 23, UINT16, "default_ip_ttl" }, + { 24, UINT32, "path_mtu_aging_timeout" }, + { 25, UINT16 | ARRAY, "path_mtu_plateau_table" }, + { 26, UINT16, "interface_mtu" }, + { 27, UINT8, "all_subnets_local" }, + { 28, IPV4 | REQUEST, "broadcast_address" }, + { 29, UINT8, "perform_mask_discovery" }, + { 30, UINT8, "mask_supplier" }, + { 31, UINT8, "router_discovery" }, + { 32, IPV4, "router_solicitation_address" }, + { 33, IPV4 | ARRAY | REQUEST, "static_routes" }, + { 34, UINT8, "trailer_encapsulation" }, + { 35, UINT32, "arp_cache_timeout" }, + { 36, UINT16, "ieee802_3_encapsulation" }, + { 37, UINT8, "default_tcp_ttl" }, + { 38, UINT32, "tcp_keepalive_interval" }, + { 39, UINT8, "tcp_keepalive_garbage" }, + { 40, STRING, "nis_domain" }, + { 41, IPV4 | ARRAY, "nis_servers" }, + { 42, IPV4 | ARRAY, "ntp_servers" }, + { 43, STRING, "vendor_encapsulated_options" }, + { 44, IPV4 | ARRAY, "netbios_name_servers" }, + { 45, IPV4, "netbios_dd_server" }, + { 46, UINT8, "netbios_node_type" }, + { 47, STRING, "netbios_scope" }, + { 48, IPV4 | ARRAY, "font_servers" }, + { 49, IPV4 | ARRAY, "x_display_manager" }, + { 50, IPV4, "dhcp_requested_address" }, + { 51, UINT32 | REQUEST, "dhcp_lease_time" }, + { 52, UINT8, "dhcp_option_overload" }, + { 53, UINT8, "dhcp_message_type" }, + { 54, IPV4, "dhcp_server_identifier" }, + { 55, UINT8 | ARRAY, "dhcp_parameter_request_list" }, + { 56, STRING, "dhcp_message" }, + { 57, UINT16, "dhcp_max_message_size" }, + { 58, UINT32 | REQUEST, "dhcp_renewal_time" }, + { 59, UINT32 | REQUEST, "dhcp_rebinding_time" }, + { 64, STRING, "nisplus_domain" }, + { 65, IPV4 | ARRAY, "nisplus_servers" }, + { 66, STRING, "tftp_server_name" }, + { 67, STRING, "bootfile_name" }, + { 68, IPV4 | ARRAY, "mobile_ip_home_agent" }, + { 69, IPV4 | ARRAY, "smtp_server" }, + { 70, IPV4 | ARRAY, "pop_server" }, + { 71, IPV4 | ARRAY, "nntp_server" }, + { 72, IPV4 | ARRAY, "www_server" }, + { 73, IPV4 | ARRAY, "finger_server" }, + { 74, IPV4 | ARRAY, "irc_server" }, + { 75, IPV4 | ARRAY, "streettalk_server" }, + { 76, IPV4 | ARRAY, "streettalk_directory_assistance_server" }, + { 77, STRING, "user_class" }, + { 85, IPV4 | ARRAY, "nds_servers" }, + { 86, STRING, "nds_tree_name" }, + { 87, STRING, "nds_context" }, + { 88, STRING | RFC3397, "bcms_controller_names" }, + { 89, IPV4 | ARRAY, "bcms_controller_address" }, + { 91, UINT32, "client_last_transaction_time" }, + { 92, IPV4 | ARRAY, "associated_ip" }, + { 98, STRING, "uap_servers" }, + { 112, IPV4 | ARRAY, "netinfo_server_address" }, + { 113, STRING, "netinfo_server_tag" }, + { 114, STRING, "default_url" }, + { 118, IPV4, "subnet_selection" }, + { 119, STRING | RFC3397, "domain_search" }, + { 121, RFC3442 | REQUEST, "classless_static_routes" }, + { 249, RFC3442, "ms-classless_static_routes" }, + { 0, 0, NULL } +}; + +struct dhcp_message * +get_lease(const char *leasefile) +{ + int fd; + struct dhcp_message *dhcp; + ssize_t bytes; + + fd = open(leasefile, O_RDONLY); + if (fd == -1) + return NULL; + dhcp = malloc(sizeof(*dhcp)); + memset(dhcp, 0, sizeof(*dhcp)); + bytes = read(fd, dhcp, sizeof(*dhcp)); + close(fd); + if (bytes < 0) { + free(dhcp); + dhcp = NULL; + } + return dhcp; +} + +static uint8_t *dhcp_opt_buffer = NULL; + +static int +valid_length(uint8_t option, int dl, int *type) +{ + const struct dhcp_opt *opt; + ssize_t sz; + + if (dl == 0) + return -1; + + for (opt = dhcp_opts; opt->option; opt++) { + if (opt->option != option) + continue; + + if (type) + *type = opt->type; + + if (opt->type == 0 || opt->type & STRING || opt->type & RFC3442) + return 0; + + sz = 0; + if (opt->type & UINT32 || opt->type & IPV4) + sz = sizeof(uint32_t); + if (opt->type & UINT16) + sz = sizeof(uint16_t); + if (opt->type & UINT8) + sz = sizeof(uint8_t); + if (opt->type & IPV4 || opt->type & ARRAY) + return dl % sz; + return (dl == sz ? 0 : -1); + } + + /* unknown option, so let it pass */ + return 0; +} + +static void +free_option_buffer(void) +{ + free(dhcp_opt_buffer); +} + + +#define get_option_raw(dhcp, opt) get_option(dhcp, opt, NULL, NULL) +static const uint8_t * +get_option(const struct dhcp_message *dhcp, uint8_t opt, int *len, int *type) +{ + const uint8_t *p = dhcp->options; + const uint8_t *e = p + sizeof(dhcp->options); + uint8_t l, ol = 0; + uint8_t o = 0; + uint8_t overl = 0; + uint8_t *bp = NULL; + const uint8_t *op = NULL; + int bl = 0; + + while (p < e) { + o = *p++; + if (o == opt) { + if (op) { + if (!dhcp_opt_buffer) { + dhcp_opt_buffer = malloc(sizeof(struct dhcp_message)); + atexit(free_option_buffer); + } + if (!bp) + bp = dhcp_opt_buffer; + memcpy(bp, op, ol); + bp += ol; + } + ol = *p; + op = p + 1; + bl += ol; + } + switch (o) { + case DHO_PAD: + continue; + case DHO_END: + if (overl & 1) { + /* bit 1 set means parse boot file */ + overl &= ~1; + p = dhcp->bootfile; + e = p + sizeof(dhcp->bootfile); + } else if (overl & 2) { + /* bit 2 set means parse server name */ + overl &= ~2; + p = dhcp->servername; + e = p + sizeof(dhcp->servername); + } else + goto exit; + break; + case DHO_OPTIONSOVERLOADED: + /* Ensure we only get this option once */ + if (!overl) + overl = p[1]; + break; + } + l = *p++; + p += l; + } + +exit: + if (valid_length(o, bl, type) == -1) { + errno = EINVAL; + return NULL; + } + if (len) + *len = bl; + if (bp) { + memcpy(bp, op, ol); + return (const uint8_t *)&dhcp_opt_buffer; + } + if (op) + return op; + errno = ENOENT; + return NULL; +} + +int +get_option_addr(uint32_t *a, const struct dhcp_message *dhcp, uint8_t option) +{ + const uint8_t *p = get_option_raw(dhcp, option); + + if (!p) + return -1; + memcpy(a, p, sizeof(*a)); + return 0; +} + +int +get_option_uint32(uint32_t *i, const struct dhcp_message *dhcp, uint8_t option) +{ + uint32_t a; + + if (get_option_addr(&a, dhcp, option) == -1) + return -1; + + *i = ntohl(a); + return 0; +} + +uint32_t +get_netmask(uint32_t addr) +{ + uint32_t dst; + + if (addr == 0) + return 0; + + dst = htonl(addr); + if (IN_CLASSA(dst)) + return ntohl(IN_CLASSA_NET); + if (IN_CLASSB (dst)) + return ntohl(IN_CLASSB_NET); + if (IN_CLASSC (dst)) + return ntohl(IN_CLASSC_NET); + + return 0; +} + +void showlease(struct dhcp_lease *lease) +{ + printf("addr: %s\n", inet_ntoa(lease->addr)); + printf("net: %s\n", inet_ntoa(lease->net)); + printf("leasetime: %d\n", lease->leasetime); + printf("renew: %d\n", lease->renewaltime); + printf("rebind: %d\n", lease->rebindtime); + printf("server: %s\n", inet_ntoa(lease->server)); +} +#define MAX_LEASETIME 2147460 + +int +main(int argc, char *argv[]) +{ + struct dhcp_message *dhcp; + struct dhcp_lease *lease; + char leasefile[PATH_MAX]; + + if (argc < 2) { + fprintf(stderr, "Usage: %s <interface>\n", argv[0]); + exit(1); + } + snprintf(leasefile, PATH_MAX, LEASEFILE, argv[1]); + if ((dhcp = get_lease(leasefile)) == NULL) { + fprintf(stderr, "Couldn't read lease file: %s\n", strerror(errno)); + exit(1); + } + lease = malloc(sizeof(*lease)); + lease->frominfo = 0; + lease->addr.s_addr = dhcp->yiaddr; + + if (get_option_addr(&lease->net.s_addr, dhcp, DHO_SUBNETMASK) == -1) + lease->net.s_addr = get_netmask(dhcp->yiaddr); + if (get_option_uint32(&lease->leasetime, dhcp, DHO_LEASETIME) != 0) + lease->leasetime = DEFAULT_LEASETIME; + get_option_addr(&lease->server.s_addr, dhcp, DHO_SERVERID); + /* Dm: limit lease time value to avoid negative numbers when + converting to milliseconds */ + if ((lease->leasetime != ~0U) && (lease->leasetime > MAX_LEASETIME)) + lease->leasetime = MAX_LEASETIME; + if (get_option_uint32(&lease->renewaltime, dhcp, DHO_RENEWALTIME) != 0) + lease->renewaltime = 0; + if (get_option_uint32(&lease->rebindtime, dhcp, DHO_REBINDTIME) != 0) + lease->rebindtime = 0; + showlease(lease); + free(lease); + return 0; +} @@ -29,7 +29,6 @@ #include <sys/socket.h> #include <errno.h> -#include <poll.h> #include <signal.h> #include <string.h> #include <unistd.h> @@ -62,31 +61,20 @@ signal_fd(void) return (signal_pipe[0]); } -/* Check if we have a signal or not */ -int -signal_exists(int fd) -{ - if (fd_hasdata(fd) == 1) - return 0; - return -1; -} - /* 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(int fd) +signal_read(void) { int sig = -1; char buf[16]; size_t bytes; - if (fd_hasdata(fd) == 1) { - memset(buf, 0, sizeof(buf)); - bytes = read(signal_pipe[0], buf, sizeof(buf)); - if (bytes >= sizeof(sig)) - memcpy(&sig, buf, sizeof(sig)); - } + memset(buf, 0, sizeof(buf)); + bytes = read(signal_pipe[0], buf, sizeof(buf)); + if (bytes >= sizeof(sig)) + memcpy(&sig, buf, sizeof(sig)); return sig; } @@ -28,13 +28,10 @@ #ifndef SIGNAL_H #define SIGNAL_H -#include <poll.h> - int signal_init(void); int signal_setup(void); int signal_reset(void); int signal_fd(void); -int signal_exists(int fd); -int signal_read(int fd); +int signal_read(void); #endif |