summaryrefslogtreecommitdiffhomepage
path: root/src/netlink.c
diff options
context:
space:
mode:
authorHans Dedecker <dedeckeh@gmail.com>2017-10-17 16:23:35 +0200
committerHans Dedecker <dedeckeh@gmail.com>2017-11-09 15:25:06 +0100
commitcf29925b29a4e4e38ab88573f12ec075eacb3d22 (patch)
tree50824b4e52d0b15592b58383e61a53334fbed545 /src/netlink.c
parent24cdc1b59f00a065dd1cf0a04145ca6aaf6f23f1 (diff)
treewide: rework handling of netlink events
Rework the handling of netlink events by letting the different modules ndp, ra, dhcpv6 and dhcpv4 install netevent handlers. The installed netevent handlers are called by the netlink logic passing an event indication together with event data. Each netevent handler implements its own event logic; this makes the code more modular and less complex by moving all netlink code to netlink.c While at it rename ia_addr and ia_addr_len into addr6 and addr6_len respectively Signed-off-by: Hans Dedecker <dedeckeh@gmail.com>
Diffstat (limited to 'src/netlink.c')
-rw-r--r--src/netlink.c383
1 files changed, 372 insertions, 11 deletions
diff --git a/src/netlink.c b/src/netlink.c
index 485fd57..ca9376e 100644
--- a/src/netlink.c
+++ b/src/netlink.c
@@ -25,23 +25,348 @@
#include <netlink/socket.h>
#include <netlink/attr.h>
+#include <arpa/inet.h>
+#include <libubox/list.h>
+
#include "odhcpd.h"
-static struct nl_sock *rtnl_socket = NULL;
+struct event_socket {
+ struct odhcpd_event ev;
+ struct nl_sock *sock;
+ int sock_bufsize;
+};
+static void handle_rtnl_event(struct odhcpd_event *ev);
+static int cb_rtnl_valid(struct nl_msg *msg, void *arg);
+static void catch_rtnl_err(struct odhcpd_event *e, int error);
+static struct nl_sock *create_socket(int protocol);
+
+static struct nl_sock *rtnl_socket = NULL;
+struct list_head netevent_handler_list = LIST_HEAD_INIT(netevent_handler_list);
+static struct event_socket rtnl_event = {
+ .ev = {
+ .uloop = {.fd = - 1, },
+ .handle_dgram = NULL,
+ .handle_error = catch_rtnl_err,
+ .recv_msgs = handle_rtnl_event,
+ },
+ .sock = NULL,
+ .sock_bufsize = 133120,
+};
int netlink_init(void)
{
- if (!(rtnl_socket = netlink_create_socket(NETLINK_ROUTE))) {
+ rtnl_socket = create_socket(NETLINK_ROUTE);
+ if (!rtnl_socket) {
syslog(LOG_ERR, "Unable to open nl socket: %s", strerror(errno));
- return -1;
+ goto err;
+ }
+
+ rtnl_event.sock = create_socket(NETLINK_ROUTE);
+ if (!rtnl_event.sock) {
+ syslog(LOG_ERR, "Unable to open nl event socket: %s", strerror(errno));
+ goto err;
+ }
+
+ rtnl_event.ev.uloop.fd = nl_socket_get_fd(rtnl_event.sock);
+
+ if (nl_socket_set_buffer_size(rtnl_event.sock, rtnl_event.sock_bufsize, 0))
+ goto err;
+
+ nl_socket_disable_seq_check(rtnl_event.sock);
+
+ nl_socket_modify_cb(rtnl_event.sock, NL_CB_VALID, NL_CB_CUSTOM,
+ cb_rtnl_valid, NULL);
+
+ // Receive IPv4 address, IPv6 address, IPv6 routes and neighbor events
+ if (nl_socket_add_memberships(rtnl_event.sock, RTNLGRP_IPV4_IFADDR,
+ RTNLGRP_IPV6_IFADDR, RTNLGRP_IPV6_ROUTE,
+ RTNLGRP_NEIGH, RTNLGRP_LINK, 0))
+ goto err;
+
+ odhcpd_register(&rtnl_event.ev);
+
+ return 0;
+
+err:
+ if (rtnl_socket) {
+ nl_socket_free(rtnl_socket);
+ rtnl_socket = NULL;
}
+ if (rtnl_event.sock) {
+ nl_socket_free(rtnl_event.sock);
+ rtnl_event.sock = NULL;
+ rtnl_event.ev.uloop.fd = -1;
+ }
+
+ return -1;
+}
+
+
+int netlink_add_netevent_handler(struct netevent_handler *handler)
+{
+ if (!handler->cb)
+ return -1;
+
+ list_add(&handler->head, &netevent_handler_list);
+
return 0;
}
+static void call_netevent_handler_list(unsigned long event, struct netevent_handler_info *info)
+{
+ struct netevent_handler *handler;
-struct nl_sock *netlink_create_socket(int protocol)
+ list_for_each_entry(handler, &netevent_handler_list, head)
+ handler->cb(event, info);
+}
+
+static void handle_rtnl_event(struct odhcpd_event *e)
+{
+ struct event_socket *ev_sock = container_of(e, struct event_socket, ev);
+
+ nl_recvmsgs_default(ev_sock->sock);
+}
+
+static void refresh_iface_addr4(struct netevent_handler_info *event_info)
+{
+ struct odhcpd_ipaddr *addr = NULL;
+ struct interface *iface = event_info->iface;
+ ssize_t len = netlink_get_interface_addrs(iface->ifindex, false, &addr);
+
+ if (len < 0)
+ return;
+
+ bool change = len != (ssize_t)iface->addr4_len;
+ for (ssize_t i = 0; !change && i < len; ++i)
+ if (addr[i].addr.in.s_addr != iface->addr4[i].addr.in.s_addr)
+ change = true;
+
+ event_info->addrs_old.addrs = iface->addr4;
+ event_info->addrs_old.len = iface->addr4_len;
+
+ iface->addr4 = addr;
+ iface->addr4_len = len;
+
+ if (change)
+ call_netevent_handler_list(NETEV_ADDRLIST_CHANGE, event_info);
+
+ free(event_info->addrs_old.addrs);
+}
+
+static void refresh_iface_addr6(struct netevent_handler_info *event_info)
+{
+ struct odhcpd_ipaddr *addr = NULL;
+ struct interface *iface = event_info->iface;
+ ssize_t len = netlink_get_interface_addrs(iface->ifindex, true, &addr);
+
+ if (len < 0)
+ return;
+
+ bool change = len != (ssize_t)iface->addr6_len;
+ for (ssize_t i = 0; !change && i < len; ++i)
+ if (!IN6_ARE_ADDR_EQUAL(&addr[i].addr.in6, &iface->addr6[i].addr.in6) ||
+ (addr[i].preferred > 0) != (iface->addr6[i].preferred > 0) ||
+ addr[i].valid < iface->addr6[i].valid ||
+ addr[i].preferred < iface->addr6[i].preferred)
+ change = true;
+
+ event_info->addrs_old.addrs = iface->addr6;
+ event_info->addrs_old.len = iface->addr6_len;
+
+ iface->addr6 = addr;
+ iface->addr6_len = len;
+
+ if (change)
+ call_netevent_handler_list(NETEV_ADDR6LIST_CHANGE, event_info);
+
+ free(event_info->addrs_old.addrs);
+}
+
+// Handler for neighbor cache entries from the kernel. This is our source
+// to learn and unlearn hosts on interfaces.
+static int cb_rtnl_valid(struct nl_msg *msg, _unused void *arg)
+{
+ struct nlmsghdr *hdr = nlmsg_hdr(msg);
+ struct netevent_handler_info event_info;
+ bool add = false;
+ char ipbuf[INET6_ADDRSTRLEN];
+
+ memset(&event_info, 0, sizeof(event_info));
+ switch (hdr->nlmsg_type) {
+ case RTM_NEWLINK: {
+ struct ifinfomsg *ifi = nlmsg_data(hdr);
+ struct nlattr *nla[__IFLA_MAX];
+
+ if (!nlmsg_valid_hdr(hdr, sizeof(*ifi)) ||
+ ifi->ifi_family != AF_UNSPEC)
+ return NL_SKIP;
+
+ nlmsg_parse(hdr, sizeof(*ifi), nla, __IFLA_MAX - 1, NULL);
+ if (!nla[IFLA_IFNAME])
+ return NL_SKIP;
+
+ event_info.iface = odhcpd_get_interface_by_name(nla_get_string(nla[IFLA_IFNAME]));
+ if (!event_info.iface)
+ return NL_SKIP;
+
+ if (event_info.iface->ifindex != ifi->ifi_index) {
+ event_info.iface->ifindex = ifi->ifi_index;
+ call_netevent_handler_list(NETEV_IFINDEX_CHANGE, &event_info);
+ }
+ break;
+ }
+
+ case RTM_NEWROUTE:
+ add = true;
+ /* fall through */
+ case RTM_DELROUTE: {
+ struct rtmsg *rtm = nlmsg_data(hdr);
+ struct nlattr *nla[__RTA_MAX];
+
+ if (!nlmsg_valid_hdr(hdr, sizeof(*rtm)) ||
+ rtm->rtm_family != AF_INET6)
+ return NL_SKIP;
+
+ nlmsg_parse(hdr, sizeof(*rtm), nla, __RTA_MAX - 1, NULL);
+
+ event_info.rt.dst_len = rtm->rtm_dst_len;
+ if (nla[RTA_DST])
+ nla_memcpy(&event_info.rt.dst, nla[RTA_DST],
+ sizeof(&event_info.rt.dst));
+
+ if (nla[RTA_OIF])
+ event_info.iface = odhcpd_get_interface_by_index(nla_get_u32(nla[RTA_OIF]));
+
+ if (nla[RTA_GATEWAY])
+ nla_memcpy(&event_info.rt.gateway, nla[RTA_GATEWAY],
+ sizeof(&event_info.rt.gateway));
+
+ call_netevent_handler_list(add ? NETEV_ROUTE6_ADD : NETEV_ROUTE6_DEL,
+ &event_info);
+ break;
+ }
+
+ case RTM_NEWADDR:
+ add = true;
+ /* fall through */
+ case RTM_DELADDR: {
+ struct ifaddrmsg *ifa = nlmsg_data(hdr);
+ struct nlattr *nla[__IFA_MAX];
+
+ if (!nlmsg_valid_hdr(hdr, sizeof(*ifa)) ||
+ (ifa->ifa_family != AF_INET6 &&
+ ifa->ifa_family != AF_INET))
+ return NL_SKIP;
+
+ event_info.iface = odhcpd_get_interface_by_index(ifa->ifa_index);
+ if (!event_info.iface)
+ return NL_SKIP;
+
+ nlmsg_parse(hdr, sizeof(*ifa), nla, __IFA_MAX - 1, NULL);
+
+ if (ifa->ifa_family == AF_INET6) {
+ if (!nla[IFA_ADDRESS])
+ return NL_SKIP;
+
+ nla_memcpy(&event_info.addr, nla[IFA_ADDRESS], sizeof(event_info.addr));
+
+ if (IN6_IS_ADDR_LINKLOCAL(&event_info.addr) ||
+ IN6_IS_ADDR_MULTICAST(&event_info.addr))
+ return NL_SKIP;
+
+ inet_ntop(AF_INET6, &event_info.addr, ipbuf, sizeof(ipbuf));
+ syslog(LOG_DEBUG, "Netlink %s %s%%%s", add ? "newaddr" : "deladdr",
+ ipbuf, event_info.iface->ifname);
+
+ call_netevent_handler_list(add ? NETEV_ADDR6_ADD : NETEV_ADDR6_DEL,
+ &event_info);
+
+ refresh_iface_addr6(&event_info);
+ } else {
+ if (!nla[IFA_LOCAL])
+ return NL_SKIP;
+
+ nla_memcpy(&event_info.addr, nla[IFA_LOCAL], sizeof(event_info.addr));
+
+ inet_ntop(AF_INET, &event_info.addr, ipbuf, sizeof(ipbuf));
+ syslog(LOG_DEBUG, "Netlink %s %s%%%s", add ? "newaddr" : "deladdr",
+ ipbuf, event_info.iface->ifname);
+
+ call_netevent_handler_list(add ? NETEV_ADDR_ADD : NETEV_ADDR_DEL,
+ &event_info);
+
+ refresh_iface_addr4(&event_info);
+ }
+ break;
+ }
+
+ case RTM_NEWNEIGH:
+ add = true;
+ /* fall through */
+ case RTM_DELNEIGH: {
+ struct ndmsg *ndm = nlmsg_data(hdr);
+ struct nlattr *nla[__NDA_MAX];
+
+ if (!nlmsg_valid_hdr(hdr, sizeof(*ndm)) ||
+ ndm->ndm_family != AF_INET6)
+ return NL_SKIP;
+
+ event_info.iface = odhcpd_get_interface_by_index(ndm->ndm_ifindex);
+ if (!event_info.iface)
+ return NL_SKIP;
+
+ nlmsg_parse(hdr, sizeof(*ndm), nla, __NDA_MAX - 1, NULL);
+ if (!nla[NDA_DST])
+ return NL_SKIP;
+
+ nla_memcpy(&event_info.neigh.dst, nla[NDA_DST], sizeof(event_info.neigh.dst));
+
+ if (IN6_IS_ADDR_LINKLOCAL(&event_info.neigh.dst) ||
+ IN6_IS_ADDR_MULTICAST(&event_info.neigh.dst))
+ return NL_SKIP;
+
+ inet_ntop(AF_INET6, &event_info.neigh.dst, ipbuf, sizeof(ipbuf));
+ syslog(LOG_DEBUG, "Netlink %s %s%%%s", true ? "newneigh" : "delneigh",
+ ipbuf, event_info.iface->ifname);
+
+ event_info.neigh.state = ndm->ndm_state;
+ event_info.neigh.flags = ndm->ndm_flags;
+
+ call_netevent_handler_list(add ? NETEV_NEIGH6_ADD : NETEV_NEIGH6_DEL,
+ &event_info);
+ break;
+ }
+
+ default:
+ return NL_SKIP;
+ }
+
+ return NL_OK;
+}
+
+static void catch_rtnl_err(struct odhcpd_event *e, int error)
+{
+ struct event_socket *ev_sock = container_of(e, struct event_socket, ev);
+
+ if (error != ENOBUFS)
+ goto err;
+
+ /* Double netlink event buffer size */
+ ev_sock->sock_bufsize *= 2;
+
+ if (nl_socket_set_buffer_size(ev_sock->sock, ev_sock->sock_bufsize, 0))
+ goto err;
+
+ netlink_dump_addr_table(true);
+ return;
+
+err:
+ odhcpd_deregister(e);
+}
+
+static struct nl_sock *create_socket(int protocol)
{
struct nl_sock *nl_sock;
@@ -252,7 +577,7 @@ out:
int netlink_setup_route(const struct in6_addr *addr, const int prefixlen,
- const struct interface *iface, const struct in6_addr *gw,
+ const int ifindex, const struct in6_addr *gw,
const uint32_t metric, const bool add)
{
struct nl_msg *msg;
@@ -275,7 +600,7 @@ int netlink_setup_route(const struct in6_addr *addr, const int prefixlen,
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_OIF, ifindex);
nla_put_u32(msg, RTA_PRIORITY, metric);
if (gw)
@@ -292,13 +617,13 @@ int netlink_setup_route(const struct in6_addr *addr, const int prefixlen,
int netlink_setup_proxy_neigh(const struct in6_addr *addr,
- const struct interface *iface, const bool add)
+ const int ifindex, const bool add)
{
struct nl_msg *msg;
struct ndmsg ndm = {
.ndm_family = AF_INET6,
.ndm_flags = NTF_PROXY,
- .ndm_ifindex = iface->ifindex,
+ .ndm_ifindex = ifindex,
};
int ret = 0, flags = NLM_F_REQUEST;
@@ -324,8 +649,7 @@ int netlink_setup_proxy_neigh(const struct in6_addr *addr,
int netlink_setup_addr(struct odhcpd_ipaddr *addr,
- const struct interface *iface, const bool v6,
- const bool add)
+ const int ifindex, const bool v6, const bool add)
{
struct nl_msg *msg;
struct ifaddrmsg ifa = {
@@ -333,7 +657,7 @@ int netlink_setup_addr(struct odhcpd_ipaddr *addr,
.ifa_prefixlen = addr->prefix,
.ifa_flags = 0,
.ifa_scope = 0,
- .ifa_index = iface->ifindex, };
+ .ifa_index = ifindex, };
int ret = 0, flags = NLM_F_REQUEST;
if (add)
@@ -390,3 +714,40 @@ int netlink_setup_addr(struct odhcpd_ipaddr *addr,
return nl_wait_for_ack(rtnl_socket);
}
+
+void netlink_dump_neigh_table(const bool proxy)
+{
+ struct nl_msg *msg;
+ struct ndmsg ndm = {
+ .ndm_family = AF_INET6,
+ .ndm_flags = proxy ? NTF_PROXY : 0,
+ };
+
+ msg = nlmsg_alloc_simple(RTM_GETNEIGH, NLM_F_REQUEST | NLM_F_DUMP);
+ if (!msg)
+ return;
+
+ nlmsg_append(msg, &ndm, sizeof(ndm), 0);
+
+ nl_send_auto_complete(rtnl_event.sock, msg);
+
+ nlmsg_free(msg);
+}
+
+void netlink_dump_addr_table(const bool v6)
+{
+ struct nl_msg *msg;
+ struct ifaddrmsg ifa = {
+ .ifa_family = v6 ? AF_INET6 : AF_INET,
+ };
+
+ msg = nlmsg_alloc_simple(RTM_GETADDR, NLM_F_REQUEST | NLM_F_DUMP);
+ if (!msg)
+ return;
+
+ nlmsg_append(msg, &ifa, sizeof(ifa), 0);
+
+ nl_send_auto_complete(rtnl_event.sock, msg);
+
+ nlmsg_free(msg);
+}