summaryrefslogtreecommitdiff
path: root/nhrp/sysdep_netlink.c
diff options
context:
space:
mode:
Diffstat (limited to 'nhrp/sysdep_netlink.c')
-rw-r--r--nhrp/sysdep_netlink.c1159
1 files changed, 1159 insertions, 0 deletions
diff --git a/nhrp/sysdep_netlink.c b/nhrp/sysdep_netlink.c
new file mode 100644
index 0000000..d058a98
--- /dev/null
+++ b/nhrp/sysdep_netlink.c
@@ -0,0 +1,1159 @@
+/* sysdep_netlink.c - Linux netlink glue
+ *
+ * Copyright (C) 2007-2009 Timo Teräs <timo.teras@iki.fi>
+ * All rights reserved.
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 or later as
+ * published by the Free Software Foundation.
+ *
+ * See http://www.gnu.org/ for details.
+ */
+
+#include <time.h>
+#include <stdio.h>
+#include <fcntl.h>
+#include <errno.h>
+#include <unistd.h>
+#include <stdlib.h>
+#include <malloc.h>
+#include <string.h>
+#include <sys/uio.h>
+#include <sys/wait.h>
+#include <sys/types.h>
+#include <sys/ioctl.h>
+#include <sys/socket.h>
+#include <asm/types.h>
+#include <arpa/inet.h>
+#include <linux/netlink.h>
+#include <linux/rtnetlink.h>
+#include <linux/ip.h>
+#include <linux/if_arp.h>
+#include <linux/if_tunnel.h>
+
+#include "libev.h"
+#include "nhrp_common.h"
+#include "nhrp_interface.h"
+#include "nhrp_peer.h"
+
+#define NETLINK_KERNEL_BUFFER (256 * 1024)
+#define NETLINK_RECV_BUFFER (8 * 1024)
+
+#define NLMSG_TAIL(nmsg) \
+ ((struct rtattr *) (((void *) (nmsg)) + NLMSG_ALIGN((nmsg)->nlmsg_len)))
+
+#define NDA_RTA(r) ((struct rtattr*)(((char*)(r)) + NLMSG_ALIGN(sizeof(struct ndmsg))))
+#define NDA_PAYLOAD(n) NLMSG_PAYLOAD(n,sizeof(struct ndmsg))
+
+typedef void (*netlink_dispatch_f)(struct nlmsghdr *msg);
+
+struct netlink_fd {
+ int fd;
+ __u32 seq;
+ struct ev_io io;
+
+ int dispatch_size;
+ const netlink_dispatch_f *dispatch;
+};
+
+static const int netlink_groups[] = {
+ 0,
+ RTMGRP_NEIGH,
+ RTMGRP_LINK,
+ RTMGRP_IPV4_IFADDR,
+ RTMGRP_IPV4_ROUTE,
+};
+static struct netlink_fd netlink_fds[ARRAY_SIZE(netlink_groups)];
+#define talk_fd netlink_fds[0]
+
+static struct ev_io packet_io;
+
+static u_int16_t translate_mtu(u_int16_t mtu)
+{
+ /* if mtu is ethernet standard, do not advertise it
+ * pmtu should be working */
+ if (mtu == 1500)
+ return 0;
+ return mtu;
+}
+
+static void netlink_parse_rtattr(struct rtattr *tb[], int max, struct rtattr *rta, int len)
+{
+ memset(tb, 0, sizeof(struct rtattr *) * (max + 1));
+ while (RTA_OK(rta, len)) {
+ if (rta->rta_type <= max)
+ tb[rta->rta_type] = rta;
+ rta = RTA_NEXT(rta,len);
+ }
+}
+
+static int netlink_add_rtattr_l(struct nlmsghdr *n, int maxlen, int type,
+ const void *data, int alen)
+{
+ int len = RTA_LENGTH(alen);
+ struct rtattr *rta;
+
+ if (NLMSG_ALIGN(n->nlmsg_len) + RTA_ALIGN(len) > maxlen)
+ return FALSE;
+
+ rta = NLMSG_TAIL(n);
+ rta->rta_type = type;
+ rta->rta_len = len;
+ memcpy(RTA_DATA(rta), data, alen);
+#ifdef VALGRIND
+ /* Clear the padding area to avoid spurious warnings */
+ memset(RTA_DATA(rta) + alen, 0, RTA_ALIGN(len) - alen);
+#endif
+ n->nlmsg_len = NLMSG_ALIGN(n->nlmsg_len) + RTA_ALIGN(len);
+ return TRUE;
+}
+
+static int netlink_receive(struct netlink_fd *fd, struct nlmsghdr *reply)
+{
+ struct sockaddr_nl nladdr;
+ struct iovec iov;
+ struct msghdr msg = {
+ .msg_name = &nladdr,
+ .msg_namelen = sizeof(nladdr),
+ .msg_iov = &iov,
+ .msg_iovlen = 1,
+ };
+ int got_reply = FALSE, len;
+ char buf[NETLINK_RECV_BUFFER];
+
+ iov.iov_base = buf;
+ while (!got_reply) {
+ int status;
+ struct nlmsghdr *h;
+
+ iov.iov_len = sizeof(buf);
+ status = recvmsg(fd->fd, &msg, MSG_DONTWAIT);
+ if (status < 0) {
+ if (errno == EINTR)
+ continue;
+ if (errno == EAGAIN)
+ return reply == NULL;
+ nhrp_perror("Netlink overrun");
+ continue;
+ }
+
+ if (status == 0) {
+ nhrp_error("Netlink returned EOF");
+ return FALSE;
+ }
+
+ h = (struct nlmsghdr *) buf;
+ while (NLMSG_OK(h, status)) {
+ if (reply != NULL &&
+ h->nlmsg_seq == reply->nlmsg_seq) {
+ len = h->nlmsg_len;
+ if (len > reply->nlmsg_len) {
+ nhrp_error("Netlink message truncated");
+ len = reply->nlmsg_len;
+ }
+ memcpy(reply, h, len);
+ got_reply = TRUE;
+ } else if (h->nlmsg_type <= fd->dispatch_size &&
+ fd->dispatch[h->nlmsg_type] != NULL) {
+ fd->dispatch[h->nlmsg_type](h);
+ } else if (h->nlmsg_type != NLMSG_DONE) {
+ nhrp_info("Unknown NLmsg: 0x%08x, len %d",
+ h->nlmsg_type, h->nlmsg_len);
+ }
+ h = NLMSG_NEXT(h, status);
+ }
+ }
+
+ return TRUE;
+}
+
+static int netlink_send(struct netlink_fd *fd, struct nlmsghdr *req)
+{
+ struct sockaddr_nl nladdr;
+ struct iovec iov = {
+ .iov_base = (void*) req,
+ .iov_len = req->nlmsg_len
+ };
+ struct msghdr msg = {
+ .msg_name = &nladdr,
+ .msg_namelen = sizeof(nladdr),
+ .msg_iov = &iov,
+ .msg_iovlen = 1,
+ };
+ int status;
+
+ memset(&nladdr, 0, sizeof(nladdr));
+ nladdr.nl_family = AF_NETLINK;
+
+ req->nlmsg_seq = ++fd->seq;
+
+ status = sendmsg(fd->fd, &msg, 0);
+ if (status < 0) {
+ nhrp_perror("Cannot talk to rtnetlink");
+ return FALSE;
+ }
+ return TRUE;
+}
+
+static int netlink_talk(struct netlink_fd *fd, struct nlmsghdr *req,
+ size_t replysize, struct nlmsghdr *reply)
+{
+ if (reply == NULL)
+ req->nlmsg_flags |= NLM_F_ACK;
+
+ if (!netlink_send(fd, req))
+ return FALSE;
+
+ if (reply == NULL)
+ return TRUE;
+
+ reply->nlmsg_len = replysize;
+ return netlink_receive(fd, reply);
+}
+
+static int netlink_enumerate(struct netlink_fd *fd, int family, int type)
+{
+ struct {
+ struct nlmsghdr nlh;
+ struct rtgenmsg g;
+ } req;
+ struct sockaddr_nl addr;
+
+ memset(&addr, 0, sizeof(addr));
+ addr.nl_family = AF_NETLINK;
+
+ memset(&req, 0, sizeof(req));
+ req.nlh.nlmsg_len = sizeof(req);
+ req.nlh.nlmsg_type = type;
+ req.nlh.nlmsg_flags = NLM_F_ROOT | NLM_F_MATCH | NLM_F_REQUEST;
+ req.nlh.nlmsg_pid = 0;
+ req.nlh.nlmsg_seq = ++fd->seq;
+ req.g.rtgen_family = family;
+
+ return sendto(fd->fd, (void *) &req, sizeof(req), 0,
+ (struct sockaddr *) &addr, sizeof(addr)) >= 0;
+}
+
+static void netlink_read_cb(struct ev_io *w, int revents)
+{
+ struct netlink_fd *nfd = container_of(w, struct netlink_fd, io);
+
+ if (revents & EV_READ)
+ netlink_receive(nfd, NULL);
+}
+
+static int do_get_ioctl(const char *basedev, struct ip_tunnel_parm *p)
+{
+ struct ifreq ifr;
+
+#ifdef VALGRIND
+ /* Valgrind does not have SIOCGETTUNNEL description, so clear
+ * the memory structs to avoid spurious warnings */
+ memset(&ifr, 0, sizeof(ifr));
+ memset(p, 0, sizeof(*p));
+#endif
+
+ strncpy(ifr.ifr_name, basedev, IFNAMSIZ);
+ ifr.ifr_ifru.ifru_data = (void *) p;
+ if (ioctl(packet_io.fd, SIOCGETTUNNEL, &ifr)) {
+ nhrp_perror("ioctl(SIOCGETTUNNEL)");
+ return FALSE;
+ }
+ return TRUE;
+}
+
+#ifndef NHRP_NO_NBMA_GRE
+
+static int netlink_add_nested_rtattr_u32(struct rtattr *rta, int maxlen,
+ int type, uint32_t value)
+{
+ int len = RTA_LENGTH(4);
+ struct rtattr *subrta;
+
+ if (RTA_ALIGN(rta->rta_len) + len > maxlen)
+ return FALSE;
+
+ subrta = (struct rtattr*)(((char*)rta) + RTA_ALIGN(rta->rta_len));
+ subrta->rta_type = type;
+ subrta->rta_len = len;
+ memcpy(RTA_DATA(subrta), &value, 4);
+ rta->rta_len = NLMSG_ALIGN(rta->rta_len) + len;
+ return TRUE;
+}
+
+static int netlink_configure_arp(struct nhrp_interface *iface, int pf)
+{
+ struct {
+ struct nlmsghdr n;
+ struct ndtmsg ndtm;
+ char buf[256];
+ } req;
+ struct {
+ struct rtattr rta;
+ char buf[256];
+ } parms;
+
+ memset(&req.n, 0, sizeof(req.n));
+ memset(&req.ndtm, 0, sizeof(req.ndtm));
+
+ req.n.nlmsg_len = NLMSG_LENGTH(sizeof(struct ndtmsg));
+ req.n.nlmsg_flags = NLM_F_REQUEST | NLM_F_REPLACE;
+ req.n.nlmsg_type = RTM_SETNEIGHTBL;
+
+ req.ndtm.ndtm_family = pf;
+
+ netlink_add_rtattr_l(&req.n, sizeof(req), NDTA_NAME,
+ "arp_cache", 10);
+
+ parms.rta.rta_type = NDTA_PARMS;
+ parms.rta.rta_len = RTA_LENGTH(0);
+ netlink_add_nested_rtattr_u32(&parms.rta, sizeof(parms),
+ NDTPA_IFINDEX, iface->index);
+ netlink_add_nested_rtattr_u32(&parms.rta, sizeof(parms),
+ NDTPA_APP_PROBES, 1);
+ netlink_add_nested_rtattr_u32(&parms.rta, sizeof(parms),
+ NDTPA_MCAST_PROBES, 0);
+ netlink_add_nested_rtattr_u32(&parms.rta, sizeof(parms),
+ NDTPA_UCAST_PROBES, 0);
+
+ netlink_add_rtattr_l(&req.n, sizeof(req), NDTA_PARMS,
+ parms.buf, parms.rta.rta_len - RTA_LENGTH(0));
+
+ return netlink_send(&talk_fd, &req.n);
+}
+
+static int netlink_link_arp_on(struct nhrp_interface *iface)
+{
+ struct ifreq ifr;
+
+ strncpy(ifr.ifr_name, iface->name, IFNAMSIZ);
+ if (ioctl(packet_io.fd, SIOCGIFFLAGS, &ifr)) {
+ nhrp_perror("ioctl(SIOCGIFFLAGS)");
+ return FALSE;
+ }
+ if (ifr.ifr_flags & IFF_NOARP) {
+ ifr.ifr_flags &= ~IFF_NOARP;
+ if (ioctl(packet_io.fd, SIOCSIFFLAGS, &ifr)) {
+ nhrp_perror("ioctl(SIOCSIFFLAGS)");
+ return FALSE;
+ }
+ }
+ return TRUE;
+}
+
+#else
+
+static int netlink_configure_arp(struct nhrp_interface *iface, int pf)
+{
+ return TRUE;
+}
+
+static int netlink_link_arp_on(struct nhrp_interface *iface)
+{
+ return TRUE;
+}
+
+#endif
+
+static int proc_icmp_redirect_off(const char *interface)
+{
+ char fname[256];
+ int fd, ret = FALSE;
+
+ sprintf(fname, "/proc/sys/net/ipv4/conf/%s/send_redirects", interface);
+ fd = open(fname, O_WRONLY);
+ if (fd < 0)
+ return FALSE;
+ if (write(fd, "0\n", 2) == 2)
+ ret = TRUE;
+ close(fd);
+
+ return ret;
+}
+
+static void netlink_neigh_request(struct nlmsghdr *msg)
+{
+ struct ndmsg *ndm = NLMSG_DATA(msg);
+ struct rtattr *rta[NDA_MAX+1];
+ struct nhrp_peer *peer;
+ struct nhrp_address addr;
+ struct nhrp_interface *iface;
+ char tmp[64];
+
+ netlink_parse_rtattr(rta, NDA_MAX, NDA_RTA(ndm), NDA_PAYLOAD(msg));
+ if (rta[NDA_DST] == NULL)
+ return;
+
+ iface = nhrp_interface_get_by_index(ndm->ndm_ifindex, 0);
+ if (iface == NULL)
+ return;
+
+ nhrp_address_set(&addr, ndm->ndm_family,
+ RTA_PAYLOAD(rta[NDA_DST]),
+ RTA_DATA(rta[NDA_DST]));
+
+ nhrp_debug("NL-ARP(%s) who-has %s",
+ iface->name, nhrp_address_format(&addr, sizeof(tmp), tmp));
+
+ peer = nhrp_peer_route(iface, &addr, 0, ~BIT(NHRP_PEER_TYPE_LOCAL_ROUTE));
+ if (peer == NULL)
+ return;
+
+ if (peer->flags & NHRP_PEER_FLAG_UP)
+ kernel_inject_neighbor(&addr, &peer->next_hop_address, iface);
+
+ if (peer->next_hop_address.type != PF_UNSPEC &&
+ nhrp_address_cmp(&addr, &peer->protocol_address) != 0)
+ nhrp_peer_traffic_indication(iface, peer->afnum, &addr);
+}
+
+static void netlink_neigh_update(struct nlmsghdr *msg)
+{
+ struct ndmsg *ndm = NLMSG_DATA(msg);
+ struct rtattr *rta[NDA_MAX+1];
+ struct nhrp_interface *iface;
+ struct nhrp_peer_selector sel;
+ int used = FALSE;
+
+ netlink_parse_rtattr(rta, NDA_MAX, NDA_RTA(ndm), NDA_PAYLOAD(msg));
+ if (rta[NDA_DST] == NULL)
+ return;
+
+ if (!(ndm->ndm_state & (NUD_STALE | NUD_FAILED | NUD_REACHABLE)))
+ return;
+
+ iface = nhrp_interface_get_by_index(ndm->ndm_ifindex, 0);
+ if (iface == NULL)
+ return;
+
+ memset(&sel, 0, sizeof(sel));
+ sel.flags = NHRP_PEER_FIND_EXACT;
+ sel.interface = iface;
+ nhrp_address_set(&sel.protocol_address, ndm->ndm_family,
+ RTA_PAYLOAD(rta[NDA_DST]),
+ RTA_DATA(rta[NDA_DST]));
+
+ if (msg->nlmsg_type == RTM_NEWNEIGH && (ndm->ndm_state & NUD_REACHABLE))
+ used = TRUE;
+
+ nhrp_peer_foreach(nhrp_peer_set_used_matching,
+ (void*) (intptr_t) used, &sel);
+}
+
+static void netlink_link_new(struct nlmsghdr *msg)
+{
+ struct nhrp_interface *iface;
+ struct ifinfomsg *ifi = NLMSG_DATA(msg);
+ struct rtattr *rta[IFLA_MAX+1];
+ const char *ifname;
+ struct ip_tunnel_parm cfg;
+ int configuration_changed = FALSE;
+
+ netlink_parse_rtattr(rta, IFLA_MAX, IFLA_RTA(ifi), IFLA_PAYLOAD(msg));
+ if (rta[IFLA_IFNAME] == NULL)
+ return;
+
+ ifname = RTA_DATA(rta[IFLA_IFNAME]);
+ iface = nhrp_interface_get_by_name(ifname, TRUE);
+ if (iface == NULL)
+ return;
+
+ if (rta[IFLA_MTU])
+ iface->mtu = *((unsigned*)RTA_DATA(rta[IFLA_MTU]));
+
+ if (iface->index == 0 || (ifi->ifi_flags & ifi->ifi_change & IFF_UP)) {
+ nhrp_info("Interface %s: new or configured up, mtu=%d",
+ ifname, iface->mtu);
+ nhrp_interface_run_script(iface, "interface-up");
+ } else {
+ nhrp_info("Interface %s: config change, mtu=%d",
+ ifname, iface->mtu);
+ }
+
+ iface->index = ifi->ifi_index;
+ nhrp_interface_hash(iface);
+
+ if (!(iface->flags & NHRP_INTERFACE_FLAG_CONFIGURED))
+ return;
+
+ switch (ifi->ifi_type) {
+ case ARPHRD_IPGRE:
+ iface->afnum = AFNUM_INET;
+ /* try hard to get the interface nbma address */
+ do_get_ioctl(ifname, &cfg);
+ if (iface->gre_key != ntohl(cfg.i_key)) {
+ configuration_changed = TRUE;
+ iface->gre_key = ntohl(cfg.i_key);
+ }
+ if (cfg.iph.saddr) {
+ struct nhrp_address saddr;
+ nhrp_address_set(&saddr, PF_INET, 4, (uint8_t *) &cfg.iph.saddr);
+ if (nhrp_address_cmp(&iface->nbma_address, &saddr) || iface->link_index) {
+ configuration_changed = TRUE;
+ iface->nbma_address = saddr;
+ iface->link_index = 0;
+ }
+ } else if (cfg.link) {
+ if (cfg.link != iface->link_index) {
+ configuration_changed = TRUE;
+ nhrp_address_set_type(&iface->nbma_address, PF_UNSPEC);
+ iface->link_index = cfg.link;
+ }
+ } else {
+ if (iface->link_index || iface->nbma_address.type != PF_UNSPEC) {
+ configuration_changed = TRUE;
+ /* Mark the interface as owning all NBMA addresses
+ * this works when there's only one GRE interface */
+ iface->link_index = 0;
+ nhrp_address_set_type(&iface->nbma_address, PF_UNSPEC);
+ nhrp_info("WARNING: Cannot figure out NBMA address for "
+ "interface '%s'. Using route hints.", ifname);
+ }
+ }
+ break;
+ }
+
+ if (!(iface->flags & NHRP_INTERFACE_FLAG_SHORTCUT_DEST)) {
+ netlink_configure_arp(iface, PF_INET);
+ netlink_link_arp_on(iface);
+ proc_icmp_redirect_off(iface->name);
+ }
+
+ if (configuration_changed) {
+ struct nhrp_peer_selector sel;
+ int count = 0;
+
+ /* Reset the interface values we detect later */
+ memset(&iface->nat_cie, 0, sizeof(iface->nat_cie));
+ iface->nbma_mtu = 0;
+ if (iface->link_index) {
+ /* Reenumerate addresses if needed */
+ netlink_enumerate(&talk_fd, PF_UNSPEC, RTM_GETADDR);
+ netlink_read_cb(&talk_fd.io, EV_READ);
+ }
+
+ /* Purge all NHRP entries for this interface */
+ memset(&sel, 0, sizeof(sel));
+ sel.type_mask = NHRP_PEER_TYPEMASK_PURGEABLE;
+ sel.interface = iface;
+ nhrp_peer_foreach(nhrp_peer_purge_matching, &count, &sel);
+ nhrp_info("Interface %s: GRE configuration changed. Purged %d peers.",
+ ifname, count);
+ }
+}
+
+static void netlink_link_del(struct nlmsghdr *msg)
+{
+ struct nhrp_interface *iface;
+ struct ifinfomsg *ifi = NLMSG_DATA(msg);
+ struct rtattr *rta[IFLA_MAX+1];
+ const char *ifname;
+
+ netlink_parse_rtattr(rta, IFLA_MAX, IFLA_RTA(ifi), IFLA_PAYLOAD(msg));
+ if (rta[IFLA_IFNAME] == NULL)
+ return;
+
+ ifname = RTA_DATA(rta[IFLA_IFNAME]);
+ iface = nhrp_interface_get_by_name(ifname, FALSE);
+ if (iface == NULL)
+ return;
+
+ nhrp_info("Interface '%s' deleted", ifname);
+ iface->index = 0;
+ iface->link_index = 0;
+ nhrp_interface_hash(iface);
+
+ nhrp_address_set_type(&iface->nbma_address, PF_UNSPEC);
+ nhrp_address_set_type(&iface->protocol_address, PF_UNSPEC);
+}
+
+static int netlink_addr_new_nbma(void *ctx, struct nhrp_interface *iface)
+{
+ struct nlmsghdr *msg = (struct nlmsghdr *) ctx;
+ struct ifaddrmsg *ifa = NLMSG_DATA(msg);
+ struct rtattr *rta[IFA_MAX+1];
+ struct nhrp_interface *nbma_iface;
+
+ if (iface->link_index == ifa->ifa_index) {
+ netlink_parse_rtattr(rta, IFA_MAX, IFA_RTA(ifa),
+ IFA_PAYLOAD(msg));
+
+ if (rta[IFA_LOCAL] == NULL)
+ return 0;
+
+ nhrp_address_set(&iface->nbma_address, ifa->ifa_family,
+ RTA_PAYLOAD(rta[IFA_LOCAL]),
+ RTA_DATA(rta[IFA_LOCAL]));
+
+ nbma_iface = nhrp_interface_get_by_index(ifa->ifa_index, FALSE);
+ if (nbma_iface != NULL) {
+ iface->nbma_mtu = translate_mtu(nbma_iface->mtu);
+ }
+ }
+
+ return 0;
+}
+
+static void netlink_addr_new(struct nlmsghdr *msg)
+{
+ struct nhrp_interface *iface;
+ struct nhrp_peer *peer, *bcast;
+ struct ifaddrmsg *ifa = NLMSG_DATA(msg);
+ struct rtattr *rta[IFA_MAX+1];
+
+ if (!(ifa->ifa_flags & IFA_F_SECONDARY))
+ nhrp_interface_foreach(netlink_addr_new_nbma, msg);
+
+ netlink_parse_rtattr(rta, IFA_MAX, IFA_RTA(ifa), IFA_PAYLOAD(msg));
+ iface = nhrp_interface_get_by_index(ifa->ifa_index, FALSE);
+ if (iface == NULL || rta[IFA_LOCAL] == NULL)
+ return;
+
+ /* Shortcut destination stuff is extracted from routes;
+ * not from local address information. */
+ if (iface->flags & NHRP_INTERFACE_FLAG_SHORTCUT_DEST)
+ return;
+ if (!(iface->flags & NHRP_INTERFACE_FLAG_CONFIGURED))
+ return;
+
+ nhrp_address_set(&iface->protocol_address, ifa->ifa_family,
+ RTA_PAYLOAD(rta[IFA_LOCAL]),
+ RTA_DATA(rta[IFA_LOCAL]));
+ iface->protocol_address_prefix = ifa->ifa_prefixlen;
+
+ peer = nhrp_peer_alloc(iface);
+ peer->type = NHRP_PEER_TYPE_LOCAL_ADDR;
+ peer->afnum = AFNUM_RESERVED;
+ nhrp_address_set(&peer->protocol_address, ifa->ifa_family,
+ RTA_PAYLOAD(rta[IFA_LOCAL]),
+ RTA_DATA(rta[IFA_LOCAL]));
+ switch (ifa->ifa_family) {
+ case PF_INET:
+ peer->protocol_type = ETHPROTO_IP;
+ peer->prefix_length = peer->protocol_address.addr_len * 8;
+ nhrp_peer_insert(peer);
+ break;
+ default:
+ nhrp_peer_put(peer);
+ return;
+ }
+
+ bcast = nhrp_peer_alloc(iface);
+ bcast->type = peer->type;
+ bcast->afnum = peer->afnum;
+ bcast->protocol_type = peer->protocol_type;
+ bcast->prefix_length = peer->prefix_length;
+ bcast->protocol_address = peer->protocol_address;
+ nhrp_address_set_broadcast(&bcast->protocol_address,
+ ifa->ifa_prefixlen);
+ bcast->next_hop_address = peer->protocol_address;
+ nhrp_peer_insert(bcast);
+ nhrp_peer_put(bcast);
+
+ nhrp_peer_put(peer);
+}
+
+struct netlink_del_addr_msg {
+ int interface_index;
+ struct nhrp_address address;
+};
+
+static int netlink_addr_del_nbma(void *ctx, struct nhrp_interface *iface)
+{
+ struct netlink_del_addr_msg *msg = (struct netlink_del_addr_msg *) ctx;
+
+ if (iface->link_index == msg->interface_index &&
+ nhrp_address_cmp(&msg->address, &iface->nbma_address) == 0)
+ nhrp_address_set_type(&iface->nbma_address, PF_UNSPEC);
+
+ return 0;
+}
+
+static int netlink_addr_purge_nbma(void *ctx, struct nhrp_peer *peer)
+{
+ struct netlink_del_addr_msg *msg = (struct netlink_del_addr_msg *) ctx;
+
+ if (nhrp_address_cmp(&peer->my_nbma_address, &msg->address) == 0)
+ nhrp_peer_purge(peer, "address-removed");
+
+ return 0;
+}
+
+static void netlink_addr_del(struct nlmsghdr *nlmsg)
+{
+ struct netlink_del_addr_msg msg;
+ struct nhrp_interface *iface;
+ struct ifaddrmsg *ifa = NLMSG_DATA(nlmsg);
+ struct rtattr *rta[IFA_MAX+1];
+ struct nhrp_peer_selector sel;
+
+ netlink_parse_rtattr(rta, IFA_MAX, IFA_RTA(ifa), IFA_PAYLOAD(nlmsg));
+ if (rta[IFA_LOCAL] == NULL)
+ return;
+
+ msg.interface_index = ifa->ifa_index;
+ nhrp_address_set(&msg.address, ifa->ifa_family,
+ RTA_PAYLOAD(rta[IFA_LOCAL]),
+ RTA_DATA(rta[IFA_LOCAL]));
+
+ if (!(ifa->ifa_flags & IFA_F_SECONDARY))
+ nhrp_interface_foreach(netlink_addr_del_nbma, &msg);
+ nhrp_peer_foreach(netlink_addr_purge_nbma, &msg, NULL);
+
+ iface = nhrp_interface_get_by_index(ifa->ifa_index, FALSE);
+ if (iface == NULL)
+ return;
+
+ memset(&sel, 0, sizeof(sel));
+ sel.flags = NHRP_PEER_FIND_EXACT;
+ sel.type_mask = BIT(NHRP_PEER_TYPE_LOCAL_ADDR);
+ sel.interface = iface;
+ sel.protocol_address = msg.address;
+ sel.prefix_length = sel.protocol_address.addr_len * 8;
+
+ if (nhrp_address_cmp(&sel.protocol_address, &iface->protocol_address) == 0)
+ nhrp_address_set_type(&iface->protocol_address, PF_UNSPEC);
+ nhrp_peer_foreach(nhrp_peer_remove_matching, NULL, &sel);
+
+ nhrp_address_set_broadcast(&sel.protocol_address, ifa->ifa_prefixlen);
+ sel.next_hop_address = msg.address;
+ nhrp_peer_foreach(nhrp_peer_remove_matching, NULL, &sel);
+}
+
+static void netlink_route_new(struct nlmsghdr *msg)
+{
+ struct nhrp_interface *iface;
+ struct nhrp_peer *peer;
+ struct rtmsg *rtm = NLMSG_DATA(msg);
+ struct rtattr *rta[RTA_MAX+1];
+ int type = 0;
+
+ netlink_parse_rtattr(rta, RTA_MAX, RTM_RTA(rtm), RTM_PAYLOAD(msg));
+ if (rta[RTA_OIF] == NULL || rta[RTA_DST] == NULL)
+ return;
+
+ if (rtm->rtm_family != PF_INET)
+ return;
+
+ iface = nhrp_interface_get_by_index(*(int*)RTA_DATA(rta[RTA_OIF]),
+ FALSE);
+ if (iface == NULL)
+ return;
+
+ if (iface->flags & NHRP_INTERFACE_FLAG_SHORTCUT_DEST) {
+ /* Local shortcut target routes */
+ if (rtm->rtm_table != RT_TABLE_MAIN)
+ return;
+ type = NHRP_PEER_TYPE_LOCAL_ADDR;
+ } else if (iface->flags & NHRP_INTERFACE_FLAG_CONFIGURED) {
+ /* Routes which might get additional outbound
+ * shortcuts */
+ if (rtm->rtm_table != iface->route_table ||
+ rtm->rtm_protocol == RTPROT_KERNEL)
+ return;
+ type = NHRP_PEER_TYPE_LOCAL_ROUTE;
+ }
+ if (type == 0)
+ return;
+
+ peer = nhrp_peer_alloc(iface);
+ peer->type = type;
+ peer->afnum = AFNUM_RESERVED;
+ nhrp_address_set(&peer->protocol_address, rtm->rtm_family,
+ RTA_PAYLOAD(rta[RTA_DST]),
+ RTA_DATA(rta[RTA_DST]));
+ if (rta[RTA_GATEWAY] != NULL) {
+ nhrp_address_set(&peer->next_hop_address,
+ rtm->rtm_family,
+ RTA_PAYLOAD(rta[RTA_GATEWAY]),
+ RTA_DATA(rta[RTA_GATEWAY]));
+ }
+ peer->protocol_type = nhrp_protocol_from_pf(rtm->rtm_family);
+ peer->prefix_length = rtm->rtm_dst_len;
+ nhrp_peer_insert(peer);
+ nhrp_peer_put(peer);
+}
+
+static void netlink_route_del(struct nlmsghdr *msg)
+{
+ struct nhrp_interface *iface;
+ struct rtmsg *rtm = NLMSG_DATA(msg);
+ struct rtattr *rta[RTA_MAX+1];
+ struct nhrp_peer_selector sel;
+ int type = 0;
+
+ netlink_parse_rtattr(rta, RTA_MAX, RTM_RTA(rtm), RTM_PAYLOAD(msg));
+ if (rta[RTA_OIF] == NULL || rta[RTA_DST] == NULL)
+ return;
+
+ if (rtm->rtm_family != PF_INET)
+ return;
+
+ iface = nhrp_interface_get_by_index(*(int*)RTA_DATA(rta[RTA_OIF]),
+ FALSE);
+ if (iface == NULL)
+ return;
+
+ if (iface->flags & NHRP_INTERFACE_FLAG_SHORTCUT_DEST) {
+ /* Local shortcut target routes */
+ if (rtm->rtm_table != RT_TABLE_MAIN)
+ return;
+ type = NHRP_PEER_TYPE_LOCAL_ADDR;
+ } else if (iface->flags & NHRP_INTERFACE_FLAG_CONFIGURED) {
+ /* Routes which might get additional outbound
+ * shortcuts */
+ if (rtm->rtm_table != iface->route_table ||
+ rtm->rtm_protocol == RTPROT_KERNEL)
+ return;
+ type = NHRP_PEER_TYPE_LOCAL_ROUTE;
+ }
+ if (type == 0)
+ return;
+
+ memset(&sel, 0, sizeof(sel));
+ sel.flags = NHRP_PEER_FIND_EXACT;
+ sel.type_mask = BIT(type);
+ sel.interface = iface;
+ nhrp_address_set(&sel.protocol_address, rtm->rtm_family,
+ RTA_PAYLOAD(rta[RTA_DST]),
+ RTA_DATA(rta[RTA_DST]));
+ if (rta[RTA_GATEWAY] != NULL) {
+ nhrp_address_set(&sel.next_hop_address,
+ rtm->rtm_family,
+ RTA_PAYLOAD(rta[RTA_GATEWAY]),
+ RTA_DATA(rta[RTA_GATEWAY]));
+ }
+ sel.prefix_length = rtm->rtm_dst_len;
+ nhrp_peer_foreach(nhrp_peer_remove_matching, NULL, &sel);
+}
+
+static const netlink_dispatch_f route_dispatch[RTM_MAX] = {
+ [RTM_GETNEIGH] = netlink_neigh_request,
+ [RTM_NEWNEIGH] = netlink_neigh_update,
+ [RTM_DELNEIGH] = netlink_neigh_update,
+ [RTM_NEWLINK] = netlink_link_new,
+ [RTM_DELLINK] = netlink_link_del,
+ [RTM_NEWADDR] = netlink_addr_new,
+ [RTM_DELADDR] = netlink_addr_del,
+ [RTM_NEWROUTE] = netlink_route_new,
+ [RTM_DELROUTE] = netlink_route_del,
+};
+
+static void netlink_stop_listening(struct netlink_fd *fd)
+{
+ ev_io_stop(&fd->io);
+}
+
+static void netlink_close(struct netlink_fd *fd)
+{
+ if (fd->fd >= 0) {
+ netlink_stop_listening(fd);
+ close(fd->fd);
+ fd->fd = 0;
+ }
+}
+
+static int netlink_open(struct netlink_fd *fd, int protocol, int groups)
+{
+ struct sockaddr_nl addr;
+ int buf = NETLINK_KERNEL_BUFFER;
+
+ fd->fd = socket(AF_NETLINK, SOCK_RAW, protocol);
+ fd->seq = time(NULL);
+ if (fd->fd < 0) {
+ nhrp_perror("Cannot open netlink socket");
+ return FALSE;
+ }
+
+ fcntl(fd->fd, F_SETFD, FD_CLOEXEC);
+ if (setsockopt(fd->fd, SOL_SOCKET, SO_SNDBUF, &buf, sizeof(buf)) < 0) {
+ nhrp_perror("SO_SNDBUF");
+ goto error;
+ }
+
+ if (setsockopt(fd->fd, SOL_SOCKET, SO_RCVBUF, &buf, sizeof(buf)) < 0) {
+ nhrp_perror("SO_RCVBUF");
+ goto error;
+ }
+
+ memset(&addr, 0, sizeof(addr));
+ addr.nl_family = AF_NETLINK;
+ addr.nl_groups = groups;
+ if (bind(fd->fd, (struct sockaddr *) &addr, sizeof(addr)) < 0) {
+ nhrp_perror("Cannot bind netlink socket");
+ goto error;
+ }
+
+ ev_io_init(&fd->io, netlink_read_cb, fd->fd, EV_READ);
+ ev_io_start(&fd->io);
+
+ return TRUE;
+
+error:
+ netlink_close(fd);
+ return FALSE;
+}
+
+static void pfpacket_read_cb(struct ev_io *w, int revents)
+{
+ struct sockaddr_ll lladdr;
+ struct nhrp_interface *iface;
+ struct iovec iov;
+ struct msghdr msg = {
+ .msg_name = &lladdr,
+ .msg_namelen = sizeof(lladdr),
+ .msg_iov = &iov,
+ .msg_iovlen = 1,
+ };
+ uint8_t buf[1500];
+ struct nhrp_address from;
+ int fd = w->fd;
+ int i;
+
+ iov.iov_base = buf;
+ for (i = 0; i < 2; i++) {
+ int status;
+
+ iov.iov_len = sizeof(buf);
+ status = recvmsg(fd, &msg, MSG_DONTWAIT);
+ if (status < 0) {
+ if (errno == EINTR)
+ continue;
+ if (errno == EAGAIN)
+ return;
+ nhrp_perror("PF_PACKET overrun");
+ continue;
+ }
+
+ if (status == 0) {
+ nhrp_error("PF_PACKET returned EOF");
+ return;
+ }
+
+ iface = nhrp_interface_get_by_index(lladdr.sll_ifindex, FALSE);
+ if (iface == NULL)
+ continue;
+
+ nhrp_address_set(&from, PF_INET, lladdr.sll_halen, lladdr.sll_addr);
+ if (memcmp(lladdr.sll_addr, "\x00\x00\x00\x00", 4) == 0)
+ nhrp_address_set_type(&from, PF_UNSPEC);
+ nhrp_packet_receive(buf, status, iface, &from);
+ }
+}
+
+int kernel_init(void)
+{
+ int fd, i;
+
+ proc_icmp_redirect_off("all");
+
+ fd = socket(PF_PACKET, SOCK_DGRAM, ETHPROTO_NHRP);
+ if (fd < 0) {
+ nhrp_error("Unable to create PF_PACKET socket");
+ return FALSE;
+ }
+
+ fcntl(fd, F_SETFD, FD_CLOEXEC);
+ ev_io_init(&packet_io, pfpacket_read_cb, fd, EV_READ);
+ ev_io_start(&packet_io);
+
+ for (i = 0; i < ARRAY_SIZE(netlink_groups); i++) {
+ netlink_fds[i].dispatch_size = sizeof(route_dispatch) / sizeof(route_dispatch[0]);
+ netlink_fds[i].dispatch = route_dispatch;
+ if (!netlink_open(&netlink_fds[i], NETLINK_ROUTE,
+ netlink_groups[i]))
+ goto err_close_all;
+ }
+
+ netlink_enumerate(&talk_fd, PF_UNSPEC, RTM_GETLINK);
+ netlink_read_cb(&talk_fd.io, EV_READ);
+
+ netlink_enumerate(&talk_fd, PF_UNSPEC, RTM_GETADDR);
+ netlink_read_cb(&talk_fd.io, EV_READ);
+
+ netlink_enumerate(&talk_fd, PF_UNSPEC, RTM_GETROUTE);
+ netlink_read_cb(&talk_fd.io, EV_READ);
+
+ return TRUE;
+
+err_close_all:
+ kernel_cleanup();
+ return FALSE;
+}
+
+void kernel_stop_listening(void)
+{
+ int i;
+
+ for (i = 0; i < ARRAY_SIZE(netlink_groups); i++)
+ netlink_stop_listening(&netlink_fds[i]);
+ ev_io_stop(&packet_io);
+}
+
+void kernel_cleanup(void)
+{
+ int i;
+
+ for (i = 0; i < ARRAY_SIZE(netlink_groups); i++)
+ netlink_close(&netlink_fds[i]);
+ ev_io_stop(&packet_io);
+ close(packet_io.fd);
+}
+
+int kernel_route(struct nhrp_interface *out_iface,
+ struct nhrp_address *dest,
+ struct nhrp_address *default_source,
+ struct nhrp_address *next_hop,
+ u_int16_t *mtu)
+{
+ struct {
+ struct nlmsghdr n;
+ struct rtmsg r;
+ char buf[1024];
+ } req;
+ struct rtmsg *r = NLMSG_DATA(&req.n);
+ struct rtattr *rta[RTA_MAX+1];
+
+ memset(&req, 0, sizeof(req));
+ req.n.nlmsg_len = NLMSG_LENGTH(sizeof(struct rtmsg));
+ req.n.nlmsg_flags = NLM_F_REQUEST;
+ req.n.nlmsg_type = RTM_GETROUTE;
+ req.r.rtm_family = dest->type;
+
+ netlink_add_rtattr_l(&req.n, sizeof(req), RTA_DST,
+ dest->addr, dest->addr_len);
+ req.r.rtm_dst_len = dest->addr_len * 8;
+
+ if (default_source != NULL && default_source->type != PF_UNSPEC)
+ netlink_add_rtattr_l(&req.n, sizeof(req), RTA_SRC,
+ default_source->addr,
+ default_source->addr_len);
+ if (out_iface != NULL)
+ netlink_add_rtattr_l(&req.n, sizeof(req), RTA_OIF,
+ &out_iface->index, sizeof(int));
+
+ if (!netlink_talk(&talk_fd, &req.n, sizeof(req), &req.n))
+ return FALSE;
+
+ netlink_parse_rtattr(rta, RTA_MAX, RTM_RTA(r), RTM_PAYLOAD(&req.n));
+
+ if (default_source != NULL && default_source->type == PF_UNSPEC &&
+ rta[RTA_PREFSRC] != NULL) {
+ nhrp_address_set(default_source, dest->type,
+ RTA_PAYLOAD(rta[RTA_PREFSRC]),
+ RTA_DATA(rta[RTA_PREFSRC]));
+ }
+
+ if (next_hop != NULL) {
+ if (rta[RTA_GATEWAY] != NULL) {
+ nhrp_address_set(next_hop, dest->type,
+ RTA_PAYLOAD(rta[RTA_GATEWAY]),
+ RTA_DATA(rta[RTA_GATEWAY]));
+ } else {
+ *next_hop = *dest;
+ }
+ }
+
+ if (mtu != NULL) {
+ *mtu = 0;
+
+ if (rta[RTA_OIF] != NULL) {
+ struct nhrp_interface *nbma_iface;
+
+ /* We use interface MTU here instead of the route
+ * cache MTU from RTA_METRICS/RTAX_MTU since we
+ * don't want to announce mtu if PMTU works */
+ nbma_iface = nhrp_interface_get_by_index(
+ *(int*)RTA_DATA(rta[RTA_OIF]),
+ FALSE);
+ if (nbma_iface != NULL)
+ *mtu = translate_mtu(nbma_iface->mtu);
+ }
+ }
+
+ return TRUE;
+}
+
+int kernel_send(uint8_t *packet, size_t bytes, struct nhrp_interface *out,
+ struct nhrp_address *to)
+{
+ struct sockaddr_ll lladdr;
+ struct iovec iov = {
+ .iov_base = (void*) packet,
+ .iov_len = bytes
+ };
+ struct msghdr msg = {
+ .msg_name = &lladdr,
+ .msg_namelen = sizeof(lladdr),
+ .msg_iov = &iov,
+ .msg_iovlen = 1,
+ };
+ int status;
+
+ if (to->addr_len > sizeof(lladdr.sll_addr)) {
+ nhrp_error("Destination NBMA address too long");
+ return FALSE;
+ }
+
+ memset(&lladdr, 0, sizeof(lladdr));
+ lladdr.sll_family = AF_PACKET;
+ lladdr.sll_protocol = ETHPROTO_NHRP;
+ lladdr.sll_ifindex = out->index;
+ lladdr.sll_halen = to->addr_len;
+ memcpy(lladdr.sll_addr, to->addr, to->addr_len);
+
+ status = sendmsg(packet_io.fd, &msg, 0);
+ if (status < 0) {
+ nhrp_error("Cannot send packet to %s(%d): %s",
+ out->name, out->index, strerror(errno));
+ return FALSE;
+ }
+
+ return TRUE;
+}
+
+int kernel_inject_neighbor(struct nhrp_address *neighbor,
+ struct nhrp_address *hwaddr,
+ struct nhrp_interface *dev)
+{
+ struct {
+ struct nlmsghdr n;
+ struct ndmsg ndm;
+ char buf[256];
+ } req;
+ char neigh[64], nbma[64];
+
+ memset(&req.n, 0, sizeof(req.n));
+ memset(&req.ndm, 0, sizeof(req.ndm));
+
+ req.n.nlmsg_len = NLMSG_LENGTH(sizeof(struct ndmsg));
+ req.n.nlmsg_flags = NLM_F_REQUEST | NLM_F_REPLACE | NLM_F_CREATE;
+ req.n.nlmsg_type = RTM_NEWNEIGH;
+ req.ndm.ndm_family = neighbor->type;
+ req.ndm.ndm_ifindex = dev->index;
+ req.ndm.ndm_type = RTN_UNICAST;
+
+ netlink_add_rtattr_l(&req.n, sizeof(req), NDA_DST,
+ neighbor->addr, neighbor->addr_len);
+
+ if (hwaddr != NULL && hwaddr->type != PF_UNSPEC) {
+ req.ndm.ndm_state = NUD_REACHABLE;
+
+ netlink_add_rtattr_l(&req.n, sizeof(req), NDA_LLADDR,
+ hwaddr->addr, hwaddr->addr_len);
+
+ nhrp_debug("NL-ARP(%s) %s is-at %s",
+ dev->name,
+ nhrp_address_format(neighbor, sizeof(neigh), neigh),
+ nhrp_address_format(hwaddr, sizeof(nbma), nbma));
+ } else {
+ req.ndm.ndm_state = NUD_FAILED;
+
+ nhrp_debug("NL-ARP(%s) %s not-reachable",
+ dev->name,
+ nhrp_address_format(neighbor, sizeof(neigh), neigh));
+ }
+
+ return netlink_send(&talk_fd, &req.n);
+}
+