Make sure that the netlink socket will never block after program

initialization.  Fetching if/address/index/mac mappings is done only once at
program init, so it is done synchronously as an exception to this rule.

Rewrite the netlink handling.  Now uses NIH code that should be safe, small,
and correct.  No external deps FTW.
This commit is contained in:
Nicholas J. Kain 2011-07-03 17:30:55 -04:00
parent 2c2cc1cf22
commit 01c4731403
7 changed files with 240 additions and 78 deletions

View File

@ -7,8 +7,6 @@ set(CMAKE_CXX_FLAGS "${CMAKE_C_FLAGS} -s -std=gnu99 -pedantic -Wall -lcap -DHAVE
include_directories("${PROJECT_SOURCE_DIR}/ncmlib")
add_subdirectory(ncmlib)
include_directories("${PROJECT_SOURCE_DIR}/libmnl")
add_subdirectory(libmnl)
add_subdirectory(ifchd)
add_subdirectory(ndhc)

View File

@ -5,4 +5,4 @@ cmake_minimum_required (VERSION 2.6)
file(GLOB NDHC_SRCS "*.c")
add_executable(ndhc ${NDHC_SRCS})
target_link_libraries(ndhc ncmlib libmnl)
target_link_libraries(ndhc ncmlib)

View File

@ -1,5 +1,5 @@
/* ndhc.c - DHCP client
* Time-stamp: <2011-07-03 05:43:58 njk>
* Time-stamp: <2011-07-03 17:19:25 njk>
*
* (c) 2004-2011 Nicholas J. Kain <njkain at gmail dot com>
*
@ -47,6 +47,7 @@
#include "sys.h"
#include "ifchange.h"
#include "arp.h"
#include "nl.h"
#include "netlink.h"
#include "leasefile.h"
@ -308,7 +309,7 @@ int main(int argc, char **argv)
write_pid(pidfile);
}
if (nl_open(&cs) < 0) {
if ((cs.nlFd = nl_open(NETLINK_ROUTE, RTMGRP_LINK, &nlportid)) < 0) {
log_line("FATAL - failed to open netlink socket");
exit(EXIT_FAILURE);
}

View File

@ -21,8 +21,6 @@
#include <assert.h>
#include <asm/types.h>
#include <sys/socket.h>
#include <linux/netlink.h>
#include <linux/rtnetlink.h>
#include <unistd.h>
#include <net/if.h>
#include <stdio.h>
@ -32,37 +30,16 @@
#include <sys/select.h>
#include <fcntl.h>
#include <time.h>
#include <libmnl/libmnl.h>
#include <errno.h>
#include "netlink.h"
#include "ifchange.h"
#include "arp.h"
#include "log.h"
#include "nl.h"
static struct mnl_socket *mls;
static void nl_close(struct client_state_t *cs)
{
mnl_socket_close(mls);
cs->nlFd = -1;
}
int nl_open(struct client_state_t *cs)
{
assert(cs->nlFd == -1);
if ((mls = mnl_socket_open(NETLINK_ROUTE)) == (struct mnl_socket *)-1)
return -1;
cs->nlFd = mnl_socket_get_fd(mls);
if (fcntl(cs->nlFd, F_SETFD, FD_CLOEXEC))
goto err_close;
if (mnl_socket_bind(mls, RTMGRP_LINK, 0))
goto err_close;
return 0;
err_close:
nl_close(cs);
return -1;
}
static char nlbuf[8192];
int nlportid;
static void restart_if(struct client_state_t *cs)
{
@ -84,50 +61,34 @@ static void sleep_if(struct client_state_t *cs)
cs->timeout = -1;
}
static int nl_process_msgs_attr(const struct nlattr *attr, void *data)
{
const struct nlattr **tb = data;
int type = mnl_attr_get_type(attr);
// skip unsupported attribute in user-space
if (mnl_attr_type_valid(attr, IFLA_MAX) < 0)
return MNL_CB_OK;
switch (type) {
case IFLA_IFNAME:
if (mnl_attr_validate(attr, MNL_TYPE_STRING) < 0) {
log_warning("nl: IFLA_IFNAME failed validation.");
return MNL_CB_ERROR;
}
break;
case IFLA_ADDRESS:
if (mnl_attr_validate(attr, MNL_TYPE_STRING) < 0) {
log_warning("nl: IFLA_ADDRESS failed validation.");
return MNL_CB_ERROR;
}
break;
}
static int nlrtattr_assign(struct nlattr *attr, int type, void *data)
{
struct nlattr **tb = data;
if (type >= IFLA_MAX)
return 0;
tb[type] = attr;
return MNL_CB_OK;
return 0;
}
static void get_if_index_and_mac(const struct nlmsghdr *nlh,
struct ifinfomsg *ifm,
const struct nlattr **tb)
struct ifinfomsg *ifm)
{
mnl_attr_parse(nlh, sizeof *ifm, nl_process_msgs_attr, tb);
struct nlattr *tb[IFLA_MAX] = {0};
nl_attr_parse(nlh, sizeof *ifm, nlrtattr_assign, tb);
if (!tb[IFLA_IFNAME])
return;
if (!strcmp(client_config.interface, mnl_attr_get_str(tb[IFLA_IFNAME]))) {
if (!strcmp(client_config.interface, nlattr_get_data(tb[IFLA_IFNAME]))) {
client_config.ifindex = ifm->ifi_index;
if (!tb[IFLA_ADDRESS])
suicide("FATAL: adapter %s lacks a hardware address");
int maclen = mnl_attr_get_len(tb[IFLA_ADDRESS]) - 4;
int maclen = nlattr_get_len(tb[IFLA_ADDRESS]) - 4;
if (maclen != 6)
suicide("FATAL: adapter hardware address length should be 6, but is %u",
maclen);
const unsigned char *mac =
(unsigned char *)mnl_attr_get_str(tb[IFLA_ADDRESS]);
(unsigned char *)nlattr_get_data(tb[IFLA_ADDRESS]);
log_line("%s hardware address %x:%x:%x:%x:%x:%x",
client_config.interface,
mac[0], mac[1], mac[2], mac[3], mac[4], mac[5]);
@ -137,14 +98,13 @@ static void get_if_index_and_mac(const struct nlmsghdr *nlh,
static int nl_process_msgs(const struct nlmsghdr *nlh, void *data)
{
struct nlattr *tb[IFLA_MAX+1] = {0};
struct ifinfomsg *ifm = mnl_nlmsg_get_payload(nlh);
struct ifinfomsg *ifm = nlmsg_get_data(nlh);
struct client_state_t *cs = data;
switch(nlh->nlmsg_type) {
case RTM_NEWLINK:
if (!client_config.ifindex)
get_if_index_and_mac(nlh, ifm, (const struct nlattr **)tb);
get_if_index_and_mac(nlh, ifm);
if (ifm->ifi_index != client_config.ifindex)
break;
// IFF_UP corresponds to ifconfig down or ifconfig up.
@ -194,41 +154,50 @@ static int nl_process_msgs(const struct nlmsghdr *nlh, void *data)
default:
break;
}
return MNL_CB_OK;
return 1;
}
void handle_nl_message(struct client_state_t *cs)
{
char buf[MNL_SOCKET_BUFFER_SIZE];
int ret;
ssize_t ret;
assert(cs->nlFd != -1);
do {
ret = mnl_socket_recvfrom(mls, buf, sizeof buf);
ret = mnl_cb_run(buf, ret, 0, 0, nl_process_msgs, cs);
} while (ret > 0);
ret = nl_recv_buf(cs->nlFd, nlbuf, sizeof nlbuf);
if (ret == -1)
log_line("nl callback function returned error: %s", strerror(errno));
break;
if (nl_foreach_nlmsg(nlbuf, ret, nlportid, nl_process_msgs, cs) == -1)
break;
} while (ret > 0);
}
int nl_getifdata(const char *ifname, struct client_state_t *cs)
{
char buf[MNL_SOCKET_BUFFER_SIZE];
struct nlmsghdr *nlh;
char buf[8192];
struct nlmsghdr *nlh = (struct nlmsghdr *)buf;
struct ifinfomsg *ifinfo;
unsigned int seq;
size_t msgsize = NLMSG_HDRLEN + NLMSG_ALIGN(sizeof *ifinfo);
nlh = mnl_nlmsg_put_header(buf);
memset(buf, 0, msgsize);
nlh->nlmsg_len = msgsize;
nlh->nlmsg_type = RTM_GETLINK;
nlh->nlmsg_flags = NLM_F_REQUEST | NLM_F_ROOT;
nlh->nlmsg_seq = seq = time(NULL);
ifinfo = mnl_nlmsg_put_extra_header(nlh, sizeof(struct ifinfomsg));
nlh->nlmsg_seq = time(NULL);
ifinfo = (struct ifinfomsg *)((char *)buf + NLMSG_HDRLEN);
ifinfo->ifi_family = AF_UNSPEC;
if (mnl_socket_sendto(mls, nlh, nlh->nlmsg_len) < 0)
struct sockaddr_nl addr = {
.nl_family = AF_NETLINK,
};
if (sendto(cs->nlFd, buf, nlh->nlmsg_len, 0, (struct sockaddr *)&addr,
sizeof addr) == -1)
return -1;
// This is rather ugly, but hey!
if (fcntl(cs->nlFd, F_SETFL, fcntl(cs->nlFd, F_GETFL) & ~O_NONBLOCK) == -1)
suicide("nl_getifdata: failed to remove O_NONBLOCK");
handle_nl_message(cs);
if (fcntl(cs->nlFd, F_SETFL, fcntl(cs->nlFd, F_GETFL) | O_NONBLOCK) == -1)
suicide("nl_getifdata: failed to restore O_NONBLOCK");
return 0;
}

View File

@ -20,6 +20,7 @@
#ifndef NK_NETLINK_H_
#define NK_NETLINK_H_
#include <linux/rtnetlink.h>
#include "state.h"
enum {
@ -30,7 +31,8 @@ enum {
IFS_REMOVED
};
int nl_open(struct client_state_t *cs);
extern int nlportid;
void handle_nl_message(struct client_state_t *cs);
int nl_getifdata(const char *ifname, struct client_state_t *cs);

137
ndhc/nl.c Normal file
View File

@ -0,0 +1,137 @@
#include <assert.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <unistd.h>
#include <string.h>
#include <fcntl.h>
#include <errno.h>
#include "log.h"
#include "nl.h"
void nl_attr_parse(const struct nlmsghdr *nlh, size_t offset,
nl_attr_parse_fn workfn, void *data)
{
struct nlattr *attr;
for (attr = (struct nlattr *)
((char *)nlh + NLMSG_HDRLEN + NLMSG_ALIGN(offset));
nl_attr_ok(attr, (char *)nlh + NLMSG_ALIGN(nlh->nlmsg_len) -
(char *)attr);
attr = (struct nlattr *)((char *)attr + NLMSG_ALIGN(attr->nla_len)))
{
int type = attr->nla_type & NLA_TYPE_MASK;
if (workfn(attr, type, data) < 0)
break;
}
}
ssize_t nl_recv_buf(int fd, char *buf, size_t blen)
{
struct sockaddr_nl addr;
struct iovec iov = {
.iov_base = buf,
.iov_len = blen,
};
struct msghdr msg = {
.msg_name = &addr,
.msg_namelen = sizeof addr,
.msg_iov = &iov,
.msg_iovlen = 1,
};
size_t ret = recvmsg(fd, &msg, 0);
if (ret == -1) {
if (errno != EAGAIN && errno != EWOULDBLOCK)
log_error("nl_fill_buf: recvmsg failed: %s", strerror(errno));
return -1;
}
if (msg.msg_flags & MSG_TRUNC) {
log_error("nl_fill_buf: buffer not long enough for message");
return -1;
}
if (msg.msg_namelen != sizeof addr) {
log_error("nl_fill_buf: response was not of the same address family");
return -1;
}
return ret;
}
int nl_foreach_nlmsg(char *buf, size_t blen, int portid,
nlmsg_foreach_fn pfn, void *fnarg)
{
const struct nlmsghdr *nlh = (const struct nlmsghdr *)buf;
assert(pfn);
while (NLMSG_OK(nlh, blen)) {
// PortID should be zero for messages from the kernel.
if (nlh->nlmsg_pid && nlh->nlmsg_pid != portid)
continue;
// XXX don't bother with sequence # tracking (0 = kernel, ours = ??)
if (nlh->nlmsg_type >= NLMSG_MIN_TYPE) {
pfn(nlh, fnarg);
} else {
switch (nlh->nlmsg_type) {
case NLMSG_ERROR:
log_line("nl: received a NLMSG_ERROR: %s",
strerror(nlmsg_get_error(nlh)));
return -1;
case NLMSG_DONE:
return 0;
case NLMSG_OVERRUN:
log_line("nl: received a NLMSG_OVERRUN");
case NLMSG_NOOP:
default:
break;
}
}
nlh = NLMSG_NEXT(nlh, blen);
}
return 0;
}
int nl_open(int nltype, int nlgroup, int *nlportid)
{
int fd;
fd = socket(AF_NETLINK, SOCK_RAW, nltype);
if (fd == -1) {
log_error("nl_open: socket failed: %s", strerror(errno));
return -1;
}
if (fcntl(fd, F_SETFL, fcntl(fd, F_GETFL) | O_NONBLOCK) == -1) {
log_error("nl_open: set non-blocking failed: %s", strerror(errno));
goto err_close;
}
if (fcntl(fd, F_SETFD, FD_CLOEXEC)) {
log_error("nl_open: set close-on-exec failed: %s", strerror(errno));
goto err_close;
}
socklen_t al;
struct sockaddr_nl nlsock = {
.nl_family = AF_NETLINK,
.nl_groups = nlgroup,
};
if (bind(fd, (struct sockaddr *)&nlsock, sizeof nlsock) == -1) {
log_error("nl_open: bind to group failed: %s", strerror(errno));
goto err_close;
}
al = sizeof nlsock;
if (getsockname(fd, (struct sockaddr *)&nlsock, &al) == -1) {
log_error("nl_open: getsockname failed: %s", strerror(errno));
goto err_close;
}
if (al != sizeof nlsock) {
log_error("nl_open: bound socket doesn't have right family size");
goto err_close;
}
if (nlsock.nl_family != AF_NETLINK) {
log_error("nl_open: bound socket isn't AF_NETLINK");
goto err_close;
}
if (nlportid)
*nlportid = nlsock.nl_pid;
return fd;
err_close:
close(fd);
return -1;
}

55
ndhc/nl.h Normal file
View File

@ -0,0 +1,55 @@
#ifndef NK_NL_H_
#define NK_NL_H_
// Limited netlink code. The horrors...
#include <linux/netlink.h>
//#include <linux/rtnetlink.h>
static inline int nl_attr_ok(const struct nlattr *attr, size_t len)
{
if (len < sizeof *attr)
return 0;
if (attr->nla_len < sizeof *attr)
return 0;
if (attr->nla_len > len)
return 0;
return 1;
}
static inline size_t nlattr_get_len(const struct nlattr *attr)
{
return attr->nla_len;
}
static inline void *nlattr_get_data(const struct nlattr *attr)
{
return (char *)attr + NLA_HDRLEN;
}
static inline void *nlmsg_get_data(const struct nlmsghdr *nlh)
{
return (char *)nlh + NLMSG_HDRLEN;
}
static inline int nlmsg_get_error(const struct nlmsghdr *nlh)
{
const struct nlmsgerr *err = nlmsg_get_data(nlh);
if (nlh->nlmsg_len < sizeof(struct nlmsgerr) + NLMSG_HDRLEN)
return EBADMSG;
return err->error & 0x7fffffff;
}
typedef int (*nl_attr_parse_fn)(struct nlattr *attr, int type, void *data);
void nl_attr_parse(const struct nlmsghdr *nlh, size_t offset,
nl_attr_parse_fn workfn, void *data);
ssize_t nl_recv_buf(int fd, char *buf, size_t blen);
typedef int (*nlmsg_foreach_fn)(const struct nlmsghdr *, void *);
int nl_foreach_nlmsg(char *buf, size_t blen, int portid,
nlmsg_foreach_fn pfn, void *fnarg);
int nl_open(int nltype, int nlgroup, int *nlportid);
#endif /* NK_NL_H_ */