summaryrefslogtreecommitdiffhomepage
path: root/src/ndp.c
diff options
context:
space:
mode:
authorsfan5 <sfan5@live.de>2019-02-02 22:48:18 +0100
committerHans Dedecker <dedeckeh@gmail.com>2019-09-15 18:54:12 +0200
commit91a28e4566bdae6532d3801332bef9999f43605c (patch)
tree1e2cc18379b0edbe2ba54df6c4642bb34a6dcc13 /src/ndp.c
parentfd93e36727bc605f6f860759d7710a43bee5215d (diff)
ndp: answer global-addressed NS manually
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>
Diffstat (limited to 'src/ndp.c')
-rw-r--r--src/ndp.c38
1 files changed, 38 insertions, 0 deletions
diff --git a/src/ndp.c b/src/ndp.c
index aaaa69c..3f0a037 100644
--- 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 */