summaryrefslogtreecommitdiffhomepage
diff options
context:
space:
mode:
-rw-r--r--src/ndp.c38
-rw-r--r--src/netlink.c107
-rw-r--r--src/odhcpd.h1
3 files changed, 146 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 */
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);