From df5042974622d72ce2424de8ef532941ac4f7fc9 Mon Sep 17 00:00:00 2001 From: Hans Dedecker Date: Tue, 31 Jan 2017 22:11:20 +0100 Subject: [PATCH] odhcpd: properly handle netlink messages (FS#388) 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 | 7 +- src/ndp.c | 6 +- src/odhcpd.c | 277 ++++++++++++++++++++++++++++--------------------- src/odhcpd.h | 8 +- 4 files changed, 172 insertions(+), 126 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 8c338f3..0855458 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -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/) diff --git a/src/ndp.c b/src/ndp.c index 10acc3b..f2bf19c 100644 --- 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))) diff --git a/src/odhcpd.c b/src/odhcpd.c index b1e89b3..f259239 100644 --- a/src/odhcpd.c +++ b/src/odhcpd.c @@ -30,6 +30,7 @@ #include #include #include +#include #include #include @@ -39,14 +40,16 @@ #include #include +#include +#include +#include #include #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) diff --git a/src/odhcpd.h b/src/odhcpd.h index 043360b..a2ef9f7 100644 --- a/src/odhcpd.h +++ b/src/odhcpd.h @@ -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); -- 2.30.2