summaryrefslogtreecommitdiffhomepage
path: root/src/netlink.c
diff options
context:
space:
mode:
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);
+}