ndp: answer global-addressed NS manually
authorsfan5 <sfan5@live.de>
Sat, 2 Feb 2019 21:48:18 +0000 (22:48 +0100)
committerHans Dedecker <dedeckeh@gmail.com>
Sun, 15 Sep 2019 16:54:12 +0000 (18:54 +0200)
An upstream router may address solicits to the global address of the target,
these will not be answered by the kernel and not routed either due to link-local
source. The NS will eventually be retried as multicast, but we want to avoid this.

see also https://forum.archive.openwrt.org/viewtopic.php?id=40871

Signed-off-by: Stefan Alfers <sfan5@live.de>
src/ndp.c
src/netlink.c
src/odhcpd.h

index aaaa69c15501564fc42a4b32215546ff0887e959..3f0a0373b7d0e2c821aad974a0b722dae624f1d0 100644 (file)
--- a/src/ndp.c
+++ b/src/ndp.c
@@ -295,6 +295,33 @@ static void ping6(struct in6_addr *addr,
        netlink_setup_route(addr, 128, iface->ifindex, NULL, 128, false);
 }
 
+/* Send a Neighbor Advertisement. */
+static void send_na(struct in6_addr *to_addr,
+               const struct interface *iface, struct in6_addr *for_addr,
+               const uint8_t *mac)
+{
+       struct sockaddr_in6 dest = { .sin6_family = AF_INET6, .sin6_addr = *to_addr };
+       char pbuf[sizeof(struct nd_neighbor_advert) + sizeof(struct nd_opt_hdr) + 6];
+       struct nd_neighbor_advert *adv = (struct nd_neighbor_advert*)pbuf;
+       struct nd_opt_hdr *opt = (struct nd_opt_hdr*) &pbuf[sizeof(struct nd_neighbor_advert)];
+       struct iovec iov = { .iov_base = &pbuf, .iov_len = sizeof(pbuf) };
+       char ipbuf[INET6_ADDRSTRLEN];
+
+       memset(pbuf, 0, sizeof(pbuf));
+       adv->nd_na_hdr = (struct icmp6_hdr) {
+               .icmp6_type = ND_NEIGHBOR_ADVERT,
+               .icmp6_dataun.icmp6_un_data32 = { 0x40000000L }
+       };
+       adv->nd_na_target = *for_addr;
+       *opt = (struct nd_opt_hdr) { .nd_opt_type = ND_OPT_TARGET_LINKADDR, .nd_opt_len = 1 };
+       memcpy(&pbuf[sizeof(struct nd_neighbor_advert) + sizeof(struct nd_opt_hdr)], mac, 6);
+
+       inet_ntop(AF_INET6, to_addr, ipbuf, sizeof(ipbuf));
+       syslog(LOG_DEBUG, "Answering NS to %s on %s", ipbuf, iface->ifname);
+
+       odhcpd_send(iface->ndp_ping_fd, &dest, &iov, 1, iface);
+}
+
 /* Handle solicitations */
 static void handle_solicit(void *addr, void *data, size_t len,
                struct interface *iface, _unused void *dest)
@@ -335,6 +362,17 @@ static void handle_solicit(void *addr, void *data, size_t len,
                                (ns_is_dad || !c->external))
                        ping6(&req->nd_ns_target, c);
        }
+
+       /* Catch global-addressed NS and answer them manually.
+        * The kernel won't answer these and cannot route them either. */
+       if (!IN6_IS_ADDR_MULTICAST(&ip6->ip6_dst) &&
+                       IN6_IS_ADDR_LINKLOCAL(&ip6->ip6_src)) {
+               bool is_proxy_neigh = netlink_get_interface_proxy_neigh(iface->ifindex,
+                               &req->nd_ns_target) == 1;
+
+               if (is_proxy_neigh)
+                       send_na(&ip6->ip6_src, iface, &req->nd_ns_target, mac);
+       }
 }
 
 /* Use rtnetlink to modify kernel routes */
index d9c2b404d47d9d8029e1720a01949ce99783404e..e3b2c3f0931c9c82a44cc32234024f492f2e6239 100644 (file)
@@ -684,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)
index 4a07252322d40d228bb0d1851e7560089e34a227..01c9ad73d5c5eb04936f453809cdde8a0157e447 100644 (file)
@@ -387,6 +387,7 @@ void dhcpv6_ia_write_statefile(void);
 int netlink_add_netevent_handler(struct netevent_handler *hdlr);
 ssize_t netlink_get_interface_addrs(const int ifindex, bool v6,
                struct odhcpd_ipaddr **addrs);
+int netlink_get_interface_proxy_neigh(int ifindex, const struct in6_addr *addr);
 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);