From 17570e9a3892849c63c73247edc37e48d129daea Mon Sep 17 00:00:00 2001 From: "Nicholas J. Kain" Date: Sat, 2 Jul 2011 01:34:50 -0400 Subject: [PATCH] Rewrite the netlink handling code to use libmnl. The size cost is very small, and it is hard to do netlink completely correctly as it is poorly documented. --- CMakeLists.txt | 2 + ndhc/CMakeLists.txt | 2 +- ndhc/ndhc.c | 4 +- ndhc/netlink.c | 524 ++++++++++++++++++++------------------------ ndhc/netlink.h | 63 +++--- 5 files changed, 269 insertions(+), 326 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 2b5a129..af9a7e6 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -7,6 +7,8 @@ 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 ff69b71..a40c411 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) +target_link_libraries(ndhc ncmlib libmnl) diff --git a/ndhc/ndhc.c b/ndhc/ndhc.c index 3fd9c3b..8851d8a 100644 --- a/ndhc/ndhc.c +++ b/ndhc/ndhc.c @@ -314,7 +314,5 @@ int main(int argc, char **argv) ifchange(NULL, IFCHANGE_DECONFIG); do_work(); - - nl_close(&cs); - return EXIT_SUCCESS; + return EXIT_SUCCESS; // Never reached. } diff --git a/ndhc/netlink.c b/ndhc/netlink.c index 59c8377..de3d087 100644 --- a/ndhc/netlink.c +++ b/ndhc/netlink.c @@ -1,288 +1,236 @@ -/* netlink.c - netlink physical link notification handling and info retrieval - * - * (c) 2011 Nicholas J. Kain - * (c) 2006-2007 Stefan Rompf - * - * This code was largely taken from Stefan Rompf's dhcpclient. - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program; if not, write to the Free Software - * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. - */ - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#include "netlink.h" -#include "ifchange.h" -#include "arp.h" -#include "log.h" - -#define NLMSG_RECVSIZE 8192 - -enum { - IFS_NONE = 0, - IFS_UP, - IFS_DOWN, - IFS_SHUT, - IFS_REMOVED -}; - -static unsigned int nl_seq; - -/* internal callback handling */ -static void (*nlcb_function)(struct nlmsghdr *msg, void **args); -static void *nlcb_args[3]; -static __u32 nlcb_pid; -static unsigned int nlcb_seq; -static char nlcb_run; - -int nl_open(struct client_state_t *cs) -{ - struct sockaddr_nl nlsock = { - .nl_family = AF_NETLINK, - .nl_pad = 0, - .nl_pid = getpid(), - .nl_groups = RTMGRP_LINK - }; - - nlcb_pid = nlsock.nl_pid; - - assert(cs->nlFd == -1); - - cs->nlFd = socket(AF_NETLINK, SOCK_DGRAM, NETLINK_ROUTE); - - if (cs->nlFd == -1) - return -1; - - if (bind(cs->nlFd, (const struct sockaddr *)&nlsock, sizeof(nlsock))) - goto err_close; - - if (fcntl(cs->nlFd, F_SETFD, FD_CLOEXEC)) - goto err_close; - - return 0; - - err_close: - nl_close(cs); - return -1; -} - -void nl_close(struct client_state_t *cs) -{ - close(cs->nlFd); - cs->nlFd = -1; -} - -void nl_queryifstatus(int ifidx, struct client_state_t *cs) -{ - struct { - struct nlmsghdr hdr; - struct ifinfomsg ifinfo; - } req; - - req.hdr.nlmsg_len = sizeof req; - req.hdr.nlmsg_type = RTM_GETLINK; - req.hdr.nlmsg_flags = NLM_F_REQUEST | NLM_F_ROOT; - req.hdr.nlmsg_seq = ++nl_seq; - req.hdr.nlmsg_pid = nlcb_pid; - req.ifinfo.ifi_family = AF_UNSPEC; - req.ifinfo.ifi_index = ifidx; /* Doesn't work... */ - req.ifinfo.ifi_flags = IFF_UP; - req.ifinfo.ifi_change = 0xffffffff; - - send(cs->nlFd, &req, sizeof req, 0); -} - -static void takedown_if(struct client_state_t *cs) -{ - log_line("nl: taking down interface"); - // Same as packet.c: line 258 - ifchange(NULL, IFCHANGE_DECONFIG); - cs->dhcpState = DS_SELECTING; - cs->timeout = 0; - cs->clientAddr = 0; - cs->packetNum = 0; - set_listen_raw(cs); -} - -// Decode netlink messages and process them -static void nl_handlemsg(struct nlmsghdr *msg, unsigned int len, - struct client_state_t *cs) -{ - if (len < sizeof(*msg)) return; - - while(NLMSG_OK(msg,len)) { - if (nlcb_run && - nlcb_pid == msg->nlmsg_pid && - nlcb_seq == msg->nlmsg_seq) { - nlcb_function(msg, nlcb_args); - - if (msg->nlmsg_type == NLMSG_DONE || - msg->nlmsg_type == NLMSG_ERROR) nlcb_run = 0; - } - - if (NLMSG_PAYLOAD(msg, msg->nlmsg_len) >= sizeof(struct ifinfomsg)) { - struct ifinfomsg *ifinfo = NLMSG_DATA(msg); - - switch(msg->nlmsg_type) { - case RTM_NEWLINK: - if (ifinfo->ifi_index != client_config.ifindex) - break; - if (ifinfo->ifi_flags & IFF_UP) { - if (ifinfo->ifi_flags & IFF_RUNNING) { - if (cs->ifsPrevState != IFS_UP) { - cs->ifsPrevState = IFS_UP; - /* - * If we have a lease, then check to see - * if our gateway is still valid (via ARP). - * If it fails, state -> INIT. - * - * If we don't have a lease, state -> INIT. - */ - if (cs->dhcpState == DS_BOUND) { - if (arp_gw_check(cs) == -1) - log_warning("arp_gw_check could not make arp socket, assuming lease is still OK"); - } else if (cs->dhcpState != DS_SELECTING) - takedown_if(cs); - } - } else { - if (cs->ifsPrevState != IFS_DOWN) { - cs->ifsPrevState = IFS_DOWN; - takedown_if(cs); - } - } - } else { - if (cs->ifsPrevState != IFS_SHUT) { - cs->ifsPrevState = IFS_SHUT; - log_line("Interface shut down; exiting."); - exit(EXIT_SUCCESS); - } - } - break; - case RTM_DELLINK: - if (ifinfo->ifi_index != client_config.ifindex) - break; - if (cs->ifsPrevState != IFS_REMOVED) { - cs->ifsPrevState = IFS_REMOVED; - log_line("Interface removed; exiting."); - exit(EXIT_SUCCESS); - } - break; - default: - break; - } - } - msg = NLMSG_NEXT(msg,len); - } -} - -void handle_nl_message(struct client_state_t *cs) -{ - char c[NLMSG_RECVSIZE]; - struct nlmsghdr *msg = (struct nlmsghdr *)c; - int n; - - assert(cs->nlFd != -1); - n = recv(cs->nlFd, c, NLMSG_RECVSIZE, 0); - nl_handlemsg(msg, n, cs); -} - -// Wait for and synchronously process netlink replies until a callback completes -static void nl_sync_dump(struct client_state_t *cs) -{ - char c[NLMSG_RECVSIZE]; - struct nlmsghdr *msg = (struct nlmsghdr *)c; - int n; - - nlcb_seq = nl_seq; - for(nlcb_run = 1; nlcb_run;) { - n = recv(cs->nlFd, c, NLMSG_RECVSIZE, 0); - assert(n >= 0); - nl_handlemsg(msg, n, cs); - } -} - -// Callback function for getting interface mac address and index. -static void copy_ifdata(struct nlmsghdr *msg, void **args) -{ - struct ifinfomsg *ifinfo = NLMSG_DATA(msg); - struct rtattr *rta = IFLA_RTA(ifinfo); - int len = NLMSG_PAYLOAD(msg, sizeof(*ifinfo)); - int found = 0; - - if (msg->nlmsg_type != RTM_NEWLINK) - return; - if (client_config.ifindex) - return; - - for(; RTA_OK(rta, len); rta = RTA_NEXT(rta, len)) { - switch(rta->rta_type) { - case IFLA_IFNAME: - if (!strncmp(client_config.interface, - (char *)RTA_DATA(rta), RTA_PAYLOAD(rta))) { - client_config.ifindex = ifinfo->ifi_index; - found |= 1; - } - break; - case IFLA_ADDRESS: - if (found != 1) - break; - /* We can only handle ethernet like devices with 6 octet MAC */ - if (RTA_PAYLOAD(rta) == 6) { - memcpy(client_config.arp, RTA_DATA(rta), 6); - found |= 2; - } - break; - } - } - if (found == 3) - nlcb_args[0] = (void *)1; -} - -// Gets interface mac address and index (synchronous). -int nl_getifdata(const char *ifname, struct client_state_t *cs) -{ - struct { - struct nlmsghdr hdr; - struct ifinfomsg ifinfo; - } req; - - req.hdr.nlmsg_len = sizeof(req); - req.hdr.nlmsg_type = RTM_GETLINK; - req.hdr.nlmsg_flags = NLM_F_REQUEST | NLM_F_ROOT; - req.hdr.nlmsg_seq = ++nl_seq; - req.hdr.nlmsg_pid = nlcb_pid; - req.ifinfo.ifi_family = AF_UNSPEC; - - if (send(cs->nlFd, &req, sizeof(req), 0) != sizeof(req)) return -1; - - nlcb_function = copy_ifdata; - nlcb_args[0] = NULL; - - nl_sync_dump(cs); - - return nlcb_args[0]?0:-1; -} +/* netlink.c - netlink physical link notification handling and info retrieval + * + * (c) 2011 Nicholas J. Kain + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "netlink.h" +#include "ifchange.h" +#include "arp.h" +#include "log.h" + +enum { + IFS_NONE = 0, + IFS_UP, + IFS_DOWN, + IFS_SHUT, + IFS_REMOVED +}; + +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 void takedown_if(struct client_state_t *cs) +{ + log_line("nl: taking down interface"); + // XXX: Same as packet.c - merge somehow? + ifchange(NULL, IFCHANGE_DECONFIG); + cs->dhcpState = DS_SELECTING; + cs->timeout = 0; + cs->clientAddr = 0; + cs->packetNum = 0; + set_listen_raw(cs); +} + +static int data_attr_cb(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; + } + tb[type] = attr; + return MNL_CB_OK; +} + +static void get_if_index_and_mac(const struct nlmsghdr *nlh, + struct ifinfomsg *ifm, + const struct nlattr **tb) +{ + mnl_attr_parse(nlh, sizeof(*ifm), data_attr_cb, tb); + if (!tb[IFLA_IFNAME]) + return; + if (!strcmp(client_config.interface, mnl_attr_get_str(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; + 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]); + 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]); + memcpy(client_config.arp, mac, 6); + } +} + +// XXX: Rather than exit, go into RELEASE state until a new hardware event +// forces wakeup. +static int data_cb(const struct nlmsghdr *nlh, void *data) +{ + struct nlattr *tb[IFLA_MAX+1] = {0}; + struct ifinfomsg *ifm = mnl_nlmsg_get_payload(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); + if (ifm->ifi_index != client_config.ifindex) + break; + if (ifm->ifi_flags & IFF_UP) { + if (ifm->ifi_flags & IFF_RUNNING) { + if (cs->ifsPrevState != IFS_UP) { + cs->ifsPrevState = IFS_UP; + // If we have a lease, then check to see + // if our gateway is still valid (via ARP). + // If it fails, state -> SELECTING. + if (cs->dhcpState == DS_BOUND) { + if (arp_gw_check(cs) == -1) + log_warning("nl: arp_gw_check could not make arp socket, assuming lease is still OK"); + else + log_line("nl: interface back, revalidating lease"); + // If we don't have a lease, state -> SELECTING. + } else if (cs->dhcpState != DS_SELECTING) { + log_line("nl: interface back, querying for new lease"); + takedown_if(cs); + } + } + } else { + if (cs->ifsPrevState != IFS_DOWN) { + // Interface was marked up but not running. + // Get a new lease from scratch. + cs->ifsPrevState = IFS_DOWN; + takedown_if(cs); + } + } + } else { + // No hardware carrier. + if (cs->ifsPrevState != IFS_SHUT) { + cs->ifsPrevState = IFS_SHUT; + log_line("Interface shut down; exiting."); + exit(EXIT_SUCCESS); + } + } + break; + case RTM_DELLINK: + if (ifm->ifi_index != client_config.ifindex) + break; + if (cs->ifsPrevState != IFS_REMOVED) { + cs->ifsPrevState = IFS_REMOVED; + log_line("Interface removed; exiting."); + exit(EXIT_SUCCESS); + } + break; + default: + break; + } + return MNL_CB_OK; +} + +void handle_nl_message(struct client_state_t *cs) +{ + char buf[MNL_SOCKET_BUFFER_SIZE]; + int ret; + assert(cs->nlFd != -1); + do { + ret = mnl_socket_recvfrom(mls, buf, sizeof buf); + ret = mnl_cb_run(buf, ret, 0, 0, data_cb, cs); + } 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; + struct ifinfomsg *ifinfo; + unsigned int seq; + + nlh = mnl_nlmsg_put_header(buf); + 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)); + ifinfo->ifi_family = AF_UNSPEC; + + if (mnl_socket_sendto(mls, nlh, nlh->nlmsg_len) < 0) + return -1; + + handle_nl_message(cs); + return 0; +} + diff --git a/ndhc/netlink.h b/ndhc/netlink.h index 6a1ed87..35fcedf 100644 --- a/ndhc/netlink.h +++ b/ndhc/netlink.h @@ -1,34 +1,29 @@ -/* netlink.h - netlink physical link notification handling and info retrieval - * - * (c) 2011 Nicholas J. Kain - * (c) 2006-2007 Stefan Rompf - * - * This code was largely taken from Stefan Rompf's dhcpclient. - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program; if not, write to the Free Software - * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. - */ - -#ifndef NK_NETLINK_H_ -#define NK_NETLINK_H_ - -#include "state.h" - -int nl_open(struct client_state_t *cs); -void nl_close(struct client_state_t *cs); -void nl_queryifstatus(int ifidx, struct client_state_t *cs); -void handle_nl_message(struct client_state_t *cs); -int nl_getifdata(const char *ifname, struct client_state_t *cs); - -#endif /* NK_NETLINK_H_ */ +/* netlink.h - netlink physical link notification handling and info retrieval + * + * (c) 2011 Nicholas J. Kain + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + */ + +#ifndef NK_NETLINK_H_ +#define NK_NETLINK_H_ + +#include "state.h" + +int nl_open(struct client_state_t *cs); +void handle_nl_message(struct client_state_t *cs); +int nl_getifdata(const char *ifname, struct client_state_t *cs); + +#endif /* NK_NETLINK_H_ */