#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
}
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);
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 {
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");
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();
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 ||
}
#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)
{
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);
/* 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);
.\" 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
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
.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
.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
.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
/* 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;
{"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},
}
#endif
break;
+ case 'K':
+ options->options &= ~DHCPCD_LINK;
+ break;
case 'L':
options->options &= ~DHCPCD_IPV4LL;
break;
return -1;
}
break;
+ case 'W':
+ options->options |= DHCPCD_NOWAIT;
+ break;
case 'X':
options->options &= ~DHCPCD_DAEMONISE;
break;
#endif
#endif
+ options->options |= DHCPCD_LINK;
+
#ifdef CMDLINE_COMPAT
add_reqmask(options->reqmask, DHCP_DNSSERVER);
add_reqmask(options->reqmask, DHCP_DNSDOMAIN);
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.
.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
#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];
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;
+}
#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) \
#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>
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)
{
#ifdef ENABLE_ARP
iface->arp_fd = -1;
#endif
+ iface->link_fd = -1;
eexit:
close(s);
#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)
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;
#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