ndp: answer global-addressed NS manually
[project/odhcpd.git] / src / netlink.c
index 56d35df86a222839dfc23efa65d709c338e09c2b..e3b2c3f0931c9c82a44cc32234024f492f2e6239 100644 (file)
@@ -78,7 +78,7 @@ int netlink_init(void)
        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))
@@ -129,221 +129,329 @@ static void handle_rtnl_event(struct odhcpd_event *e)
        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)
@@ -500,7 +608,7 @@ static int prefix_cmp(const void *va, const void *vb)
 }
 
 
-// compare IPv6 prefixes
+/* compare IPv6 prefixes */
 static int prefix6_cmp(const void *va, const void *vb)
 {
        const struct odhcpd_ipaddr *a = va, *b = vb;
@@ -510,7 +618,7 @@ static int prefix6_cmp(const void *va, const void *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;
@@ -576,6 +684,113 @@ out:
 }
 
 
+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)