odhcpd: properly handle netlink messages (FS#388)
authorHans Dedecker <dedeckeh@gmail.com>
Tue, 31 Jan 2017 21:11:20 +0000 (22:11 +0100)
committerHans Dedecker <dedeckeh@gmail.com>
Tue, 31 Jan 2017 21:11:20 +0000 (22:11 +0100)
Use libnl-tiny to construct and process netlink messages when
manipulating IPv6 routes and fetching IPv6 addresses.
This fixes lingering netlink error messages on the netlink socket
in case route deletion failed causing fetching of IPv6 addresses
to be aborted and odhcpd faultly assuming no IPv6 addresses being
present on the interface.

CMakeLists.txt
src/ndp.c
src/odhcpd.c
src/odhcpd.h

index 8c338f38c5d2844b49f5c7ae80af0aab34b714ea..0855458489c5a687c716e220e3f428f3907ea268 100644 (file)
@@ -8,7 +8,10 @@ set(CMAKE_SHARED_LIBRARY_LINK_C_FLAGS "")
 set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -g -std=c99")
 
 FIND_PATH(ubox_include_dir libubox/uloop.h)
-INCLUDE_DIRECTORIES(${ubox_include_dir})
+FIND_PATH(libnl-tiny_include_dir netlink-generic.h PATH_SUFFIXES libnl-tiny)
+INCLUDE_DIRECTORIES(${ubox_include_dir} ${libnl-tiny_include_dir})
+
+FIND_LIBRARY(libnl NAMES nl-tiny)
 
 add_definitions(-D_GNU_SOURCE -Wall -Werror -Wextra)
 
@@ -23,7 +26,7 @@ if(${UBUS})
 endif(${UBUS})
 
 add_executable(odhcpd src/odhcpd.c src/config.c src/router.c src/dhcpv6.c src/ndp.c src/dhcpv6-ia.c src/dhcpv4.c ${EXT_SRC})
-target_link_libraries(odhcpd resolv ubox uci ${EXT_LINK})
+target_link_libraries(odhcpd resolv ubox uci ${libnl} ${EXT_LINK})
 
 # Installation
 install(TARGETS odhcpd DESTINATION sbin/)
index 10acc3b6dc61d8dd8fff6046088e2c4e70141bbf..f2bf19c1f7eb776deec5457909482c25ded0267d 100644 (file)
--- a/src/ndp.c
+++ b/src/ndp.c
@@ -64,7 +64,11 @@ int init_ndp(void)
        int val = 256 * 1024;
 
        // Setup netlink socket
-       if ((rtnl_event.uloop.fd = odhcpd_open_rtnl()) < 0)
+       if ((rtnl_event.uloop.fd = socket(AF_NETLINK, SOCK_RAW | SOCK_CLOEXEC, NETLINK_ROUTE)) < 0)
+               return -1;
+
+       struct sockaddr_nl nl = {.nl_family = AF_NETLINK};
+       if (connect(rtnl_event.uloop.fd, (struct sockaddr*)&nl, sizeof(nl)) < 0)
                return -1;
 
        if (setsockopt(rtnl_event.uloop.fd, SOL_SOCKET, SO_RCVBUF, &val, sizeof(val)))
index b1e89b3594624bc2373f3fbd561ecf06afb1a8ad..f259239c7848104ff177fd5ff2ed7c387fe2672d 100644 (file)
@@ -30,6 +30,7 @@
 #include <netinet/ip6.h>
 #include <netpacket/packet.h>
 #include <linux/netlink.h>
+#include <linux/if_addr.h>
 #include <linux/rtnetlink.h>
 
 #include <sys/socket.h>
 #include <sys/wait.h>
 #include <sys/syscall.h>
 
+#include <netlink/msg.h>
+#include <netlink/socket.h>
+#include <netlink/attr.h>
 #include <libubox/uloop.h>
 #include "odhcpd.h"
 
 
 
 static int ioctl_sock;
-static int rtnl_socket = -1;
-static int rtnl_seq = 0;
+static struct nl_sock *rtnl_socket = NULL;
 static int urandom_fd = -1;
 
 
@@ -91,8 +94,8 @@ int main(int argc, char **argv)
 
        ioctl_sock = socket(AF_INET, SOCK_DGRAM | SOCK_CLOEXEC, 0);
 
-       if ((rtnl_socket = odhcpd_open_rtnl()) < 0) {
-               syslog(LOG_ERR, "Unable to open socket: %s", strerror(errno));
+       if (!(rtnl_socket = odhcpd_create_nl_socket(NETLINK_ROUTE, 0))) {
+               syslog(LOG_ERR, "Unable to open nl socket: %s", strerror(errno));
                return 2;
        }
 
@@ -119,19 +122,27 @@ int main(int argc, char **argv)
        return 0;
 }
 
-int odhcpd_open_rtnl(void)
+struct nl_sock *odhcpd_create_nl_socket(int protocol, int groups)
 {
-       int sock = socket(AF_NETLINK, SOCK_RAW | SOCK_CLOEXEC, NETLINK_ROUTE);
+       struct nl_sock *nl_sock;
 
-       // Connect to the kernel netlink interface
-       struct sockaddr_nl nl = {.nl_family = AF_NETLINK};
-       if (connect(sock, (struct sockaddr*)&nl, sizeof(nl))) {
-               syslog(LOG_ERR, "Failed to connect to kernel rtnetlink: %s",
-                               strerror(errno));
-               return -1;
-       }
+       nl_sock = nl_socket_alloc();
+       if (!nl_sock)
+               goto err;
+
+       if (groups)
+               nl_join_groups(nl_sock, groups);
+
+       if (nl_connect(nl_sock, protocol) < 0)
+               goto err;
 
-       return sock;
+       return nl_sock;
+
+err:
+       if (nl_sock)
+               nl_socket_free(nl_sock);
+
+       return NULL;
 }
 
 
@@ -210,84 +221,121 @@ ssize_t odhcpd_send(int socket, struct sockaddr_in6 *dest,
        return sent;
 }
 
+struct addr_info {
+       int ifindex;
+       struct odhcpd_ipaddr *addrs;
+       size_t addrs_sz;
+       int pending;
+       ssize_t ret;
+};
 
-// Detect an IPV6-address currently assigned to the given interface
-ssize_t odhcpd_get_interface_addresses(int ifindex,
-               struct odhcpd_ipaddr *addrs, size_t cnt)
+static int cb_valid_handler(struct nl_msg *msg, void *arg)
 {
-       struct {
-               struct nlmsghdr nhm;
-               struct ifaddrmsg ifa;
-       } req = {{sizeof(req), RTM_GETADDR, NLM_F_REQUEST | NLM_F_DUMP,
-                       ++rtnl_seq, 0}, {AF_INET6, 0, 0, 0, ifindex}};
-       if (send(rtnl_socket, &req, sizeof(req), 0) < (ssize_t)sizeof(req)) {
-               syslog(LOG_WARNING, "Request failed to dump IPv6 addresses (%s)", strerror(errno));
-               return 0;
+       struct addr_info *ctxt = (struct addr_info *)arg;
+       struct nlmsghdr *hdr = nlmsg_hdr(msg);
+       struct ifaddrmsg *ifa;
+       struct nlattr *nla[__IFA_MAX];
+
+       if (hdr->nlmsg_type != RTM_NEWADDR || ctxt->ret >= (ssize_t)ctxt->addrs_sz)
+               return NL_SKIP;
+
+       ifa = NLMSG_DATA(hdr);
+       if (ifa->ifa_scope != RT_SCOPE_UNIVERSE ||
+                       (ctxt->ifindex && ifa->ifa_index != (unsigned)ctxt->ifindex))
+               return NL_SKIP;
+
+       nlmsg_parse(hdr, sizeof(*ifa), nla, __IFA_MAX - 1, NULL);
+       if (!nla[IFA_ADDRESS])
+               return NL_SKIP;
+
+       memset(&ctxt->addrs[ctxt->ret], 0, sizeof(ctxt->addrs[ctxt->ret]));
+       ctxt->addrs[ctxt->ret].prefix = ifa->ifa_prefixlen;
+
+       nla_memcpy(&ctxt->addrs[ctxt->ret].addr, nla[IFA_ADDRESS],
+                       sizeof(ctxt->addrs[ctxt->ret].addr));
+
+       if (nla[IFA_CACHEINFO]) {
+               struct ifa_cacheinfo *ifc = nla_data(nla[IFA_CACHEINFO]);
+
+               ctxt->addrs[ctxt->ret].preferred = ifc->ifa_prefered;
+               ctxt->addrs[ctxt->ret].valid = ifc->ifa_valid;
        }
 
-       uint8_t buf[8192];
-       ssize_t len = 0, ret = 0;
+       if (ifa->ifa_flags & IFA_F_DEPRECATED)
+               ctxt->addrs[ctxt->ret].preferred = 0;
 
-       for (struct nlmsghdr *nhm = NULL; ; nhm = NLMSG_NEXT(nhm, len)) {
-               while (len < 0 || !NLMSG_OK(nhm, (size_t)len)) {
-                       len = recv(rtnl_socket, buf, sizeof(buf), 0);
-                       nhm = (struct nlmsghdr*)buf;
-                       if (len < 0 || !NLMSG_OK(nhm, (size_t)len)) {
-                               if (errno == EINTR)
-                                       continue;
+       ctxt->ret++;
 
-                               syslog(LOG_WARNING, "Failed to receive IPv6 address rtnetlink message (%s)", strerror(errno));
-                               ret = -1;
-                               goto out;
-                       }
-               }
+       return NL_OK;
+}
 
-               switch (nhm->nlmsg_type) {
-               case RTM_NEWADDR: {
-                       // Skip address but keep clearing socket buffer
-                       if (ret >= (ssize_t)cnt)
-                               continue;
+static int cb_finish_handler(_unused struct nl_msg *msg, void *arg)
+{
+       struct addr_info *ctxt = (struct addr_info *)arg;
 
-                       struct ifaddrmsg *ifa = NLMSG_DATA(nhm);
-                       if (ifa->ifa_scope != RT_SCOPE_UNIVERSE ||
-                                       (ifindex && ifa->ifa_index != (unsigned)ifindex))
-                               continue;
+       ctxt->pending = 0;
 
-                       struct rtattr *rta = (struct rtattr*)&ifa[1];
-                       size_t alen = NLMSG_PAYLOAD(nhm, sizeof(*ifa));
-                       memset(&addrs[ret], 0, sizeof(addrs[ret]));
-                       addrs[ret].prefix = ifa->ifa_prefixlen;
-
-                       while (RTA_OK(rta, alen)) {
-                               if (rta->rta_type == IFA_ADDRESS) {
-                                       memcpy(&addrs[ret].addr, RTA_DATA(rta),
-                                                       sizeof(struct in6_addr));
-                               } else if (rta->rta_type == IFA_CACHEINFO) {
-                                       struct ifa_cacheinfo *ifc = RTA_DATA(rta);
-                                       addrs[ret].preferred = ifc->ifa_prefered;
-                                       addrs[ret].valid = ifc->ifa_valid;
-                               }
-
-                               rta = RTA_NEXT(rta, alen);
-                       }
+       return NL_STOP;
+}
 
-                       if (ifa->ifa_flags & IFA_F_DEPRECATED)
-                               addrs[ret].preferred = 0;
+static int cb_error_handler(_unused struct sockaddr_nl *nla, struct nlmsgerr *err,
+               void *arg)
+{
+       struct addr_info *ctxt = (struct addr_info *)arg;
 
-                       ++ret;
-                       break;
-               }
-               case NLMSG_DONE:
-                       goto out;
-               default:
-                       syslog(LOG_WARNING, "Unexpected rtnetlink message (%d) in response to IPv6 address dump", nhm->nlmsg_type);
-                       ret = -1;
-                       goto out;
-               }
+       ctxt->pending = 0;
+       ctxt->ret = err->error;
+
+       return NL_STOP;
+}
+
+// Detect an IPV6-address currently assigned to the given interface
+ssize_t odhcpd_get_interface_addresses(int ifindex,
+               struct odhcpd_ipaddr *addrs, size_t cnt)
+{
+       struct nl_msg *msg;
+       struct ifaddrmsg ifa = {
+               .ifa_family = AF_INET6,
+               .ifa_prefixlen = 0,
+               .ifa_flags = 0,
+               .ifa_scope = 0,
+               .ifa_index = ifindex, };
+       struct nl_cb *cb = nl_cb_alloc(NL_CB_DEFAULT);
+       struct addr_info ctxt = {
+               .ifindex = ifindex,
+               .addrs = addrs,
+               .addrs_sz = cnt,
+               .ret = 0,
+               .pending = 1,
+       };
+
+       if (!cb) {
+               ctxt.ret = -1;
+               goto out;
+       }
 
+       msg = nlmsg_alloc_simple(RTM_GETADDR, NLM_F_REQUEST | NLM_F_DUMP);
+
+       if (!msg) {
+               ctxt.ret = - 1;
+               goto out;
        }
+
+       nlmsg_append(msg, &ifa, sizeof(ifa), 0);
+
+       nl_cb_set(cb, NL_CB_VALID, NL_CB_CUSTOM, cb_valid_handler, &ctxt);
+       nl_cb_set(cb, NL_CB_FINISH, NL_CB_CUSTOM, cb_finish_handler, &ctxt);
+       nl_cb_err(cb, NL_CB_CUSTOM, cb_error_handler, &ctxt);
+
+       nl_send_auto_complete(rtnl_socket, msg);
+       while (ctxt.pending > 0)
+               nl_recvmsgs(rtnl_socket, cb);
+
+       nlmsg_free(msg);
 out:
-       return ret;
+       nl_cb_put(cb);
+
+       return ctxt.ret;
 }
 
 int odhcpd_get_linklocal_interface_address(int ifindex, struct in6_addr *lladdr)
@@ -308,54 +356,43 @@ int odhcpd_get_linklocal_interface_address(int ifindex, struct in6_addr *lladdr)
        return status;
 }
 
-void odhcpd_setup_route(const struct in6_addr *addr, int prefixlen,
+int odhcpd_setup_route(const struct in6_addr *addr, int prefixlen,
                const struct interface *iface, const struct in6_addr *gw,
-               int metric, bool add)
+               uint32_t metric, bool add)
 {
-       struct req {
-               struct nlmsghdr nh;
-               struct rtmsg rtm;
-               struct rtattr rta_dst;
-               struct in6_addr dst_addr;
-               struct rtattr rta_oif;
-               uint32_t ifindex;
-               struct rtattr rta_table;
-               uint32_t table;
-               struct rtattr rta_prio;
-               uint32_t prio;
-               struct rtattr rta_gw;
-               struct in6_addr gw;
-       } req = {
-               {sizeof(req), 0, NLM_F_REQUEST, ++rtnl_seq, 0},
-               {AF_INET6, prefixlen, 0, 0, 0, 0, 0, 0, 0},
-               {sizeof(struct rtattr) + sizeof(struct in6_addr), RTA_DST},
-               *addr,
-               {sizeof(struct rtattr) + sizeof(uint32_t), RTA_OIF},
-               iface->ifindex,
-               {sizeof(struct rtattr) + sizeof(uint32_t), RTA_TABLE},
-               RT_TABLE_MAIN,
-               {sizeof(struct rtattr) + sizeof(uint32_t), RTA_PRIORITY},
-               metric,
-               {sizeof(struct rtattr) + sizeof(struct in6_addr), RTA_GATEWAY},
-               IN6ADDR_ANY_INIT,
+       struct nl_msg *msg;
+       struct rtmsg rtm = {
+               .rtm_family = AF_INET6,
+               .rtm_dst_len = prefixlen,
+               .rtm_src_len = 0,
+               .rtm_table = RT_TABLE_MAIN,
+               .rtm_protocol = (add ? RTPROT_STATIC : RTPROT_UNSPEC),
+               .rtm_scope = (add ? (gw ? RT_SCOPE_UNIVERSE : RT_SCOPE_LINK) : RT_SCOPE_NOWHERE),
+               .rtm_type = (add ? RTN_UNICAST : RTN_UNSPEC),
        };
+       int ret = 0;
+
+       msg = nlmsg_alloc_simple(add ? RTM_NEWROUTE : RTM_DELROUTE,
+                                       add ? NLM_F_CREATE | NLM_F_REPLACE : 0);
+       if (!msg)
+               return -1;
+
+       nlmsg_append(msg, &rtm, sizeof(rtm), 0);
+
+       nla_put(msg, RTA_DST, sizeof(*addr), addr);
+       nla_put_u32(msg, RTA_OIF, iface->ifindex);
+       nla_put_u32(msg, RTA_PRIORITY, metric);
 
        if (gw)
-               req.gw = *gw;
-
-       if (add) {
-               req.nh.nlmsg_type = RTM_NEWROUTE;
-               req.nh.nlmsg_flags |= (NLM_F_CREATE | NLM_F_REPLACE);
-               req.rtm.rtm_protocol = RTPROT_STATIC;
-               req.rtm.rtm_scope = (gw) ? RT_SCOPE_UNIVERSE : RT_SCOPE_LINK;
-               req.rtm.rtm_type = RTN_UNICAST;
-       } else {
-               req.nh.nlmsg_type = RTM_DELROUTE;
-               req.rtm.rtm_scope = RT_SCOPE_NOWHERE;
-       }
+               nla_put(msg, RTA_GATEWAY, sizeof(*gw), gw);
+
+       ret = nl_send_auto_complete(rtnl_socket, msg);
+       nlmsg_free(msg);
+
+       if (ret < 0)
+               return ret;
 
-       req.nh.nlmsg_len = (gw) ? sizeof(req) : offsetof(struct req, rta_gw);
-       send(rtnl_socket, &req, req.nh.nlmsg_len, MSG_DONTWAIT);
+       return nl_wait_for_ack(rtnl_socket);
 }
 
 struct interface* odhcpd_get_interface_by_index(int ifindex)
index 043360b8ba44841fddaba7521e8406de12962b4c..a2ef9f70f973b8ba499462f7c7d19aa4d3df3e4f 100644 (file)
@@ -59,6 +59,7 @@
 
 
 struct interface;
+struct nl_sock;
 extern struct list_head leases;
 
 struct odhcpd_event {
@@ -185,10 +186,11 @@ extern struct list_head interfaces;
 
 
 // Exported main functions
-int odhcpd_open_rtnl(void);
+struct nl_sock *odhcpd_open_rtnl(int protocol, int groups);
 int odhcpd_register(struct odhcpd_event *event);
 void odhcpd_process(struct odhcpd_event *event);
 
+struct nl_sock *odhcpd_create_nl_socket(int protocol, int groups);
 ssize_t odhcpd_send(int socket, struct sockaddr_in6 *dest,
                struct iovec *iov, size_t iov_len,
                const struct interface *iface);
@@ -201,9 +203,9 @@ int odhcpd_get_mac(const struct interface *iface, uint8_t mac[6]);
 struct interface* odhcpd_get_interface_by_index(int ifindex);
 struct interface* odhcpd_get_master_interface(void);
 int odhcpd_urandom(void *data, size_t len);
-void odhcpd_setup_route(const struct in6_addr *addr, int prefixlen,
+int odhcpd_setup_route(const struct in6_addr *addr, int prefixlen,
                const struct interface *iface, const struct in6_addr *gw,
-               int metric, bool add);
+               uint32_t metric, bool add);
 
 void odhcpd_run(void);
 time_t odhcpd_time(void);