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
+ /* 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))
nl_recvmsgs_default(ev_sock->sock);
}
-static void refresh_iface_addr4(struct netevent_handler_info *event_info)
+static void refresh_iface_addr4(int ifindex)
{
struct odhcpd_ipaddr *addr = NULL;
- struct interface *iface = event_info->iface;
- ssize_t len = netlink_get_interface_addrs(iface->ifindex, false, &addr);
+ struct interface *iface;
+ ssize_t len = netlink_get_interface_addrs(ifindex, false, &addr);
+ bool change = false;
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;
+ avl_for_each_element(&interfaces, iface, avl) {
+ struct netevent_handler_info event_info;
- event_info->addrs_old.addrs = iface->addr4;
- event_info->addrs_old.len = iface->addr4_len;
+ if (iface->ifindex != ifindex)
+ continue;
- iface->addr4 = addr;
- iface->addr4_len = len;
+ memset(&event_info, 0, sizeof(event_info));
+ event_info.iface = iface;
+ event_info.addrs_old.addrs = iface->addr4;
+ event_info.addrs_old.len = iface->addr4_len;
- if (change)
- call_netevent_handler_list(NETEV_ADDRLIST_CHANGE, event_info);
+ if (!change) {
+ 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;
+ }
+ }
+
+ iface->addr4 = addr;
+ iface->addr4_len = len;
+
+ if (change)
+ call_netevent_handler_list(NETEV_ADDRLIST_CHANGE, &event_info);
+
+ free(event_info.addrs_old.addrs);
+
+ if (!len)
+ continue;
+
+ addr = malloc(len * sizeof(*addr));
+ if (!addr)
+ break;
- free(event_info->addrs_old.addrs);
+ memcpy(addr, iface->addr4, len * sizeof(*addr));
+ }
+
+ free(addr);
}
-static void refresh_iface_addr6(struct netevent_handler_info *event_info)
+static void refresh_iface_addr6(int ifindex)
{
struct odhcpd_ipaddr *addr = NULL;
- struct interface *iface = event_info->iface;
- ssize_t len = netlink_get_interface_addrs(iface->ifindex, true, &addr);
+ struct interface *iface;
+ ssize_t len = netlink_get_interface_addrs(ifindex, true, &addr);
+ time_t now = odhcpd_time();
+ bool change = false;
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;
+ avl_for_each_element(&interfaces, iface, avl) {
+ struct netevent_handler_info event_info;
- event_info->addrs_old.addrs = iface->addr6;
- event_info->addrs_old.len = iface->addr6_len;
+ if (iface->ifindex != ifindex)
+ continue;
- iface->addr6 = addr;
- iface->addr6_len = len;
+ memset(&event_info, 0, sizeof(event_info));
+ event_info.iface = iface;
+ event_info.addrs_old.addrs = iface->addr6;
+ event_info.addrs_old.len = iface->addr6_len;
+
+ if (!change) {
+ 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 > (uint32_t)now) != (iface->addr6[i].preferred > (uint32_t)now) ||
+ addr[i].valid < iface->addr6[i].valid || addr[i].preferred < iface->addr6[i].preferred)
+ change = true;
+ }
+ }
- if (change)
- call_netevent_handler_list(NETEV_ADDR6LIST_CHANGE, event_info);
+ iface->addr6 = addr;
+ iface->addr6_len = len;
- free(event_info->addrs_old.addrs);
+ if (change)
+ call_netevent_handler_list(NETEV_ADDR6LIST_CHANGE, &event_info);
+
+ free(event_info.addrs_old.addrs);
+
+ if (!len)
+ continue;
+
+ addr = malloc(len * sizeof(*addr));
+ if (!addr)
+ break;
+
+ memcpy(addr, iface->addr6, len * sizeof(*addr));
+ }
+
+ free(addr);
}
-// 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)
+static int handle_rtm_link(struct nlmsghdr *hdr)
{
- struct nlmsghdr *hdr = nlmsg_hdr(msg);
+ struct ifinfomsg *ifi = nlmsg_data(hdr);
+ struct nlattr *nla[__IFLA_MAX];
+ struct interface *iface;
struct netevent_handler_info event_info;
- bool add = false;
- char ipbuf[INET6_ADDRSTRLEN];
+ const char *ifname;
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;
+ 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;
+ 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;
+ ifname = nla_get_string(nla[IFLA_IFNAME]);
- 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;
+ avl_for_each_element(&interfaces, iface, avl) {
+ if (strcmp(iface->ifname, ifname) || iface->ifindex == ifi->ifi_index)
+ continue;
+
+ iface->ifindex = ifi->ifi_index;
+ event_info.iface = iface;
+ call_netevent_handler_list(NETEV_IFINDEX_CHANGE, &event_info);
}
- case RTM_NEWROUTE:
- add = true;
- /* fall through */
- case RTM_DELROUTE: {
- struct rtmsg *rtm = nlmsg_data(hdr);
- struct nlattr *nla[__RTA_MAX];
+ return NL_OK;
+}
- if (!nlmsg_valid_hdr(hdr, sizeof(*rtm)) ||
- rtm->rtm_family != AF_INET6)
- return NL_SKIP;
+static int handle_rtm_route(struct nlmsghdr *hdr, bool add)
+{
+ struct rtmsg *rtm = nlmsg_data(hdr);
+ struct nlattr *nla[__RTA_MAX];
+ struct interface *iface;
+ struct netevent_handler_info event_info;
+ int ifindex = 0;
+
+ 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);
+
+ memset(&event_info, 0, sizeof(event_info));
+ event_info.rt.dst_len = rtm->rtm_dst_len;
- nlmsg_parse(hdr, sizeof(*rtm), nla, __RTA_MAX - 1, NULL);
+ if (nla[RTA_DST])
+ nla_memcpy(&event_info.rt.dst, nla[RTA_DST],
+ sizeof(event_info.rt.dst));
- 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])
+ ifindex = nla_get_u32(nla[RTA_OIF]);
- 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));
- if (nla[RTA_GATEWAY])
- nla_memcpy(&event_info.rt.gateway, nla[RTA_GATEWAY],
- sizeof(event_info.rt.gateway));
+ avl_for_each_element(&interfaces, iface, avl) {
+ if (ifindex && iface->ifindex != ifindex)
+ continue;
+ event_info.iface = ifindex ? iface : NULL;
call_netevent_handler_list(add ? NETEV_ROUTE6_ADD : NETEV_ROUTE6_DEL,
- &event_info);
- break;
+ &event_info);
}
- case RTM_NEWADDR:
- add = true;
- /* fall through */
- case RTM_DELADDR: {
- struct ifaddrmsg *ifa = nlmsg_data(hdr);
- struct nlattr *nla[__IFA_MAX];
+ return NL_OK;
+}
- if (!nlmsg_valid_hdr(hdr, sizeof(*ifa)) ||
- (ifa->ifa_family != AF_INET6 &&
- ifa->ifa_family != AF_INET))
- return NL_SKIP;
+static int handle_rtm_addr(struct nlmsghdr *hdr, bool add)
+{
+ struct ifaddrmsg *ifa = nlmsg_data(hdr);
+ struct nlattr *nla[__IFA_MAX];
+ struct interface *iface;
+ struct netevent_handler_info event_info;
+ char buf[INET6_ADDRSTRLEN];
- event_info.iface = odhcpd_get_interface_by_index(ifa->ifa_index);
- if (!event_info.iface)
+ if (!nlmsg_valid_hdr(hdr, sizeof(*ifa)) ||
+ (ifa->ifa_family != AF_INET6 &&
+ ifa->ifa_family != AF_INET))
+ return NL_SKIP;
+
+ memset(&event_info, 0, sizeof(event_info));
+
+ nlmsg_parse(hdr, sizeof(*ifa), nla, __IFA_MAX - 1, NULL);
+
+ if (ifa->ifa_family == AF_INET6) {
+ if (!nla[IFA_ADDRESS])
return NL_SKIP;
- nlmsg_parse(hdr, sizeof(*ifa), nla, __IFA_MAX - 1, NULL);
+ nla_memcpy(&event_info.addr, nla[IFA_ADDRESS], sizeof(event_info.addr));
- if (ifa->ifa_family == AF_INET6) {
- if (!nla[IFA_ADDRESS])
- return NL_SKIP;
+ if (IN6_IS_ADDR_LINKLOCAL(&event_info.addr) || IN6_IS_ADDR_MULTICAST(&event_info.addr))
+ return NL_SKIP;
- nla_memcpy(&event_info.addr, nla[IFA_ADDRESS], sizeof(event_info.addr));
+ inet_ntop(AF_INET6, &event_info.addr, buf, sizeof(buf));
- if (IN6_IS_ADDR_LINKLOCAL(&event_info.addr) ||
- IN6_IS_ADDR_MULTICAST(&event_info.addr))
- return NL_SKIP;
+ avl_for_each_element(&interfaces, iface, avl) {
+ if (iface->ifindex != (int)ifa->ifa_index)
+ continue;
- 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);
+ syslog(LOG_DEBUG, "Netlink %s %s on %s", add ? "newaddr" : "deladdr",
+ buf, iface->name);
+ event_info.iface = iface;
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;
+ refresh_iface_addr6(ifa->ifa_index);
+ } 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, buf, sizeof(buf));
- nla_memcpy(&event_info.addr, nla[IFA_LOCAL], sizeof(event_info.addr));
+ avl_for_each_element(&interfaces, iface, avl) {
+ if (iface->ifindex != (int)ifa->ifa_index)
+ continue;
- 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);
+ syslog(LOG_DEBUG, "Netlink %s %s on %s", add ? "newaddr" : "deladdr",
+ buf, iface->name);
+ event_info.iface = iface;
call_netevent_handler_list(add ? NETEV_ADDR_ADD : NETEV_ADDR_DEL,
&event_info);
-
- refresh_iface_addr4(&event_info);
}
- break;
+
+ refresh_iface_addr4(ifa->ifa_index);
}
- case RTM_NEWNEIGH:
- add = true;
- /* fall through */
- case RTM_DELNEIGH: {
- struct ndmsg *ndm = nlmsg_data(hdr);
- struct nlattr *nla[__NDA_MAX];
+ return NL_OK;
+}
- if (!nlmsg_valid_hdr(hdr, sizeof(*ndm)) ||
- ndm->ndm_family != AF_INET6)
- return NL_SKIP;
+static int handle_rtm_neigh(struct nlmsghdr *hdr, bool add)
+{
+ struct ndmsg *ndm = nlmsg_data(hdr);
+ struct nlattr *nla[__NDA_MAX];
+ struct interface *iface;
+ struct netevent_handler_info event_info;
+ char buf[INET6_ADDRSTRLEN];
- event_info.iface = odhcpd_get_interface_by_index(ndm->ndm_ifindex);
- if (!event_info.iface)
- return NL_SKIP;
+ if (!nlmsg_valid_hdr(hdr, sizeof(*ndm)) ||
+ ndm->ndm_family != AF_INET6)
+ return NL_SKIP;
- nlmsg_parse(hdr, sizeof(*ndm), nla, __NDA_MAX - 1, NULL);
- if (!nla[NDA_DST])
- 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));
+ memset(&event_info, 0, sizeof(event_info));
- if (IN6_IS_ADDR_LINKLOCAL(&event_info.neigh.dst) ||
- IN6_IS_ADDR_MULTICAST(&event_info.neigh.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, buf, sizeof(buf));
+
+ avl_for_each_element(&interfaces, iface, avl) {
+ if (iface->ifindex != ndm->ndm_ifindex)
+ continue;
- 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);
+ syslog(LOG_DEBUG, "Netlink %s %s on %s", true ? "newneigh" : "delneigh",
+ buf, iface->name);
+ event_info.iface = iface;
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;
}
+ return NL_OK;
+}
+
+/* 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);
+ int ret = NL_SKIP;
+ bool add = false;
+
+ switch (hdr->nlmsg_type) {
+ case RTM_NEWLINK:
+ ret = handle_rtm_link(hdr);
+ break;
+
+ case RTM_NEWROUTE:
+ add = true;
+ /* fall through */
+ case RTM_DELROUTE:
+ ret = handle_rtm_route(hdr, add);
+ break;
+
+ case RTM_NEWADDR:
+ add = true;
+ /* fall through */
+ case RTM_DELADDR:
+ ret = handle_rtm_addr(hdr, add);
+ break;
+
+ case RTM_NEWNEIGH:
+ add = true;
+ /* fall through */
+ case RTM_DELNEIGH:
+ ret = handle_rtm_neigh(hdr, add);
+ break;
+
default:
- return NL_SKIP;
+ break;
}
- return NL_OK;
+ return ret;
}
static void catch_rtnl_err(struct odhcpd_event *e, int error)
}
-// compare IPv6 prefixes
+/* compare IPv6 prefixes */
static int prefix6_cmp(const void *va, const void *vb)
{
const struct odhcpd_ipaddr *a = va, *b = vb;
}
-// Detect an IPV6-address currently assigned to the given interface
+/* Detect an IPV6-address currently assigned to the given interface */
ssize_t netlink_get_interface_addrs(int ifindex, bool v6, struct odhcpd_ipaddr **addrs)
{
struct nl_msg *msg;
}
+struct neigh_info {
+ int ifindex;
+ int pending;
+ const struct in6_addr *addr;
+ int ret;
+};
+
+
+static int cb_valid_handler2(struct nl_msg *msg, void *arg)
+{
+ struct neigh_info *ctxt = (struct neigh_info *)arg;
+ struct nlmsghdr *hdr = nlmsg_hdr(msg);
+ struct ndmsg *ndm;
+ struct nlattr *nla_dst;
+
+ if (hdr->nlmsg_type != RTM_NEWNEIGH)
+ return NL_SKIP;
+
+ ndm = NLMSG_DATA(hdr);
+ if (ndm->ndm_family != AF_INET6 ||
+ (ctxt->ifindex && ndm->ndm_ifindex != ctxt->ifindex))
+ return NL_SKIP;
+
+ if (!(ndm->ndm_flags & NTF_PROXY))
+ return NL_SKIP;
+
+ nla_dst = nlmsg_find_attr(hdr, sizeof(*ndm), NDA_DST);
+ if (!nla_dst)
+ return NL_SKIP;
+
+ if (nla_memcmp(nla_dst,ctxt->addr, 16) == 0)
+ ctxt->ret = 1;
+
+ return NL_OK;
+}
+
+
+static int cb_finish_handler2(_unused struct nl_msg *msg, void *arg)
+{
+ struct neigh_info *ctxt = (struct neigh_info *)arg;
+
+ ctxt->pending = 0;
+
+ return NL_STOP;
+}
+
+
+static int cb_error_handler2(_unused struct sockaddr_nl *nla, struct nlmsgerr *err,
+ void *arg)
+{
+ struct neigh_info *ctxt = (struct neigh_info *)arg;
+
+ ctxt->pending = 0;
+ ctxt->ret = err->error;
+
+ return NL_STOP;
+}
+
+/* Detect an IPV6-address proxy neighbor for the given interface */
+int netlink_get_interface_proxy_neigh(int ifindex, const struct in6_addr *addr)
+{
+ struct nl_msg *msg;
+ struct ndmsg ndm = {
+ .ndm_family = AF_INET6,
+ .ndm_flags = NTF_PROXY,
+ .ndm_ifindex = ifindex,
+ };
+ struct nl_cb *cb = nl_cb_alloc(NL_CB_DEFAULT);
+ struct neigh_info ctxt = {
+ .ifindex = ifindex,
+ .addr = addr,
+ .ret = 0,
+ .pending = 1,
+ };
+
+ if (!cb) {
+ ctxt.ret = -1;
+ goto out;
+ }
+
+ msg = nlmsg_alloc_simple(RTM_GETNEIGH, NLM_F_REQUEST | NLM_F_MATCH);
+
+ if (!msg) {
+ ctxt.ret = -1;
+ goto out;
+ }
+
+ nlmsg_append(msg, &ndm, sizeof(ndm), 0);
+ nla_put(msg, NDA_DST, sizeof(*addr), addr);
+
+ nl_cb_set(cb, NL_CB_VALID, NL_CB_CUSTOM, cb_valid_handler2, &ctxt);
+ nl_cb_set(cb, NL_CB_FINISH, NL_CB_CUSTOM, cb_finish_handler2, &ctxt);
+ nl_cb_err(cb, NL_CB_CUSTOM, cb_error_handler2, &ctxt);
+
+ nl_send_auto_complete(rtnl_socket, msg);
+ while (ctxt.pending > 0)
+ nl_recvmsgs(rtnl_socket, cb);
+
+ nlmsg_free(msg);
+
+out:
+ nl_cb_put(cb);
+
+ return ctxt.ret;
+}
+
+
int netlink_setup_route(const struct in6_addr *addr, const int prefixlen,
const int ifindex, const struct in6_addr *gw,
const uint32_t metric, const bool add)