From 01c47314033ddaa93d8d1aaf98895856bc9b152d Mon Sep 17 00:00:00 2001 From: "Nicholas J. Kain" Date: Sun, 3 Jul 2011 17:30:55 -0400 Subject: [PATCH] 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. --- CMakeLists.txt | 2 - ndhc/CMakeLists.txt | 2 +- ndhc/ndhc.c | 5 +- ndhc/netlink.c | 113 +++++++++++++----------------------- ndhc/netlink.h | 4 +- ndhc/nl.c | 137 ++++++++++++++++++++++++++++++++++++++++++++ ndhc/nl.h | 55 ++++++++++++++++++ 7 files changed, 240 insertions(+), 78 deletions(-) create mode 100644 ndhc/nl.c create mode 100644 ndhc/nl.h diff --git a/CMakeLists.txt b/CMakeLists.txt index af9a7e6..2b5a129 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -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) diff --git a/ndhc/CMakeLists.txt b/ndhc/CMakeLists.txt index a40c411..ff69b71 100644 --- a/ndhc/CMakeLists.txt +++ b/ndhc/CMakeLists.txt @@ -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) diff --git a/ndhc/ndhc.c b/ndhc/ndhc.c index 8c44f3d..e7091fd 100644 --- a/ndhc/ndhc.c +++ b/ndhc/ndhc.c @@ -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 * @@ -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); } diff --git a/ndhc/netlink.c b/ndhc/netlink.c index 6362821..7d2814d 100644 --- a/ndhc/netlink.c +++ b/ndhc/netlink.c @@ -21,8 +21,6 @@ #include #include #include -#include -#include #include #include #include @@ -32,37 +30,16 @@ #include #include #include -#include #include #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); + ret = nl_recv_buf(cs->nlFd, nlbuf, sizeof nlbuf); + if (ret == -1) + break; + if (nl_foreach_nlmsg(nlbuf, ret, nlportid, nl_process_msgs, cs) == -1) + break; } while (ret > 0); - if (ret == -1) - log_line("nl callback function returned error: %s", strerror(errno)); } 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; } diff --git a/ndhc/netlink.h b/ndhc/netlink.h index fe5bc30..80df66c 100644 --- a/ndhc/netlink.h +++ b/ndhc/netlink.h @@ -20,6 +20,7 @@ #ifndef NK_NETLINK_H_ #define NK_NETLINK_H_ +#include #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); diff --git a/ndhc/nl.c b/ndhc/nl.c new file mode 100644 index 0000000..69b889b --- /dev/null +++ b/ndhc/nl.c @@ -0,0 +1,137 @@ +#include +#include +#include +#include +#include +#include +#include +#include +#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; +} + diff --git a/ndhc/nl.h b/ndhc/nl.h new file mode 100644 index 0000000..edf0912 --- /dev/null +++ b/ndhc/nl.h @@ -0,0 +1,55 @@ +#ifndef NK_NL_H_ +#define NK_NL_H_ + +// Limited netlink code. The horrors... + +#include +//#include + +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_ */