aboutsummaryrefslogtreecommitdiffstats
path: root/if-linux.c
diff options
context:
space:
mode:
Diffstat (limited to 'if-linux.c')
-rw-r--r--if-linux.c349
1 files changed, 267 insertions, 82 deletions
diff --git a/if-linux.c b/if-linux.c
index 114a084..4a06259 100644
--- a/if-linux.c
+++ b/if-linux.c
@@ -1,6 +1,6 @@
/*
* dhcpcd - DHCP client daemon
- * Copyright 2006-2008 Roy Marples <roy@marples.name>
+ * Copyright (c) 2006-2010 Roy Marples <roy@marples.name>
* All rights reserved
* Redistribution and use in source and binary forms, with or without
@@ -29,68 +29,127 @@
#include <sys/types.h>
#include <sys/socket.h>
-#include <sys/stat.h>
#include <sys/ioctl.h>
#include <sys/param.h>
-#include <arpa/inet.h>
#include <linux/netlink.h>
#include <linux/rtnetlink.h>
-#include <netinet/ether.h>
-#include <netpacket/packet.h>
+
+/* Support older kernels */
+#ifndef IFLA_WIRELESS
+# define IFLA_WIRELESS (IFLA_MASTER + 1)
+#endif
#include <errno.h>
+#include <ctype.h>
#include <stddef.h>
#include <stdio.h>
#include <stdlib.h>
#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 "configure.h"
#include "dhcp.h"
#include "net.h"
-#define BUFFERLEN 256
+static int sock_fd;
+static struct sockaddr_nl sock_nl;
+
+int
+if_init(struct interface *iface)
+{
+ char path[PATH_MAX];
+ FILE *fp;
+ int n;
+
+ /* We enable promote_secondaries so that we can do this
+ * add 192.168.1.2/24
+ * add 192.168.1.3/24
+ * del 192.168.1.2/24
+ * and the subnet mask moves onto 192.168.1.3/24
+ * This matches the behaviour of BSD which makes coding dhcpcd
+ * a little easier as there's just one behaviour. */
+ snprintf(path, sizeof(path),
+ "/proc/sys/net/ipv4/conf/%s/promote_secondaries",
+ iface->name);
+
+ fp = fopen(path, "w");
+ if (fp == NULL)
+ return errno == ENOENT ? 0 : -1;
+ n = fprintf(fp, "1");
+ fclose(fp);
+ return n == -1 ? -1 : 0;
+}
int
-open_link_socket(struct interface *iface)
+if_conf(struct interface *iface)
+{
+ char path[PATH_MAX], buf[1];
+ FILE *fp;
+
+ /* Some qeth setups require the use of the broadcast flag. */
+ snprintf(path, sizeof(path),
+ "/sys/class/net/%s/device/layer2",
+ iface->name);
+
+ fp = fopen(path, "r");
+ if (fp == NULL)
+ return errno == ENOENT ? 0 : -1;
+ if (fgets(buf, sizeof(buf), fp) != NULL && buf[0] == '0')
+ iface->state->options->options |= DHCPCD_BROADCAST;
+ fclose(fp);
+ return 0;
+}
+
+static int
+_open_link_socket(struct sockaddr_nl *nl)
{
int fd;
- struct sockaddr_nl nl;
if ((fd = socket(AF_NETLINK, SOCK_RAW, NETLINK_ROUTE)) == -1)
return -1;
- memset(&nl, 0, sizeof(nl));
- nl.nl_family = AF_NETLINK;
- nl.nl_groups = RTMGRP_LINK;
- if (bind(fd, (struct sockaddr *)&nl, sizeof(nl)) == -1)
+ nl->nl_family = AF_NETLINK;
+ 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;
+ return fd;
+}
+
+int
+init_sockets(void)
+{
+ if ((socket_afnet = socket(AF_INET, SOCK_DGRAM, 0)) == -1)
+ return -1;
+ set_cloexec(socket_afnet);
+ sock_fd = _open_link_socket(&sock_nl);
+ set_cloexec(sock_fd);
+ return sock_fd;
+}
+
+int
+open_link_socket(void)
+{
+ struct sockaddr_nl snl;
+
+ memset(&snl, 0, sizeof(snl));
+ snl.nl_groups = RTMGRP_LINK | RTMGRP_IPV4_ROUTE | RTMGRP_IPV4_IFADDR;
+ return _open_link_socket(&snl);
}
static int
get_netlink(int fd, int flags,
- int (*callback)(struct nlmsghdr *, const char *),
- const char *ifname)
+ int (*callback)(struct nlmsghdr *))
{
- char *buffer = NULL;
- ssize_t bytes;
+ char *buf = NULL, *nbuf;
+ ssize_t buflen = 0, bytes;
struct nlmsghdr *nlm;
int r = -1;
- buffer = xzalloc(sizeof(char) * BUFFERLEN);
for (;;) {
- bytes = recv(fd, buffer, BUFFERLEN, flags);
+ bytes = recv(fd, NULL, 0,
+ flags | MSG_PEEK | MSG_DONTWAIT | MSG_TRUNC);
if (bytes == -1) {
if (errno == EAGAIN) {
r = 0;
@@ -99,24 +158,48 @@ get_netlink(int fd, int flags,
if (errno == EINTR)
continue;
goto eexit;
+ } else if (bytes == buflen) {
+ /* Support kernels older than 2.6.22 */
+ if (bytes == 0)
+ bytes = 512;
+ else
+ bytes *= 2;
}
- for (nlm = (struct nlmsghdr *)buffer;
+ if (buflen < bytes) {
+ /* Alloc 1 more so we work with older kernels */
+ buflen = bytes + 1;
+ nbuf = realloc(buf, buflen);
+ if (nbuf == NULL)
+ goto eexit;
+ buf = nbuf;
+ }
+ bytes = recv(fd, buf, buflen, flags);
+ if (bytes == -1) {
+ if (errno == EAGAIN) {
+ r = 0;
+ goto eexit;
+ }
+ if (errno == EINTR)
+ continue;
+ goto eexit;
+ }
+ for (nlm = (struct nlmsghdr *)buf;
NLMSG_OK(nlm, (size_t)bytes);
nlm = NLMSG_NEXT(nlm, bytes))
{
- r = callback(nlm, ifname);
+ r = callback(nlm);
if (r != 0)
goto eexit;
}
}
eexit:
- free(buffer);
+ free(buf);
return r;
}
static int
-err_netlink(struct nlmsghdr *nlm, _unused const char *ifname)
+err_netlink(struct nlmsghdr *nlm)
{
struct nlmsgerr *err;
int l;
@@ -136,13 +219,131 @@ err_netlink(struct nlmsghdr *nlm, _unused const char *ifname)
}
static int
-link_netlink(struct nlmsghdr *nlm, const char *ifname)
+link_route(struct nlmsghdr *nlm)
+{
+ int len, idx, metric;
+ struct rtattr *rta;
+ struct rtmsg *rtm;
+ struct rt rt;
+ char ifn[IF_NAMESIZE + 1];
+
+ if (nlm->nlmsg_type != RTM_DELROUTE)
+ return 0;
+
+ len = nlm->nlmsg_len - sizeof(*nlm);
+ if ((size_t)len < sizeof(*rtm)) {
+ errno = EBADMSG;
+ return -1;
+ }
+ rtm = NLMSG_DATA(nlm);
+ if (rtm->rtm_type != RTN_UNICAST ||
+ rtm->rtm_table != RT_TABLE_MAIN ||
+ rtm->rtm_family != AF_INET ||
+ nlm->nlmsg_pid == (uint32_t)getpid())
+ return 1;
+ rta = (struct rtattr *) ((char *)rtm + NLMSG_ALIGN(sizeof(*rtm)));
+ len = NLMSG_PAYLOAD(nlm, sizeof(*rtm));
+ rt.iface = NULL;
+ rt.dest.s_addr = INADDR_ANY;
+ rt.net.s_addr = INADDR_ANY;
+ rt.gate.s_addr = INADDR_ANY;
+ rt.next = NULL;
+ metric = 0;
+ while (RTA_OK(rta, len)) {
+ switch (rta->rta_type) {
+ case RTA_DST:
+ memcpy(&rt.dest.s_addr, RTA_DATA(rta),
+ sizeof(rt.dest.s_addr));
+ break;
+ case RTA_GATEWAY:
+ memcpy(&rt.gate.s_addr, RTA_DATA(rta),
+ sizeof(rt.gate.s_addr));
+ break;
+ case RTA_OIF:
+ idx = *(int *)RTA_DATA(rta);
+ if (if_indextoname(idx, ifn))
+ rt.iface = find_interface(ifn);
+ break;
+ case RTA_PRIORITY:
+ metric = *(int *)RTA_DATA(rta);
+ break;
+ }
+ rta = RTA_NEXT(rta, len);
+ }
+ if (rt.iface != NULL) {
+ if (metric == rt.iface->metric) {
+ inet_cidrtoaddr(rtm->rtm_dst_len, &rt.net);
+ route_deleted(&rt);
+ }
+ }
+ return 1;
+}
+
+static int
+link_addr(struct nlmsghdr *nlm)
+{
+ int len;
+ struct rtattr *rta;
+ struct ifaddrmsg *ifa;
+ struct in_addr addr, net, dest;
+ char ifn[IF_NAMESIZE + 1];
+ struct interface *iface;
+
+ if (nlm->nlmsg_type != RTM_DELADDR && nlm->nlmsg_type != RTM_NEWADDR)
+ return 0;
+
+ len = nlm->nlmsg_len - sizeof(*nlm);
+ if ((size_t)len < sizeof(*ifa)) {
+ errno = EBADMSG;
+ return -1;
+ }
+ if (nlm->nlmsg_pid == (uint32_t)getpid())
+ return 1;
+ ifa = NLMSG_DATA(nlm);
+ if (if_indextoname(ifa->ifa_index, ifn) == NULL)
+ return -1;
+ iface = find_interface(ifn);
+ if (iface == NULL)
+ return 1;
+ rta = (struct rtattr *) IFA_RTA(ifa);
+ len = NLMSG_PAYLOAD(nlm, sizeof(*ifa));
+ addr.s_addr = dest.s_addr = INADDR_ANY;
+ dest.s_addr = INADDR_ANY;
+ inet_cidrtoaddr(ifa->ifa_prefixlen, &net);
+ while (RTA_OK(rta, len)) {
+ switch (rta->rta_type) {
+ case IFA_ADDRESS:
+ if (iface->flags & IFF_POINTOPOINT) {
+ memcpy(&dest.s_addr, RTA_DATA(rta),
+ sizeof(addr.s_addr));
+ }
+ break;
+ case IFA_LOCAL:
+ memcpy(&addr.s_addr, RTA_DATA(rta),
+ sizeof(addr.s_addr));
+ break;
+ }
+ rta = RTA_NEXT(rta, len);
+ }
+ handle_ifa(nlm->nlmsg_type, ifn, &addr, &net, &dest);
+ return 1;
+}
+
+static int
+link_netlink(struct nlmsghdr *nlm)
{
int len;
struct rtattr *rta;
struct ifinfomsg *ifi;
char ifn[IF_NAMESIZE + 1];
+ len = link_route(nlm);
+ if (len != 0)
+ return len;
+ len = link_addr(nlm);
+ if (len != 0)
+ return len;
+
if (nlm->nlmsg_type != RTM_NEWLINK && nlm->nlmsg_type != RTM_DELLINK)
return 0;
len = nlm->nlmsg_len - sizeof(*nlm);
@@ -152,7 +353,7 @@ link_netlink(struct nlmsghdr *nlm, const char *ifname)
}
ifi = NLMSG_DATA(nlm);
if (ifi->ifi_flags & IFF_LOOPBACK)
- return 0;
+ return 1;
rta = (struct rtattr *) ((char *)ifi + NLMSG_ALIGN(sizeof(*ifi)));
len = NLMSG_PAYLOAD(nlm, sizeof(*ifi));
*ifn = '\0';
@@ -161,8 +362,8 @@ link_netlink(struct nlmsghdr *nlm, const char *ifname)
case IFLA_WIRELESS:
/* Ignore wireless messages */
if (nlm->nlmsg_type == RTM_NEWLINK &&
- ifi->ifi_change == 0)
- return 0;
+ ifi->ifi_change == 0)
+ return 1;
break;
case IFLA_IFNAME:
strlcpy(ifn, RTA_DATA(rta), sizeof(ifn));
@@ -170,62 +371,53 @@ link_netlink(struct nlmsghdr *nlm, const char *ifname)
}
rta = RTA_NEXT(rta, len);
}
-
- if (strncmp(ifname, ifn, sizeof(ifn)) == 0)
- return 1;
- return 0;
+ if (nlm->nlmsg_type == RTM_NEWLINK)
+ len = ifi->ifi_change == ~0U ? 1 : 0;
+ else
+ len = -1;
+ handle_interface(len, ifn);
+ return 1;
}
int
-link_changed(struct interface *iface)
+manage_link(int fd)
{
- return get_netlink(iface->link_fd, MSG_DONTWAIT,
- &link_netlink, iface->name);
+ return get_netlink(fd, MSG_DONTWAIT, &link_netlink);
}
static int
send_netlink(struct nlmsghdr *hdr)
{
- int fd, r;
- struct sockaddr_nl nl;
+ int r;
struct iovec iov;
struct msghdr msg;
static unsigned int seq;
- 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_name = &sock_nl;
+ msg.msg_namelen = sizeof(sock_nl);
msg.msg_iov = &iov;
msg.msg_iovlen = 1;
/* Request a reply */
hdr->nlmsg_flags |= NLM_F_ACK;
hdr->nlmsg_seq = ++seq;
- if (sendmsg(fd, &msg, 0) != -1)
- r = get_netlink(fd, 0, &err_netlink, NULL);
+ if (sendmsg(sock_fd, &msg, 0) != -1)
+ r = get_netlink(sock_fd, 0, &err_netlink);
else
r = -1;
- close(fd);
return r;
}
-#define NLMSG_TAIL(nmsg) \
+#define NLMSG_TAIL(nmsg) \
((struct rtattr *)(((ptrdiff_t)(nmsg))+NLMSG_ALIGN((nmsg)->nlmsg_len)))
static int
add_attr_l(struct nlmsghdr *n, unsigned int maxlen, int type,
- const void *data, int alen)
+ const void *data, int alen)
{
int len = RTA_LENGTH(alen);
struct rtattr *rta;
@@ -279,9 +471,9 @@ struct nlmr
};
int
-if_address(const char *ifname,
- const struct in_addr *address, const struct in_addr *netmask,
- const struct in_addr *broadcast, int action)
+if_address(const struct interface *iface,
+ const struct in_addr *address, const struct in_addr *netmask,
+ const struct in_addr *broadcast, int action)
{
struct nlma *nlm;
int retval = 0;
@@ -294,7 +486,7 @@ if_address(const char *ifname,
nlm->hdr.nlmsg_type = RTM_NEWADDR;
} else
nlm->hdr.nlmsg_type = RTM_DELADDR;
- if (!(nlm->ifa.ifa_index = if_nametoindex(ifname))) {
+ if (!(nlm->ifa.ifa_index = if_nametoindex(iface->name))) {
free(nlm);
errno = ENODEV;
return -1;
@@ -303,12 +495,12 @@ if_address(const char *ifname,
nlm->ifa.ifa_prefixlen = inet_ntocidr(*netmask);
/* This creates the aliased interface */
add_attr_l(&nlm->hdr, sizeof(*nlm), IFA_LABEL,
- ifname, strlen(ifname) + 1);
+ iface->name, strlen(iface->name) + 1);
add_attr_l(&nlm->hdr, sizeof(*nlm), IFA_LOCAL,
- &address->s_addr, sizeof(address->s_addr));
- if (action >= 0)
+ &address->s_addr, sizeof(address->s_addr));
+ if (action >= 0 && broadcast)
add_attr_l(&nlm->hdr, sizeof(*nlm), IFA_BROADCAST,
- &broadcast->s_addr, sizeof(broadcast->s_addr));
+ &broadcast->s_addr, sizeof(broadcast->s_addr));
if (send_netlink(&nlm->hdr) == -1)
retval = -1;
@@ -318,8 +510,8 @@ if_address(const char *ifname,
int
if_route(const struct interface *iface,
- const struct in_addr *destination, const struct in_addr *netmask,
- const struct in_addr *gateway, int metric, int action)
+ const struct in_addr *destination, const struct in_addr *netmask,
+ const struct in_addr *gateway, int metric, int action)
{
struct nlmr *nlm;
unsigned int ifindex;
@@ -336,14 +528,7 @@ if_route(const struct interface *iface,
if (action == 0)
nlm->hdr.nlmsg_flags = NLM_F_REPLACE;
else if (action == 1)
- /*
- * 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;
@@ -353,7 +538,7 @@ if_route(const struct interface *iface,
if (action == -1 || action == -2)
nlm->rt.rtm_scope = RT_SCOPE_NOWHERE;
else {
- nlm->hdr.nlmsg_flags |= NLM_F_CREATE /*| NLM_F_EXCL*/;
+ nlm->hdr.nlmsg_flags |= NLM_F_CREATE | NLM_F_EXCL;
/* We only change route metrics for kernel routes */
if (destination->s_addr ==
(iface->addr.s_addr & iface->net.s_addr) &&
@@ -363,7 +548,7 @@ if_route(const struct interface *iface,
nlm->rt.rtm_protocol = RTPROT_BOOT;
if (gateway->s_addr == INADDR_ANY ||
(gateway->s_addr == destination->s_addr &&
- netmask->s_addr == INADDR_BROADCAST))
+ netmask->s_addr == INADDR_BROADCAST))
nlm->rt.rtm_scope = RT_SCOPE_LINK;
else
nlm->rt.rtm_scope = RT_SCOPE_UNIVERSE;
@@ -372,16 +557,16 @@ if_route(const struct interface *iface,
nlm->rt.rtm_dst_len = inet_ntocidr(*netmask);
add_attr_l(&nlm->hdr, sizeof(*nlm), RTA_DST,
- &destination->s_addr, sizeof(destination->s_addr));
+ &destination->s_addr, sizeof(destination->s_addr));
if (nlm->rt.rtm_protocol == RTPROT_KERNEL) {
add_attr_l(&nlm->hdr, sizeof(*nlm), RTA_PREFSRC,
- &iface->addr.s_addr, sizeof(iface->addr.s_addr));
+ &iface->addr.s_addr, sizeof(iface->addr.s_addr));
}
/* If destination == gateway then don't add the gateway */
if (destination->s_addr != gateway->s_addr ||
netmask->s_addr != INADDR_BROADCAST)
add_attr_l(&nlm->hdr, sizeof(*nlm), RTA_GATEWAY,
- &gateway->s_addr, sizeof(gateway->s_addr));
+ &gateway->s_addr, sizeof(gateway->s_addr));
add_attr_32(&nlm->hdr, sizeof(*nlm), RTA_OIF, ifindex);
add_attr_32(&nlm->hdr, sizeof(*nlm), RTA_PRIORITY, metric);