diff options
-rw-r--r-- | src/ndp.c | 38 | ||||
-rw-r--r-- | src/netlink.c | 107 | ||||
-rw-r--r-- | src/odhcpd.h | 1 |
3 files changed, 146 insertions, 0 deletions
@@ -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); |