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.
This commit is contained in:
Nicholas J. Kain 2011-07-02 01:34:50 -04:00
parent 38ad2399ed
commit 17570e9a38
5 changed files with 269 additions and 326 deletions

View File

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

View File

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

View File

@ -314,7 +314,5 @@ int main(int argc, char **argv)
ifchange(NULL, IFCHANGE_DECONFIG); ifchange(NULL, IFCHANGE_DECONFIG);
do_work(); do_work();
return EXIT_SUCCESS; // Never reached.
nl_close(&cs);
return EXIT_SUCCESS;
} }

View File

@ -1,288 +1,236 @@
/* netlink.c - netlink physical link notification handling and info retrieval /* netlink.c - netlink physical link notification handling and info retrieval
* *
* (c) 2011 Nicholas J. Kain <njkain at gmail dot com> * (c) 2011 Nicholas J. Kain <njkain at gmail dot com>
* (c) 2006-2007 Stefan Rompf <sux@loplof.de> *
* * This program is free software; you can redistribute it and/or modify
* This code was largely taken from Stefan Rompf's dhcpclient. * it under the terms of the GNU General Public License as published by
* * the Free Software Foundation; either version 2 of the License, or
* This program is free software; you can redistribute it and/or modify * (at your option) any later version.
* it under the terms of the GNU General Public License as published by *
* the Free Software Foundation; either version 2 of the License, or * This program is distributed in the hope that it will be useful,
* (at your option) any later version. * but WITHOUT ANY WARRANTY; without even the implied warranty of
* * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* This program is distributed in the hope that it will be useful, * GNU General Public License for more details.
* but WITHOUT ANY WARRANTY; without even the implied warranty of *
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * You should have received a copy of the GNU General Public License
* GNU General Public License for more details. * along with this program; if not, write to the Free Software
* * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
* 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 <arpa/inet.h>
*/ #include <assert.h>
#include <asm/types.h>
#include <arpa/inet.h> #include <sys/socket.h>
#include <assert.h> #include <linux/netlink.h>
#include <asm/types.h> #include <linux/rtnetlink.h>
#include <sys/socket.h> #include <unistd.h>
#include <linux/netlink.h> #include <net/if.h>
#include <linux/rtnetlink.h> #include <stdio.h>
#include <unistd.h> #include <stdlib.h>
#include <net/if.h> #include <strings.h>
#include <stdio.h> #include <string.h>
#include <stdlib.h> #include <sys/select.h>
#include <strings.h> #include <fcntl.h>
#include <string.h> #include <time.h>
#include <sys/select.h> #include <libmnl/libmnl.h>
#include <fcntl.h> #include <errno.h>
#include "netlink.h" #include "netlink.h"
#include "ifchange.h" #include "ifchange.h"
#include "arp.h" #include "arp.h"
#include "log.h" #include "log.h"
#define NLMSG_RECVSIZE 8192 enum {
IFS_NONE = 0,
enum { IFS_UP,
IFS_NONE = 0, IFS_DOWN,
IFS_UP, IFS_SHUT,
IFS_DOWN, IFS_REMOVED
IFS_SHUT, };
IFS_REMOVED
}; static struct mnl_socket *mls;
static unsigned int nl_seq; static void nl_close(struct client_state_t *cs)
{
/* internal callback handling */ mnl_socket_close(mls);
static void (*nlcb_function)(struct nlmsghdr *msg, void **args); cs->nlFd = -1;
static void *nlcb_args[3]; }
static __u32 nlcb_pid;
static unsigned int nlcb_seq; int nl_open(struct client_state_t *cs)
static char nlcb_run; {
assert(cs->nlFd == -1);
int nl_open(struct client_state_t *cs) if ((mls = mnl_socket_open(NETLINK_ROUTE)) == (struct mnl_socket *)-1)
{ return -1;
struct sockaddr_nl nlsock = { cs->nlFd = mnl_socket_get_fd(mls);
.nl_family = AF_NETLINK, if (fcntl(cs->nlFd, F_SETFD, FD_CLOEXEC))
.nl_pad = 0, goto err_close;
.nl_pid = getpid(), if (mnl_socket_bind(mls, RTMGRP_LINK, 0))
.nl_groups = RTMGRP_LINK goto err_close;
}; return 0;
err_close:
nlcb_pid = nlsock.nl_pid; nl_close(cs);
return -1;
assert(cs->nlFd == -1); }
cs->nlFd = socket(AF_NETLINK, SOCK_DGRAM, NETLINK_ROUTE); static void takedown_if(struct client_state_t *cs)
{
if (cs->nlFd == -1) log_line("nl: taking down interface");
return -1; // XXX: Same as packet.c - merge somehow?
ifchange(NULL, IFCHANGE_DECONFIG);
if (bind(cs->nlFd, (const struct sockaddr *)&nlsock, sizeof(nlsock))) cs->dhcpState = DS_SELECTING;
goto err_close; cs->timeout = 0;
cs->clientAddr = 0;
if (fcntl(cs->nlFd, F_SETFD, FD_CLOEXEC)) cs->packetNum = 0;
goto err_close; set_listen_raw(cs);
}
return 0;
static int data_attr_cb(const struct nlattr *attr, void *data)
err_close: {
nl_close(cs); const struct nlattr **tb = data;
return -1; int type = mnl_attr_get_type(attr);
}
/* skip unsupported attribute in user-space */
void nl_close(struct client_state_t *cs) if (mnl_attr_type_valid(attr, IFLA_MAX) < 0)
{ return MNL_CB_OK;
close(cs->nlFd); switch (type) {
cs->nlFd = -1; case IFLA_IFNAME:
} if (mnl_attr_validate(attr, MNL_TYPE_STRING) < 0) {
log_warning("nl: IFLA_IFNAME failed validation.");
void nl_queryifstatus(int ifidx, struct client_state_t *cs) return MNL_CB_ERROR;
{ }
struct { break;
struct nlmsghdr hdr; case IFLA_ADDRESS:
struct ifinfomsg ifinfo; if (mnl_attr_validate(attr, MNL_TYPE_STRING) < 0) {
} req; log_warning("nl: IFLA_ADDRESS failed validation.");
return MNL_CB_ERROR;
req.hdr.nlmsg_len = sizeof req; }
req.hdr.nlmsg_type = RTM_GETLINK; break;
req.hdr.nlmsg_flags = NLM_F_REQUEST | NLM_F_ROOT; }
req.hdr.nlmsg_seq = ++nl_seq; tb[type] = attr;
req.hdr.nlmsg_pid = nlcb_pid; return MNL_CB_OK;
req.ifinfo.ifi_family = AF_UNSPEC; }
req.ifinfo.ifi_index = ifidx; /* Doesn't work... */
req.ifinfo.ifi_flags = IFF_UP; static void get_if_index_and_mac(const struct nlmsghdr *nlh,
req.ifinfo.ifi_change = 0xffffffff; struct ifinfomsg *ifm,
const struct nlattr **tb)
send(cs->nlFd, &req, sizeof req, 0); {
} mnl_attr_parse(nlh, sizeof(*ifm), data_attr_cb, tb);
if (!tb[IFLA_IFNAME])
static void takedown_if(struct client_state_t *cs) return;
{ if (!strcmp(client_config.interface, mnl_attr_get_str(tb[IFLA_IFNAME]))) {
log_line("nl: taking down interface"); client_config.ifindex = ifm->ifi_index;
// Same as packet.c: line 258 if (!tb[IFLA_ADDRESS])
ifchange(NULL, IFCHANGE_DECONFIG); suicide("FATAL: adapter %s lacks a hardware address");
cs->dhcpState = DS_SELECTING; int maclen = mnl_attr_get_len(tb[IFLA_ADDRESS]) - 4;
cs->timeout = 0; if (maclen != 6)
cs->clientAddr = 0; suicide("FATAL: adapter hardware address length should be 6, but is %u",
cs->packetNum = 0; maclen);
set_listen_raw(cs);
} const unsigned char *mac =
(unsigned char *)mnl_attr_get_str(tb[IFLA_ADDRESS]);
// Decode netlink messages and process them log_line("%s hardware address %x:%x:%x:%x:%x:%x",
static void nl_handlemsg(struct nlmsghdr *msg, unsigned int len, client_config.interface,
struct client_state_t *cs) mac[0], mac[1], mac[2], mac[3], mac[4], mac[5]);
{ memcpy(client_config.arp, mac, 6);
if (len < sizeof(*msg)) return; }
}
while(NLMSG_OK(msg,len)) {
if (nlcb_run && // XXX: Rather than exit, go into RELEASE state until a new hardware event
nlcb_pid == msg->nlmsg_pid && // forces wakeup.
nlcb_seq == msg->nlmsg_seq) { static int data_cb(const struct nlmsghdr *nlh, void *data)
nlcb_function(msg, nlcb_args); {
struct nlattr *tb[IFLA_MAX+1] = {0};
if (msg->nlmsg_type == NLMSG_DONE || struct ifinfomsg *ifm = mnl_nlmsg_get_payload(nlh);
msg->nlmsg_type == NLMSG_ERROR) nlcb_run = 0; struct client_state_t *cs = data;
}
switch(nlh->nlmsg_type) {
if (NLMSG_PAYLOAD(msg, msg->nlmsg_len) >= sizeof(struct ifinfomsg)) { case RTM_NEWLINK:
struct ifinfomsg *ifinfo = NLMSG_DATA(msg); if (!client_config.ifindex)
get_if_index_and_mac(nlh, ifm, (const struct nlattr **)tb);
switch(msg->nlmsg_type) { if (ifm->ifi_index != client_config.ifindex)
case RTM_NEWLINK: break;
if (ifinfo->ifi_index != client_config.ifindex) if (ifm->ifi_flags & IFF_UP) {
break; if (ifm->ifi_flags & IFF_RUNNING) {
if (ifinfo->ifi_flags & IFF_UP) { if (cs->ifsPrevState != IFS_UP) {
if (ifinfo->ifi_flags & IFF_RUNNING) { cs->ifsPrevState = IFS_UP;
if (cs->ifsPrevState != IFS_UP) { // If we have a lease, then check to see
cs->ifsPrevState = IFS_UP; // if our gateway is still valid (via ARP).
/* // If it fails, state -> SELECTING.
* If we have a lease, then check to see if (cs->dhcpState == DS_BOUND) {
* if our gateway is still valid (via ARP). if (arp_gw_check(cs) == -1)
* If it fails, state -> INIT. log_warning("nl: arp_gw_check could not make arp socket, assuming lease is still OK");
* else
* If we don't have a lease, state -> INIT. log_line("nl: interface back, revalidating lease");
*/ // If we don't have a lease, state -> SELECTING.
if (cs->dhcpState == DS_BOUND) { } else if (cs->dhcpState != DS_SELECTING) {
if (arp_gw_check(cs) == -1) log_line("nl: interface back, querying for new lease");
log_warning("arp_gw_check could not make arp socket, assuming lease is still OK"); takedown_if(cs);
} else if (cs->dhcpState != DS_SELECTING) }
takedown_if(cs); }
} } else {
} else { if (cs->ifsPrevState != IFS_DOWN) {
if (cs->ifsPrevState != IFS_DOWN) { // Interface was marked up but not running.
cs->ifsPrevState = IFS_DOWN; // Get a new lease from scratch.
takedown_if(cs); cs->ifsPrevState = IFS_DOWN;
} takedown_if(cs);
} }
} else { }
if (cs->ifsPrevState != IFS_SHUT) { } else {
cs->ifsPrevState = IFS_SHUT; // No hardware carrier.
log_line("Interface shut down; exiting."); if (cs->ifsPrevState != IFS_SHUT) {
exit(EXIT_SUCCESS); 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;
break; case RTM_DELLINK:
if (cs->ifsPrevState != IFS_REMOVED) { if (ifm->ifi_index != client_config.ifindex)
cs->ifsPrevState = IFS_REMOVED; break;
log_line("Interface removed; exiting."); if (cs->ifsPrevState != IFS_REMOVED) {
exit(EXIT_SUCCESS); cs->ifsPrevState = IFS_REMOVED;
} log_line("Interface removed; exiting.");
break; exit(EXIT_SUCCESS);
default: }
break; break;
} default:
} break;
msg = NLMSG_NEXT(msg,len); }
} return MNL_CB_OK;
} }
void handle_nl_message(struct client_state_t *cs) void handle_nl_message(struct client_state_t *cs)
{ {
char c[NLMSG_RECVSIZE]; char buf[MNL_SOCKET_BUFFER_SIZE];
struct nlmsghdr *msg = (struct nlmsghdr *)c; int ret;
int n; assert(cs->nlFd != -1);
do {
assert(cs->nlFd != -1); ret = mnl_socket_recvfrom(mls, buf, sizeof buf);
n = recv(cs->nlFd, c, NLMSG_RECVSIZE, 0); ret = mnl_cb_run(buf, ret, 0, 0, data_cb, cs);
nl_handlemsg(msg, n, cs); } while (ret > 0);
} if (ret == -1)
log_line("nl callback function returned error: %s", strerror(errno));
// Wait for and synchronously process netlink replies until a callback completes }
static void nl_sync_dump(struct client_state_t *cs)
{ int nl_getifdata(const char *ifname, struct client_state_t *cs)
char c[NLMSG_RECVSIZE]; {
struct nlmsghdr *msg = (struct nlmsghdr *)c; char buf[MNL_SOCKET_BUFFER_SIZE];
int n; struct nlmsghdr *nlh;
struct ifinfomsg *ifinfo;
nlcb_seq = nl_seq; unsigned int seq;
for(nlcb_run = 1; nlcb_run;) {
n = recv(cs->nlFd, c, NLMSG_RECVSIZE, 0); nlh = mnl_nlmsg_put_header(buf);
assert(n >= 0); nlh->nlmsg_type = RTM_GETLINK;
nl_handlemsg(msg, n, cs); 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));
// Callback function for getting interface mac address and index. ifinfo->ifi_family = AF_UNSPEC;
static void copy_ifdata(struct nlmsghdr *msg, void **args)
{ if (mnl_socket_sendto(mls, nlh, nlh->nlmsg_len) < 0)
struct ifinfomsg *ifinfo = NLMSG_DATA(msg); return -1;
struct rtattr *rta = IFLA_RTA(ifinfo);
int len = NLMSG_PAYLOAD(msg, sizeof(*ifinfo)); handle_nl_message(cs);
int found = 0; return 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;
}

View File

@ -1,34 +1,29 @@
/* netlink.h - netlink physical link notification handling and info retrieval /* netlink.h - netlink physical link notification handling and info retrieval
* *
* (c) 2011 Nicholas J. Kain <njkain at gmail dot com> * (c) 2011 Nicholas J. Kain <njkain at gmail dot com>
* (c) 2006-2007 Stefan Rompf <sux@loplof.de> *
* * This program is free software; you can redistribute it and/or modify
* This code was largely taken from Stefan Rompf's dhcpclient. * it under the terms of the GNU General Public License as published by
* * the Free Software Foundation; either version 2 of the License, or
* This program is free software; you can redistribute it and/or modify * (at your option) any later version.
* it under the terms of the GNU General Public License as published by *
* the Free Software Foundation; either version 2 of the License, or * This program is distributed in the hope that it will be useful,
* (at your option) any later version. * but WITHOUT ANY WARRANTY; without even the implied warranty of
* * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* This program is distributed in the hope that it will be useful, * GNU General Public License for more details.
* but WITHOUT ANY WARRANTY; without even the implied warranty of *
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * You should have received a copy of the GNU General Public License
* GNU General Public License for more details. * along with this program; if not, write to the Free Software
* * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
* 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_
#ifndef NK_NETLINK_H_ #include "state.h"
#define NK_NETLINK_H_
int nl_open(struct client_state_t *cs);
#include "state.h" void handle_nl_message(struct client_state_t *cs);
int nl_getifdata(const char *ifname, struct client_state_t *cs);
int nl_open(struct client_state_t *cs);
void nl_close(struct client_state_t *cs); #endif /* NK_NETLINK_H_ */
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_ */