From 91a28e4566bdae6532d3801332bef9999f43605c Mon Sep 17 00:00:00 2001 From: sfan5 Date: Sat, 2 Feb 2019 22:48:18 +0100 Subject: [PATCH] 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 --- src/ndp.c | 38 ++++++++++++++++++ src/netlink.c | 107 ++++++++++++++++++++++++++++++++++++++++++++++++++ src/odhcpd.h | 1 + 3 files changed, 146 insertions(+) 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 */ diff --git a/src/netlink.c b/src/netlink.c index d9c2b40..e3b2c3f 100644 --- a/src/netlink.c +++ b/src/netlink.c @@ -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) diff --git a/src/odhcpd.h b/src/odhcpd.h index 4a07252..01c9ad7 100644 --- a/src/odhcpd.h +++ b/src/odhcpd.h @@ -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); -- 2.25.1