From: Roy Marples Date: Wed, 16 Jul 2008 22:23:07 +0000 (+0000) Subject: Add support for link carrier detection. For Linux this involved a big change to the... X-Git-Tag: v4.0.2~183 X-Git-Url: http://git.ipfire.org/cgi-bin/gitweb.cgi?a=commitdiff_plain;h=a26af49123674044a0216fbae83bec30db5f8ab3;p=thirdparty%2Fdhcpcd.git Add support for link carrier detection. For Linux this involved a big change to the netlink code to add callbacks, for BSD just an extra function. We also have an option not to wait for a DHCP lease and fork right away - useful for startup scripts. --- diff --git a/client.c b/client.c index 404a6490..240373cd 100644 --- a/client.c +++ b/client.c @@ -76,6 +76,7 @@ #define STATE_INIT_IPV4LL 8 #define STATE_PROBING 9 #define STATE_ANNOUNCING 10 +#define STATE_CARRIER 11 /* Constants taken from RFC 2131. */ #define T1 0.5 @@ -691,7 +692,8 @@ send_message(struct if_state *state, int type, const struct options *options) } 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); @@ -717,7 +719,17 @@ wait_for_packet(struct if_state *state) fds[nfds].fd = state->signal_fd; fds[nfds].events = POLLIN; nfds++; - if (state->lease.leasetime == ~0U && state->state == STATE_BOUND) { + /* And links */ + if (state->interface->link_fd != -1) { + fds[nfds].fd = state->interface->link_fd; + fds[nfds].events = POLLIN; + nfds++; + } + if (state->state == STATE_CARRIER) { + timeout = INFTIM; + } else if (state->lease.leasetime == ~0U && + state->state == STATE_BOUND) + { logger(LOG_DEBUG, "waiting for infinity"); timeout = INFTIM; } else { @@ -1035,7 +1047,10 @@ handle_timeout_fail(struct if_state *state, const struct options *options) 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) + tv.tv_sec = options->timeout; + else + tv.tv_sec = lease->rebindtime - lease->renewaltime; break; case STATE_REBINDING: logger(LOG_ERR, "failed to rebind, attempting to discover"); @@ -1155,9 +1170,14 @@ handle_timeout(struct if_state *state, const struct options *options) timerclear(&tv); switch (state->state) { - case STATE_INIT: /* FALLTHROUGH */ - case STATE_BOUND: /* FALLTHROUGH */ case STATE_RENEW_REQUESTED: + /* If a renew was requested (ie, didn't timeout) + * we need to remove the server address so we enter the + * INIT-REBOOT state correctly. */ + lease->server.s_addr = 0; + /* FALLTHROUGH */ + case STATE_INIT: /* FALLTHROUGH */ + case STATE_BOUND: up_interface(iface->name); do_socket(state, SOCKET_OPEN); state->xid = arc4random(); @@ -1202,6 +1222,13 @@ handle_timeout(struct if_state *state, const struct options *options) timerclear(&state->timeout); return 0; } + if (lease->addr.s_addr) { + logger(LOG_INFO, "renewing lease of %s",inet_ntoa(lease->addr)); + state->state = STATE_RENEWING; + tv.tv_sec = options->timeout; + timeradd(&state->start, &tv, &state->stop); + break; + } /* FALLTHROUGH */ case STATE_BOUND: if (lease->addr.s_addr == 0 || @@ -1564,6 +1591,37 @@ handle_arp_fail(struct if_state *state, const struct options *options) } #endif +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; + switch (carrier_status(state->interface->name)) { + case -1: + logger(LOG_ERR, "carrier_status: %s", strerror(errno)); + return -1; + case 0: + logger(LOG_INFO, "carrier lost"); + state->state = STATE_CARRIER; + do_socket(state, SOCKET_CLOSED); + break; + default: + logger(LOG_INFO, "carrier acquired"); + state->state = STATE_RENEW_REQUESTED; + break; + } + timerclear(&state->timeout); + timerclear(&state->stop); + return 0; +} + int dhcp_run(const struct options *options, int *pid_fd) { @@ -1593,6 +1651,18 @@ dhcp_run(const struct options *options, int *pid_fd) goto eexit; state->signal_fd = signal_fd(); + if (state->options & DHCPCD_LINK) { + open_link_socket(iface); + if (carrier_status(iface->name) == 0) { + if (!(state->options & DHCPCD_NOWAIT)) + logger(LOG_INFO, "waiting for carrier"); + state->state = STATE_CARRIER; + } + } + + if (state->options & DHCPCD_NOWAIT) + if (daemonise(state, options) == -1) + goto eexit; for (;;) { retval = wait_for_packet(state); @@ -1607,10 +1677,12 @@ dhcp_run(const struct options *options, int *pid_fd) /* The interupt will be handled above */ retval = 0; } else if (retval > 0) { - if (fd_hasdata(state->interface->raw_fd) == 1) + if (fd_hasdata(iface->link_fd) == 1) + retval = handle_link(state); + else if (fd_hasdata(iface->raw_fd) == 1) retval = handle_dhcp_packet(state, options); #ifdef ENABLE_ARP - else if (fd_hasdata(state->interface->arp_fd) == 1) { + else if (fd_hasdata(iface->arp_fd) == 1) { retval = handle_arp_packet(state); if (retval == -1) retval = handle_arp_fail(state, options); diff --git a/dhcpcd.8.in b/dhcpcd.8.in index 4fd6f27d..16717733 100644 --- a/dhcpcd.8.in +++ b/dhcpcd.8.in @@ -22,14 +22,14 @@ .\" OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF .\" SUCH DAMAGE. .\" -.Dd Jul 12, 2008 +.Dd Jul 16, 2008 .Dt DHCPCD 8 SMM .Sh NAME .Nm dhcpcd .Nd an RFC 2131 compliant DHCP client .Sh SYNOPSIS .Nm -.Op Fl dknpqADEGLSTXV +.Op Fl dknpqADEGKLSTVWX .Op Fl c , -script Ar script .Op Fl f , -config Ar file .Op Fl h , -hostname Ar hostname @@ -299,6 +299,10 @@ itself never does any DNS updates. 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. If not set then none is sent. +.It Fl W , -nowait +Don't wait for a DHCP lease, background immediately. +This is useful for startup scripts which don't disable link messages for +carrier status. .El .Ss Restriciting behaviour .Nm @@ -315,8 +319,6 @@ 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 C , -nohook Ar script Don't run this hook script. Matches full name, or prefixed with 2 numbers optionally ending with @@ -324,10 +326,11 @@ 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 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. .It Fl L , -noipv4ll Don't use IPv4LL at all. .It Fl O , -nooption Ar option @@ -344,6 +347,10 @@ 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 , -nodaemonise +Don't daemonise 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. .El .Sh NOTES .Nm diff --git a/dhcpcd.c b/dhcpcd.c index f8447cf9..2e344132 100644 --- a/dhcpcd.c +++ b/dhcpcd.c @@ -52,7 +52,7 @@ const char copyright[] = "Copyright (c) 2006-2008 Roy Marples"; /* 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 "c:df:h:i:kl:m:no:pqr:s:t:u:v:xAC:DEF:GI:KLO:TVWX" static int doversion = 0; static int dohelp = 0; @@ -82,10 +82,12 @@ static const struct option longopts[] = { {"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'}, {"test", no_argument, NULL, 'T'}, {"variables", no_argument, NULL, 'V'}, + {"nowait", no_argument, NULL, 'W'}, {"nodaemonise", no_argument, NULL, 'X'}, {"help", no_argument, &dohelp, 1}, {"version", no_argument, &doversion, 1}, @@ -539,6 +541,9 @@ parse_option(int opt, char *oarg, struct options *options) } #endif break; + case 'K': + options->options &= ~DHCPCD_LINK; + break; case 'L': options->options &= ~DHCPCD_IPV4LL; break; @@ -550,6 +555,9 @@ parse_option(int opt, char *oarg, struct options *options) return -1; } break; + case 'W': + options->options |= DHCPCD_NOWAIT; + break; case 'X': options->options &= ~DHCPCD_DAEMONISE; break; @@ -623,6 +631,8 @@ main(int argc, char **argv) #endif #endif + options->options |= DHCPCD_LINK; + #ifdef CMDLINE_COMPAT add_reqmask(options->reqmask, DHCP_DNSSERVER); add_reqmask(options->reqmask, DHCP_DNSDOMAIN); diff --git a/dhcpcd.conf.5.in b/dhcpcd.conf.5.in index 5ba825f2..f2f624b7 100644 --- a/dhcpcd.conf.5.in +++ b/dhcpcd.conf.5.in @@ -71,7 +71,7 @@ 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. @@ -96,6 +96,11 @@ See .Rs .%T "RFC 3927" .Re +.It Ic nolink +Don't receive link messages about carrier status. +You should only set this for buggy interface drivers. +.It Ic nowait +Don't wait to obtain a DHCP lease, fork to the background right away. .It Ic option Ar dhcp-option Requests the .Ar dhcp-option diff --git a/dhcpcd.h b/dhcpcd.h index 10d23ffb..c6c98504 100644 --- a/dhcpcd.h +++ b/dhcpcd.h @@ -68,6 +68,8 @@ 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_NOWAIT (1 << 21) struct options { char interface[IF_NAMESIZE]; diff --git a/if-bsd.c b/if-bsd.c index 2cc0c2f3..bbf1a95d 100644 --- a/if-bsd.c +++ b/if-bsd.c @@ -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; +} diff --git a/if-linux.c b/if-linux.c index 30b2f6e7..9c7ce974 100644 --- a/if-linux.c +++ b/if-linux.c @@ -46,128 +46,178 @@ #include #include +/* 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) \ diff --git a/net.c b/net.c index c364d7fe..c89a52eb 100644 --- a/net.c +++ b/net.c @@ -43,6 +43,9 @@ #define __FAVOR_BSD /* Nasty glibc hack so we can use BSD semantics for UDP */ #include #undef __FAVOR_BSD +#ifdef SIOCGIFMEDIA +#include +#endif #include #ifdef AF_LINK # include @@ -310,6 +313,51 @@ up_interface(const char *ifname) 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) { @@ -389,6 +437,7 @@ read_interface(const char *ifname, _unused int metric) #ifdef ENABLE_ARP iface->arp_fd = -1; #endif + iface->link_fd = -1; eexit: close(s); diff --git a/net.h b/net.h index 12f13211..f2b3dbe1 100644 --- a/net.h +++ b/net.h @@ -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) @@ -103,11 +103,12 @@ struct interface int raw_fd; int udp_fd; - size_t buffer_size, buffer_len, buffer_pos; - unsigned char *buffer; #ifdef ENABLE_ARP int arp_fd; #endif + int link_fd; + size_t buffer_size, buffer_len, buffer_pos; + unsigned char *buffer; struct in_addr addr; struct in_addr net; @@ -172,4 +173,8 @@ 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