From e2bbf175a2184bd76f6c54ccf8456babeb1a46fc Mon Sep 17 00:00:00 2001 From: Daniel Baumann Date: Tue, 9 Apr 2024 15:16:35 +0200 Subject: Adding upstream version 9.1. Signed-off-by: Daniel Baumann --- nhrpd/.gitignore | 1 + nhrpd/Makefile | 10 + nhrpd/README.kernel | 146 ++++++ nhrpd/README.nhrpd | 138 ++++++ nhrpd/debug.h | 17 + nhrpd/linux.c | 148 ++++++ nhrpd/netlink.h | 18 + nhrpd/netlink_arp.c | 198 ++++++++ nhrpd/nhrp_cache.c | 580 ++++++++++++++++++++++ nhrpd/nhrp_errors.c | 30 ++ nhrpd/nhrp_errors.h | 19 + nhrpd/nhrp_event.c | 286 +++++++++++ nhrpd/nhrp_interface.c | 552 +++++++++++++++++++++ nhrpd/nhrp_main.c | 168 +++++++ nhrpd/nhrp_multicast.c | 293 +++++++++++ nhrpd/nhrp_nhs.c | 464 ++++++++++++++++++ nhrpd/nhrp_packet.c | 341 +++++++++++++ nhrpd/nhrp_peer.c | 1244 +++++++++++++++++++++++++++++++++++++++++++++++ nhrpd/nhrp_protocol.h | 127 +++++ nhrpd/nhrp_route.c | 530 ++++++++++++++++++++ nhrpd/nhrp_shortcut.c | 539 ++++++++++++++++++++ nhrpd/nhrp_vc.c | 217 +++++++++ nhrpd/nhrp_vty.c | 1271 ++++++++++++++++++++++++++++++++++++++++++++++++ nhrpd/nhrpd.h | 532 ++++++++++++++++++++ nhrpd/os.h | 7 + nhrpd/reqid.c | 52 ++ nhrpd/subdir.am | 44 ++ nhrpd/vici.c | 623 ++++++++++++++++++++++++ nhrpd/vici.h | 24 + nhrpd/zbuf.c | 237 +++++++++ nhrpd/zbuf.h | 199 ++++++++ nhrpd/znl.c | 166 +++++++ nhrpd/znl.h | 25 + 33 files changed, 9246 insertions(+) create mode 100644 nhrpd/.gitignore create mode 100644 nhrpd/Makefile create mode 100644 nhrpd/README.kernel create mode 100644 nhrpd/README.nhrpd create mode 100644 nhrpd/debug.h create mode 100644 nhrpd/linux.c create mode 100644 nhrpd/netlink.h create mode 100644 nhrpd/netlink_arp.c create mode 100644 nhrpd/nhrp_cache.c create mode 100644 nhrpd/nhrp_errors.c create mode 100644 nhrpd/nhrp_errors.h create mode 100644 nhrpd/nhrp_event.c create mode 100644 nhrpd/nhrp_interface.c create mode 100644 nhrpd/nhrp_main.c create mode 100644 nhrpd/nhrp_multicast.c create mode 100644 nhrpd/nhrp_nhs.c create mode 100644 nhrpd/nhrp_packet.c create mode 100644 nhrpd/nhrp_peer.c create mode 100644 nhrpd/nhrp_protocol.h create mode 100644 nhrpd/nhrp_route.c create mode 100644 nhrpd/nhrp_shortcut.c create mode 100644 nhrpd/nhrp_vc.c create mode 100644 nhrpd/nhrp_vty.c create mode 100644 nhrpd/nhrpd.h create mode 100644 nhrpd/os.h create mode 100644 nhrpd/reqid.c create mode 100644 nhrpd/subdir.am create mode 100644 nhrpd/vici.c create mode 100644 nhrpd/vici.h create mode 100644 nhrpd/zbuf.c create mode 100644 nhrpd/zbuf.h create mode 100644 nhrpd/znl.c create mode 100644 nhrpd/znl.h (limited to 'nhrpd') diff --git a/nhrpd/.gitignore b/nhrpd/.gitignore new file mode 100644 index 0000000..3d4d56d --- /dev/null +++ b/nhrpd/.gitignore @@ -0,0 +1 @@ +nhrpd diff --git a/nhrpd/Makefile b/nhrpd/Makefile new file mode 100644 index 0000000..62c9546 --- /dev/null +++ b/nhrpd/Makefile @@ -0,0 +1,10 @@ +all: ALWAYS + @$(MAKE) -s -C .. nhrpd/nhrpd +%: ALWAYS + @$(MAKE) -s -C .. nhrpd/$@ + +Makefile: + #nothing +ALWAYS: +.PHONY: ALWAYS makefiles +.SUFFIXES: diff --git a/nhrpd/README.kernel b/nhrpd/README.kernel new file mode 100644 index 0000000..067ff98 --- /dev/null +++ b/nhrpd/README.kernel @@ -0,0 +1,146 @@ +KERNEL REQUIREMENTS +=================== + +The linux kernel has had various major regressions, performance +issues and subtle bugs (especially in pmtu). Here is a short list +of some -stable kernels and the first point release that is supposedly +working well with opennhrp/dmvpn: + 3.12.8 or later + 3.14.54 or later + 3.18.22 or later[1] + +[1] But you need to apply the following two backported commits: + 3cdaa5be9e ipv4: Don't increase PMTU with Datagram Too Big message + cb6ccf09d6 route: Use ipv4_mtu instead of raw rt_pmtu + +See below for list of known issues in various kernel versions. + +Kernels earlier than 3.12 need CONFIG_ARPD enabled in the configuration. +Many distributions do not enable it by default, and you may need to +compile your own kernel. + +KERNEL BUGS +=========== + +DMVPN and mGRE support in the kernel has been brittle. There are various +regressions in multiple kernel versions. + +This list tries to collect them to one source of information: + +- forward pmtu is disabled intentionally (but tunnel devices rely on it) + Broken since 3.14-rc1: + commit "ipv4: introduce ip_dst_mtu_maybe_forward and protect forwarding path against pmtu spoofing" + Workaround: + Set sysctl net.ipv4.ip_forward_use_pmtu=1 + See: https://marc.info/?t=143636239500003&r=1&w=2 for details + (Should fix kernel to have this by default on for tunnel devices) + +- subtle path mtu mishandling issues + Broken since (uncertain) + Fixed in 4.1-rc2: + commit "ipv4: Don't increase PMTU with Datagram Too Big message." + commit "route: Use ipv4_mtu instead of raw rt_pmtu" + +- fragmentation of large packets inside tunnel not working + Broken since 3.11-rc1 + commit "ip_tunnels: Use skb-len to PMTU check." + Fixed in 3.14.54, 3.18.22, 4.1.9, 4.2-rc3 + commit "ip_tunnel: fix ipv4 pmtu check to honor inner ip header df" + +- ipsec will crash during xfrm gc + Broke since 3.15-rc1 + commit "flowcache: Make flow cache name space aware" + Fixed in 3.18.10, 4.0 + commit "flowcache: Fix kernel panic in flow_cache_flush_task" + +- TSO on GRE tunnels failed, and resulted in very slow performance + Broke since 3.14.24, 3.18-rc3 + commit "gre: Use inner mac length when computing tunnel length" + Fixed in 3.14.30, 3.18.4 + commit "gre: fix the inner mac header in nbma tunnel xmit path" + commit "gre: Set inner mac header in gro complete" + +- NAPI GRO handling was broken; causing immediate crash (32-bit only?) + Broken since 3.13-rc1 + commit "net: gro: allow to build full sized skb" + Fixed 3.14.5, 3.15-rc7 + commit "net: gro: make sure skb->cb[] initial content has not to be zero" + +- ip_gre dst caching broke NBMA GRE tunnels + Broken since 3.14-rc1 + Fixed in 3.14.5, 3.15-rc6 + commit "ipv4: ip_tunnels: disable cache for nbma gre tunnels" + +- Few packets can be lost when neighbor entry is in NUD_PROBE state, + and there is continuous traffic to it. + Broken since dawn of time + Fixed in 3.15-rc1 + commit "neigh: probe application via netlink in NUD_PROBE" + +- GRO was implemented for GRE, but the hw capabilities were not updated + correctly. In practice forwarding from non-GRE (physical) interface + to GRE interface with gro/gso/tx offloads enabled (also on the target + interface) does not work properly. + Broken around 3.9 to 3.11, need to check details. + +- recvfrom() returned incorrect NBMA address, breaking NAT detection + Broken since 3.10-rc1 + commit "GRE: Refactor GRE tunneling code." + Fixed in 3.10.27, 3.12.8, 3.13-rc7 + commit "ip_gre: fix msg_name parsing for recvfrom/recvmsg" + +- sendto() was broken causing opennhrp not work at all + Broken since 3.10-rc1 + commit "GRE: Refactor GRE tunneling code." + Fixed in 3.10.12, 3.11-rc6 + commit "ip_gre: fix ipgre_header to return correct offset" + +- PMTU was broken due to GRE driver rewrite + Broken since 3.10-rc1 + commit "GRE: Refactor GRE tunneling code." + Fixed in 3.11-rc1 + commit "ip_tunnels: Use skb-len to PMTU check." + +- PMTU was broken due to routing cache removal + Broken since 3.6-rc1 + commit "ipv4: Cache input routes in fib_info nexthops" + Fixed in 3.11-rc1 + commit "ipv4: use next hop exceptions also for input routes" + + 3 other commits + Patches exist for 3.10, but they were not approved to 3.10-stable. + +- Race condition during bootup: changing ARP flag did not flush + existing neighbor entries, causing problems if traffic was routed + to gre interface before opennhrp was running. + Broken since dawn of time + Fixed in 3.11-rc1 + commit "arp: flush arp cache on IFF_NOARP change" + +- Crash in IPsec + Broken since 3.9-rc1 + commit "xfrm: removes a superfluous check and add a statistic" + Fixed in 3.10-rc3 + commit "xfrm: properly handle invalid states as an error" + +- An incorrect ip_gre change broke NHRP traffic over GRE + Broken since 3.8-rc2 + commit "ip_gre: make ipgre_tunnel_xmit() not parse network header as IP unconditionally" + Fixed in 3.8.5, 3.9-rc4 + commit "Revert "ip_gre: make ipgre_tunnel_xmit() not parse network header as IP unconditionally"" + +- Multicast traffic over mGRE was broken. + Broken since 2.6.34-rc2 + commit "gre: fix hard header destination address checking" + Fixed in 2.6.39-rc2 + commit "net: gre: provide multicast mappings for ipv4 and ipv6" + +- Serious performance issues causing small throughput on medium to large DMVPN networks + Broken since dawn of time + Fixed in 2.6.35 + multiple commits rewriting ipsec caching + +- Even though around 2.6.24 is the first version where opennhrp started + to work, there has been various PMTU, performance, and functionality + bugs before 2.6.34. That's one of the first version I consider stable + wrt. to opennhrp functionality. + diff --git a/nhrpd/README.nhrpd b/nhrpd/README.nhrpd new file mode 100644 index 0000000..8bb5f69 --- /dev/null +++ b/nhrpd/README.nhrpd @@ -0,0 +1,138 @@ +Quagga / NHRP Design and Configuration Notes +============================================ + +Quagga/NHRP is an NHRP (RFC2332) implementation for Linux. The primary +use case is to implement DMVPN. The aim is thus to be compatible with +Cisco DMVPN (and potentially with FlexVPN in the future). + + +Current Status +-------------- + +- IPsec integration with strongSwan (requires patched strongSwan) +- IPv4 over IPv4 NBMA GRE +- IPv6 over IPv4 NBMA GRE -- majority of code exist; but is not tested +- Spoke (NHC) functionality complete +- Hub (NHS) functionality complete +- Multicast support is not done yet + (so OSPF will not work, use BGP for now) + +The code is not (yet) compatible with Cisco FlexVPN style DMVPN. It +would require relaying IKEv2 routing messages from strongSwan to nhrpd +and parsing that. It is doable, but not implemented for the time being. + + +Routing Design +-------------- + +In contrast to opennhrp routing design, Quagga/NHRP routes each NHRP +domain address individually (similar to Cisco FlexVPN). + +To create NBMA GRE tunnel you might use following: + ip tunnel add gre1 mode gre key 42 ttl 64 dev eth0 + ip addr add 10.255.255.2/32 dev gre1 + ip link set gre1 up + +This has two important differences compared to opennhrp setup: + 1. The 'tunnel add' now specifies physical device binding. Quagga/NHRP + wants to know stable protocol address to NBMA address mapping. Thus, + add 'dev ' binding, or specify 'local '. If + neither of this is specified, NHRP will not be enabled on the interface. + Alternatively you can skip 'dev' binding on tunnel if you allow + nhrpd to manage it using 'tunnel source' command (see below). + + 2. The 'addr add' now has host prefix. In opennhrp you would have used + the GRE subnet prefix length here instead, e.g. /24. + +Quagga/NHRP will automatically create additional host routes pointing to +gre1 when a connection with these hosts is established. The gre1 subnet +should be announced by routing protocol. This allows routing protocol +to decide which is the closest hub and get the gre addresses' traffic. + +The second benefit is that hubs can then easily exchange host prefixes +of directly connected gre addresses. And thus routing of gre addresses +inside hubs is based on routing protocol's shortest path choice -- not +on random choice from next hop server list. + + +Configuring nhrpd +----------------- + +The configuration is done using vtysh, and most commands do what they +do in Cisco. As minimal configuration example one can do: + configure terminal + interface gre1 + tunnel protection vici profile dmvpn + tunnel source eth0 + ip nhrp network-id 1 + ip nhrp shortcut + ip nhrp registration no-unique + ip nhrp nhs dynamic nbma hubs.example.com + +There's important notes about the "ip nhrp nhs" command: + + 1. The 'dynamic' works only against Cisco (or nhrpd), but is not + compatible with opennhrp. To use dynamic detection of opennhrp hub's + protocol address use the GRE broadcast address there. For the above + example of 10.255.255.0/24 the configuration should read instead: + ip nhrp nhs 10.255.255.255 nbma hubs.example.com + + 2. nbma works like opennhrp dynamic-map. That is, all of the + A-records are configured as NBMA addresses of different hubs, and + each hub protocol address will be dynamically detected. + + +Hub functionality +----------------- + +Sending Traffic Indication (redirect) notifications is now accomplished +using NFLOG. + +Use: +iptables -A FORWARD -i gre1 -o gre1 \ + -m hashlimit --hashlimit-upto 4/minute --hashlimit-burst 1 \ + --hashlimit-mode srcip,dstip --hashlimit-srcmask 16 --hashlimit-dstmask 16 \ + --hashlimit-name loglimit-0 -j NFLOG --nflog-group 1 --nflog-range 128 + +or similar to get rate-limited samples of the packets that match traffic +flow needing redirection. This kernel NFLOG target's nflog-group is configured +in global nhrp config with: + nhrp nflog-group 1 + +To start sending these traffic notices out from hubs, use the nhrp per-interface +directive: + ip nhrp redirect + +opennhrp used PF_PACKET and tried to create packet filter to get only +the packets of interest. Though, this was bad if shortcut fails to +establish (remote policy, or both are behind NAT or restrictive +firewalls), all of the relayaed traffic would match always. + + +Getting information via vtysh +----------------------------- + +Some commands of interest: + - show dmvpn + - show ip nhrp cache + - show ip nhrp shortcut + - show ip route nhrp + - clear ip nhrp cache + - clear ip nhrp shortcut + + +Integration with strongSwan +--------------------------- + +Contrary to opennhrp, Quagga/NHRP has tight integration with IKE daemon. +Currently strongSwan is supported using the VICI protocol. strongSwan +is connected using UNIX socket (default /var/run/charon.vici use configure +argument --with-vici-socket= to change). +Thus nhrpd needs to be run as user that can open that file. + +Currently, you will need patched strongSwan. The working tree is at: + http://git.alpinelinux.org/cgit/user/tteras/strongswan/log/?h=tteras + +And the branch with patches against latest release are: + http://git.alpinelinux.org/cgit/user/tteras/strongswan/log/?h=tteras-release + diff --git a/nhrpd/debug.h b/nhrpd/debug.h new file mode 100644 index 0000000..f2c7022 --- /dev/null +++ b/nhrpd/debug.h @@ -0,0 +1,17 @@ +#include "log.h" + +#define NHRP_DEBUG_COMMON (1 << 0) +#define NHRP_DEBUG_KERNEL (1 << 1) +#define NHRP_DEBUG_IF (1 << 2) +#define NHRP_DEBUG_ROUTE (1 << 3) +#define NHRP_DEBUG_VICI (1 << 4) +#define NHRP_DEBUG_EVENT (1 << 5) +#define NHRP_DEBUG_ALL (0xFFFF) + +extern unsigned int debug_flags; + +#define debugf(level, ...) \ + do { \ + if (unlikely(debug_flags & level)) \ + zlog_debug(__VA_ARGS__); \ + } while (0) diff --git a/nhrpd/linux.c b/nhrpd/linux.c new file mode 100644 index 0000000..eb98166 --- /dev/null +++ b/nhrpd/linux.c @@ -0,0 +1,148 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* NHRP daemon Linux specific glue + * Copyright (c) 2014-2015 Timo Teräs + */ + +#include "zebra.h" + +#include +#include + +#include "nhrp_protocol.h" +#include "os.h" + +#ifndef HAVE_STRLCPY +size_t strlcpy(char *__restrict dest, + const char *__restrict src, size_t destsize); +#endif + +static int nhrp_socket_fd = -1; + +int os_socket(void) +{ + if (nhrp_socket_fd < 0) + nhrp_socket_fd = + socket(PF_PACKET, SOCK_DGRAM, htons(ETH_P_NHRP)); + return nhrp_socket_fd; +} + +int os_sendmsg(const uint8_t *buf, size_t len, int ifindex, const uint8_t *addr, + size_t addrlen, uint16_t protocol) +{ + struct sockaddr_ll lladdr; + struct iovec iov = { + .iov_base = (void *)buf, .iov_len = len, + }; + struct msghdr msg = { + .msg_name = &lladdr, + .msg_namelen = sizeof(lladdr), + .msg_iov = &iov, + .msg_iovlen = 1, + }; + int status, fd; + + if (addrlen > sizeof(lladdr.sll_addr)) + return -1; + + memset(&lladdr, 0, sizeof(lladdr)); + lladdr.sll_family = AF_PACKET; + lladdr.sll_protocol = htons(protocol); + lladdr.sll_ifindex = ifindex; + lladdr.sll_halen = addrlen; + memcpy(lladdr.sll_addr, addr, addrlen); + + fd = os_socket(); + if (fd < 0) + return -1; + + status = sendmsg(fd, &msg, 0); + if (status < 0) + return -errno; + + return status; +} + +int os_recvmsg(uint8_t *buf, size_t *len, int *ifindex, uint8_t *addr, + size_t *addrlen) +{ + struct sockaddr_ll lladdr; + struct iovec iov = { + .iov_base = buf, .iov_len = *len, + }; + struct msghdr msg = { + .msg_name = &lladdr, + .msg_namelen = sizeof(lladdr), + .msg_iov = &iov, + .msg_iovlen = 1, + }; + int r; + + r = recvmsg(nhrp_socket_fd, &msg, MSG_DONTWAIT); + if (r < 0) + return r; + + *len = r; + *ifindex = lladdr.sll_ifindex; + + if (*addrlen <= (size_t)lladdr.sll_addr) { + if (memcmp(lladdr.sll_addr, "\x00\x00\x00\x00", 4) != 0) { + memcpy(addr, lladdr.sll_addr, lladdr.sll_halen); + *addrlen = lladdr.sll_halen; + } else { + *addrlen = 0; + } + } + + return 0; +} + +static int linux_configure_arp(const char *iface, int on) +{ + struct ifreq ifr; + + strlcpy(ifr.ifr_name, iface, IFNAMSIZ); + if (ioctl(nhrp_socket_fd, SIOCGIFFLAGS, &ifr)) + return -1; + + if (on) + ifr.ifr_flags &= ~IFF_NOARP; + else + ifr.ifr_flags |= IFF_NOARP; + + if (ioctl(nhrp_socket_fd, SIOCSIFFLAGS, &ifr)) + return -1; + + return 0; +} + +static int linux_icmp_redirect_off(const char *iface) +{ + char fname[PATH_MAX]; + int fd, ret = -1; + + snprintf(fname, sizeof(fname), + "/proc/sys/net/ipv4/conf/%s/send_redirects", iface); + fd = open(fname, O_WRONLY); + if (fd < 0) + return -1; + if (write(fd, "0\n", 2) == 2) + ret = 0; + close(fd); + + return ret; +} + +int os_configure_dmvpn(unsigned int ifindex, const char *ifname, int af) +{ + int ret = 0; + + switch (af) { + case AF_INET: + ret |= linux_icmp_redirect_off("all"); + ret |= linux_icmp_redirect_off(ifname); + break; + } + ret |= linux_configure_arp(ifname, 1); + + return ret; +} diff --git a/nhrpd/netlink.h b/nhrpd/netlink.h new file mode 100644 index 0000000..7a3029b --- /dev/null +++ b/nhrpd/netlink.h @@ -0,0 +1,18 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* NHRP netlink/neighbor table API + * Copyright (c) 2014-2015 Timo Teräs + */ + +#include +#include +#include + + +extern int netlink_nflog_group; +extern int netlink_mcast_nflog_group; + +int netlink_configure_arp(unsigned int ifindex, int pf); +void netlink_update_binding(struct interface *ifp, union sockunion *proto, + union sockunion *nbma); +void netlink_set_nflog_group(int nlgroup); + diff --git a/nhrpd/netlink_arp.c b/nhrpd/netlink_arp.c new file mode 100644 index 0000000..2e22f8e --- /dev/null +++ b/nhrpd/netlink_arp.c @@ -0,0 +1,198 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* NHRP netlink/neighbor table arpd code + * Copyright (c) 2014-2016 Timo Teräs + */ + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include +#include +#include +#include +#include +#include + +#include "frrevent.h" +#include "stream.h" +#include "prefix.h" +#include "nhrpd.h" +#include "netlink.h" +#include "znl.h" + +int netlink_nflog_group; +static int netlink_log_fd = -1; +static struct event *netlink_log_thread; + +void netlink_update_binding(struct interface *ifp, union sockunion *proto, + union sockunion *nbma) +{ + nhrp_send_zebra_nbr(proto, nbma, ifp); +} + +static void netlink_log_register(int fd, int group) +{ + struct nlmsghdr *n; + struct nfgenmsg *nf; + struct nfulnl_msg_config_cmd cmd; + struct zbuf *zb = zbuf_alloc(512); + + n = znl_nlmsg_push(zb, (NFNL_SUBSYS_ULOG << 8) | NFULNL_MSG_CONFIG, + NLM_F_REQUEST | NLM_F_ACK); + nf = znl_push(zb, sizeof(*nf)); + *nf = (struct nfgenmsg){ + .nfgen_family = AF_UNSPEC, + .version = NFNETLINK_V0, + .res_id = htons(group), + }; + cmd.command = NFULNL_CFG_CMD_BIND; + znl_rta_push(zb, NFULA_CFG_CMD, &cmd, sizeof(cmd)); + znl_nlmsg_complete(zb, n); + + zbuf_send(zb, fd); + zbuf_free(zb); +} + +static void netlink_log_indication(struct nlmsghdr *msg, struct zbuf *zb) +{ + struct nfgenmsg *nf; + struct rtattr *rta; + struct zbuf rtapl, pktpl; + struct interface *ifp; + struct nfulnl_msg_packet_hdr *pkthdr = NULL; + uint32_t *in_ndx = NULL; + + nf = znl_pull(zb, sizeof(*nf)); + if (!nf) + return; + + memset(&pktpl, 0, sizeof(pktpl)); + while ((rta = znl_rta_pull(zb, &rtapl)) != NULL) { + switch (rta->rta_type) { + case NFULA_PACKET_HDR: + pkthdr = znl_pull(&rtapl, sizeof(*pkthdr)); + break; + case NFULA_IFINDEX_INDEV: + in_ndx = znl_pull(&rtapl, sizeof(*in_ndx)); + break; + case NFULA_PAYLOAD: + pktpl = rtapl; + break; + /* NFULA_HWHDR exists and is supposed to contain source + * hardware address. However, for ip_gre it seems to be + * the nexthop destination address if the packet matches + * route. */ + } + } + + if (!pkthdr || !in_ndx || !zbuf_used(&pktpl)) + return; + + ifp = if_lookup_by_index(htonl(*in_ndx), VRF_DEFAULT); + if (!ifp) + return; + + nhrp_peer_send_indication(ifp, htons(pkthdr->hw_protocol), &pktpl); +} + +static void netlink_log_recv(struct event *t) +{ + uint8_t buf[ZNL_BUFFER_SIZE]; + int fd = EVENT_FD(t); + struct zbuf payload, zb; + struct nlmsghdr *n; + + + zbuf_init(&zb, buf, sizeof(buf), 0); + while (zbuf_recv(&zb, fd) > 0) { + while ((n = znl_nlmsg_pull(&zb, &payload)) != NULL) { + debugf(NHRP_DEBUG_KERNEL, + "Netlink-log: Received msg_type %u, msg_flags %u", + n->nlmsg_type, n->nlmsg_flags); + switch (n->nlmsg_type) { + case (NFNL_SUBSYS_ULOG << 8) | NFULNL_MSG_PACKET: + netlink_log_indication(n, &payload); + break; + } + } + } + + event_add_read(master, netlink_log_recv, 0, netlink_log_fd, + &netlink_log_thread); +} + +void netlink_set_nflog_group(int nlgroup) +{ + if (netlink_log_fd >= 0) { + event_cancel(&netlink_log_thread); + close(netlink_log_fd); + netlink_log_fd = -1; + } + netlink_nflog_group = nlgroup; + if (nlgroup) { + netlink_log_fd = znl_open(NETLINK_NETFILTER, 0); + if (netlink_log_fd < 0) + return; + + netlink_log_register(netlink_log_fd, nlgroup); + event_add_read(master, netlink_log_recv, 0, netlink_log_fd, + &netlink_log_thread); + } +} + +int nhrp_neighbor_operation(ZAPI_CALLBACK_ARGS) +{ + union sockunion addr = {}, lladdr = {}; + struct interface *ifp; + int state, ndm_state; + struct nhrp_cache *c; + struct zapi_neigh_ip api = {}; + + zclient_neigh_ip_decode(zclient->ibuf, &api); + if (api.ip_in.ipa_type == AF_UNSPEC) + return 0; + sockunion_family(&addr) = api.ip_in.ipa_type; + memcpy((uint8_t *)sockunion_get_addr(&addr), &api.ip_in.ip.addr, + family2addrsize(api.ip_in.ipa_type)); + + sockunion_family(&lladdr) = api.ip_out.ipa_type; + if (api.ip_out.ipa_type != AF_UNSPEC) + memcpy((uint8_t *)sockunion_get_addr(&lladdr), + &api.ip_out.ip.addr, + family2addrsize(api.ip_out.ipa_type)); + + ifp = if_lookup_by_index(api.index, vrf_id); + ndm_state = api.ndm_state; + + if (!ifp) + return 0; + c = nhrp_cache_get(ifp, &addr, 0); + if (!c) + return 0; + debugf(NHRP_DEBUG_KERNEL, + "Netlink: %s %pSU dev %s lladdr %pSU nud 0x%x cache used %u type %u", + (cmd == ZEBRA_NHRP_NEIGH_GET) + ? "who-has" + : (cmd == ZEBRA_NHRP_NEIGH_ADDED) ? "new-neigh" + : "del-neigh", + &addr, ifp->name, &lladdr, ndm_state, c->used, c->cur.type); + if (cmd == ZEBRA_NHRP_NEIGH_GET) { + if (c->cur.type >= NHRP_CACHE_CACHED) { + nhrp_cache_set_used(c, 1); + debugf(NHRP_DEBUG_KERNEL, + "Netlink: update binding for %pSU dev %s from c %pSU peer.vc.nbma %pSU to lladdr %pSU", + &addr, ifp->name, &c->cur.remote_nbma_natoa, + &c->cur.peer->vc->remote.nbma, &lladdr); + /* In case of shortcuts, nbma is given by lladdr, not + * vc->remote.nbma. + */ + netlink_update_binding(ifp, &addr, &lladdr); + } + } else { + state = (cmd == ZEBRA_NHRP_NEIGH_ADDED) ? ndm_state + : ZEBRA_NEIGH_STATE_FAILED; + nhrp_cache_set_used(c, state == ZEBRA_NEIGH_STATE_REACHABLE); + } + return 0; +} diff --git a/nhrpd/nhrp_cache.c b/nhrpd/nhrp_cache.c new file mode 100644 index 0000000..1a11e0d --- /dev/null +++ b/nhrpd/nhrp_cache.c @@ -0,0 +1,580 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* NHRP cache + * Copyright (c) 2014-2015 Timo Teräs + */ + +#include "zebra.h" +#include "memory.h" +#include "frrevent.h" +#include "hash.h" +#include "nhrpd.h" + +#include "netlink.h" + +DEFINE_MTYPE_STATIC(NHRPD, NHRP_CACHE, "NHRP cache entry"); +DEFINE_MTYPE_STATIC(NHRPD, NHRP_CACHE_CONFIG, "NHRP cache config entry"); + +unsigned long nhrp_cache_counts[NHRP_CACHE_NUM_TYPES]; + +const char *const nhrp_cache_type_str[] = { + [NHRP_CACHE_INVALID] = "invalid", + [NHRP_CACHE_INCOMPLETE] = "incomplete", + [NHRP_CACHE_NEGATIVE] = "negative", + [NHRP_CACHE_CACHED] = "cached", + [NHRP_CACHE_DYNAMIC] = "dynamic", + [NHRP_CACHE_NHS] = "nhs", + [NHRP_CACHE_STATIC] = "static", + [NHRP_CACHE_LOCAL] = "local", +}; + +static unsigned int nhrp_cache_protocol_key(const void *peer_data) +{ + const struct nhrp_cache *p = peer_data; + return sockunion_hash(&p->remote_addr); +} + +static bool nhrp_cache_protocol_cmp(const void *cache_data, + const void *key_data) +{ + const struct nhrp_cache *a = cache_data; + const struct nhrp_cache *b = key_data; + + return sockunion_same(&a->remote_addr, &b->remote_addr); +} + +static void *nhrp_cache_alloc(void *data) +{ + struct nhrp_cache *p, *key = data; + + p = XMALLOC(MTYPE_NHRP_CACHE, sizeof(struct nhrp_cache)); + + *p = (struct nhrp_cache){ + .cur.type = NHRP_CACHE_INVALID, + .new.type = NHRP_CACHE_INVALID, + .remote_addr = key->remote_addr, + .ifp = key->ifp, + .notifier_list = + NOTIFIER_LIST_INITIALIZER(&p->notifier_list), + }; + nhrp_cache_counts[p->cur.type]++; + + return p; +} + +static void nhrp_cache_free(struct nhrp_cache *c) +{ + struct nhrp_interface *nifp = c->ifp->info; + + debugf(NHRP_DEBUG_COMMON, "Deleting cache entry"); + nhrp_cache_counts[c->cur.type]--; + notifier_call(&c->notifier_list, NOTIFY_CACHE_DELETE); + assert(!notifier_active(&c->notifier_list)); + hash_release(nifp->cache_hash, c); + nhrp_peer_unref(c->cur.peer); + nhrp_peer_unref(c->new.peer); + EVENT_OFF(c->t_timeout); + EVENT_OFF(c->t_auth); + XFREE(MTYPE_NHRP_CACHE, c); +} + +static unsigned int nhrp_cache_config_protocol_key(const void *peer_data) +{ + const struct nhrp_cache_config *p = peer_data; + return sockunion_hash(&p->remote_addr); +} + +static bool nhrp_cache_config_protocol_cmp(const void *cache_data, + const void *key_data) +{ + const struct nhrp_cache_config *a = cache_data; + const struct nhrp_cache_config *b = key_data; + + if (!sockunion_same(&a->remote_addr, &b->remote_addr)) + return false; + if (a->ifp != b->ifp) + return false; + return true; +} + +static void *nhrp_cache_config_alloc(void *data) +{ + struct nhrp_cache_config *p, *key = data; + + p = XCALLOC(MTYPE_NHRP_CACHE_CONFIG, sizeof(struct nhrp_cache_config)); + + *p = (struct nhrp_cache_config){ + .remote_addr = key->remote_addr, + .ifp = key->ifp, + }; + return p; +} + +void nhrp_cache_config_free(struct nhrp_cache_config *c) +{ + struct nhrp_interface *nifp = c->ifp->info; + + hash_release(nifp->cache_config_hash, c); + XFREE(MTYPE_NHRP_CACHE_CONFIG, c); +} + +struct nhrp_cache_config *nhrp_cache_config_get(struct interface *ifp, + union sockunion *remote_addr, + int create) +{ + struct nhrp_interface *nifp = ifp->info; + struct nhrp_cache_config key; + + if (!nifp->cache_config_hash) { + nifp->cache_config_hash = + hash_create(nhrp_cache_config_protocol_key, + nhrp_cache_config_protocol_cmp, + "NHRP Config Cache"); + if (!nifp->cache_config_hash) + return NULL; + } + key.remote_addr = *remote_addr; + key.ifp = ifp; + + return hash_get(nifp->cache_config_hash, &key, + create ? nhrp_cache_config_alloc : NULL); +} + +static void do_nhrp_cache_free(struct hash_bucket *hb, + void *arg __attribute__((__unused__))) +{ + struct nhrp_cache *c = hb->data; + + nhrp_cache_free(c); +} + +static void do_nhrp_cache_config_free(struct hash_bucket *hb, + void *arg __attribute__((__unused__))) +{ + struct nhrp_cache_config *cc = hb->data; + + nhrp_cache_config_free(cc); +} + +void nhrp_cache_interface_del(struct interface *ifp) +{ + struct nhrp_interface *nifp = ifp->info; + + debugf(NHRP_DEBUG_COMMON, "Cleaning up undeleted cache entries (%lu)", + nifp->cache_hash ? nifp->cache_hash->count : 0); + + if (nifp->cache_hash) { + hash_iterate(nifp->cache_hash, do_nhrp_cache_free, NULL); + hash_free(nifp->cache_hash); + } + + if (nifp->cache_config_hash) { + hash_iterate(nifp->cache_config_hash, do_nhrp_cache_config_free, + NULL); + hash_free(nifp->cache_config_hash); + } +} + +struct nhrp_cache *nhrp_cache_get(struct interface *ifp, + union sockunion *remote_addr, int create) +{ + struct nhrp_interface *nifp = ifp->info; + struct nhrp_cache key; + + if (!nifp->cache_hash) { + nifp->cache_hash = + hash_create(nhrp_cache_protocol_key, + nhrp_cache_protocol_cmp, "NHRP Cache"); + if (!nifp->cache_hash) + return NULL; + } + + key.remote_addr = *remote_addr; + key.ifp = ifp; + + return hash_get(nifp->cache_hash, &key, + create ? nhrp_cache_alloc : NULL); +} + +static void nhrp_cache_do_free(struct event *t) +{ + struct nhrp_cache *c = EVENT_ARG(t); + + c->t_timeout = NULL; + nhrp_cache_free(c); +} + +static void nhrp_cache_do_timeout(struct event *t) +{ + struct nhrp_cache *c = EVENT_ARG(t); + + c->t_timeout = NULL; + if (c->cur.type != NHRP_CACHE_INVALID) + nhrp_cache_update_binding(c, c->cur.type, -1, NULL, 0, NULL, + NULL); +} + +static void nhrp_cache_update_route(struct nhrp_cache *c) +{ + struct prefix pfx; + struct nhrp_peer *p = c->cur.peer; + struct nhrp_interface *nifp; + + if (!sockunion2hostprefix(&c->remote_addr, &pfx)) + return; + + if (p && nhrp_peer_check(p, 1)) { + if (sockunion_family(&c->cur.remote_nbma_natoa) != AF_UNSPEC) { + /* remote_nbma_natoa is already set. Therefore, binding + * should be updated to this value and not vc's remote + * nbma. + */ + debugf(NHRP_DEBUG_COMMON, + "cache (remote_nbma_natoa set): Update binding for %pSU dev %s from (deleted) peer.vc.nbma %pSU to %pSU", + &c->remote_addr, p->ifp->name, + &p->vc->remote.nbma, &c->cur.remote_nbma_natoa); + + netlink_update_binding(p->ifp, &c->remote_addr, + &c->cur.remote_nbma_natoa); + } else { + /* update binding to peer->vc->remote->nbma */ + debugf(NHRP_DEBUG_COMMON, + "cache (remote_nbma_natoa unspec): Update binding for %pSU dev %s from (deleted) to peer.vc.nbma %pSU", + &c->remote_addr, p->ifp->name, + &p->vc->remote.nbma); + + netlink_update_binding(p->ifp, &c->remote_addr, + &p->vc->remote.nbma); + } + + nhrp_route_announce(1, c->cur.type, &pfx, c->ifp, NULL, + c->cur.mtu); + if (c->cur.type >= NHRP_CACHE_DYNAMIC) { + nhrp_route_update_nhrp(&pfx, c->ifp); + c->nhrp_route_installed = 1; + } else if (c->nhrp_route_installed) { + nhrp_route_update_nhrp(&pfx, NULL); + c->nhrp_route_installed = 0; + } + if (!c->route_installed) { + notifier_call(&c->notifier_list, NOTIFY_CACHE_UP); + c->route_installed = 1; + } + } else { + /* debug the reason for peer check fail */ + if (p) { + nifp = p->ifp->info; + debugf(NHRP_DEBUG_COMMON, + "cache (peer check failed: online?%d requested?%d ipsec?%d)", + p->online, p->requested, + nifp->ipsec_profile ? 1 : 0); + } else + debugf(NHRP_DEBUG_COMMON, + "cache (peer check failed: no p)"); + + if (c->nhrp_route_installed) { + nhrp_route_update_nhrp(&pfx, NULL); + c->nhrp_route_installed = 0; + } + if (c->route_installed) { + assert(sockunion2hostprefix(&c->remote_addr, &pfx)); + notifier_call(&c->notifier_list, NOTIFY_CACHE_DOWN); + nhrp_route_announce(0, c->cur.type, &pfx, NULL, NULL, + 0); + c->route_installed = 0; + } + } +} + +static void nhrp_cache_peer_notifier(struct notifier_block *n, + unsigned long cmd) +{ + struct nhrp_cache *c = + container_of(n, struct nhrp_cache, peer_notifier); + + switch (cmd) { + case NOTIFY_PEER_UP: + nhrp_cache_update_route(c); + break; + case NOTIFY_PEER_DOWN: + case NOTIFY_PEER_IFCONFIG_CHANGED: + notifier_call(&c->notifier_list, NOTIFY_CACHE_DOWN); + nhrp_cache_update_binding(c, c->cur.type, -1, NULL, 0, NULL, + NULL); + break; + case NOTIFY_PEER_NBMA_CHANGING: + if (c->cur.type == NHRP_CACHE_DYNAMIC) + c->cur.peer->vc->abort_migration = 1; + break; + } +} + +static void nhrp_cache_reset_new(struct nhrp_cache *c) +{ + EVENT_OFF(c->t_auth); + if (notifier_list_anywhere(&c->newpeer_notifier)) + nhrp_peer_notify_del(c->new.peer, &c->newpeer_notifier); + nhrp_peer_unref(c->new.peer); + memset(&c->new, 0, sizeof(c->new)); + c->new.type = NHRP_CACHE_INVALID; +} + +static void nhrp_cache_update_timers(struct nhrp_cache *c) +{ + EVENT_OFF(c->t_timeout); + + switch (c->cur.type) { + case NHRP_CACHE_INVALID: + if (!c->t_auth) + event_add_timer_msec(master, nhrp_cache_do_free, c, 10, + &c->t_timeout); + break; + case NHRP_CACHE_INCOMPLETE: + case NHRP_CACHE_NEGATIVE: + case NHRP_CACHE_CACHED: + case NHRP_CACHE_DYNAMIC: + case NHRP_CACHE_NHS: + case NHRP_CACHE_STATIC: + case NHRP_CACHE_LOCAL: + case NHRP_CACHE_NUM_TYPES: + if (c->cur.expires) + event_add_timer(master, nhrp_cache_do_timeout, c, + c->cur.expires - monotime(NULL), + &c->t_timeout); + break; + } +} + +static void nhrp_cache_authorize_binding(struct nhrp_reqid *r, void *arg) +{ + struct nhrp_cache *c = container_of(r, struct nhrp_cache, eventid); + char buf[3][SU_ADDRSTRLEN]; + + debugf(NHRP_DEBUG_COMMON, "cache: %s %pSU: %s", c->ifp->name, + &c->remote_addr, (const char *)arg); + + nhrp_reqid_free(&nhrp_event_reqid, r); + + if (arg && strcmp(arg, "accept") == 0) { + if (c->cur.peer) { + netlink_update_binding(c->cur.peer->ifp, + &c->remote_addr, NULL); + nhrp_peer_notify_del(c->cur.peer, &c->peer_notifier); + nhrp_peer_unref(c->cur.peer); + } + nhrp_cache_counts[c->cur.type]--; + nhrp_cache_counts[c->new.type]++; + c->cur = c->new; + c->cur.peer = nhrp_peer_ref(c->cur.peer); + nhrp_cache_reset_new(c); + if (c->cur.peer) + nhrp_peer_notify_add(c->cur.peer, &c->peer_notifier, + nhrp_cache_peer_notifier); + + if (sockunion_family(&c->cur.remote_nbma_natoa) != AF_UNSPEC) { + debugf(NHRP_DEBUG_COMMON, + "cache: update binding for %pSU dev %s from (deleted) peer.vc.nbma %s to %pSU", + &c->remote_addr, c->ifp->name, + (c->cur.peer ? sockunion2str( + &c->cur.peer->vc->remote.nbma, buf[1], + sizeof(buf[1])) + : "(no peer)"), + &c->cur.remote_nbma_natoa); + + if (c->cur.peer) + netlink_update_binding( + c->cur.peer->ifp, &c->remote_addr, + &c->cur.remote_nbma_natoa); + } + + nhrp_cache_update_route(c); + notifier_call(&c->notifier_list, NOTIFY_CACHE_BINDING_CHANGE); + } else { + nhrp_cache_reset_new(c); + } + + nhrp_cache_update_timers(c); +} + +static void nhrp_cache_do_auth_timeout(struct event *t) +{ + struct nhrp_cache *c = EVENT_ARG(t); + c->t_auth = NULL; + nhrp_cache_authorize_binding(&c->eventid, (void *)"timeout"); +} + +static void nhrp_cache_newpeer_notifier(struct notifier_block *n, + unsigned long cmd) +{ + struct nhrp_cache *c = + container_of(n, struct nhrp_cache, newpeer_notifier); + + switch (cmd) { + case NOTIFY_PEER_UP: + if (nhrp_peer_check(c->new.peer, 1)) { + evmgr_notify("authorize-binding", c, + nhrp_cache_authorize_binding); + event_add_timer(master, nhrp_cache_do_auth_timeout, c, + 10, &c->t_auth); + } + break; + case NOTIFY_PEER_DOWN: + case NOTIFY_PEER_IFCONFIG_CHANGED: + nhrp_cache_reset_new(c); + break; + } +} + +int nhrp_cache_update_binding(struct nhrp_cache *c, enum nhrp_cache_type type, + int holding_time, struct nhrp_peer *p, + uint32_t mtu, union sockunion *nbma_oa, + union sockunion *nbma_claimed) +{ + char buf[2][SU_ADDRSTRLEN]; + + if (c->cur.type > type || c->new.type > type) { + nhrp_peer_unref(p); + return 0; + } + + /* Sanitize MTU */ + switch (sockunion_family(&c->remote_addr)) { + case AF_INET: + if (mtu < 576 || mtu >= 1500) + mtu = 0; + /* Opennhrp announces nbma mtu, but we use protocol mtu. + * This heuristic tries to fix up it. */ + if (mtu > 1420) + mtu = (mtu & -16) - 80; + break; + default: + mtu = 0; + break; + } + + sockunion2str(&c->cur.remote_nbma_natoa, buf[0], sizeof(buf[0])); + if (nbma_oa) + sockunion2str(nbma_oa, buf[1], sizeof(buf[1])); + + nhrp_cache_reset_new(c); + if (c->cur.type == type && c->cur.peer == p && c->cur.mtu == mtu) { + debugf(NHRP_DEBUG_COMMON, + "cache: same type %u, updating expiry and changing nbma addr from %s to %s", + type, buf[0], nbma_oa ? buf[1] : "(NULL)"); + if (holding_time > 0) + c->cur.expires = monotime(NULL) + holding_time; + + if (nbma_oa) + c->cur.remote_nbma_natoa = *nbma_oa; + else + memset(&c->cur.remote_nbma_natoa, 0, + sizeof(c->cur.remote_nbma_natoa)); + + if (nbma_claimed) + c->cur.remote_nbma_claimed = *nbma_claimed; + else + memset(&c->cur.remote_nbma_claimed, 0, + sizeof(c->cur.remote_nbma_claimed)); + + nhrp_peer_unref(p); + } else { + debugf(NHRP_DEBUG_COMMON, + "cache: new type %u/%u, or peer %s, or mtu %u/%u, nbma %s --> %s (map %d)", + c->cur.type, type, (c->cur.peer == p) ? "same" : "diff", + c->cur.mtu, mtu, buf[0], nbma_oa ? buf[1] : "(NULL)", + c->map); + c->new.type = type; + c->new.peer = p; + c->new.mtu = mtu; + c->new.holding_time = holding_time; + if (nbma_oa) + c->new.remote_nbma_natoa = *nbma_oa; + + if (nbma_claimed) + c->new.remote_nbma_claimed = *nbma_claimed; + + if (holding_time > 0) + c->new.expires = monotime(NULL) + holding_time; + else if (holding_time < 0) + nhrp_cache_reset_new(c); + + if (c->new.type == NHRP_CACHE_INVALID + || c->new.type >= NHRP_CACHE_STATIC || c->map) { + nhrp_cache_authorize_binding(&c->eventid, + (void *)"accept"); + } else { + nhrp_peer_notify_add(c->new.peer, &c->newpeer_notifier, + nhrp_cache_newpeer_notifier); + nhrp_cache_newpeer_notifier(&c->newpeer_notifier, + NOTIFY_PEER_UP); + event_add_timer(master, nhrp_cache_do_auth_timeout, c, + 60, &c->t_auth); + } + } + nhrp_cache_update_timers(c); + + return 1; +} + +void nhrp_cache_set_used(struct nhrp_cache *c, int used) +{ + c->used = used; + if (c->used) + notifier_call(&c->notifier_list, NOTIFY_CACHE_USED); +} + +struct nhrp_cache_iterator_ctx { + void (*cb)(struct nhrp_cache *, void *); + void *ctx; +}; + +struct nhrp_cache_config_iterator_ctx { + void (*cb)(struct nhrp_cache_config *, void *); + void *ctx; +}; + +static void nhrp_cache_iterator(struct hash_bucket *b, void *ctx) +{ + struct nhrp_cache_iterator_ctx *ic = ctx; + ic->cb(b->data, ic->ctx); +} + +static void nhrp_cache_config_iterator(struct hash_bucket *b, void *ctx) +{ + struct nhrp_cache_config_iterator_ctx *ic = ctx; + ic->cb(b->data, ic->ctx); +} + +void nhrp_cache_foreach(struct interface *ifp, + void (*cb)(struct nhrp_cache *, void *), void *ctx) +{ + struct nhrp_interface *nifp = ifp->info; + struct nhrp_cache_iterator_ctx ic = { + .cb = cb, .ctx = ctx, + }; + + if (nifp->cache_hash) + hash_iterate(nifp->cache_hash, nhrp_cache_iterator, &ic); +} + +void nhrp_cache_config_foreach(struct interface *ifp, + void (*cb)(struct nhrp_cache_config *, void *), void *ctx) +{ + struct nhrp_interface *nifp = ifp->info; + struct nhrp_cache_config_iterator_ctx ic = { + .cb = cb, .ctx = ctx, + }; + + if (nifp->cache_config_hash) + hash_iterate(nifp->cache_config_hash, nhrp_cache_config_iterator, &ic); +} + +void nhrp_cache_notify_add(struct nhrp_cache *c, struct notifier_block *n, + notifier_fn_t fn) +{ + notifier_add(n, &c->notifier_list, fn); +} + +void nhrp_cache_notify_del(struct nhrp_cache *c, struct notifier_block *n) +{ + notifier_del(n, &c->notifier_list); +} diff --git a/nhrpd/nhrp_errors.c b/nhrpd/nhrp_errors.c new file mode 100644 index 0000000..12ad7c6 --- /dev/null +++ b/nhrpd/nhrp_errors.c @@ -0,0 +1,30 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * NHRP-specific error messages. + * Copyright (C) 2018 Cumulus Networks, Inc. + * Donald Sharp + */ + +#include + +#include "lib/ferr.h" +#include "nhrp_errors.h" + +/* clang-format off */ +static struct log_ref ferr_nhrp_err[] = { + { + .code = EC_NHRP_SWAN, + .title = "NHRP Strong Swan Error", + .description = "NHRP has detected a error with the Strongswan code", + .suggestion = "Ensure that StrongSwan is configured correctly. Restart StrongSwan and FRR" + }, + { + .code = END_FERR, + } +}; +/* clang-format on */ + +void nhrp_error_init(void) +{ + log_ref_add(ferr_nhrp_err); +} diff --git a/nhrpd/nhrp_errors.h b/nhrpd/nhrp_errors.h new file mode 100644 index 0000000..d7867a4 --- /dev/null +++ b/nhrpd/nhrp_errors.h @@ -0,0 +1,19 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * NHRP-specific error messages. + * Copyright (C) 2018 Cumulus Networks, Inc. + * Donald Sharp + */ + +#ifndef __NHRP_ERRORS_H__ +#define __NHRP_ERRORS_H__ + +#include "lib/ferr.h" + +enum nhrp_log_refs { + EC_NHRP_SWAN = NHRP_FERR_START, +}; + +extern void nhrp_error_init(void); + +#endif diff --git a/nhrpd/nhrp_event.c b/nhrpd/nhrp_event.c new file mode 100644 index 0000000..ba31858 --- /dev/null +++ b/nhrpd/nhrp_event.c @@ -0,0 +1,286 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* NHRP event manager + * Copyright (c) 2014-2015 Timo Teräs + */ + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include +#include +#include + +#include "frrevent.h" +#include "zbuf.h" +#include "log.h" +#include "nhrpd.h" + +const char *nhrp_event_socket_path; +struct nhrp_reqid_pool nhrp_event_reqid; + +struct event_manager { + struct event *t_reconnect, *t_read, *t_write; + struct zbuf ibuf; + struct zbuf_queue obuf; + int fd; + uint8_t ibuf_data[4 * 1024]; +}; + +static void evmgr_reconnect(struct event *t); + +static void evmgr_connection_error(struct event_manager *evmgr) +{ + EVENT_OFF(evmgr->t_read); + EVENT_OFF(evmgr->t_write); + zbuf_reset(&evmgr->ibuf); + zbufq_reset(&evmgr->obuf); + + if (evmgr->fd >= 0) + close(evmgr->fd); + evmgr->fd = -1; + if (nhrp_event_socket_path) + event_add_timer_msec(master, evmgr_reconnect, evmgr, 10, + &evmgr->t_reconnect); +} + +static void evmgr_recv_message(struct event_manager *evmgr, struct zbuf *zb) +{ + struct zbuf zl; + uint32_t eventid = 0; + size_t len; + char buf[256], result[64] = ""; + + while (zbuf_may_pull_until(zb, "\n", &zl)) { + len = zbuf_used(&zl) - 1; + if (len >= sizeof(buf) - 1) + continue; + memcpy(buf, zbuf_pulln(&zl, len), len); + buf[len] = 0; + + debugf(NHRP_DEBUG_EVENT, "evmgr: msg: %s", buf); + if (sscanf(buf, "eventid=%" SCNu32, &eventid) == 1) + continue; + if (sscanf(buf, "result=%63s", result) == 1) + continue; + } + debugf(NHRP_DEBUG_EVENT, "evmgr: received: eventid=%d result=%s", + eventid, result); + if (eventid && result[0]) { + struct nhrp_reqid *r = + nhrp_reqid_lookup(&nhrp_event_reqid, eventid); + if (r) + r->cb(r, result); + } +} + +static void evmgr_read(struct event *t) +{ + struct event_manager *evmgr = EVENT_ARG(t); + struct zbuf *ibuf = &evmgr->ibuf; + struct zbuf msg; + + if (zbuf_read(ibuf, evmgr->fd, (size_t)-1) < 0) { + evmgr_connection_error(evmgr); + return; + } + + /* Process all messages in buffer */ + while (zbuf_may_pull_until(ibuf, "\n\n", &msg)) + evmgr_recv_message(evmgr, &msg); + + event_add_read(master, evmgr_read, evmgr, evmgr->fd, &evmgr->t_read); +} + +static void evmgr_write(struct event *t) +{ + struct event_manager *evmgr = EVENT_ARG(t); + int r; + + r = zbufq_write(&evmgr->obuf, evmgr->fd); + if (r > 0) { + event_add_write(master, evmgr_write, evmgr, evmgr->fd, + &evmgr->t_write); + } else if (r < 0) { + evmgr_connection_error(evmgr); + } +} + +static void evmgr_hexdump(struct zbuf *zb, const uint8_t *val, size_t vallen) +{ + static const char xd[] = "0123456789abcdef"; + size_t i; + char *ptr; + + ptr = zbuf_pushn(zb, 2 * vallen); + if (!ptr) + return; + + for (i = 0; i < vallen; i++) { + uint8_t b = val[i]; + *(ptr++) = xd[b >> 4]; + *(ptr++) = xd[b & 0xf]; + } +} + +static void evmgr_put(struct zbuf *zb, const char *fmt, ...) +{ + const char *pos, *nxt, *str; + const uint8_t *bin; + const union sockunion *su; + int len; + va_list va; + + va_start(va, fmt); + for (pos = fmt; (nxt = strchr(pos, '%')) != NULL; pos = nxt + 2) { + zbuf_put(zb, pos, nxt - pos); + switch (nxt[1]) { + case '%': + zbuf_put8(zb, '%'); + break; + case 'u': + zb->tail += + snprintf((char *)zb->tail, zbuf_tailroom(zb), + "%u", va_arg(va, uint32_t)); + break; + case 's': + str = va_arg(va, const char *); + zbuf_put(zb, str, strlen(str)); + break; + case 'U': + su = va_arg(va, const union sockunion *); + if (sockunion2str(su, (char *)zb->tail, + zbuf_tailroom(zb))) + zb->tail += strlen((char *)zb->tail); + else + zbuf_set_werror(zb); + break; + case 'H': + bin = va_arg(va, const uint8_t *); + len = va_arg(va, int); + evmgr_hexdump(zb, bin, len); + break; + } + } + va_end(va); + zbuf_put(zb, pos, strlen(pos)); +} + +static void evmgr_submit(struct event_manager *evmgr, struct zbuf *obuf) +{ + if (obuf->error) { + zbuf_free(obuf); + return; + } + zbuf_put(obuf, "\n", 1); + zbufq_queue(&evmgr->obuf, obuf); + if (evmgr->fd >= 0) + event_add_write(master, evmgr_write, evmgr, evmgr->fd, + &evmgr->t_write); +} + +static void evmgr_reconnect(struct event *t) +{ + struct event_manager *evmgr = EVENT_ARG(t); + int fd; + + if (evmgr->fd >= 0 || !nhrp_event_socket_path) + return; + + fd = sock_open_unix(nhrp_event_socket_path); + if (fd < 0) { + zlog_warn("%s: failure connecting nhrp-event socket: %s", + __func__, strerror(errno)); + zbufq_reset(&evmgr->obuf); + event_add_timer(master, evmgr_reconnect, evmgr, 10, + &evmgr->t_reconnect); + return; + } + + zlog_info("Connected to Event Manager"); + evmgr->fd = fd; + event_add_read(master, evmgr_read, evmgr, evmgr->fd, &evmgr->t_read); +} + +static struct event_manager evmgr_connection; + +void evmgr_init(void) +{ + struct event_manager *evmgr = &evmgr_connection; + + evmgr->fd = -1; + zbuf_init(&evmgr->ibuf, evmgr->ibuf_data, sizeof(evmgr->ibuf_data), 0); + zbufq_init(&evmgr->obuf); + event_add_timer_msec(master, evmgr_reconnect, evmgr, 10, + &evmgr->t_reconnect); +} + +void evmgr_set_socket(const char *socket) +{ + if (nhrp_event_socket_path) { + free((char *)nhrp_event_socket_path); + nhrp_event_socket_path = NULL; + } + if (socket) + nhrp_event_socket_path = strdup(socket); + evmgr_connection_error(&evmgr_connection); +} + +void evmgr_terminate(void) +{ +} + +void evmgr_notify(const char *name, struct nhrp_cache *c, + void (*cb)(struct nhrp_reqid *, void *)) +{ + struct event_manager *evmgr = &evmgr_connection; + struct nhrp_vc *vc; + struct nhrp_interface *nifp = c->ifp->info; + struct zbuf *zb; + afi_t afi = family2afi(sockunion_family(&c->remote_addr)); + + if (!nhrp_event_socket_path) { + cb(&c->eventid, (void *)"accept"); + return; + } + + debugf(NHRP_DEBUG_EVENT, "evmgr: sending event %s", name); + + vc = c->new.peer ? c->new.peer->vc : NULL; + zb = zbuf_alloc( + 1024 + (vc ? (vc->local.certlen + vc->remote.certlen) * 2 : 0)); + + if (cb) { + nhrp_reqid_free(&nhrp_event_reqid, &c->eventid); + evmgr_put(zb, "eventid=%u\n", + nhrp_reqid_alloc(&nhrp_event_reqid, &c->eventid, cb)); + } + + evmgr_put(zb, + "event=%s\n" + "type=%s\n" + "old_type=%s\n" + "num_nhs=%u\n" + "interface=%s\n" + "local_addr=%U\n", + name, nhrp_cache_type_str[c->new.type], + nhrp_cache_type_str[c->cur.type], + (unsigned int)nhrp_cache_counts[NHRP_CACHE_NHS], c->ifp->name, + &nifp->afi[afi].addr); + + if (vc) { + evmgr_put(zb, + "vc_initiated=%s\n" + "local_nbma=%U\n" + "local_cert=%H\n" + "remote_addr=%U\n" + "remote_nbma=%U\n" + "remote_cert=%H\n", + c->new.peer->requested ? "yes" : "no", + &vc->local.nbma, vc->local.cert, vc->local.certlen, + &c->remote_addr, &vc->remote.nbma, vc->remote.cert, + vc->remote.certlen); + } + + evmgr_submit(evmgr, zb); +} diff --git a/nhrpd/nhrp_interface.c b/nhrpd/nhrp_interface.c new file mode 100644 index 0000000..7c84fde --- /dev/null +++ b/nhrpd/nhrp_interface.c @@ -0,0 +1,552 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* NHRP interface + * Copyright (c) 2014-2015 Timo Teräs + */ + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include +#include "zebra.h" +#include "linklist.h" +#include "memory.h" +#include "frrevent.h" + +#include "nhrpd.h" +#include "os.h" +#include "hash.h" + +DEFINE_MTYPE_STATIC(NHRPD, NHRP_IF, "NHRP interface"); +DEFINE_MTYPE_STATIC(NHRPD, NHRP_IF_GRE, "NHRP GRE interface"); + +struct hash *nhrp_gre_list; + +static void nhrp_interface_update_cache_config(struct interface *ifp, + bool available, + uint8_t family); + +static unsigned int nhrp_gre_info_key(const void *data) +{ + const struct nhrp_gre_info *r = data; + + return r->ifindex; +} + +static bool nhrp_gre_info_cmp(const void *data, const void *key) +{ + const struct nhrp_gre_info *a = data, *b = key; + + if (a->ifindex == b->ifindex) + return true; + return false; +} + +static void *nhrp_interface_gre_alloc(void *data) +{ + struct nhrp_gre_info *a; + struct nhrp_gre_info *b = data; + + a = XMALLOC(MTYPE_NHRP_IF_GRE, sizeof(struct nhrp_gre_info)); + memcpy(a, b, sizeof(struct nhrp_gre_info)); + return a; +} + +struct nhrp_gre_info *nhrp_gre_info_alloc(struct nhrp_gre_info *p) +{ + struct nhrp_gre_info *a; + + a = (struct nhrp_gre_info *)hash_get(nhrp_gre_list, p, + nhrp_interface_gre_alloc); + return a; +} + +static int nhrp_if_new_hook(struct interface *ifp) +{ + struct nhrp_interface *nifp; + afi_t afi; + + nifp = XCALLOC(MTYPE_NHRP_IF, sizeof(struct nhrp_interface)); + + ifp->info = nifp; + nifp->ifp = ifp; + + notifier_init(&nifp->notifier_list); + for (afi = 0; afi < AFI_MAX; afi++) { + struct nhrp_afi_data *ad = &nifp->afi[afi]; + ad->holdtime = NHRPD_DEFAULT_HOLDTIME; + nhrp_nhslist_init(&ad->nhslist_head); + nhrp_mcastlist_init(&ad->mcastlist_head); + } + + return 0; +} + +static int nhrp_if_delete_hook(struct interface *ifp) +{ + struct nhrp_interface *nifp = ifp->info; + + debugf(NHRP_DEBUG_IF, "Deleted interface (%s)", ifp->name); + + nhrp_cache_interface_del(ifp); + nhrp_nhs_interface_del(ifp); + nhrp_multicast_interface_del(ifp); + nhrp_peer_interface_del(ifp); + + if (nifp->ipsec_profile) + free(nifp->ipsec_profile); + if (nifp->ipsec_fallback_profile) + free(nifp->ipsec_fallback_profile); + if (nifp->source) + free(nifp->source); + + XFREE(MTYPE_NHRP_IF, ifp->info); + return 0; +} + +void nhrp_interface_init(void) +{ + hook_register_prio(if_add, 0, nhrp_if_new_hook); + hook_register_prio(if_del, 0, nhrp_if_delete_hook); + + nhrp_gre_list = hash_create(nhrp_gre_info_key, nhrp_gre_info_cmp, + "NHRP GRE list Hash"); +} + +void nhrp_interface_update_mtu(struct interface *ifp, afi_t afi) +{ + struct nhrp_interface *nifp = ifp->info; + struct nhrp_afi_data *if_ad = &nifp->afi[afi]; + unsigned short new_mtu; + + if (if_ad->configured_mtu < 0) + new_mtu = nifp->nbmaifp ? nifp->nbmaifp->mtu : 0; + else + new_mtu = if_ad->configured_mtu; + if (new_mtu >= 1500) + new_mtu = 0; + + if (new_mtu != if_ad->mtu) { + debugf(NHRP_DEBUG_IF, "%s: MTU changed to %d", ifp->name, + new_mtu); + if_ad->mtu = new_mtu; + notifier_call(&nifp->notifier_list, + NOTIFY_INTERFACE_MTU_CHANGED); + } +} + +static void nhrp_interface_update_source(struct interface *ifp) +{ + struct nhrp_interface *nifp = ifp->info; + + if (!nifp->source || !nifp->nbmaifp + || ((ifindex_t)nifp->link_idx == nifp->nbmaifp->ifindex + && (nifp->link_vrf_id == nifp->nbmaifp->vrf->vrf_id))) + return; + + nifp->link_idx = nifp->nbmaifp->ifindex; + nifp->link_vrf_id = nifp->nbmaifp->vrf->vrf_id; + debugf(NHRP_DEBUG_IF, "%s: bound device index changed to %d, vr %u", + ifp->name, nifp->link_idx, nifp->link_vrf_id); + nhrp_send_zebra_gre_source_set(ifp, nifp->link_idx, nifp->link_vrf_id); +} + +static void nhrp_interface_interface_notifier(struct notifier_block *n, + unsigned long cmd) +{ + struct nhrp_interface *nifp = + container_of(n, struct nhrp_interface, nbmanifp_notifier); + struct interface *nbmaifp = nifp->nbmaifp; + struct nhrp_interface *nbmanifp = nbmaifp->info; + + switch (cmd) { + case NOTIFY_INTERFACE_CHANGED: + nhrp_interface_update_nbma(nifp->ifp, NULL); + break; + case NOTIFY_INTERFACE_ADDRESS_CHANGED: + nifp->nbma = nbmanifp->afi[AFI_IP].addr; + nhrp_interface_update(nifp->ifp); + notifier_call(&nifp->notifier_list, + NOTIFY_INTERFACE_NBMA_CHANGED); + debugf(NHRP_DEBUG_IF, "%s: NBMA change: address %pSU", + nifp->ifp->name, &nifp->nbma); + break; + } +} + +void nhrp_interface_update_nbma(struct interface *ifp, + struct nhrp_gre_info *gre_info) +{ + struct nhrp_interface *nifp = ifp->info, *nbmanifp = NULL; + struct interface *nbmaifp = NULL; + union sockunion nbma; + struct in_addr saddr = {0}; + + sockunion_family(&nbma) = AF_UNSPEC; + + if (nifp->source) + nbmaifp = if_lookup_by_name(nifp->source, nifp->link_vrf_id); + + if (ifp->ll_type != ZEBRA_LLT_IPGRE) + debugf(NHRP_DEBUG_IF, "%s: Ignoring non GRE interface type %u", + __func__, ifp->ll_type); + else { + if (!gre_info) { + nhrp_send_zebra_gre_request(ifp); + return; + } + nifp->i_grekey = gre_info->ikey; + nifp->o_grekey = gre_info->okey; + nifp->link_idx = gre_info->ifindex_link; + nifp->link_vrf_id = gre_info->vrfid_link; + saddr.s_addr = gre_info->vtep_ip.s_addr; + + debugf(NHRP_DEBUG_IF, "%s: GRE: %x %x %x", ifp->name, + nifp->i_grekey, nifp->link_idx, saddr.s_addr); + if (saddr.s_addr) + sockunion_set(&nbma, AF_INET, + (uint8_t *)&saddr.s_addr, + sizeof(saddr.s_addr)); + else if (!nbmaifp && nifp->link_idx != IFINDEX_INTERNAL) + nbmaifp = + if_lookup_by_index(nifp->link_idx, + nifp->link_vrf_id); + } + + if (nbmaifp) + nbmanifp = nbmaifp->info; + + if (nbmaifp != nifp->nbmaifp) { + if (nifp->nbmaifp) { + struct nhrp_interface *prev_nifp = nifp->nbmaifp->info; + + notifier_del(&nifp->nbmanifp_notifier, + &prev_nifp->notifier_list); + } + nifp->nbmaifp = nbmaifp; + if (nbmaifp) { + notifier_add(&nifp->nbmanifp_notifier, + &nbmanifp->notifier_list, + nhrp_interface_interface_notifier); + debugf(NHRP_DEBUG_IF, "%s: bound to %s", ifp->name, + nbmaifp->name); + } + } + + if (nbmaifp) { + if (sockunion_family(&nbma) == AF_UNSPEC) + nbma = nbmanifp->afi[AFI_IP].addr; + nhrp_interface_update_mtu(ifp, AFI_IP); + nhrp_interface_update_source(ifp); + } + + if (!sockunion_same(&nbma, &nifp->nbma)) { + nifp->nbma = nbma; + nhrp_interface_update(nifp->ifp); + debugf(NHRP_DEBUG_IF, "%s: NBMA address changed", ifp->name); + notifier_call(&nifp->notifier_list, + NOTIFY_INTERFACE_NBMA_CHANGED); + } + + nhrp_interface_update(ifp); +} + +static void nhrp_interface_update_address(struct interface *ifp, afi_t afi, + int force) +{ + const int family = afi2family(afi); + struct nhrp_interface *nifp = ifp->info; + struct nhrp_afi_data *if_ad = &nifp->afi[afi]; + struct nhrp_cache *nc; + struct connected *c, *best; + struct listnode *cnode; + union sockunion addr; + char buf[PREFIX_STRLEN]; + + /* Select new best match preferring primary address */ + best = NULL; + for (ALL_LIST_ELEMENTS_RO(ifp->connected, cnode, c)) { + if (PREFIX_FAMILY(c->address) != family) + continue; + if (best == NULL) { + best = c; + continue; + } + if ((best->flags & ZEBRA_IFA_SECONDARY) + && !(c->flags & ZEBRA_IFA_SECONDARY)) { + best = c; + continue; + } + if (!(best->flags & ZEBRA_IFA_SECONDARY) + && (c->flags & ZEBRA_IFA_SECONDARY)) + continue; + if (best->address->prefixlen > c->address->prefixlen) { + best = c; + continue; + } + if (best->address->prefixlen < c->address->prefixlen) + continue; + } + + /* On NHRP interfaces a host prefix is required */ + if (best && if_ad->configured + && best->address->prefixlen != 8 * prefix_blen(best->address)) { + zlog_notice("%s: %pFX is not a host prefix", ifp->name, + best->address); + best = NULL; + } + + /* Update address if it changed */ + if (best) + prefix2sockunion(best->address, &addr); + else + memset(&addr, 0, sizeof(addr)); + + if (!force && sockunion_same(&if_ad->addr, &addr)) + return; + + if (sockunion_family(&if_ad->addr) != AF_UNSPEC) { + nc = nhrp_cache_get(ifp, &if_ad->addr, 0); + if (nc) + nhrp_cache_update_binding(nc, NHRP_CACHE_LOCAL, -1, + NULL, 0, NULL, NULL); + } + + debugf(NHRP_DEBUG_KERNEL, "%s: IPv%d address changed to %s", ifp->name, + afi == AFI_IP ? 4 : 6, + best ? prefix2str(best->address, buf, sizeof(buf)) : "(none)"); + if_ad->addr = addr; + + if (if_ad->configured && sockunion_family(&if_ad->addr) != AF_UNSPEC) { + nc = nhrp_cache_get(ifp, &addr, 1); + if (nc) + nhrp_cache_update_binding(nc, NHRP_CACHE_LOCAL, 0, NULL, + 0, NULL, NULL); + } + + notifier_call(&nifp->notifier_list, NOTIFY_INTERFACE_ADDRESS_CHANGED); +} + +void nhrp_interface_update(struct interface *ifp) +{ + struct nhrp_interface *nifp = ifp->info; + struct nhrp_afi_data *if_ad; + afi_t afi; + int enabled = 0; + + notifier_call(&nifp->notifier_list, NOTIFY_INTERFACE_CHANGED); + + for (afi = 0; afi < AFI_MAX; afi++) { + if_ad = &nifp->afi[afi]; + + if (sockunion_family(&nifp->nbma) == AF_UNSPEC + || ifp->ifindex == IFINDEX_INTERNAL || !if_is_up(ifp) + || !if_ad->network_id) { + if (if_ad->configured) { + if_ad->configured = 0; + nhrp_interface_update_address(ifp, afi, 1); + } + continue; + } + + if (!if_ad->configured) { + os_configure_dmvpn(ifp->ifindex, ifp->name, + afi2family(afi)); + nhrp_send_zebra_configure_arp(ifp, afi2family(afi)); + if_ad->configured = 1; + nhrp_interface_update_address(ifp, afi, 1); + } + + enabled = 1; + } + + if (enabled != nifp->enabled) { + nifp->enabled = enabled; + notifier_call(&nifp->notifier_list, + enabled ? NOTIFY_INTERFACE_UP + : NOTIFY_INTERFACE_DOWN); + } +} + +int nhrp_ifp_create(struct interface *ifp) +{ + debugf(NHRP_DEBUG_IF, "if-add: %s, ifindex: %u, hw_type: %d %s", + ifp->name, ifp->ifindex, ifp->ll_type, + if_link_type_str(ifp->ll_type)); + + nhrp_interface_update_nbma(ifp, NULL); + + return 0; +} + +int nhrp_ifp_destroy(struct interface *ifp) +{ + debugf(NHRP_DEBUG_IF, "if-delete: %s", ifp->name); + + nhrp_interface_update_cache_config(ifp, false, AF_INET); + nhrp_interface_update_cache_config(ifp, false, AF_INET6); + nhrp_interface_update(ifp); + + return 0; +} + +struct map_ctx { + int family; + bool enabled; +}; + +static void interface_config_update_nhrp_map(struct nhrp_cache_config *cc, + void *data) +{ + struct map_ctx *ctx = data; + struct interface *ifp = cc->ifp; + struct nhrp_cache *c; + union sockunion nbma_addr; + + if (sockunion_family(&cc->remote_addr) != ctx->family) + return; + + /* gre layer not ready */ + if (ifp->vrf->vrf_id == VRF_UNKNOWN) + return; + + c = nhrp_cache_get(ifp, &cc->remote_addr, ctx->enabled ? 1 : 0); + if (!c && !ctx->enabled) + return; + + /* suppress */ + if (!ctx->enabled) { + if (c && c->map) { + nhrp_cache_update_binding( + c, c->cur.type, -1, + nhrp_peer_get(ifp, &nbma_addr), 0, NULL, NULL); + } + return; + } + + /* Newly created */ + assert(c != NULL); + + c->map = 1; + if (cc->type == NHRP_CACHE_LOCAL) + nhrp_cache_update_binding(c, NHRP_CACHE_LOCAL, 0, NULL, 0, + NULL, NULL); + else { + nhrp_cache_update_binding(c, NHRP_CACHE_STATIC, 0, + nhrp_peer_get(ifp, &cc->nbma), 0, + NULL, NULL); + } +} + +static void nhrp_interface_update_cache_config(struct interface *ifp, bool available, uint8_t family) +{ + struct map_ctx mapctx; + + mapctx = (struct map_ctx){ + .family = family, + .enabled = available + }; + nhrp_cache_config_foreach(ifp, interface_config_update_nhrp_map, + &mapctx); + +} + +int nhrp_ifp_up(struct interface *ifp) +{ + debugf(NHRP_DEBUG_IF, "if-up: %s", ifp->name); + nhrp_interface_update_nbma(ifp, NULL); + + return 0; +} + +int nhrp_ifp_down(struct interface *ifp) +{ + debugf(NHRP_DEBUG_IF, "if-down: %s", ifp->name); + nhrp_interface_update(ifp); + + return 0; +} + +int nhrp_interface_address_add(ZAPI_CALLBACK_ARGS) +{ + struct connected *ifc; + + ifc = zebra_interface_address_read(cmd, zclient->ibuf, vrf_id); + if (ifc == NULL) + return 0; + + debugf(NHRP_DEBUG_IF, "if-addr-add: %s: %pFX", ifc->ifp->name, + ifc->address); + + nhrp_interface_update_address( + ifc->ifp, family2afi(PREFIX_FAMILY(ifc->address)), 0); + nhrp_interface_update_cache_config(ifc->ifp, true, PREFIX_FAMILY(ifc->address)); + return 0; +} + +int nhrp_interface_address_delete(ZAPI_CALLBACK_ARGS) +{ + struct connected *ifc; + + ifc = zebra_interface_address_read(cmd, zclient->ibuf, vrf_id); + if (ifc == NULL) + return 0; + + debugf(NHRP_DEBUG_IF, "if-addr-del: %s: %pFX", ifc->ifp->name, + ifc->address); + + nhrp_interface_update_address( + ifc->ifp, family2afi(PREFIX_FAMILY(ifc->address)), 0); + connected_free(&ifc); + + return 0; +} + +void nhrp_interface_notify_add(struct interface *ifp, struct notifier_block *n, + notifier_fn_t fn) +{ + struct nhrp_interface *nifp = ifp->info; + + notifier_add(n, &nifp->notifier_list, fn); +} + +void nhrp_interface_notify_del(struct interface *ifp, struct notifier_block *n) +{ + struct nhrp_interface *nifp = ifp->info; + + notifier_del(n, &nifp->notifier_list); +} + +void nhrp_interface_set_protection(struct interface *ifp, const char *profile, + const char *fallback_profile) +{ + struct nhrp_interface *nifp = ifp->info; + + if (nifp->ipsec_profile) { + vici_terminate_vc_by_profile_name(nifp->ipsec_profile); + nhrp_vc_reset(); + free(nifp->ipsec_profile); + } + nifp->ipsec_profile = profile ? strdup(profile) : NULL; + + if (nifp->ipsec_fallback_profile) { + vici_terminate_vc_by_profile_name(nifp->ipsec_fallback_profile); + nhrp_vc_reset(); + free(nifp->ipsec_fallback_profile); + } + nifp->ipsec_fallback_profile = + fallback_profile ? strdup(fallback_profile) : NULL; + + notifier_call(&nifp->notifier_list, NOTIFY_INTERFACE_IPSEC_CHANGED); +} + +void nhrp_interface_set_source(struct interface *ifp, const char *ifname) +{ + struct nhrp_interface *nifp = ifp->info; + + if (nifp->source) + free(nifp->source); + nifp->source = ifname ? strdup(ifname) : NULL; + + nhrp_interface_update_nbma(ifp, NULL); +} diff --git a/nhrpd/nhrp_main.c b/nhrpd/nhrp_main.c new file mode 100644 index 0000000..593498c --- /dev/null +++ b/nhrpd/nhrp_main.c @@ -0,0 +1,168 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* NHRP daemon main functions + * Copyright (c) 2014-2015 Timo Teräs + */ + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include + +#include "zebra.h" +#include "privs.h" +#include "getopt.h" +#include "frrevent.h" +#include "sigevent.h" +#include "lib/version.h" +#include "log.h" +#include "memory.h" +#include "command.h" +#include "libfrr.h" +#include "filter.h" + +#include "nhrpd.h" +#include "nhrp_errors.h" + +DEFINE_MGROUP(NHRPD, "NHRP"); + +unsigned int debug_flags = 0; + +struct event_loop *master; +struct timeval current_time; + +/* nhrpd options. */ +struct option longopts[] = {{0}}; + +/* nhrpd privileges */ +static zebra_capabilities_t _caps_p[] = { + ZCAP_NET_RAW, ZCAP_NET_ADMIN, + ZCAP_DAC_OVERRIDE, /* for now needed to write to + /proc/sys/net/ipv4//send_redirect */ +}; + +struct zebra_privs_t nhrpd_privs = { +#if defined(FRR_USER) && defined(FRR_GROUP) + .user = FRR_USER, + .group = FRR_GROUP, +#endif +#ifdef VTY_GROUP + .vty_group = VTY_GROUP, +#endif + .caps_p = _caps_p, + .cap_num_p = array_size(_caps_p), + .cap_num_i = 0 +}; + + +static void parse_arguments(int argc, char **argv) +{ + int opt; + + while (1) { + opt = frr_getopt(argc, argv, 0); + if (opt < 0) + break; + + switch (opt) { + case 0: + break; + default: + frr_help_exit(1); + } + } +} + +static void nhrp_sigusr1(void) +{ + zlog_rotate(); +} + +static void nhrp_request_stop(void) +{ + debugf(NHRP_DEBUG_COMMON, "Exiting..."); + frr_early_fini(); + + nhrp_shortcut_terminate(); + nhrp_nhs_terminate(); + nhrp_zebra_terminate(); + vici_terminate(); + evmgr_terminate(); + nhrp_vc_terminate(); + vrf_terminate(); + + debugf(NHRP_DEBUG_COMMON, "Done."); + frr_fini(); + + exit(0); +} + +static struct frr_signal_t sighandlers[] = { + { + .signal = SIGUSR1, + .handler = &nhrp_sigusr1, + }, + { + .signal = SIGINT, + .handler = &nhrp_request_stop, + }, + { + .signal = SIGTERM, + .handler = &nhrp_request_stop, + }, +}; + +static const struct frr_yang_module_info *const nhrpd_yang_modules[] = { + &frr_filter_info, + &frr_interface_info, + &frr_vrf_info, +}; + +FRR_DAEMON_INFO(nhrpd, NHRP, .vty_port = NHRP_VTY_PORT, + + .proghelp = "Implementation of the NHRP routing protocol.", + + .signals = sighandlers, .n_signals = array_size(sighandlers), + + .privs = &nhrpd_privs, .yang_modules = nhrpd_yang_modules, + .n_yang_modules = array_size(nhrpd_yang_modules), +); + +int main(int argc, char **argv) +{ + frr_preinit(&nhrpd_di, argc, argv); + frr_opt_add("", longopts, ""); + + parse_arguments(argc, argv); + + /* Library inits. */ + master = frr_init(); + nhrp_error_init(); + vrf_init(NULL, NULL, NULL, NULL); + nhrp_interface_init(); + resolver_init(master); + + /* + * Run with elevated capabilities, as for all netlink activity + * we need privileges anyway. + * The assert is for clang SA code where it does + * not see the change function being set in lib + */ + assert(nhrpd_privs.change); + nhrpd_privs.change(ZPRIVS_RAISE); + + evmgr_init(); + nhrp_vc_init(); + nhrp_packet_init(); + vici_init(); + if_zapi_callbacks(nhrp_ifp_create, nhrp_ifp_up, + nhrp_ifp_down, nhrp_ifp_destroy); + nhrp_zebra_init(); + nhrp_shortcut_init(); + + nhrp_config_init(); + + frr_config_fork(); + frr_run(master); + return 0; +} diff --git a/nhrpd/nhrp_multicast.c b/nhrpd/nhrp_multicast.c new file mode 100644 index 0000000..fdc1a31 --- /dev/null +++ b/nhrpd/nhrp_multicast.c @@ -0,0 +1,293 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* NHRP Multicast Support + * Copyright (c) 2020-2021 4RF Limited + */ + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "frrevent.h" +#include "nhrpd.h" +#include "netlink.h" +#include "znl.h" +#include "os.h" + +DEFINE_MTYPE_STATIC(NHRPD, NHRP_MULTICAST, "NHRP Multicast"); + +int netlink_mcast_nflog_group; +static int netlink_mcast_log_fd = -1; +static struct event *netlink_mcast_log_thread; + +struct mcast_ctx { + struct interface *ifp; + struct zbuf *pkt; +}; + +static void nhrp_multicast_send(struct nhrp_peer *p, struct zbuf *zb) +{ + size_t addrlen; + int ret; + + addrlen = sockunion_get_addrlen(&p->vc->remote.nbma); + ret = os_sendmsg(zb->head, zbuf_used(zb), p->ifp->ifindex, + sockunion_get_addr(&p->vc->remote.nbma), addrlen, + addrlen == 4 ? ETH_P_IP : ETH_P_IPV6); + + debugf(NHRP_DEBUG_COMMON, + "Multicast Packet: %pSU -> %pSU, ret = %d, size = %zu, addrlen = %zu", + &p->vc->local.nbma, &p->vc->remote.nbma, ret, zbuf_used(zb), + addrlen); +} + +static void nhrp_multicast_forward_nbma(union sockunion *nbma_addr, + struct interface *ifp, struct zbuf *pkt) +{ + struct nhrp_peer *p = nhrp_peer_get(ifp, nbma_addr); + + if (p && p->online) { + /* Send packet */ + nhrp_multicast_send(p, pkt); + } + nhrp_peer_unref(p); +} + +static void nhrp_multicast_forward_cache(struct nhrp_cache *c, void *pctx) +{ + struct mcast_ctx *ctx = (struct mcast_ctx *)pctx; + + if (c->cur.type == NHRP_CACHE_DYNAMIC && c->cur.peer) + nhrp_multicast_forward_nbma(&c->cur.peer->vc->remote.nbma, + ctx->ifp, ctx->pkt); +} + +static void nhrp_multicast_forward(struct nhrp_multicast *mcast, void *pctx) +{ + struct mcast_ctx *ctx = (struct mcast_ctx *)pctx; + struct nhrp_interface *nifp = ctx->ifp->info; + + if (!nifp->enabled) + return; + + /* dynamic */ + if (sockunion_family(&mcast->nbma_addr) == AF_UNSPEC) { + nhrp_cache_foreach(ctx->ifp, nhrp_multicast_forward_cache, + pctx); + return; + } + + /* Fixed IP Address */ + nhrp_multicast_forward_nbma(&mcast->nbma_addr, ctx->ifp, ctx->pkt); +} + +static void netlink_mcast_log_handler(struct nlmsghdr *msg, struct zbuf *zb) +{ + struct nfgenmsg *nf; + struct rtattr *rta; + struct zbuf rtapl; + uint32_t *out_ndx = NULL; + afi_t afi; + struct mcast_ctx ctx; + + nf = znl_pull(zb, sizeof(*nf)); + if (!nf) + return; + + ctx.pkt = NULL; + while ((rta = znl_rta_pull(zb, &rtapl)) != NULL) { + switch (rta->rta_type) { + case NFULA_IFINDEX_OUTDEV: + out_ndx = znl_pull(&rtapl, sizeof(*out_ndx)); + break; + case NFULA_PAYLOAD: + ctx.pkt = &rtapl; + break; + /* NFULA_HWHDR exists and is supposed to contain source + * hardware address. However, for ip_gre it seems to be + * the nexthop destination address if the packet matches + * route. + */ + } + } + + if (!out_ndx || !ctx.pkt) + return; + + ctx.ifp = if_lookup_by_index(htonl(*out_ndx), VRF_DEFAULT); + if (!ctx.ifp) + return; + + debugf(NHRP_DEBUG_COMMON, + "Intercepted multicast packet leaving %s len %zu", + ctx.ifp->name, zbuf_used(ctx.pkt)); + + for (afi = 0; afi < AFI_MAX; afi++) { + nhrp_multicast_foreach(ctx.ifp, afi, nhrp_multicast_forward, + (void *)&ctx); + } +} + +static void netlink_mcast_log_recv(struct event *t) +{ + uint8_t buf[65535]; /* Max OSPF Packet size */ + int fd = EVENT_FD(t); + struct zbuf payload, zb; + struct nlmsghdr *n; + + + zbuf_init(&zb, buf, sizeof(buf), 0); + while (zbuf_recv(&zb, fd) > 0) { + while ((n = znl_nlmsg_pull(&zb, &payload)) != NULL) { + debugf(NHRP_DEBUG_COMMON, + "Netlink-mcast-log: Received msg_type %u, msg_flags %u", + n->nlmsg_type, n->nlmsg_flags); + switch (n->nlmsg_type) { + case (NFNL_SUBSYS_ULOG << 8) | NFULNL_MSG_PACKET: + netlink_mcast_log_handler(n, &payload); + break; + } + } + } + + event_add_read(master, netlink_mcast_log_recv, 0, netlink_mcast_log_fd, + &netlink_mcast_log_thread); +} + +static void netlink_mcast_log_register(int fd, int group) +{ + struct nlmsghdr *n; + struct nfgenmsg *nf; + struct nfulnl_msg_config_cmd cmd; + struct zbuf *zb = zbuf_alloc(512); + + n = znl_nlmsg_push(zb, (NFNL_SUBSYS_ULOG << 8) | NFULNL_MSG_CONFIG, + NLM_F_REQUEST | NLM_F_ACK); + nf = znl_push(zb, sizeof(*nf)); + *nf = (struct nfgenmsg){ + .nfgen_family = AF_UNSPEC, + .version = NFNETLINK_V0, + .res_id = htons(group), + }; + cmd.command = NFULNL_CFG_CMD_BIND; + znl_rta_push(zb, NFULA_CFG_CMD, &cmd, sizeof(cmd)); + znl_nlmsg_complete(zb, n); + + zbuf_send(zb, fd); + zbuf_free(zb); +} + +void netlink_mcast_set_nflog_group(int nlgroup) +{ + if (netlink_mcast_log_fd >= 0) { + EVENT_OFF(netlink_mcast_log_thread); + close(netlink_mcast_log_fd); + netlink_mcast_log_fd = -1; + debugf(NHRP_DEBUG_COMMON, "De-register nflog group"); + } + netlink_mcast_nflog_group = nlgroup; + if (nlgroup) { + netlink_mcast_log_fd = znl_open(NETLINK_NETFILTER, 0); + if (netlink_mcast_log_fd < 0) + return; + + netlink_mcast_log_register(netlink_mcast_log_fd, nlgroup); + event_add_read(master, netlink_mcast_log_recv, 0, + netlink_mcast_log_fd, &netlink_mcast_log_thread); + debugf(NHRP_DEBUG_COMMON, "Register nflog group: %d", + netlink_mcast_nflog_group); + } +} + +static int nhrp_multicast_free(struct interface *ifp, + struct nhrp_multicast *mcast) +{ + struct nhrp_interface *nifp = ifp->info; + + nhrp_mcastlist_del(&nifp->afi[mcast->afi].mcastlist_head, mcast); + XFREE(MTYPE_NHRP_MULTICAST, mcast); + return 0; +} + +int nhrp_multicast_add(struct interface *ifp, afi_t afi, + union sockunion *nbma_addr) +{ + struct nhrp_interface *nifp = ifp->info; + struct nhrp_multicast *mcast; + + frr_each (nhrp_mcastlist, &nifp->afi[afi].mcastlist_head, mcast) { + if (sockunion_same(&mcast->nbma_addr, nbma_addr)) + return NHRP_ERR_ENTRY_EXISTS; + } + + mcast = XMALLOC(MTYPE_NHRP_MULTICAST, sizeof(struct nhrp_multicast)); + + *mcast = (struct nhrp_multicast){ + .afi = afi, .ifp = ifp, .nbma_addr = *nbma_addr, + }; + nhrp_mcastlist_add_tail(&nifp->afi[afi].mcastlist_head, mcast); + + debugf(NHRP_DEBUG_COMMON, "Adding multicast entry (%pSU)", nbma_addr); + + return NHRP_OK; +} + +int nhrp_multicast_del(struct interface *ifp, afi_t afi, + union sockunion *nbma_addr) +{ + struct nhrp_interface *nifp = ifp->info; + struct nhrp_multicast *mcast; + + frr_each_safe (nhrp_mcastlist, &nifp->afi[afi].mcastlist_head, mcast) { + if (!sockunion_same(&mcast->nbma_addr, nbma_addr)) + continue; + + debugf(NHRP_DEBUG_COMMON, "Deleting multicast entry (%pSU)", + nbma_addr); + + nhrp_multicast_free(ifp, mcast); + + return NHRP_OK; + } + + return NHRP_ERR_ENTRY_NOT_FOUND; +} + +void nhrp_multicast_interface_del(struct interface *ifp) +{ + struct nhrp_interface *nifp = ifp->info; + struct nhrp_multicast *mcast; + afi_t afi; + + for (afi = 0; afi < AFI_MAX; afi++) { + debugf(NHRP_DEBUG_COMMON, "Cleaning up multicast entries (%zu)", + nhrp_mcastlist_count(&nifp->afi[afi].mcastlist_head)); + + frr_each_safe (nhrp_mcastlist, &nifp->afi[afi].mcastlist_head, + mcast) { + nhrp_multicast_free(ifp, mcast); + } + } +} + +void nhrp_multicast_foreach(struct interface *ifp, afi_t afi, + void (*cb)(struct nhrp_multicast *, void *), + void *ctx) +{ + struct nhrp_interface *nifp = ifp->info; + struct nhrp_multicast *mcast; + + frr_each (nhrp_mcastlist, &nifp->afi[afi].mcastlist_head, mcast) { + cb(mcast, ctx); + } +} diff --git a/nhrpd/nhrp_nhs.c b/nhrpd/nhrp_nhs.c new file mode 100644 index 0000000..acd3b7d --- /dev/null +++ b/nhrpd/nhrp_nhs.c @@ -0,0 +1,464 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* NHRP NHC nexthop server functions (registration) + * Copyright (c) 2014-2015 Timo Teräs + */ + +#include "zebra.h" +#include "zbuf.h" +#include "memory.h" +#include "frrevent.h" +#include "nhrpd.h" +#include "nhrp_protocol.h" + +DEFINE_MTYPE_STATIC(NHRPD, NHRP_NHS, "NHRP next hop server"); +DEFINE_MTYPE_STATIC(NHRPD, NHRP_REGISTRATION, "NHRP registration entries"); + +static void nhrp_nhs_resolve(struct event *t); +static void nhrp_reg_send_req(struct event *t); + +static void nhrp_reg_reply(struct nhrp_reqid *reqid, void *arg) +{ + struct nhrp_packet_parser *p = arg; + struct nhrp_registration *r = + container_of(reqid, struct nhrp_registration, reqid); + struct nhrp_nhs *nhs = r->nhs; + struct interface *ifp = nhs->ifp; + struct nhrp_interface *nifp = ifp->info; + struct nhrp_extension_header *ext; + struct nhrp_cie_header *cie; + struct nhrp_cache *c; + struct zbuf extpl; + union sockunion cie_nbma, cie_nbma_nhs, cie_proto, cie_proto_nhs, + *proto; + int ok = 0, holdtime; + unsigned short mtu = 0; + + nhrp_reqid_free(&nhrp_packet_reqid, &r->reqid); + + if (p->hdr->type != NHRP_PACKET_REGISTRATION_REPLY) { + debugf(NHRP_DEBUG_COMMON, "NHS: Registration failed"); + return; + } + + debugf(NHRP_DEBUG_COMMON, "NHS: Reg.reply received"); + + ok = 1; + while ((cie = nhrp_cie_pull(&p->payload, p->hdr, &cie_nbma, &cie_proto)) + != NULL) { + proto = sockunion_family(&cie_proto) != AF_UNSPEC + ? &cie_proto + : &p->src_proto; + debugf(NHRP_DEBUG_COMMON, "NHS: CIE registration: %pSU: %d", + proto, cie->code); + if (!((cie->code == NHRP_CODE_SUCCESS) + || (cie->code == NHRP_CODE_ADMINISTRATIVELY_PROHIBITED + && nhs->hub))) + ok = 0; + mtu = ntohs(cie->mtu); + debugf(NHRP_DEBUG_COMMON, "NHS: CIE MTU: %d", mtu); + } + + if (!ok) + return; + + /* Parse extensions */ + sockunion_family(&nifp->nat_nbma) = AF_UNSPEC; + sockunion_family(&cie_nbma_nhs) = AF_UNSPEC; + while ((ext = nhrp_ext_pull(&p->extensions, &extpl)) != NULL) { + switch (htons(ext->type) & ~NHRP_EXTENSION_FLAG_COMPULSORY) { + case NHRP_EXTENSION_NAT_ADDRESS: + /* NHS adds second CIE if NAT is detected */ + if (nhrp_cie_pull(&extpl, p->hdr, &cie_nbma, &cie_proto) + && nhrp_cie_pull(&extpl, p->hdr, &cie_nbma, + &cie_proto)) { + nifp->nat_nbma = cie_nbma; + debugf(NHRP_DEBUG_IF, + "%s: NAT detected, real NBMA address: %pSU", + ifp->name, &nifp->nbma); + } + break; + case NHRP_EXTENSION_RESPONDER_ADDRESS: + /* NHS adds its own record as responder address */ + nhrp_cie_pull(&extpl, p->hdr, &cie_nbma_nhs, + &cie_proto_nhs); + break; + } + } + + /* Success - schedule next registration, and route NHS */ + r->timeout = 2; + holdtime = nifp->afi[nhs->afi].holdtime; + EVENT_OFF(r->t_register); + + /* RFC 2332 5.2.3 - Registration is recommend to be renewed + * every one third of holdtime */ + event_add_timer(master, nhrp_reg_send_req, r, holdtime / 3, + &r->t_register); + + r->proto_addr = p->dst_proto; + c = nhrp_cache_get(ifp, &p->dst_proto, 1); + if (c) + nhrp_cache_update_binding(c, NHRP_CACHE_NHS, holdtime, + nhrp_peer_ref(r->peer), mtu, NULL, + &cie_nbma_nhs); +} + +static void nhrp_reg_timeout(struct event *t) +{ + struct nhrp_registration *r = EVENT_ARG(t); + struct nhrp_cache *c; + + + if (r->timeout >= 16 && sockunion_family(&r->proto_addr) != AF_UNSPEC) { + nhrp_reqid_free(&nhrp_packet_reqid, &r->reqid); + c = nhrp_cache_get(r->nhs->ifp, &r->proto_addr, 0); + if (c) + nhrp_cache_update_binding(c, NHRP_CACHE_NHS, -1, NULL, + 0, NULL, NULL); + sockunion_family(&r->proto_addr) = AF_UNSPEC; + } + + r->timeout <<= 1; + if (r->timeout > 64) { + /* If registration fails repeatedly, this may be because the + * IPSec connection is not working. Close the connection so it + * can be re-established correctly + */ + if (r->peer && r->peer->vc && r->peer->vc->ike_uniqueid) { + debugf(NHRP_DEBUG_COMMON, + "Terminating IPSec Connection for %d", + r->peer->vc->ike_uniqueid); + vici_terminate_vc_by_ike_id(r->peer->vc->ike_uniqueid); + r->peer->vc->ike_uniqueid = 0; + } + r->timeout = 2; + } + event_add_timer_msec(master, nhrp_reg_send_req, r, 10, &r->t_register); +} + +static void nhrp_reg_peer_notify(struct notifier_block *n, unsigned long cmd) +{ + struct nhrp_registration *r = + container_of(n, struct nhrp_registration, peer_notifier); + + switch (cmd) { + case NOTIFY_PEER_UP: + case NOTIFY_PEER_DOWN: + case NOTIFY_PEER_IFCONFIG_CHANGED: + case NOTIFY_PEER_MTU_CHANGED: + debugf(NHRP_DEBUG_COMMON, "NHS: Flush timer for %pSU", + &r->peer->vc->remote.nbma); + EVENT_OFF(r->t_register); + event_add_timer_msec(master, nhrp_reg_send_req, r, 10, + &r->t_register); + break; + } +} + +static void nhrp_reg_send_req(struct event *t) +{ + struct nhrp_registration *r = EVENT_ARG(t); + struct nhrp_nhs *nhs = r->nhs; + struct interface *ifp = nhs->ifp; + struct nhrp_interface *nifp = ifp->info; + struct nhrp_afi_data *if_ad = &nifp->afi[nhs->afi]; + union sockunion *dst_proto, nhs_proto; + struct zbuf *zb; + struct nhrp_packet_header *hdr; + struct nhrp_extension_header *ext; + struct nhrp_cie_header *cie; + + if (!nhrp_peer_check(r->peer, 2)) { + debugf(NHRP_DEBUG_COMMON, "NHS: Waiting link for %pSU", + &r->peer->vc->remote.nbma); + event_add_timer(master, nhrp_reg_send_req, r, 120, + &r->t_register); + return; + } + + event_add_timer(master, nhrp_reg_timeout, r, r->timeout, + &r->t_register); + + /* RFC2332 5.2.3 NHC uses it's own address as dst if NHS is unknown */ + dst_proto = &nhs->proto_addr; + if (sockunion_family(dst_proto) == AF_UNSPEC) + dst_proto = &if_ad->addr; + + debugf(NHRP_DEBUG_COMMON, "NHS: Register %pSU -> %pSU (timeout %d)", + &if_ad->addr, dst_proto, r->timeout); + + /* No protocol address configured for tunnel interface */ + if (sockunion_family(&if_ad->addr) == AF_UNSPEC) + return; + + zb = zbuf_alloc(1400); + hdr = nhrp_packet_push(zb, NHRP_PACKET_REGISTRATION_REQUEST, + &nifp->nbma, &if_ad->addr, dst_proto); + hdr->hop_count = 1; + if (!(if_ad->flags & NHRP_IFF_REG_NO_UNIQUE)) + hdr->flags |= htons(NHRP_FLAG_REGISTRATION_UNIQUE); + + hdr->u.request_id = htonl(nhrp_reqid_alloc(&nhrp_packet_reqid, + &r->reqid, nhrp_reg_reply)); + + /* FIXME: push CIE for each local protocol address */ + cie = nhrp_cie_push(zb, NHRP_CODE_SUCCESS, NULL, NULL); + /* RFC2332 5.2.1 if unique is set then prefix length must be 0xff */ + cie->prefix_length = (if_ad->flags & NHRP_IFF_REG_NO_UNIQUE) + ? 8 * sockunion_get_addrlen(dst_proto) + : 0xff; + cie->holding_time = htons(if_ad->holdtime); + cie->mtu = htons(if_ad->mtu); + + nhrp_ext_request(zb, hdr, ifp); + + /* Cisco NAT detection extension */ + if (sockunion_family(&r->proto_addr) != AF_UNSPEC) { + nhs_proto = r->proto_addr; + } else if (sockunion_family(&nhs->proto_addr) != AF_UNSPEC) { + nhs_proto = nhs->proto_addr; + } else { + /* cisco magic: If NHS is not known then use all 0s as + * client protocol address in NAT Extension header + */ + memset(&nhs_proto, 0, sizeof(nhs_proto)); + sockunion_family(&nhs_proto) = afi2family(nhs->afi); + } + + hdr->flags |= htons(NHRP_FLAG_REGISTRATION_NAT); + ext = nhrp_ext_push(zb, hdr, NHRP_EXTENSION_NAT_ADDRESS); + /* push NHS details */ + cie = nhrp_cie_push(zb, NHRP_CODE_SUCCESS, &r->peer->vc->remote.nbma, + &nhs_proto); + cie->prefix_length = 8 * sockunion_get_addrlen(&if_ad->addr); + cie->mtu = htons(if_ad->mtu); + nhrp_ext_complete(zb, ext); + + nhrp_packet_complete(zb, hdr); + nhrp_peer_send(r->peer, zb); + zbuf_free(zb); +} + +static void nhrp_reg_delete(struct nhrp_registration *r) +{ + nhrp_peer_notify_del(r->peer, &r->peer_notifier); + nhrp_peer_unref(r->peer); + nhrp_reglist_del(&r->nhs->reglist_head, r); + EVENT_OFF(r->t_register); + XFREE(MTYPE_NHRP_REGISTRATION, r); +} + +static struct nhrp_registration * +nhrp_reg_by_nbma(struct nhrp_nhs *nhs, const union sockunion *nbma_addr) +{ + struct nhrp_registration *r; + + frr_each (nhrp_reglist, &nhs->reglist_head, r) + if (sockunion_same(&r->peer->vc->remote.nbma, nbma_addr)) + return r; + return NULL; +} + +static void nhrp_nhs_resolve_cb(struct resolver_query *q, const char *errstr, + int n, union sockunion *addrs) +{ + struct nhrp_nhs *nhs = container_of(q, struct nhrp_nhs, dns_resolve); + struct nhrp_interface *nifp = nhs->ifp->info; + struct nhrp_registration *reg; + int i; + + if (n < 0) { + /* Failed, retry in a moment */ + event_add_timer(master, nhrp_nhs_resolve, nhs, 5, + &nhs->t_resolve); + return; + } + + event_add_timer(master, nhrp_nhs_resolve, nhs, 2 * 60 * 60, + &nhs->t_resolve); + + frr_each (nhrp_reglist, &nhs->reglist_head, reg) + reg->mark = 1; + + nhs->hub = 0; + for (i = 0; i < n; i++) { + if (sockunion_same(&addrs[i], &nifp->nbma)) { + nhs->hub = 1; + continue; + } + + reg = nhrp_reg_by_nbma(nhs, &addrs[i]); + if (reg) { + reg->mark = 0; + continue; + } + + reg = XCALLOC(MTYPE_NHRP_REGISTRATION, sizeof(*reg)); + reg->peer = nhrp_peer_get(nhs->ifp, &addrs[i]); + reg->nhs = nhs; + reg->timeout = 1; + nhrp_reglist_add_tail(&nhs->reglist_head, reg); + nhrp_peer_notify_add(reg->peer, ®->peer_notifier, + nhrp_reg_peer_notify); + event_add_timer_msec(master, nhrp_reg_send_req, reg, 50, + ®->t_register); + } + + frr_each_safe (nhrp_reglist, &nhs->reglist_head, reg) + if (reg->mark) + nhrp_reg_delete(reg); +} + +static void nhrp_nhs_resolve(struct event *t) +{ + struct nhrp_nhs *nhs = EVENT_ARG(t); + + resolver_resolve(&nhs->dns_resolve, AF_INET, VRF_DEFAULT, + nhs->nbma_fqdn, nhrp_nhs_resolve_cb); +} + +int nhrp_nhs_add(struct interface *ifp, afi_t afi, union sockunion *proto_addr, + const char *nbma_fqdn) +{ + struct nhrp_interface *nifp = ifp->info; + struct nhrp_nhs *nhs; + + if (sockunion_family(proto_addr) != AF_UNSPEC + && sockunion_family(proto_addr) != afi2family(afi)) + return NHRP_ERR_PROTOCOL_ADDRESS_MISMATCH; + + frr_each (nhrp_nhslist, &nifp->afi[afi].nhslist_head, nhs) { + if (sockunion_family(&nhs->proto_addr) != AF_UNSPEC + && sockunion_family(proto_addr) != AF_UNSPEC + && sockunion_same(&nhs->proto_addr, proto_addr)) + return NHRP_ERR_ENTRY_EXISTS; + + if (strcmp(nhs->nbma_fqdn, nbma_fqdn) == 0) + return NHRP_ERR_ENTRY_EXISTS; + } + + nhs = XMALLOC(MTYPE_NHRP_NHS, sizeof(struct nhrp_nhs)); + + *nhs = (struct nhrp_nhs){ + .afi = afi, + .ifp = ifp, + .proto_addr = *proto_addr, + .nbma_fqdn = strdup(nbma_fqdn), + .reglist_head = INIT_DLIST(nhs->reglist_head), + }; + nhrp_nhslist_add_tail(&nifp->afi[afi].nhslist_head, nhs); + event_add_timer_msec(master, nhrp_nhs_resolve, nhs, 1000, + &nhs->t_resolve); + + return NHRP_OK; +} + +int nhrp_nhs_del(struct interface *ifp, afi_t afi, union sockunion *proto_addr, + const char *nbma_fqdn) +{ + struct nhrp_interface *nifp = ifp->info; + struct nhrp_nhs *nhs; + int ret = NHRP_ERR_ENTRY_NOT_FOUND; + + if (sockunion_family(proto_addr) != AF_UNSPEC + && sockunion_family(proto_addr) != afi2family(afi)) + return NHRP_ERR_PROTOCOL_ADDRESS_MISMATCH; + + frr_each_safe (nhrp_nhslist, &nifp->afi[afi].nhslist_head, nhs) { + if (!sockunion_same(&nhs->proto_addr, proto_addr)) + continue; + if (strcmp(nhs->nbma_fqdn, nbma_fqdn) != 0) + continue; + + nhrp_nhs_free(nifp, afi, nhs); + ret = NHRP_OK; + } + + return ret; +} + +int nhrp_nhs_free(struct nhrp_interface *nifp, afi_t afi, struct nhrp_nhs *nhs) +{ + struct nhrp_registration *r; + + frr_each_safe (nhrp_reglist, &nhs->reglist_head, r) + nhrp_reg_delete(r); + EVENT_OFF(nhs->t_resolve); + nhrp_nhslist_del(&nifp->afi[afi].nhslist_head, nhs); + free((void *)nhs->nbma_fqdn); + XFREE(MTYPE_NHRP_NHS, nhs); + return 0; +} + +void nhrp_nhs_interface_del(struct interface *ifp) +{ + struct nhrp_interface *nifp = ifp->info; + struct nhrp_nhs *nhs; + afi_t afi; + + for (afi = 0; afi < AFI_MAX; afi++) { + debugf(NHRP_DEBUG_COMMON, "Cleaning up nhs entries (%zu)", + nhrp_nhslist_count(&nifp->afi[afi].nhslist_head)); + + frr_each_safe (nhrp_nhslist, &nifp->afi[afi].nhslist_head, nhs) + nhrp_nhs_free(nifp, afi, nhs); + } +} + +void nhrp_nhs_terminate(void) +{ + struct vrf *vrf = vrf_lookup_by_id(VRF_DEFAULT); + struct interface *ifp; + struct nhrp_interface *nifp; + struct nhrp_nhs *nhs; + afi_t afi; + + FOR_ALL_INTERFACES (vrf, ifp) { + nifp = ifp->info; + for (afi = 0; afi < AFI_MAX; afi++) { + frr_each_safe (nhrp_nhslist, + &nifp->afi[afi].nhslist_head, nhs) + nhrp_nhs_free(nifp, afi, nhs); + } + nhrp_peer_interface_del(ifp); + } +} + +void nhrp_nhs_foreach(struct interface *ifp, afi_t afi, + void (*cb)(struct nhrp_nhs *, struct nhrp_registration *, + void *), + void *ctx) +{ + struct nhrp_interface *nifp = ifp->info; + struct nhrp_nhs *nhs; + struct nhrp_registration *reg; + + frr_each (nhrp_nhslist, &nifp->afi[afi].nhslist_head, nhs) { + if (nhrp_reglist_count(&nhs->reglist_head)) { + frr_each (nhrp_reglist, &nhs->reglist_head, reg) + cb(nhs, reg, ctx); + } else + cb(nhs, 0, ctx); + } +} + +int nhrp_nhs_match_ip(union sockunion *in_ip, struct nhrp_interface *nifp) +{ + int i; + struct nhrp_nhs *nhs; + struct nhrp_registration *reg; + + for (i = 0; i < AFI_MAX; i++) { + frr_each (nhrp_nhslist, &nifp->afi[i].nhslist_head, nhs) { + if (!nhrp_reglist_count(&nhs->reglist_head)) + continue; + + frr_each (nhrp_reglist, &nhs->reglist_head, reg) { + if (!sockunion_cmp(in_ip, + ®->peer->vc->remote.nbma)) + return 1; + } + } + } + return 0; +} diff --git a/nhrpd/nhrp_packet.c b/nhrpd/nhrp_packet.c new file mode 100644 index 0000000..9d0b30c --- /dev/null +++ b/nhrpd/nhrp_packet.c @@ -0,0 +1,341 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* NHRP packet handling functions + * Copyright (c) 2014-2015 Timo Teräs + */ + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include +#include "nhrpd.h" +#include "zbuf.h" +#include "frrevent.h" +#include "hash.h" + +#include "nhrp_protocol.h" +#include "os.h" + +struct nhrp_reqid_pool nhrp_packet_reqid; + +static uint16_t family2proto(int family) +{ + switch (family) { + case AF_INET: + return ETH_P_IP; + case AF_INET6: + return ETH_P_IPV6; + } + return 0; +} + +static int proto2family(uint16_t proto) +{ + switch (proto) { + case ETH_P_IP: + return AF_INET; + case ETH_P_IPV6: + return AF_INET6; + } + return AF_UNSPEC; +} + +struct nhrp_packet_header *nhrp_packet_push(struct zbuf *zb, uint8_t type, + const union sockunion *src_nbma, + const union sockunion *src_proto, + const union sockunion *dst_proto) +{ + struct nhrp_packet_header *hdr; + + hdr = zbuf_push(zb, struct nhrp_packet_header); + if (!hdr) + return NULL; + + *hdr = (struct nhrp_packet_header){ + .afnum = htons(family2afi(sockunion_family(src_nbma))), + .protocol_type = + htons(family2proto(sockunion_family(src_proto))), + .version = NHRP_VERSION_RFC2332, + .type = type, + .hop_count = 64, + .src_nbma_address_len = sockunion_get_addrlen(src_nbma), + .src_protocol_address_len = sockunion_get_addrlen(src_proto), + .dst_protocol_address_len = sockunion_get_addrlen(dst_proto), + }; + + zbuf_put(zb, sockunion_get_addr(src_nbma), hdr->src_nbma_address_len); + zbuf_put(zb, sockunion_get_addr(src_proto), + hdr->src_protocol_address_len); + zbuf_put(zb, sockunion_get_addr(dst_proto), + hdr->dst_protocol_address_len); + + return hdr; +} + +struct nhrp_packet_header *nhrp_packet_pull(struct zbuf *zb, + union sockunion *src_nbma, + union sockunion *src_proto, + union sockunion *dst_proto) +{ + struct nhrp_packet_header *hdr; + + hdr = zbuf_pull(zb, struct nhrp_packet_header); + if (!hdr) + return NULL; + + sockunion_set(src_nbma, afi2family(htons(hdr->afnum)), + zbuf_pulln(zb, + hdr->src_nbma_address_len + + hdr->src_nbma_subaddress_len), + hdr->src_nbma_address_len + hdr->src_nbma_subaddress_len); + sockunion_set(src_proto, proto2family(htons(hdr->protocol_type)), + zbuf_pulln(zb, hdr->src_protocol_address_len), + hdr->src_protocol_address_len); + sockunion_set(dst_proto, proto2family(htons(hdr->protocol_type)), + zbuf_pulln(zb, hdr->dst_protocol_address_len), + hdr->dst_protocol_address_len); + + return hdr; +} + +uint16_t nhrp_packet_calculate_checksum(const uint8_t *pdu, uint16_t len) +{ + const uint16_t *pdu16 = (const uint16_t *)pdu; + uint32_t csum = 0; + int i; + + for (i = 0; i < len / 2; i++) + csum += pdu16[i]; + if (len & 1) + csum += htons(pdu[len - 1]); + + while (csum & 0xffff0000) + csum = (csum & 0xffff) + (csum >> 16); + + return (~csum) & 0xffff; +} + +void nhrp_packet_complete(struct zbuf *zb, struct nhrp_packet_header *hdr) +{ + unsigned short size; + + if (hdr->extension_offset) + nhrp_ext_push(zb, hdr, + NHRP_EXTENSION_END + | NHRP_EXTENSION_FLAG_COMPULSORY); + + size = zb->tail - (uint8_t *)hdr; + hdr->packet_size = htons(size); + hdr->checksum = 0; + hdr->checksum = nhrp_packet_calculate_checksum((uint8_t *)hdr, size); +} + +struct nhrp_cie_header *nhrp_cie_push(struct zbuf *zb, uint8_t code, + const union sockunion *nbma, + const union sockunion *proto) +{ + struct nhrp_cie_header *cie; + + cie = zbuf_push(zb, struct nhrp_cie_header); + *cie = (struct nhrp_cie_header){ + .code = code, + }; + if (nbma) { + cie->nbma_address_len = sockunion_get_addrlen(nbma); + zbuf_put(zb, sockunion_get_addr(nbma), cie->nbma_address_len); + } + if (proto) { + cie->protocol_address_len = sockunion_get_addrlen(proto); + zbuf_put(zb, sockunion_get_addr(proto), + cie->protocol_address_len); + } + + return cie; +} + +struct nhrp_cie_header *nhrp_cie_pull(struct zbuf *zb, + struct nhrp_packet_header *hdr, + union sockunion *nbma, + union sockunion *proto) +{ + struct nhrp_cie_header *cie; + + cie = zbuf_pull(zb, struct nhrp_cie_header); + if (!cie) + return NULL; + + if (cie->nbma_address_len + cie->nbma_subaddress_len > 0) { + sockunion_set(nbma, afi2family(htons(hdr->afnum)), + zbuf_pulln(zb, + cie->nbma_address_len + + cie->nbma_subaddress_len), + cie->nbma_address_len + cie->nbma_subaddress_len); + } else { + sockunion_family(nbma) = AF_UNSPEC; + } + + if (cie->protocol_address_len) { + sockunion_set(proto, proto2family(htons(hdr->protocol_type)), + zbuf_pulln(zb, cie->protocol_address_len), + cie->protocol_address_len); + } else { + sockunion_family(proto) = AF_UNSPEC; + } + + return cie; +} + +struct nhrp_extension_header * +nhrp_ext_push(struct zbuf *zb, struct nhrp_packet_header *hdr, uint16_t type) +{ + struct nhrp_extension_header *ext; + ext = zbuf_push(zb, struct nhrp_extension_header); + if (!ext) + return NULL; + + if (!hdr->extension_offset) + hdr->extension_offset = + htons(zb->tail - (uint8_t *)hdr + - sizeof(struct nhrp_extension_header)); + + *ext = (struct nhrp_extension_header){ + .type = htons(type), .length = 0, + }; + return ext; +} + +void nhrp_ext_complete(struct zbuf *zb, struct nhrp_extension_header *ext) +{ + ext->length = htons(zb->tail - (uint8_t *)ext + - sizeof(struct nhrp_extension_header)); +} + +struct nhrp_extension_header *nhrp_ext_pull(struct zbuf *zb, + struct zbuf *payload) +{ + struct nhrp_extension_header *ext; + uint16_t plen; + + ext = zbuf_pull(zb, struct nhrp_extension_header); + if (!ext) + return NULL; + + plen = htons(ext->length); + zbuf_init(payload, zbuf_pulln(zb, plen), plen, plen); + return ext; +} + +void nhrp_ext_request(struct zbuf *zb, struct nhrp_packet_header *hdr, + struct interface *ifp) +{ + /* Place holders for standard extensions */ + nhrp_ext_push(zb, hdr, + NHRP_EXTENSION_FORWARD_TRANSIT_NHS + | NHRP_EXTENSION_FLAG_COMPULSORY); + nhrp_ext_push(zb, hdr, + NHRP_EXTENSION_REVERSE_TRANSIT_NHS + | NHRP_EXTENSION_FLAG_COMPULSORY); + nhrp_ext_push(zb, hdr, + NHRP_EXTENSION_RESPONDER_ADDRESS + | NHRP_EXTENSION_FLAG_COMPULSORY); +} + +int nhrp_ext_reply(struct zbuf *zb, struct nhrp_packet_header *hdr, + struct interface *ifp, struct nhrp_extension_header *ext, + struct zbuf *extpayload) +{ + struct nhrp_interface *nifp = ifp->info; + struct nhrp_afi_data *ad = &nifp->afi[htons(hdr->afnum)]; + struct nhrp_extension_header *dst; + struct nhrp_cie_header *cie; + uint16_t type; + + type = htons(ext->type) & ~NHRP_EXTENSION_FLAG_COMPULSORY; + if (type == NHRP_EXTENSION_END) + return 0; + + dst = nhrp_ext_push(zb, hdr, htons(ext->type)); + if (!dst) + goto err; + + switch (type) { + case NHRP_EXTENSION_RESPONDER_ADDRESS: + cie = nhrp_cie_push(zb, NHRP_CODE_SUCCESS, &nifp->nbma, + &ad->addr); + if (!cie) + goto err; + cie->mtu = htons(ad->mtu); + cie->holding_time = htons(ad->holdtime); + break; + default: + if (type & NHRP_EXTENSION_FLAG_COMPULSORY) + goto err; + /* fallthru */ + case NHRP_EXTENSION_FORWARD_TRANSIT_NHS: + case NHRP_EXTENSION_REVERSE_TRANSIT_NHS: + /* Supported compulsory extensions, and any + * non-compulsory that is not explicitly handled, + * should be just copied. */ + zbuf_copy(zb, extpayload, zbuf_used(extpayload)); + break; + } + nhrp_ext_complete(zb, dst); + return 0; +err: + zbuf_set_werror(zb); + return -1; +} + +static void nhrp_packet_recvraw(struct event *t) +{ + int fd = EVENT_FD(t), ifindex; + struct zbuf *zb; + struct interface *ifp; + struct nhrp_peer *p; + union sockunion remote_nbma; + uint8_t addr[64]; + size_t len, addrlen; + + event_add_read(master, nhrp_packet_recvraw, 0, fd, NULL); + + zb = zbuf_alloc(1500); + if (!zb) + return; + + len = zbuf_size(zb); + addrlen = sizeof(addr); + if (os_recvmsg(zb->buf, &len, &ifindex, addr, &addrlen) < 0) + goto err; + + zb->head = zb->buf; + zb->tail = zb->buf + len; + + switch (addrlen) { + case 4: + sockunion_set(&remote_nbma, AF_INET, addr, addrlen); + break; + default: + goto err; + } + + ifp = if_lookup_by_index(ifindex, VRF_DEFAULT); + if (!ifp) + goto err; + + p = nhrp_peer_get(ifp, &remote_nbma); + if (!p) + goto err; + + nhrp_peer_recv(p, zb); + nhrp_peer_unref(p); + return; + +err: + zbuf_free(zb); +} + +int nhrp_packet_init(void) +{ + event_add_read(master, nhrp_packet_recvraw, 0, os_socket(), NULL); + return 0; +} diff --git a/nhrpd/nhrp_peer.c b/nhrpd/nhrp_peer.c new file mode 100644 index 0000000..ffb6cf7 --- /dev/null +++ b/nhrpd/nhrp_peer.c @@ -0,0 +1,1244 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* NHRP peer functions + * Copyright (c) 2014-2015 Timo Teräs + */ + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include + +#include "zebra.h" +#include "memory.h" +#include "frrevent.h" +#include "hash.h" +#include "network.h" + +#include "nhrpd.h" +#include "nhrp_protocol.h" +#include "os.h" + +DEFINE_MTYPE_STATIC(NHRPD, NHRP_PEER, "NHRP peer entry"); + +struct ipv6hdr { + uint8_t priority_version; + uint8_t flow_lbl[3]; + uint16_t payload_len; + uint8_t nexthdr; + uint8_t hop_limit; + struct in6_addr saddr; + struct in6_addr daddr; +}; + +static void nhrp_packet_debug(struct zbuf *zb, const char *dir); + +static void nhrp_peer_check_delete(struct nhrp_peer *p) +{ + struct nhrp_interface *nifp = p->ifp->info; + + if (p->ref || notifier_active(&p->notifier_list)) + return; + + debugf(NHRP_DEBUG_COMMON, "Deleting peer ref:%d remote:%pSU local:%pSU", + p->ref, &p->vc->remote.nbma, &p->vc->local.nbma); + + EVENT_OFF(p->t_fallback); + EVENT_OFF(p->t_timer); + hash_release(nifp->peer_hash, p); + nhrp_interface_notify_del(p->ifp, &p->ifp_notifier); + nhrp_vc_notify_del(p->vc, &p->vc_notifier); + XFREE(MTYPE_NHRP_PEER, p); +} + +static void nhrp_peer_notify_up(struct event *t) +{ + struct nhrp_peer *p = EVENT_ARG(t); + struct nhrp_vc *vc = p->vc; + struct interface *ifp = p->ifp; + struct nhrp_interface *nifp = ifp->info; + + p->t_fallback = NULL; + if (nifp->enabled && (!nifp->ipsec_profile || vc->ipsec)) { + p->online = 1; + nhrp_peer_ref(p); + notifier_call(&p->notifier_list, NOTIFY_PEER_UP); + nhrp_peer_unref(p); + } +} + +static void __nhrp_peer_check(struct nhrp_peer *p) +{ + struct nhrp_vc *vc = p->vc; + struct interface *ifp = p->ifp; + struct nhrp_interface *nifp = ifp->info; + unsigned online; + + online = nifp->enabled && (!nifp->ipsec_profile || vc->ipsec); + if (p->online != online) { + EVENT_OFF(p->t_fallback); + if (online && notifier_active(&p->notifier_list)) { + /* If we requested the IPsec connection, delay + * the up notification a bit to allow things + * settle down. This allows IKE to install + * SPDs and SAs. */ + event_add_timer_msec(master, nhrp_peer_notify_up, p, 50, + &p->t_fallback); + } else { + nhrp_peer_ref(p); + p->online = online; + if (online) { + notifier_call(&p->notifier_list, + NOTIFY_PEER_UP); + } else { + p->requested = p->fallback_requested = 0; + notifier_call(&p->notifier_list, + NOTIFY_PEER_DOWN); + } + nhrp_peer_unref(p); + } + } +} + +static void nhrp_peer_vc_notify(struct notifier_block *n, unsigned long cmd) +{ + struct nhrp_peer *p = container_of(n, struct nhrp_peer, vc_notifier); + + switch (cmd) { + case NOTIFY_VC_IPSEC_CHANGED: + __nhrp_peer_check(p); + break; + case NOTIFY_VC_IPSEC_UPDATE_NBMA: + nhrp_peer_ref(p); + notifier_call(&p->notifier_list, NOTIFY_PEER_NBMA_CHANGING); + nhrp_peer_unref(p); + break; + } +} + +static void nhrp_peer_ifp_notify(struct notifier_block *n, unsigned long cmd) +{ + struct nhrp_peer *p = container_of(n, struct nhrp_peer, ifp_notifier); + struct nhrp_interface *nifp; + struct nhrp_vc *vc; + + nhrp_peer_ref(p); + switch (cmd) { + case NOTIFY_INTERFACE_UP: + case NOTIFY_INTERFACE_DOWN: + __nhrp_peer_check(p); + break; + case NOTIFY_INTERFACE_NBMA_CHANGED: + /* Source NBMA changed, rebind to new VC */ + nifp = p->ifp->info; + vc = nhrp_vc_get(&nifp->nbma, &p->vc->remote.nbma, 1); + if (vc && p->vc != vc) { + nhrp_vc_notify_del(p->vc, &p->vc_notifier); + p->vc = vc; + nhrp_vc_notify_add(p->vc, &p->vc_notifier, + nhrp_peer_vc_notify); + __nhrp_peer_check(p); + } + /* fallthru */ /* to post config update */ + case NOTIFY_INTERFACE_ADDRESS_CHANGED: + notifier_call(&p->notifier_list, NOTIFY_PEER_IFCONFIG_CHANGED); + break; + case NOTIFY_INTERFACE_IPSEC_CHANGED: + __nhrp_peer_check(p); + notifier_call(&p->notifier_list, NOTIFY_PEER_IFCONFIG_CHANGED); + break; + case NOTIFY_INTERFACE_MTU_CHANGED: + notifier_call(&p->notifier_list, NOTIFY_PEER_MTU_CHANGED); + break; + } + nhrp_peer_unref(p); +} + +static unsigned int nhrp_peer_key(const void *peer_data) +{ + const struct nhrp_peer *p = peer_data; + return sockunion_hash(&p->vc->remote.nbma); +} + +static bool nhrp_peer_cmp(const void *cache_data, const void *key_data) +{ + const struct nhrp_peer *a = cache_data; + const struct nhrp_peer *b = key_data; + + return a->ifp == b->ifp && a->vc == b->vc; +} + +static void *nhrp_peer_create(void *data) +{ + struct nhrp_peer *p, *key = data; + + p = XMALLOC(MTYPE_NHRP_PEER, sizeof(*p)); + + *p = (struct nhrp_peer){ + .ref = 0, + .ifp = key->ifp, + .vc = key->vc, + .notifier_list = NOTIFIER_LIST_INITIALIZER(&p->notifier_list), + }; + nhrp_vc_notify_add(p->vc, &p->vc_notifier, nhrp_peer_vc_notify); + nhrp_interface_notify_add(p->ifp, &p->ifp_notifier, + nhrp_peer_ifp_notify); + + return p; +} + +static void do_peer_hash_free(void *hb_data) +{ + struct nhrp_peer *p = (struct nhrp_peer *)hb_data; + + nhrp_peer_check_delete(p); +} + +void nhrp_peer_interface_del(struct interface *ifp) +{ + struct nhrp_interface *nifp = ifp->info; + + debugf(NHRP_DEBUG_COMMON, "Cleaning up undeleted peer entries (%lu)", + nifp->peer_hash ? nifp->peer_hash->count : 0); + + hash_clean_and_free(&nifp->peer_hash, do_peer_hash_free); +} + +struct nhrp_peer *nhrp_peer_get(struct interface *ifp, + const union sockunion *remote_nbma) +{ + struct nhrp_interface *nifp = ifp->info; + struct nhrp_peer key, *p; + struct nhrp_vc *vc; + + if (!nifp->peer_hash) { + nifp->peer_hash = hash_create(nhrp_peer_key, nhrp_peer_cmp, + "NHRP Peer Hash"); + if (!nifp->peer_hash) + return NULL; + } + + vc = nhrp_vc_get(&nifp->nbma, remote_nbma, 1); + if (!vc) + return NULL; + + key.ifp = ifp; + key.vc = vc; + + p = hash_get(nifp->peer_hash, &key, nhrp_peer_create); + nhrp_peer_ref(p); + if (p->ref == 1) + __nhrp_peer_check(p); + + return p; +} + +struct nhrp_peer *nhrp_peer_ref(struct nhrp_peer *p) +{ + if (p) + p->ref++; + return p; +} + +void nhrp_peer_unref(struct nhrp_peer *p) +{ + if (p) { + p->ref--; + nhrp_peer_check_delete(p); + } +} + +static void nhrp_peer_request_timeout(struct event *t) +{ + struct nhrp_peer *p = EVENT_ARG(t); + struct nhrp_vc *vc = p->vc; + struct interface *ifp = p->ifp; + struct nhrp_interface *nifp = ifp->info; + + + if (p->online) + return; + + if (nifp->ipsec_fallback_profile && !p->prio + && !p->fallback_requested) { + p->fallback_requested = 1; + vici_request_vc(nifp->ipsec_fallback_profile, &vc->local.nbma, + &vc->remote.nbma, p->prio); + event_add_timer(master, nhrp_peer_request_timeout, p, 30, + &p->t_fallback); + } else { + p->requested = p->fallback_requested = 0; + } +} + +static void nhrp_peer_defer_vici_request(struct event *t) +{ + struct nhrp_peer *p = EVENT_ARG(t); + struct nhrp_vc *vc = p->vc; + struct interface *ifp = p->ifp; + struct nhrp_interface *nifp = ifp->info; + + EVENT_OFF(p->t_timer); + + if (p->online) { + debugf(NHRP_DEBUG_COMMON, + "IPsec connection to %pSU already established", + &vc->remote.nbma); + } else { + vici_request_vc(nifp->ipsec_profile, &vc->local.nbma, + &vc->remote.nbma, p->prio); + event_add_timer(master, nhrp_peer_request_timeout, p, + (nifp->ipsec_fallback_profile && !p->prio) ? 15 + : 30, + &p->t_fallback); + } +} + +int nhrp_peer_check(struct nhrp_peer *p, int establish) +{ + struct nhrp_vc *vc = p->vc; + struct interface *ifp = p->ifp; + struct nhrp_interface *nifp = ifp->info; + + if (p->online) + return 1; + if (!establish) + return 0; + if (p->requested) + return 0; + if (!nifp->ipsec_profile) + return 0; + if (sockunion_family(&vc->local.nbma) == AF_UNSPEC) + return 0; + if (vc->ipsec) + return 1; + + p->prio = establish > 1; + p->requested = 1; + + /* All NHRP registration requests are prioritized */ + if (p->prio) { + vici_request_vc(nifp->ipsec_profile, &vc->local.nbma, + &vc->remote.nbma, p->prio); + event_add_timer(master, nhrp_peer_request_timeout, p, + (nifp->ipsec_fallback_profile && !p->prio) ? 15 + : 30, + &p->t_fallback); + } else { + /* Maximum timeout is 1 second */ + int r_time_ms = frr_weak_random() % 1000; + + debugf(NHRP_DEBUG_COMMON, + "Initiating IPsec connection request to %pSU after %d ms:", + &vc->remote.nbma, r_time_ms); + event_add_timer_msec(master, nhrp_peer_defer_vici_request, p, + r_time_ms, &p->t_timer); + } + + return 0; +} + +void nhrp_peer_notify_add(struct nhrp_peer *p, struct notifier_block *n, + notifier_fn_t fn) +{ + notifier_add(n, &p->notifier_list, fn); +} + +void nhrp_peer_notify_del(struct nhrp_peer *p, struct notifier_block *n) +{ + notifier_del(n, &p->notifier_list); + nhrp_peer_check_delete(p); +} + +void nhrp_peer_send(struct nhrp_peer *p, struct zbuf *zb) +{ + nhrp_packet_debug(zb, "Send"); + + if (!p->online) + return; + + debugf(NHRP_DEBUG_KERNEL, "PACKET: Send %pSU -> %pSU", + &p->vc->local.nbma, &p->vc->remote.nbma); + + os_sendmsg(zb->head, zbuf_used(zb), p->ifp->ifindex, + sockunion_get_addr(&p->vc->remote.nbma), + sockunion_get_addrlen(&p->vc->remote.nbma), ETH_P_NHRP); + zbuf_reset(zb); +} + +static void nhrp_process_nat_extension(struct nhrp_packet_parser *pp, + union sockunion *proto, + union sockunion *cie_nbma) +{ + union sockunion cie_proto; + struct zbuf payload; + struct nhrp_extension_header *ext; + struct zbuf *extensions; + + if (!cie_nbma) + return; + + sockunion_family(cie_nbma) = AF_UNSPEC; + + if (!proto || sockunion_family(proto) == AF_UNSPEC) + return; + + /* Handle extensions */ + extensions = zbuf_alloc(zbuf_used(&pp->extensions)); + if (extensions) { + zbuf_copy_peek(extensions, &pp->extensions, + zbuf_used(&pp->extensions)); + while ((ext = nhrp_ext_pull(extensions, &payload)) != NULL) { + switch (htons(ext->type) + & ~NHRP_EXTENSION_FLAG_COMPULSORY) { + case NHRP_EXTENSION_NAT_ADDRESS: + /* Process the NBMA and proto address in NAT + * extension and update the cache without which + * the neighbor table in the kernel contains the + * source NBMA address which is not reachable + * since it is behind a NAT device + */ + debugf(NHRP_DEBUG_COMMON, + "shortcut res_resp: Processing NAT Extension for %pSU", + proto); + while (nhrp_cie_pull(&payload, pp->hdr, + cie_nbma, &cie_proto)) { + if (sockunion_family(&cie_proto) + == AF_UNSPEC) + continue; + + if (!sockunion_cmp(proto, &cie_proto)) { + debugf(NHRP_DEBUG_COMMON, + "cie_nbma for proto %pSU is %pSU", + proto, cie_nbma); + break; + } + } + } + } + zbuf_free(extensions); + } +} + +static void nhrp_handle_resolution_req(struct nhrp_packet_parser *pp) +{ + struct interface *ifp = pp->ifp; + struct zbuf *zb, payload; + struct nhrp_packet_header *hdr; + struct nhrp_cie_header *cie; + struct nhrp_extension_header *ext; + struct nhrp_cache *c; + union sockunion cie_nbma, cie_nbma_nat, cie_proto, *proto_addr, + *nbma_addr, *claimed_nbma_addr; + int holdtime, prefix_len, hostprefix_len; + struct nhrp_interface *nifp = ifp->info; + struct nhrp_peer *peer; + size_t paylen; + + if (!(pp->if_ad->flags & NHRP_IFF_SHORTCUT)) { + debugf(NHRP_DEBUG_COMMON, "Shortcuts disabled"); + /* FIXME: Send error indication? */ + return; + } + + if (pp->if_ad->network_id && pp->route_type == NHRP_ROUTE_OFF_NBMA + && pp->route_prefix.prefixlen < 8) { + debugf(NHRP_DEBUG_COMMON, + "Shortcut to more generic than /8 dropped"); + return; + } + + debugf(NHRP_DEBUG_COMMON, "Parsing and replying to Resolution Req"); + + if (nhrp_route_address(ifp, &pp->src_proto, NULL, &peer) + != NHRP_ROUTE_NBMA_NEXTHOP) + return; + + /* Copy payload CIE */ + hostprefix_len = 8 * sockunion_get_addrlen(&pp->if_ad->addr); + paylen = zbuf_used(&pp->payload); + debugf(NHRP_DEBUG_COMMON, "shortcut res_rep: paylen %zu", paylen); + + while ((cie = nhrp_cie_pull(&pp->payload, pp->hdr, &cie_nbma, + &cie_proto)) + != NULL) { + prefix_len = cie->prefix_length; + debugf(NHRP_DEBUG_COMMON, + "shortcut res_rep: parsing CIE with prefixlen=%u", + prefix_len); + if (prefix_len == 0 || prefix_len >= hostprefix_len) + prefix_len = hostprefix_len; + + if (prefix_len != hostprefix_len + && !(pp->hdr->flags + & htons(NHRP_FLAG_REGISTRATION_UNIQUE))) { + cie->code = NHRP_CODE_BINDING_NON_UNIQUE; + continue; + } + + /* We currently support only unique prefix registrations */ + if (prefix_len != hostprefix_len) { + cie->code = NHRP_CODE_ADMINISTRATIVELY_PROHIBITED; + continue; + } + + proto_addr = (sockunion_family(&cie_proto) == AF_UNSPEC) + ? &pp->src_proto + : &cie_proto; + + /* Check for this proto_addr in NHRP_NAT_EXTENSION */ + nhrp_process_nat_extension(pp, proto_addr, &cie_nbma_nat); + + if (sockunion_family(&cie_nbma_nat) == AF_UNSPEC) { + /* It may be possible that this resolution reply is + * coming directly from NATTED Spoke and there is not + * NAT Extension present + */ + debugf(NHRP_DEBUG_COMMON, + "shortcut res_rep: No NAT Extension for %pSU", + proto_addr); + + if (!sockunion_same(&pp->src_nbma, + &pp->peer->vc->remote.nbma) + && !nhrp_nhs_match_ip(&pp->peer->vc->remote.nbma, + nifp)) { + cie_nbma_nat = pp->peer->vc->remote.nbma; + debugf(NHRP_DEBUG_COMMON, + "shortcut res_rep: NAT detected using %pSU as cie_nbma", + &cie_nbma_nat); + } + } + + if (sockunion_family(&cie_nbma_nat) != AF_UNSPEC) + nbma_addr = &cie_nbma_nat; + else if (sockunion_family(&cie_nbma) != AF_UNSPEC) + nbma_addr = &cie_nbma; + else + nbma_addr = &pp->src_nbma; + + if (sockunion_family(&cie_nbma) != AF_UNSPEC) + claimed_nbma_addr = &cie_nbma; + else + claimed_nbma_addr = &pp->src_nbma; + + holdtime = htons(cie->holding_time); + debugf(NHRP_DEBUG_COMMON, + "shortcut res_rep: holdtime is %u (if 0, using %u)", + holdtime, pp->if_ad->holdtime); + if (!holdtime) + holdtime = pp->if_ad->holdtime; + + c = nhrp_cache_get(ifp, proto_addr, 1); + if (!c) { + debugf(NHRP_DEBUG_COMMON, + "shortcut res_rep: no cache found"); + cie->code = NHRP_CODE_INSUFFICIENT_RESOURCES; + continue; + } + + debugf(NHRP_DEBUG_COMMON, + "shortcut res_rep: updating binding for nmba addr %pSU", + nbma_addr); + if (!nhrp_cache_update_binding( + c, NHRP_CACHE_DYNAMIC, holdtime, + nhrp_peer_get(pp->ifp, nbma_addr), htons(cie->mtu), + nbma_addr, claimed_nbma_addr)) { + cie->code = NHRP_CODE_ADMINISTRATIVELY_PROHIBITED; + continue; + } + + cie->code = NHRP_CODE_SUCCESS; + } + + /* Create reply */ + zb = zbuf_alloc(1500); + hdr = nhrp_packet_push(zb, NHRP_PACKET_RESOLUTION_REPLY, &pp->src_nbma, + &pp->src_proto, &pp->dst_proto); + + /* Copied information from request */ + hdr->flags = pp->hdr->flags + & htons(NHRP_FLAG_RESOLUTION_SOURCE_IS_ROUTER + | NHRP_FLAG_RESOLUTION_SOURCE_STABLE); + hdr->flags |= htons(NHRP_FLAG_RESOLUTION_DESTINATION_STABLE + | NHRP_FLAG_RESOLUTION_AUTHORATIVE); + hdr->u.request_id = pp->hdr->u.request_id; + + /* CIE payload for the reply packet */ + cie = nhrp_cie_push(zb, NHRP_CODE_SUCCESS, &nifp->nbma, + &pp->if_ad->addr); + cie->holding_time = htons(pp->if_ad->holdtime); + cie->mtu = htons(pp->if_ad->mtu); + if (pp->if_ad->network_id && pp->route_type == NHRP_ROUTE_OFF_NBMA) + cie->prefix_length = pp->route_prefix.prefixlen; + else + cie->prefix_length = + 8 * sockunion_get_addrlen(&pp->if_ad->addr); + + /* Handle extensions */ + while ((ext = nhrp_ext_pull(&pp->extensions, &payload)) != NULL) { + switch (htons(ext->type) & ~NHRP_EXTENSION_FLAG_COMPULSORY) { + case NHRP_EXTENSION_NAT_ADDRESS: + ext = nhrp_ext_push(zb, hdr, + NHRP_EXTENSION_NAT_ADDRESS); + if (!ext) + goto err; + if (sockunion_family(&nifp->nat_nbma) != AF_UNSPEC) { + cie = nhrp_cie_push(zb, NHRP_CODE_SUCCESS, + &nifp->nat_nbma, + &pp->if_ad->addr); + if (!cie) + goto err; + cie->prefix_length = + 8 * sockunion_get_addrlen( + &pp->if_ad->addr); + + cie->mtu = htons(pp->if_ad->mtu); + nhrp_ext_complete(zb, ext); + } + break; + default: + if (nhrp_ext_reply(zb, hdr, ifp, ext, &payload) < 0) + goto err; + break; + } + } + nhrp_packet_complete(zb, hdr); + nhrp_peer_send(peer, zb); +err: + nhrp_peer_unref(peer); + zbuf_free(zb); +} + +static void nhrp_handle_registration_request(struct nhrp_packet_parser *p) +{ + struct interface *ifp = p->ifp; + struct zbuf *zb, payload; + struct nhrp_packet_header *hdr; + struct nhrp_cie_header *cie; + struct nhrp_extension_header *ext; + struct nhrp_cache *c; + union sockunion cie_nbma, cie_proto, *proto_addr, *nbma_addr, + *nbma_natoa; + int holdtime, prefix_len, hostprefix_len, natted = 0; + size_t paylen; + void *pay; + + debugf(NHRP_DEBUG_COMMON, "Parsing and replying to Registration Req"); + hostprefix_len = 8 * sockunion_get_addrlen(&p->if_ad->addr); + + if (!sockunion_same(&p->src_nbma, &p->peer->vc->remote.nbma)) + natted = 1; + + /* Create reply */ + zb = zbuf_alloc(1500); + hdr = nhrp_packet_push(zb, NHRP_PACKET_REGISTRATION_REPLY, &p->src_nbma, + &p->src_proto, &p->if_ad->addr); + + /* Copied information from request */ + hdr->flags = p->hdr->flags & htons(NHRP_FLAG_REGISTRATION_UNIQUE + | NHRP_FLAG_REGISTRATION_NAT); + hdr->u.request_id = p->hdr->u.request_id; + + /* Copy payload CIEs */ + paylen = zbuf_used(&p->payload); + pay = zbuf_pushn(zb, paylen); + if (!pay) + goto err; + memcpy(pay, zbuf_pulln(&p->payload, paylen), paylen); + zbuf_init(&payload, pay, paylen, paylen); + + while ((cie = nhrp_cie_pull(&payload, hdr, &cie_nbma, &cie_proto)) + != NULL) { + prefix_len = cie->prefix_length; + if (prefix_len == 0 || prefix_len >= hostprefix_len) + prefix_len = hostprefix_len; + + if (prefix_len != hostprefix_len + && !(p->hdr->flags + & htons(NHRP_FLAG_REGISTRATION_UNIQUE))) { + cie->code = NHRP_CODE_BINDING_NON_UNIQUE; + continue; + } + + /* We currently support only unique prefix registrations */ + if (prefix_len != hostprefix_len) { + cie->code = NHRP_CODE_ADMINISTRATIVELY_PROHIBITED; + continue; + } + + proto_addr = (sockunion_family(&cie_proto) == AF_UNSPEC) + ? &p->src_proto + : &cie_proto; + nbma_addr = (sockunion_family(&cie_nbma) == AF_UNSPEC) + ? &p->src_nbma + : &cie_nbma; + nbma_natoa = NULL; + if (natted) { + nbma_natoa = + (sockunion_family(&p->peer->vc->remote.nbma) + == AF_UNSPEC) + ? nbma_addr + : &p->peer->vc->remote.nbma; + } + + holdtime = htons(cie->holding_time); + if (!holdtime) + holdtime = p->if_ad->holdtime; + + c = nhrp_cache_get(ifp, proto_addr, 1); + if (!c) { + cie->code = NHRP_CODE_INSUFFICIENT_RESOURCES; + continue; + } + + if (!nhrp_cache_update_binding(c, NHRP_CACHE_DYNAMIC, holdtime, + nhrp_peer_ref(p->peer), + htons(cie->mtu), nbma_natoa, + nbma_addr)) { + cie->code = NHRP_CODE_ADMINISTRATIVELY_PROHIBITED; + continue; + } + + cie->code = NHRP_CODE_SUCCESS; + } + + /* Handle extensions */ + while ((ext = nhrp_ext_pull(&p->extensions, &payload)) != NULL) { + switch (htons(ext->type) & ~NHRP_EXTENSION_FLAG_COMPULSORY) { + case NHRP_EXTENSION_NAT_ADDRESS: + ext = nhrp_ext_push(zb, hdr, + NHRP_EXTENSION_NAT_ADDRESS); + if (!ext) + goto err; + zbuf_copy(zb, &payload, zbuf_used(&payload)); + if (natted) { + cie = nhrp_cie_push(zb, NHRP_CODE_SUCCESS, + &p->peer->vc->remote.nbma, + &p->src_proto); + cie->prefix_length = + 8 * sockunion_get_addrlen( + &p->if_ad->addr); + cie->mtu = htons(p->if_ad->mtu); + } + nhrp_ext_complete(zb, ext); + break; + default: + if (nhrp_ext_reply(zb, hdr, ifp, ext, &payload) < 0) + goto err; + break; + } + } + + nhrp_packet_complete(zb, hdr); + nhrp_peer_send(p->peer, zb); +err: + zbuf_free(zb); +} + +static int parse_ether_packet(struct zbuf *zb, uint16_t protocol_type, + union sockunion *src, union sockunion *dst) +{ + switch (protocol_type) { + case ETH_P_IP: { + struct iphdr *iph = zbuf_pull(zb, struct iphdr); + if (iph) { + if (src) + sockunion_set(src, AF_INET, + (uint8_t *)&iph->saddr, + sizeof(iph->saddr)); + if (dst) + sockunion_set(dst, AF_INET, + (uint8_t *)&iph->daddr, + sizeof(iph->daddr)); + } + } break; + case ETH_P_IPV6: { + struct ipv6hdr *iph = zbuf_pull(zb, struct ipv6hdr); + if (iph) { + if (src) + sockunion_set(src, AF_INET6, + (uint8_t *)&iph->saddr, + sizeof(iph->saddr)); + if (dst) + sockunion_set(dst, AF_INET6, + (uint8_t *)&iph->daddr, + sizeof(iph->daddr)); + } + } break; + default: + return 0; + } + return 1; +} + +void nhrp_peer_send_indication(struct interface *ifp, uint16_t protocol_type, + struct zbuf *pkt) +{ + union sockunion dst; + struct zbuf *zb, payload; + struct nhrp_interface *nifp = ifp->info; + struct nhrp_afi_data *if_ad; + struct nhrp_packet_header *hdr; + struct nhrp_peer *p; + + if (!nifp->enabled) + return; + + payload = *pkt; + if (!parse_ether_packet(&payload, protocol_type, &dst, NULL)) + return; + + if (nhrp_route_address(ifp, &dst, NULL, &p) != NHRP_ROUTE_NBMA_NEXTHOP) + return; + + if_ad = &nifp->afi[family2afi(sockunion_family(&dst))]; + if (!(if_ad->flags & NHRP_IFF_REDIRECT)) { + debugf(NHRP_DEBUG_COMMON, + "Send Traffic Indication to %pSU about packet to %pSU ignored", + &p->vc->remote.nbma, &dst); + return; + } + + debugf(NHRP_DEBUG_COMMON, + "Send Traffic Indication to %pSU (online=%d) about packet to %pSU", + &p->vc->remote.nbma, p->online, &dst); + + /* Create reply */ + zb = zbuf_alloc(1500); + hdr = nhrp_packet_push(zb, NHRP_PACKET_TRAFFIC_INDICATION, &nifp->nbma, + &if_ad->addr, &dst); + hdr->hop_count = 1; + + /* Payload is the packet causing indication */ + zbuf_copy(zb, pkt, zbuf_used(pkt)); + nhrp_packet_complete(zb, hdr); + nhrp_peer_send(p, zb); + nhrp_peer_unref(p); + zbuf_free(zb); +} + +static void nhrp_handle_error_ind(struct nhrp_packet_parser *pp) +{ + struct zbuf origmsg = pp->payload; + struct nhrp_packet_header *hdr; + struct nhrp_reqid *reqid; + union sockunion src_nbma, src_proto, dst_proto; + + hdr = nhrp_packet_pull(&origmsg, &src_nbma, &src_proto, &dst_proto); + if (!hdr) + return; + + debugf(NHRP_DEBUG_COMMON, + "Error Indication from %pSU about packet to %pSU ignored", + &pp->src_proto, &dst_proto); + + reqid = nhrp_reqid_lookup(&nhrp_packet_reqid, htonl(hdr->u.request_id)); + if (reqid) + reqid->cb(reqid, pp); +} + +static void nhrp_handle_traffic_ind(struct nhrp_packet_parser *p) +{ + union sockunion dst; + + if (!parse_ether_packet(&p->payload, htons(p->hdr->protocol_type), NULL, + &dst)) + return; + + debugf(NHRP_DEBUG_COMMON, + "Traffic Indication from %pSU about packet to %pSU: %s", + &p->src_proto, &dst, + (p->if_ad->flags & NHRP_IFF_SHORTCUT) ? "trying shortcut" + : "ignored"); + + if (p->if_ad->flags & NHRP_IFF_SHORTCUT) + nhrp_shortcut_initiate(&dst); +} + +enum packet_type_t { + PACKET_UNKNOWN = 0, + PACKET_REQUEST, + PACKET_REPLY, + PACKET_INDICATION, +}; + +static struct { + enum packet_type_t type; + const char *name; + void (*handler)(struct nhrp_packet_parser *); +} packet_types[] = {[0] = + { + .type = PACKET_UNKNOWN, + .name = "UNKNOWN", + }, + [NHRP_PACKET_RESOLUTION_REQUEST] = + { + .type = PACKET_REQUEST, + .name = "Resolution-Request", + .handler = nhrp_handle_resolution_req, + }, + [NHRP_PACKET_RESOLUTION_REPLY] = + { + .type = PACKET_REPLY, + .name = "Resolution-Reply", + }, + [NHRP_PACKET_REGISTRATION_REQUEST] = + { + .type = PACKET_REQUEST, + .name = "Registration-Request", + .handler = nhrp_handle_registration_request, + }, + [NHRP_PACKET_REGISTRATION_REPLY] = + { + .type = PACKET_REPLY, + .name = "Registration-Reply", + }, + [NHRP_PACKET_PURGE_REQUEST] = + { + .type = PACKET_REQUEST, + .name = "Purge-Request", + }, + [NHRP_PACKET_PURGE_REPLY] = + { + .type = PACKET_REPLY, + .name = "Purge-Reply", + }, + [NHRP_PACKET_ERROR_INDICATION] = + { + .type = PACKET_INDICATION, + .name = "Error-Indication", + .handler = nhrp_handle_error_ind, + }, + [NHRP_PACKET_TRAFFIC_INDICATION] = { + .type = PACKET_INDICATION, + .name = "Traffic-Indication", + .handler = nhrp_handle_traffic_ind, + }}; + +static void nhrp_peer_forward(struct nhrp_peer *p, + struct nhrp_packet_parser *pp) +{ + struct zbuf *zb, *zb_copy, extpl; + struct nhrp_packet_header *hdr; + struct nhrp_extension_header *ext, *dst; + struct nhrp_cie_header *cie; + struct nhrp_interface *nifp = pp->ifp->info; + struct nhrp_afi_data *if_ad = pp->if_ad; + union sockunion cie_nbma, cie_protocol, cie_protocol_mandatory, *proto; + uint16_t type, len; + struct nhrp_cache *c; + + if (pp->hdr->hop_count == 0) + return; + + /* Create forward packet - copy header */ + zb = zbuf_alloc(1500); + zb_copy = zbuf_alloc(1500); + + hdr = nhrp_packet_push(zb, pp->hdr->type, &pp->src_nbma, &pp->src_proto, + &pp->dst_proto); + hdr->flags = pp->hdr->flags; + hdr->hop_count = pp->hdr->hop_count - 1; + hdr->u.request_id = pp->hdr->u.request_id; + + /* Copy payload */ + zbuf_copy_peek(zb_copy, &pp->payload, zbuf_used(&pp->payload)); + zbuf_copy(zb, &pp->payload, zbuf_used(&pp->payload)); + + /* Get CIE Extension from Mandatory part */ + sockunion_family(&cie_protocol_mandatory) = AF_UNSPEC; + nhrp_cie_pull(zb_copy, pp->hdr, &cie_nbma, &cie_protocol_mandatory); + + /* Copy extensions */ + while ((ext = nhrp_ext_pull(&pp->extensions, &extpl)) != NULL) { + type = htons(ext->type) & ~NHRP_EXTENSION_FLAG_COMPULSORY; + len = htons(ext->length); + + if (type == NHRP_EXTENSION_END) + break; + + dst = nhrp_ext_push(zb, hdr, htons(ext->type)); + if (!dst) + goto err; + + switch (type) { + case NHRP_EXTENSION_FORWARD_TRANSIT_NHS: + case NHRP_EXTENSION_REVERSE_TRANSIT_NHS: + zbuf_put(zb, extpl.head, len); + if ((type == NHRP_EXTENSION_REVERSE_TRANSIT_NHS) + == (packet_types[hdr->type].type == PACKET_REPLY)) { + /* Check NHS list for forwarding loop */ + while (nhrp_cie_pull(&extpl, pp->hdr, + &cie_nbma, + &cie_protocol) != NULL) { + if (sockunion_same(&p->vc->remote.nbma, + &cie_nbma)) + goto err; + } + /* Append our selves to the list */ + cie = nhrp_cie_push(zb, NHRP_CODE_SUCCESS, + &nifp->nbma, &if_ad->addr); + if (!cie) + goto err; + cie->mtu = htons(if_ad->mtu); + cie->holding_time = htons(if_ad->holdtime); + } + break; + case NHRP_EXTENSION_NAT_ADDRESS: + c = NULL; + proto = NULL; + + /* If NAT extension is empty then attempt to populate + * it with cached NBMA information + */ + if (len == 0) { + if (packet_types[hdr->type].type + == PACKET_REQUEST) { + debugf(NHRP_DEBUG_COMMON, + "Processing NHRP_EXTENSION_NAT_ADDRESS while forwarding the request packet"); + proto = &pp->src_proto; + } else if (packet_types[hdr->type].type + == PACKET_REPLY) { + debugf(NHRP_DEBUG_COMMON, + "Processing NHRP_EXTENSION_NAT_ADDRESS while forwarding the reply packet"); + /* For reply packet use protocol + * specified in CIE of mandatory part + * for cache lookup + */ + if (sockunion_family( + &cie_protocol_mandatory) + != AF_UNSPEC) + proto = &cie_protocol_mandatory; + } + } + + if (proto) { + debugf(NHRP_DEBUG_COMMON, "Proto is %pSU", + proto); + c = nhrp_cache_get(nifp->ifp, proto, 0); + } + + if (c) { + debugf(NHRP_DEBUG_COMMON, + "c->cur.remote_nbma_natoa is %pSU", + &c->cur.remote_nbma_natoa); + if (sockunion_family(&c->cur.remote_nbma_natoa) + != AF_UNSPEC) { + cie = nhrp_cie_push( + zb, + NHRP_CODE_SUCCESS, + &c->cur.remote_nbma_natoa, + proto); + if (!cie) + goto err; + } + } else { + if (proto) + debugf(NHRP_DEBUG_COMMON, + "No cache entry for proto %pSU", + proto); + /* Copy existing NAT extension to new packet if + * either it was already not-empty, or we do not + * have valid cache information + */ + zbuf_put(zb, extpl.head, len); + } + break; + default: + if (htons(ext->type) & NHRP_EXTENSION_FLAG_COMPULSORY) + /* FIXME: RFC says to just copy, but not + * append our selves to the transit NHS list + */ + goto err; + /* fallthru */ + case NHRP_EXTENSION_RESPONDER_ADDRESS: + /* Supported compulsory extensions, and any + * non-compulsory that is not explicitly handled, + * should be just copied. + */ + zbuf_copy(zb, &extpl, len); + break; + } + nhrp_ext_complete(zb, dst); + } + + nhrp_packet_complete(zb, hdr); + nhrp_peer_send(p, zb); + zbuf_free(zb); + zbuf_free(zb_copy); + return; +err: + nhrp_packet_debug(pp->pkt, "FWD-FAIL"); + zbuf_free(zb); + zbuf_free(zb_copy); +} + +static void nhrp_packet_debug(struct zbuf *zb, const char *dir) +{ + union sockunion src_nbma, src_proto, dst_proto; + struct nhrp_packet_header *hdr; + struct zbuf zhdr; + int reply; + + if (likely(!(debug_flags & NHRP_DEBUG_COMMON))) + return; + + zbuf_init(&zhdr, zb->buf, zb->tail - zb->buf, zb->tail - zb->buf); + hdr = nhrp_packet_pull(&zhdr, &src_nbma, &src_proto, &dst_proto); + + reply = packet_types[hdr->type].type == PACKET_REPLY; + debugf(NHRP_DEBUG_COMMON, "%s %s(%d) %pSU -> %pSU", dir, + (packet_types[hdr->type].name ? packet_types[hdr->type].name + : "Unknown"), + hdr->type, reply ? &dst_proto : &src_proto, + reply ? &src_proto : &dst_proto); +} + +static int proto2afi(uint16_t proto) +{ + switch (proto) { + case ETH_P_IP: + return AFI_IP; + case ETH_P_IPV6: + return AFI_IP6; + } + return AF_UNSPEC; +} + +struct nhrp_route_info { + int local; + struct interface *ifp; + struct nhrp_vc *vc; +}; + +void nhrp_peer_recv(struct nhrp_peer *p, struct zbuf *zb) +{ + struct nhrp_packet_header *hdr; + struct nhrp_vc *vc = p->vc; + struct interface *ifp = p->ifp; + struct nhrp_interface *nifp = ifp->info; + struct nhrp_packet_parser pp; + struct nhrp_peer *peer = NULL; + struct nhrp_reqid *reqid; + const char *info = NULL; + union sockunion *target_addr; + unsigned paylen, extoff, extlen, realsize; + afi_t nbma_afi, proto_afi; + + debugf(NHRP_DEBUG_KERNEL, "PACKET: Recv %pSU -> %pSU", &vc->remote.nbma, + &vc->local.nbma); + + if (!p->online) { + info = "peer not online"; + goto drop; + } + + if (nhrp_packet_calculate_checksum(zb->head, zbuf_used(zb)) != 0) { + info = "bad checksum"; + goto drop; + } + + realsize = zbuf_used(zb); + hdr = nhrp_packet_pull(zb, &pp.src_nbma, &pp.src_proto, &pp.dst_proto); + if (!hdr) { + info = "corrupt header"; + goto drop; + } + + pp.ifp = ifp; + pp.pkt = zb; + pp.hdr = hdr; + pp.peer = p; + + nbma_afi = htons(hdr->afnum); + proto_afi = proto2afi(htons(hdr->protocol_type)); + if (hdr->type > NHRP_PACKET_MAX || hdr->version != NHRP_VERSION_RFC2332 + || nbma_afi >= AFI_MAX || proto_afi == AF_UNSPEC + || packet_types[hdr->type].type == PACKET_UNKNOWN + || htons(hdr->packet_size) > realsize) { + zlog_info( + "From %pSU: error: packet type %d, version %d, AFI %d, proto %x, size %d (real size %d)", + &vc->remote.nbma, (int)hdr->type, (int)hdr->version, + (int)nbma_afi, (int)htons(hdr->protocol_type), + (int)htons(hdr->packet_size), (int)realsize); + goto drop; + } + pp.if_ad = &((struct nhrp_interface *)ifp->info)->afi[proto_afi]; + + extoff = htons(hdr->extension_offset); + if (extoff) { + assert(zb->head > zb->buf); + uint32_t header_offset = zb->head - zb->buf; + if (extoff >= realsize) { + info = "extoff larger than packet"; + goto drop; + } + if (extoff < header_offset) { + info = "extoff smaller than header offset"; + goto drop; + } + paylen = extoff - header_offset; + } else { + paylen = zbuf_used(zb); + } + zbuf_init(&pp.payload, zbuf_pulln(zb, paylen), paylen, paylen); + extlen = zbuf_used(zb); + zbuf_init(&pp.extensions, zbuf_pulln(zb, extlen), extlen, extlen); + + if (!nifp->afi[proto_afi].network_id) { + info = "nhrp not enabled"; + goto drop; + } + + nhrp_packet_debug(zb, "Recv"); + + /* FIXME: Check authentication here. This extension needs to be + * pre-handled. */ + + /* Figure out if this is local */ + target_addr = (packet_types[hdr->type].type == PACKET_REPLY) + ? &pp.src_proto + : &pp.dst_proto; + + if (sockunion_same(&pp.src_proto, &pp.dst_proto)) + pp.route_type = NHRP_ROUTE_LOCAL; + else + pp.route_type = nhrp_route_address(pp.ifp, target_addr, + &pp.route_prefix, &peer); + + switch (pp.route_type) { + case NHRP_ROUTE_LOCAL: + nhrp_packet_debug(zb, "!LOCAL"); + if (packet_types[hdr->type].type == PACKET_REPLY) { + reqid = nhrp_reqid_lookup(&nhrp_packet_reqid, + htonl(hdr->u.request_id)); + if (reqid) { + reqid->cb(reqid, &pp); + break; + } else { + nhrp_packet_debug(zb, "!UNKNOWN-REQID"); + /* FIXME: send error-indication */ + } + } + /* fallthru */ /* FIXME: double check, is this correct? */ + case NHRP_ROUTE_OFF_NBMA: + if (packet_types[hdr->type].handler) { + packet_types[hdr->type].handler(&pp); + break; + } + break; + case NHRP_ROUTE_NBMA_NEXTHOP: + nhrp_peer_forward(peer, &pp); + break; + case NHRP_ROUTE_BLACKHOLE: + break; + } + +drop: + if (info) { + zlog_info("From %pSU: error: %s", &vc->remote.nbma, info); + } + if (peer) + nhrp_peer_unref(peer); + zbuf_free(zb); +} diff --git a/nhrpd/nhrp_protocol.h b/nhrpd/nhrp_protocol.h new file mode 100644 index 0000000..8cf1ebb --- /dev/null +++ b/nhrpd/nhrp_protocol.h @@ -0,0 +1,127 @@ +// SPDX-License-Identifier: MIT +/* nhrp_protocol.h - NHRP protocol definitions + * + * Copyright (c) 2007-2012 Timo Teräs + */ + +#ifndef NHRP_PROTOCOL_H +#define NHRP_PROTOCOL_H + +#include + +/* NHRP Ethernet protocol number */ +#define ETH_P_NHRP 0x2001 + +/* NHRP Version */ +#define NHRP_VERSION_RFC2332 1 + +/* NHRP Packet Types */ +#define NHRP_PACKET_RESOLUTION_REQUEST 1 +#define NHRP_PACKET_RESOLUTION_REPLY 2 +#define NHRP_PACKET_REGISTRATION_REQUEST 3 +#define NHRP_PACKET_REGISTRATION_REPLY 4 +#define NHRP_PACKET_PURGE_REQUEST 5 +#define NHRP_PACKET_PURGE_REPLY 6 +#define NHRP_PACKET_ERROR_INDICATION 7 +#define NHRP_PACKET_TRAFFIC_INDICATION 8 +#define NHRP_PACKET_MAX 8 + +/* NHRP Extension Types */ +#define NHRP_EXTENSION_FLAG_COMPULSORY 0x8000 +#define NHRP_EXTENSION_END 0 +#define NHRP_EXTENSION_PAYLOAD 0 +#define NHRP_EXTENSION_RESPONDER_ADDRESS 3 +#define NHRP_EXTENSION_FORWARD_TRANSIT_NHS 4 +#define NHRP_EXTENSION_REVERSE_TRANSIT_NHS 5 +#define NHRP_EXTENSION_AUTHENTICATION 7 +#define NHRP_EXTENSION_VENDOR 8 +#define NHRP_EXTENSION_NAT_ADDRESS 9 + +/* NHRP Error Indication Codes */ +#define NHRP_ERROR_UNRECOGNIZED_EXTENSION 1 +#define NHRP_ERROR_LOOP_DETECTED 2 +#define NHRP_ERROR_PROTOCOL_ADDRESS_UNREACHABLE 6 +#define NHRP_ERROR_PROTOCOL_ERROR 7 +#define NHRP_ERROR_SDU_SIZE_EXCEEDED 8 +#define NHRP_ERROR_INVALID_EXTENSION 9 +#define NHRP_ERROR_INVALID_RESOLUTION_REPLY 10 +#define NHRP_ERROR_AUTHENTICATION_FAILURE 11 +#define NHRP_ERROR_HOP_COUNT_EXCEEDED 15 + +/* NHRP CIE Codes */ +#define NHRP_CODE_SUCCESS 0 +#define NHRP_CODE_ADMINISTRATIVELY_PROHIBITED 4 +#define NHRP_CODE_INSUFFICIENT_RESOURCES 5 +#define NHRP_CODE_NO_BINDING_EXISTS 11 +#define NHRP_CODE_BINDING_NON_UNIQUE 13 +#define NHRP_CODE_UNIQUE_ADDRESS_REGISTERED 14 + +/* NHRP Flags for Resolution request/reply */ +#define NHRP_FLAG_RESOLUTION_SOURCE_IS_ROUTER 0x8000 +#define NHRP_FLAG_RESOLUTION_AUTHORATIVE 0x4000 +#define NHRP_FLAG_RESOLUTION_DESTINATION_STABLE 0x2000 +#define NHRP_FLAG_RESOLUTION_UNIQUE 0x1000 +#define NHRP_FLAG_RESOLUTION_SOURCE_STABLE 0x0800 +#define NHRP_FLAG_RESOLUTION_NAT 0x0002 + +/* NHRP Flags for Registration request/reply */ +#define NHRP_FLAG_REGISTRATION_UNIQUE 0x8000 +#define NHRP_FLAG_REGISTRATION_NAT 0x0002 + +/* NHRP Flags for Purge request/reply */ +#define NHRP_FLAG_PURGE_NO_REPLY 0x8000 + +/* NHRP Authentication extension types (ala Cisco) */ +#define NHRP_AUTHENTICATION_PLAINTEXT 0x00000001 + +/* NHRP Packet Structures */ +struct nhrp_packet_header { + /* Fixed header */ + uint16_t afnum; + uint16_t protocol_type; + uint8_t snap[5]; + uint8_t hop_count; + uint16_t packet_size; + uint16_t checksum; + uint16_t extension_offset; + uint8_t version; + uint8_t type; + uint8_t src_nbma_address_len; + uint8_t src_nbma_subaddress_len; + + /* Mandatory header */ + uint8_t src_protocol_address_len; + uint8_t dst_protocol_address_len; + uint16_t flags; + union { + uint32_t request_id; + struct { + uint16_t code; + uint16_t offset; + } error; + } u; +} __attribute__((packed)); + +struct nhrp_cie_header { + uint8_t code; + uint8_t prefix_length; + uint16_t unused; + uint16_t mtu; + uint16_t holding_time; + uint8_t nbma_address_len; + uint8_t nbma_subaddress_len; + uint8_t protocol_address_len; + uint8_t preference; +} __attribute__((packed)); + +struct nhrp_extension_header { + uint16_t type; + uint16_t length; +} __attribute__((packed)); + +struct nhrp_cisco_authentication_extension { + uint32_t type; + uint8_t secret[8]; +} __attribute__((packed)); + +#endif diff --git a/nhrpd/nhrp_route.c b/nhrpd/nhrp_route.c new file mode 100644 index 0000000..060e603 --- /dev/null +++ b/nhrpd/nhrp_route.c @@ -0,0 +1,530 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* NHRP routing functions + * Copyright (c) 2014-2015 Timo Teräs + */ + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include "nhrpd.h" +#include "table.h" +#include "memory.h" +#include "stream.h" +#include "log.h" +#include "zclient.h" + +DEFINE_MTYPE_STATIC(NHRPD, NHRP_ROUTE, "NHRP routing entry"); + +static struct zclient *zclient; +static struct route_table *zebra_rib[AFI_MAX]; + +struct route_info { + union sockunion via; + struct interface *ifp; + struct interface *nhrp_ifp; +}; + +static struct route_node *nhrp_route_update_get(const struct prefix *p, + int create) +{ + struct route_node *rn; + afi_t afi = family2afi(PREFIX_FAMILY(p)); + + if (!zebra_rib[afi]) + return NULL; + + if (create) { + rn = route_node_get(zebra_rib[afi], p); + if (!rn->info) { + rn->info = XCALLOC(MTYPE_NHRP_ROUTE, + sizeof(struct route_info)); + route_lock_node(rn); + } + return rn; + } else { + return route_node_lookup(zebra_rib[afi], p); + } +} + +static void nhrp_route_update_put(struct route_node *rn) +{ + struct route_info *ri = rn->info; + + if (!ri->ifp && !ri->nhrp_ifp + && sockunion_is_null(&ri->via)) { + XFREE(MTYPE_NHRP_ROUTE, rn->info); + route_unlock_node(rn); + } + route_unlock_node(rn); +} + +static void nhrp_route_update_zebra(const struct prefix *p, + union sockunion *nexthop, + struct interface *ifp) +{ + struct route_node *rn; + struct route_info *ri; + + rn = nhrp_route_update_get(p, !sockunion_is_null(nexthop) || ifp); + if (rn) { + ri = rn->info; + ri->via = *nexthop; + ri->ifp = ifp; + nhrp_route_update_put(rn); + } +} + +static void nhrp_zebra_register_neigh(vrf_id_t vrf_id, afi_t afi, bool reg) +{ + struct stream *s; + + if (!zclient || zclient->sock < 0) + return; + + s = zclient->obuf; + stream_reset(s); + + zclient_create_header(s, reg ? ZEBRA_NHRP_NEIGH_REGISTER : + ZEBRA_NHRP_NEIGH_UNREGISTER, + vrf_id); + stream_putw(s, afi); + stream_putw_at(s, 0, stream_get_endp(s)); + zclient_send_message(zclient); +} + +void nhrp_route_update_nhrp(const struct prefix *p, struct interface *ifp) +{ + struct route_node *rn; + struct route_info *ri; + + rn = nhrp_route_update_get(p, ifp != NULL); + if (rn) { + ri = rn->info; + ri->nhrp_ifp = ifp; + nhrp_route_update_put(rn); + } +} + +void nhrp_route_announce(int add, enum nhrp_cache_type type, + const struct prefix *p, struct interface *ifp, + const union sockunion *nexthop_ref, uint32_t mtu) +{ + struct zapi_route api; + struct zapi_nexthop *api_nh; + + if (zclient->sock < 0) + return; + + memset(&api, 0, sizeof(api)); + api.type = ZEBRA_ROUTE_NHRP; + api.safi = SAFI_UNICAST; + api.vrf_id = VRF_DEFAULT; + api.prefix = *p; + + switch (type) { + case NHRP_CACHE_NEGATIVE: + /* Fill in a blackhole nexthop */ + zapi_route_set_blackhole(&api, BLACKHOLE_REJECT); + ifp = NULL; + nexthop_ref = NULL; + break; + case NHRP_CACHE_DYNAMIC: + case NHRP_CACHE_NHS: + case NHRP_CACHE_STATIC: + /* Regular route, so these are announced + * to other routing daemons */ + break; + case NHRP_CACHE_INVALID: + case NHRP_CACHE_INCOMPLETE: + /* + * I cannot believe that we want to set a FIB_OVERRIDE + * for invalid state or incomplete. But this matches + * the original code. Someone will probably notice + * the problem eventually + */ + case NHRP_CACHE_CACHED: + case NHRP_CACHE_LOCAL: + case NHRP_CACHE_NUM_TYPES: + SET_FLAG(api.flags, ZEBRA_FLAG_FIB_OVERRIDE); + break; + } + SET_FLAG(api.flags, ZEBRA_FLAG_ALLOW_RECURSION); + + SET_FLAG(api.message, ZAPI_MESSAGE_NEXTHOP); + api.nexthop_num = 1; + api_nh = &api.nexthops[0]; + api_nh->vrf_id = VRF_DEFAULT; + + switch (api.prefix.family) { + case AF_INET: + if (api.prefix.prefixlen == IPV4_MAX_BITLEN && + nexthop_ref && + memcmp(&nexthop_ref->sin.sin_addr, &api.prefix.u.prefix4, + sizeof(struct in_addr)) == 0) { + nexthop_ref = NULL; + } + if (nexthop_ref) { + api_nh->gate.ipv4 = nexthop_ref->sin.sin_addr; + api_nh->type = NEXTHOP_TYPE_IPV4; + } + if (ifp) { + api_nh->ifindex = ifp->ifindex; + if (api_nh->type == NEXTHOP_TYPE_IPV4) + api_nh->type = NEXTHOP_TYPE_IPV4_IFINDEX; + else + api_nh->type = NEXTHOP_TYPE_IFINDEX; + } + break; + case AF_INET6: + if (api.prefix.prefixlen == IPV6_MAX_BITLEN && + nexthop_ref && + memcmp(&nexthop_ref->sin6.sin6_addr, &api.prefix.u.prefix6, + sizeof(struct in6_addr)) == 0) { + nexthop_ref = NULL; + } + if (nexthop_ref) { + api_nh->gate.ipv6 = nexthop_ref->sin6.sin6_addr; + api_nh->type = NEXTHOP_TYPE_IPV6; + } + if (ifp) { + api_nh->ifindex = ifp->ifindex; + if (api_nh->type == NEXTHOP_TYPE_IPV6) + api_nh->type = NEXTHOP_TYPE_IPV6_IFINDEX; + else + api_nh->type = NEXTHOP_TYPE_IFINDEX; + } + break; + } + if (mtu) { + SET_FLAG(api.message, ZAPI_MESSAGE_MTU); + api.mtu = mtu; + } + + if (unlikely(debug_flags & NHRP_DEBUG_ROUTE)) { + char buf[PREFIX_STRLEN]; + + zlog_debug( + "Zebra send: route %s %pFX nexthop %s metric %u count %d dev %s", + add ? "add" : "del", &api.prefix, + nexthop_ref ? inet_ntop(api.prefix.family, + &api_nh->gate, + buf, sizeof(buf)) + : "", + api.metric, api.nexthop_num, ifp ? ifp->name : "none"); + } + + zclient_route_send(add ? ZEBRA_ROUTE_ADD : ZEBRA_ROUTE_DELETE, zclient, + &api); +} + +int nhrp_route_read(ZAPI_CALLBACK_ARGS) +{ + struct zapi_route api; + struct zapi_nexthop *api_nh; + struct interface *ifp = NULL; + union sockunion nexthop_addr; + int added; + + if (zapi_route_decode(zclient->ibuf, &api) < 0) + return -1; + + /* we completely ignore srcdest routes for now. */ + if (CHECK_FLAG(api.message, ZAPI_MESSAGE_SRCPFX)) + return 0; + + /* ignore our routes */ + if (api.type == ZEBRA_ROUTE_NHRP) + return 0; + + sockunion_family(&nexthop_addr) = AF_UNSPEC; + if (CHECK_FLAG(api.message, ZAPI_MESSAGE_NEXTHOP)) { + api_nh = &api.nexthops[0]; + + nexthop_addr.sa.sa_family = api.prefix.family; + switch (nexthop_addr.sa.sa_family) { + case AF_INET: + nexthop_addr.sin.sin_addr = api_nh->gate.ipv4; + break; + case AF_INET6: + nexthop_addr.sin6.sin6_addr = api_nh->gate.ipv6; + break; + } + + if (api_nh->ifindex != IFINDEX_INTERNAL) + ifp = if_lookup_by_index(api_nh->ifindex, VRF_DEFAULT); + } + + added = (cmd == ZEBRA_REDISTRIBUTE_ROUTE_ADD); + debugf(NHRP_DEBUG_ROUTE, "if-route-%s: %pFX via %pSU dev %s", + added ? "add" : "del", &api.prefix, &nexthop_addr, + ifp ? ifp->name : "(none)"); + + nhrp_route_update_zebra(&api.prefix, &nexthop_addr, added ? ifp : NULL); + nhrp_shortcut_prefix_change(&api.prefix, !added); + + return 0; +} + +int nhrp_route_get_nexthop(const union sockunion *addr, struct prefix *p, + union sockunion *via, struct interface **ifp) +{ + struct route_node *rn; + struct route_info *ri; + struct prefix lookup; + afi_t afi = family2afi(sockunion_family(addr)); + + sockunion2hostprefix(addr, &lookup); + + rn = route_node_match(zebra_rib[afi], &lookup); + if (!rn) + return 0; + + ri = rn->info; + if (ri->nhrp_ifp) { + debugf(NHRP_DEBUG_ROUTE, "lookup %pFX: nhrp_if=%s", &lookup, + ri->nhrp_ifp->name); + + if (via) + sockunion_family(via) = AF_UNSPEC; + if (ifp) + *ifp = ri->nhrp_ifp; + } else { + debugf(NHRP_DEBUG_ROUTE, "lookup %pFX: zebra route dev %s", + &lookup, ri->ifp ? ri->ifp->name : "(none)"); + + if (via) + *via = ri->via; + if (ifp) + *ifp = ri->ifp; + } + if (p) + *p = rn->p; + route_unlock_node(rn); + return 1; +} + +enum nhrp_route_type nhrp_route_address(struct interface *in_ifp, + union sockunion *addr, struct prefix *p, + struct nhrp_peer **peer) +{ + struct interface *ifp = in_ifp; + struct nhrp_interface *nifp; + struct nhrp_cache *c; + union sockunion via[4]; + uint32_t network_id = 0; + afi_t afi = family2afi(sockunion_family(addr)); + int i; + + if (ifp) { + nifp = ifp->info; + network_id = nifp->afi[afi].network_id; + + c = nhrp_cache_get(ifp, addr, 0); + if (c && c->cur.type == NHRP_CACHE_LOCAL) { + if (p) + memset(p, 0, sizeof(*p)); + return NHRP_ROUTE_LOCAL; + } + } + + for (i = 0; i < 4; i++) { + if (!nhrp_route_get_nexthop(addr, p, &via[i], &ifp)) + return NHRP_ROUTE_BLACKHOLE; + if (ifp) { + /* Departing from nbma network? */ + nifp = ifp->info; + if (network_id + && network_id != nifp->afi[afi].network_id) + return NHRP_ROUTE_OFF_NBMA; + } + if (sockunion_family(&via[i]) == AF_UNSPEC) + break; + /* Resolve via node, but return the prefix of first match */ + addr = &via[i]; + p = NULL; + } + + if (ifp) { + c = nhrp_cache_get(ifp, addr, 0); + if (c && c->cur.type >= NHRP_CACHE_DYNAMIC) { + if (p) + memset(p, 0, sizeof(*p)); + if (c->cur.type == NHRP_CACHE_LOCAL) + return NHRP_ROUTE_LOCAL; + if (peer) + *peer = nhrp_peer_ref(c->cur.peer); + return NHRP_ROUTE_NBMA_NEXTHOP; + } + } + + return NHRP_ROUTE_BLACKHOLE; +} + +static void nhrp_zebra_connected(struct zclient *zclient) +{ + zclient_send_reg_requests(zclient, VRF_DEFAULT); + zebra_redistribute_send(ZEBRA_REDISTRIBUTE_ADD, zclient, AFI_IP, + ZEBRA_ROUTE_ALL, 0, VRF_DEFAULT); + zebra_redistribute_send(ZEBRA_REDISTRIBUTE_ADD, zclient, AFI_IP6, + ZEBRA_ROUTE_ALL, 0, VRF_DEFAULT); + nhrp_zebra_register_neigh(VRF_DEFAULT, AFI_IP, true); + nhrp_zebra_register_neigh(VRF_DEFAULT, AFI_IP6, true); +} + +static zclient_handler *const nhrp_handlers[] = { + [ZEBRA_INTERFACE_ADDRESS_ADD] = nhrp_interface_address_add, + [ZEBRA_INTERFACE_ADDRESS_DELETE] = nhrp_interface_address_delete, + [ZEBRA_REDISTRIBUTE_ROUTE_ADD] = nhrp_route_read, + [ZEBRA_REDISTRIBUTE_ROUTE_DEL] = nhrp_route_read, + [ZEBRA_NHRP_NEIGH_ADDED] = nhrp_neighbor_operation, + [ZEBRA_NHRP_NEIGH_REMOVED] = nhrp_neighbor_operation, + [ZEBRA_NHRP_NEIGH_GET] = nhrp_neighbor_operation, + [ZEBRA_GRE_UPDATE] = nhrp_gre_update, +}; + +void nhrp_zebra_init(void) +{ + zebra_rib[AFI_IP] = route_table_init(); + zebra_rib[AFI_IP6] = route_table_init(); + + zclient = zclient_new(master, &zclient_options_default, nhrp_handlers, + array_size(nhrp_handlers)); + zclient->zebra_connected = nhrp_zebra_connected; + zclient_init(zclient, ZEBRA_ROUTE_NHRP, 0, &nhrpd_privs); +} + +static void nhrp_table_node_cleanup(struct route_table *table, + struct route_node *node) +{ + if (!node->info) + return; + + XFREE(MTYPE_NHRP_ROUTE, node->info); +} + +void nhrp_send_zebra_configure_arp(struct interface *ifp, int family) +{ + struct stream *s; + + if (!zclient || zclient->sock < 0) { + debugf(NHRP_DEBUG_COMMON, "%s() : zclient not ready", + __func__); + return; + } + s = zclient->obuf; + stream_reset(s); + zclient_create_header(s, ZEBRA_CONFIGURE_ARP, ifp->vrf->vrf_id); + stream_putc(s, family); + stream_putl(s, ifp->ifindex); + stream_putw_at(s, 0, stream_get_endp(s)); + zclient_send_message(zclient); +} + +void nhrp_send_zebra_gre_source_set(struct interface *ifp, + unsigned int link_idx, + vrf_id_t link_vrf_id) +{ + struct stream *s; + + if (!zclient || zclient->sock < 0) { + zlog_err("%s : zclient not ready", __func__); + return; + } + if (link_idx == IFINDEX_INTERNAL || link_vrf_id == VRF_UNKNOWN) { + /* silently ignore */ + return; + } + s = zclient->obuf; + stream_reset(s); + zclient_create_header(s, ZEBRA_GRE_SOURCE_SET, ifp->vrf->vrf_id); + stream_putl(s, ifp->ifindex); + stream_putl(s, link_idx); + stream_putl(s, link_vrf_id); + stream_putl(s, 0); /* mtu provisioning */ + stream_putw_at(s, 0, stream_get_endp(s)); + zclient_send_message(zclient); +} + +void nhrp_send_zebra_nbr(union sockunion *in, + union sockunion *out, + struct interface *ifp) +{ + struct stream *s; + + if (!zclient || zclient->sock < 0) + return; + s = zclient->obuf; + stream_reset(s); + zclient_neigh_ip_encode(s, out ? ZEBRA_NEIGH_IP_ADD : + ZEBRA_NEIGH_IP_DEL, in, out, + ifp, out ? ZEBRA_NEIGH_STATE_REACHABLE + : ZEBRA_NEIGH_STATE_FAILED); + stream_putw_at(s, 0, stream_get_endp(s)); + zclient_send_message(zclient); +} + +int nhrp_send_zebra_gre_request(struct interface *ifp) +{ + return zclient_send_zebra_gre_request(zclient, ifp); +} + +void nhrp_zebra_terminate(void) +{ + nhrp_zebra_register_neigh(VRF_DEFAULT, AFI_IP, false); + nhrp_zebra_register_neigh(VRF_DEFAULT, AFI_IP6, false); + zclient_stop(zclient); + zclient_free(zclient); + + zebra_rib[AFI_IP]->cleanup = nhrp_table_node_cleanup; + zebra_rib[AFI_IP6]->cleanup = nhrp_table_node_cleanup; + route_table_finish(zebra_rib[AFI_IP]); + route_table_finish(zebra_rib[AFI_IP6]); +} + +int nhrp_gre_update(ZAPI_CALLBACK_ARGS) +{ + struct stream *s; + struct nhrp_gre_info gre_info, *val; + struct interface *ifp; + + /* result */ + s = zclient->ibuf; + if (vrf_id != VRF_DEFAULT) + return 0; + + /* read GRE information */ + STREAM_GETL(s, gre_info.ifindex); + STREAM_GETL(s, gre_info.ikey); + STREAM_GETL(s, gre_info.okey); + STREAM_GETL(s, gre_info.ifindex_link); + STREAM_GETL(s, gre_info.vrfid_link); + STREAM_GETL(s, gre_info.vtep_ip.s_addr); + STREAM_GETL(s, gre_info.vtep_ip_remote.s_addr); + if (gre_info.ifindex == IFINDEX_INTERNAL) + val = NULL; + else + val = hash_lookup(nhrp_gre_list, &gre_info); + if (val) { + if (gre_info.vtep_ip.s_addr != val->vtep_ip.s_addr || + gre_info.vrfid_link != val->vrfid_link || + gre_info.ifindex_link != val->ifindex_link || + gre_info.ikey != val->ikey || + gre_info.okey != val->okey) { + /* update */ + memcpy(val, &gre_info, sizeof(struct nhrp_gre_info)); + } + } else { + val = nhrp_gre_info_alloc(&gre_info); + } + ifp = if_lookup_by_index(gre_info.ifindex, vrf_id); + debugf(NHRP_DEBUG_EVENT, "%s: gre interface %d vr %d obtained from system", + ifp ? ifp->name : "", gre_info.ifindex, vrf_id); + if (ifp) + nhrp_interface_update_nbma(ifp, val); + return 0; + +stream_failure: + zlog_err("%s(): error reading response ..", __func__); + return -1; +} diff --git a/nhrpd/nhrp_shortcut.c b/nhrpd/nhrp_shortcut.c new file mode 100644 index 0000000..04dad2a --- /dev/null +++ b/nhrpd/nhrp_shortcut.c @@ -0,0 +1,539 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* NHRP shortcut related functions + * Copyright (c) 2014-2015 Timo Teräs + */ + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include "nhrpd.h" +#include "table.h" +#include "memory.h" +#include "frrevent.h" +#include "log.h" +#include "nhrp_protocol.h" + +DEFINE_MTYPE_STATIC(NHRPD, NHRP_SHORTCUT, "NHRP shortcut"); + +static struct route_table *shortcut_rib[AFI_MAX]; + +static void nhrp_shortcut_do_purge(struct event *t); +static void nhrp_shortcut_delete(struct nhrp_shortcut *s); +static void nhrp_shortcut_send_resolution_req(struct nhrp_shortcut *s); + +static void nhrp_shortcut_check_use(struct nhrp_shortcut *s) +{ + if (s->expiring && s->cache && s->cache->used) { + debugf(NHRP_DEBUG_ROUTE, "Shortcut %pFX used and expiring", + s->p); + nhrp_shortcut_send_resolution_req(s); + } +} + +static void nhrp_shortcut_do_expire(struct event *t) +{ + struct nhrp_shortcut *s = EVENT_ARG(t); + + event_add_timer(master, nhrp_shortcut_do_purge, s, s->holding_time / 3, + &s->t_timer); + s->expiring = 1; + nhrp_shortcut_check_use(s); +} + +static void nhrp_shortcut_cache_notify(struct notifier_block *n, + unsigned long cmd) +{ + struct nhrp_shortcut *s = + container_of(n, struct nhrp_shortcut, cache_notifier); + struct nhrp_cache *c = s->cache; + + switch (cmd) { + case NOTIFY_CACHE_UP: + if (!s->route_installed) { + debugf(NHRP_DEBUG_ROUTE, + "Shortcut: route install %pFX nh %pSU dev %s", + s->p, &c->remote_addr, + c && c->ifp ? c->ifp->name : ""); + + nhrp_route_announce(1, s->type, s->p, c ? c->ifp : NULL, + c ? &c->remote_addr : NULL, 0); + s->route_installed = 1; + } + break; + case NOTIFY_CACHE_USED: + nhrp_shortcut_check_use(s); + break; + case NOTIFY_CACHE_DOWN: + case NOTIFY_CACHE_DELETE: + if (s->route_installed) { + nhrp_route_announce(0, NHRP_CACHE_INVALID, s->p, NULL, + NULL, 0); + s->route_installed = 0; + } + if (cmd == NOTIFY_CACHE_DELETE) + nhrp_shortcut_delete(s); + break; + } +} + +static void nhrp_shortcut_update_binding(struct nhrp_shortcut *s, + enum nhrp_cache_type type, + struct nhrp_cache *c, int holding_time) +{ + s->type = type; + if (c != s->cache) { + if (s->cache) { + nhrp_cache_notify_del(s->cache, &s->cache_notifier); + s->cache = NULL; + } + s->cache = c; + if (s->cache) { + nhrp_cache_notify_add(s->cache, &s->cache_notifier, + nhrp_shortcut_cache_notify); + if (s->cache->route_installed) { + /* Force renewal of Zebra announce on prefix + * change */ + s->route_installed = 0; + debugf(NHRP_DEBUG_ROUTE, + "Shortcut: forcing renewal of zebra announce on prefix change peer %pSU ht %u cur nbma %pSU dev %s", + &s->cache->remote_addr, holding_time, + &s->cache->cur.remote_nbma_natoa, + s->cache->ifp->name); + nhrp_shortcut_cache_notify(&s->cache_notifier, + NOTIFY_CACHE_UP); + } + } + if (!s->cache || !s->cache->route_installed) { + debugf(NHRP_DEBUG_ROUTE, + "Shortcut: notify cache down because cache?%s or ri?%s", + s->cache ? "yes" : "no", + s->cache ? (s->cache->route_installed ? "yes" + : "no") + : "n/a"); + nhrp_shortcut_cache_notify(&s->cache_notifier, + NOTIFY_CACHE_DOWN); + } + } + if (s->type == NHRP_CACHE_NEGATIVE && !s->route_installed) { + nhrp_route_announce(1, s->type, s->p, NULL, NULL, 0); + s->route_installed = 1; + } else if (s->type == NHRP_CACHE_INVALID && s->route_installed) { + nhrp_route_announce(0, NHRP_CACHE_INVALID, s->p, NULL, NULL, 0); + s->route_installed = 0; + } + + EVENT_OFF(s->t_timer); + if (holding_time) { + s->expiring = 0; + s->holding_time = holding_time; + event_add_timer(master, nhrp_shortcut_do_expire, s, + 2 * holding_time / 3, &s->t_timer); + } +} + +static void nhrp_shortcut_delete(struct nhrp_shortcut *s) +{ + struct route_node *rn; + afi_t afi = family2afi(PREFIX_FAMILY(s->p)); + + EVENT_OFF(s->t_timer); + nhrp_reqid_free(&nhrp_packet_reqid, &s->reqid); + + debugf(NHRP_DEBUG_ROUTE, "Shortcut %pFX purged", s->p); + + nhrp_shortcut_update_binding(s, NHRP_CACHE_INVALID, NULL, 0); + + /* Delete node */ + rn = route_node_lookup(shortcut_rib[afi], s->p); + if (rn) { + XFREE(MTYPE_NHRP_SHORTCUT, rn->info); + rn->info = NULL; + route_unlock_node(rn); + route_unlock_node(rn); + } +} + +static void nhrp_shortcut_do_purge(struct event *t) +{ + struct nhrp_shortcut *s = EVENT_ARG(t); + s->t_timer = NULL; + nhrp_shortcut_delete(s); +} + +static struct nhrp_shortcut *nhrp_shortcut_get(struct prefix *p) +{ + struct nhrp_shortcut *s; + struct route_node *rn; + afi_t afi = family2afi(PREFIX_FAMILY(p)); + + if (!shortcut_rib[afi]) + return 0; + + rn = route_node_get(shortcut_rib[afi], p); + if (!rn->info) { + s = rn->info = XCALLOC(MTYPE_NHRP_SHORTCUT, + sizeof(struct nhrp_shortcut)); + s->type = NHRP_CACHE_INVALID; + s->p = &rn->p; + + debugf(NHRP_DEBUG_ROUTE, "Shortcut %pFX created", s->p); + } else { + s = rn->info; + route_unlock_node(rn); + } + return s; +} + +static void nhrp_shortcut_recv_resolution_rep(struct nhrp_reqid *reqid, + void *arg) +{ + struct nhrp_packet_parser *pp = arg; + struct interface *ifp = pp->ifp; + struct nhrp_interface *nifp = ifp->info; + struct nhrp_shortcut *s = + container_of(reqid, struct nhrp_shortcut, reqid); + struct nhrp_shortcut *ps; + struct nhrp_extension_header *ext; + struct nhrp_cie_header *cie; + struct nhrp_cache *c = NULL; + struct nhrp_cache *c_dst = NULL; + union sockunion *proto, cie_proto, *nbma, cie_nbma, nat_nbma; + struct prefix prefix, route_prefix; + struct zbuf extpl; + int holding_time = pp->if_ad->holdtime; + + nhrp_reqid_free(&nhrp_packet_reqid, &s->reqid); + EVENT_OFF(s->t_timer); + event_add_timer(master, nhrp_shortcut_do_purge, s, 1, &s->t_timer); + + if (pp->hdr->type != NHRP_PACKET_RESOLUTION_REPLY) { + if (pp->hdr->type == NHRP_PACKET_ERROR_INDICATION + && pp->hdr->u.error.code + == NHRP_ERROR_PROTOCOL_ADDRESS_UNREACHABLE) { + debugf(NHRP_DEBUG_COMMON, + "Shortcut: Resolution: Protocol address unreachable"); + nhrp_shortcut_update_binding(s, NHRP_CACHE_NEGATIVE, + NULL, holding_time); + } else { + debugf(NHRP_DEBUG_COMMON, + "Shortcut: Resolution failed"); + } + return; + } + + /* Minor sanity check */ + prefix2sockunion(s->p, &cie_proto); + if (!sockunion_same(&cie_proto, &pp->dst_proto)) { + debugf(NHRP_DEBUG_COMMON, + "Shortcut: Warning dst_proto altered from %pSU to %pSU", + &cie_proto, &pp->dst_proto); + ; + } + + /* One or more CIEs should be given as reply, we support only one */ + cie = nhrp_cie_pull(&pp->payload, pp->hdr, &cie_nbma, &cie_proto); + if (!cie || cie->code != NHRP_CODE_SUCCESS) { + debugf(NHRP_DEBUG_COMMON, "Shortcut: CIE code %d", + cie ? cie->code : -1); + return; + } + + proto = sockunion_family(&cie_proto) != AF_UNSPEC ? &cie_proto + : &pp->dst_proto; + if (cie->holding_time) + holding_time = htons(cie->holding_time); + + prefix = *s->p; + prefix.prefixlen = cie->prefix_length; + + /* Sanity check prefix length */ + if (prefix.prefixlen >= 8 * prefix_blen(&prefix) + || prefix.prefixlen == 0) { + prefix.prefixlen = 8 * prefix_blen(&prefix); + } else if (nhrp_route_address(NULL, &pp->dst_proto, &route_prefix, NULL) + == NHRP_ROUTE_NBMA_NEXTHOP) { + if (prefix.prefixlen < route_prefix.prefixlen) + prefix.prefixlen = route_prefix.prefixlen; + } + + /* Parse extensions */ + memset(&nat_nbma, 0, sizeof(nat_nbma)); + while ((ext = nhrp_ext_pull(&pp->extensions, &extpl)) != NULL) { + switch (htons(ext->type) & ~NHRP_EXTENSION_FLAG_COMPULSORY) { + case NHRP_EXTENSION_NAT_ADDRESS: { + struct nhrp_cie_header *cie_nat; + + do { + union sockunion cie_nat_proto, cie_nat_nbma; + + sockunion_family(&cie_nat_proto) = AF_UNSPEC; + sockunion_family(&cie_nat_nbma) = AF_UNSPEC; + cie_nat = nhrp_cie_pull(&extpl, pp->hdr, + &cie_nat_nbma, + &cie_nat_proto); + /* We are interested only in peer CIE */ + if (cie_nat + && sockunion_same(&cie_nat_proto, proto)) { + nat_nbma = cie_nat_nbma; + } + } while (cie_nat); + } break; + default: + break; + } + } + + /* Update cache entry for the protocol to nbma binding */ + if (sockunion_family(&nat_nbma) != AF_UNSPEC) { + debugf(NHRP_DEBUG_COMMON, + "Shortcut: NAT detected (NAT extension) proto %pSU NBMA %pSU claimed-NBMA %pSU", + proto, &nat_nbma, &cie_nbma); + nbma = &nat_nbma; + } + /* For NHRP resolution reply the cie_nbma in mandatory part is the + * address of the actual address of the sender + */ + else if (!sockunion_same(&cie_nbma, &pp->peer->vc->remote.nbma) + && !nhrp_nhs_match_ip(&pp->peer->vc->remote.nbma, nifp)) { + debugf(NHRP_DEBUG_COMMON, + "Shortcut: NAT detected (no NAT Extension) proto %pSU NBMA %pSU claimed-NBMA %pSU", + proto, &pp->peer->vc->remote.nbma, &cie_nbma); + nbma = &pp->peer->vc->remote.nbma; + nat_nbma = *nbma; + } else { + nbma = &cie_nbma; + } + + debugf(NHRP_DEBUG_COMMON, + "Shortcut: %pFX is at proto %pSU dst_proto %pSU NBMA %pSU cie-holdtime %d", + &prefix, proto, &pp->dst_proto, nbma, + htons(cie->holding_time)); + + if (sockunion_family(nbma)) { + c = nhrp_cache_get(pp->ifp, proto, 1); + if (c) { + debugf(NHRP_DEBUG_COMMON, + "Shortcut: cache found, update binding"); + nhrp_cache_update_binding(c, NHRP_CACHE_DYNAMIC, + holding_time, + nhrp_peer_get(pp->ifp, nbma), + htons(cie->mtu), + nbma, + &cie_nbma); + } else { + debugf(NHRP_DEBUG_COMMON, + "Shortcut: no cache for proto %pSU", proto); + } + + /* Update cache binding for dst_proto as well */ + if (sockunion_cmp(proto, &pp->dst_proto)) { + c_dst = nhrp_cache_get(pp->ifp, &pp->dst_proto, 1); + if (c_dst) { + debugf(NHRP_DEBUG_COMMON, + "Shortcut: cache found, update binding"); + nhrp_cache_update_binding(c_dst, + NHRP_CACHE_DYNAMIC, + holding_time, + nhrp_peer_get(pp->ifp, nbma), + htons(cie->mtu), + nbma, + &cie_nbma); + } else { + debugf(NHRP_DEBUG_COMMON, + "Shortcut: no cache for proto %pSU", + &pp->dst_proto); + } + } + } + + /* Update shortcut entry for subnet to protocol gw binding */ + if (c) { + ps = nhrp_shortcut_get(&prefix); + if (ps) { + ps->addr = s->addr; + debugf(NHRP_DEBUG_COMMON, + "Shortcut: calling update_binding"); + nhrp_shortcut_update_binding(ps, NHRP_CACHE_DYNAMIC, c, + holding_time); + } else { + debugf(NHRP_DEBUG_COMMON, + "Shortcut: proto diff but no ps"); + } + } else { + debugf(NHRP_DEBUG_COMMON, + "NO Shortcut because c NULL?%s or same proto?%s", + c ? "no" : "yes", + proto && pp && sockunion_same(proto, &pp->dst_proto) + ? "yes" + : "no"); + } + + debugf(NHRP_DEBUG_COMMON, "Shortcut: Resolution reply handled"); +} + +static void nhrp_shortcut_send_resolution_req(struct nhrp_shortcut *s) +{ + struct zbuf *zb; + struct nhrp_packet_header *hdr; + struct interface *ifp; + struct nhrp_interface *nifp; + struct nhrp_afi_data *if_ad; + struct nhrp_peer *peer; + struct nhrp_cie_header *cie; + struct nhrp_extension_header *ext; + + if (nhrp_route_address(NULL, &s->addr, NULL, &peer) + != NHRP_ROUTE_NBMA_NEXTHOP) + return; + + if (s->type == NHRP_CACHE_INVALID || s->type == NHRP_CACHE_NEGATIVE) + s->type = NHRP_CACHE_INCOMPLETE; + + ifp = peer->ifp; + nifp = ifp->info; + + /* Create request */ + zb = zbuf_alloc(1500); + hdr = nhrp_packet_push( + zb, NHRP_PACKET_RESOLUTION_REQUEST, &nifp->nbma, + &nifp->afi[family2afi(sockunion_family(&s->addr))].addr, + &s->addr); + hdr->u.request_id = + htonl(nhrp_reqid_alloc(&nhrp_packet_reqid, &s->reqid, + nhrp_shortcut_recv_resolution_rep)); + hdr->flags = htons(NHRP_FLAG_RESOLUTION_SOURCE_IS_ROUTER + | NHRP_FLAG_RESOLUTION_AUTHORATIVE + | NHRP_FLAG_RESOLUTION_SOURCE_STABLE); + + /* RFC2332 - One or zero CIEs, if CIE is present contains: + * - Prefix length: widest acceptable prefix we accept (if U set, 0xff) + * - MTU: MTU of the source station + * - Holding Time: Max time to cache the source information + * */ + /* FIXME: push CIE for each local protocol address */ + cie = nhrp_cie_push(zb, NHRP_CODE_SUCCESS, NULL, NULL); + if_ad = &nifp->afi[family2afi(sockunion_family(&s->addr))]; + cie->prefix_length = (if_ad->flags & NHRP_IFF_REG_NO_UNIQUE) + ? 8 * sockunion_get_addrlen(&s->addr) + : 0xff; + cie->holding_time = htons(if_ad->holdtime); + cie->mtu = htons(if_ad->mtu); + debugf(NHRP_DEBUG_COMMON, + "Shortcut res_req: set cie ht to %u and mtu to %u. shortcut ht is %u", + ntohs(cie->holding_time), ntohs(cie->mtu), s->holding_time); + + nhrp_ext_request(zb, hdr, ifp); + + /* Cisco NAT detection extension */ + hdr->flags |= htons(NHRP_FLAG_RESOLUTION_NAT); + ext = nhrp_ext_push(zb, hdr, NHRP_EXTENSION_NAT_ADDRESS); + if (sockunion_family(&nifp->nat_nbma) != AF_UNSPEC) { + cie = nhrp_cie_push(zb, NHRP_CODE_SUCCESS, &nifp->nat_nbma, + &if_ad->addr); + cie->prefix_length = 8 * sockunion_get_addrlen(&if_ad->addr); + cie->mtu = htons(if_ad->mtu); + nhrp_ext_complete(zb, ext); + } + + nhrp_packet_complete(zb, hdr); + + nhrp_peer_send(peer, zb); + nhrp_peer_unref(peer); + zbuf_free(zb); +} + +void nhrp_shortcut_initiate(union sockunion *addr) +{ + struct prefix p; + struct nhrp_shortcut *s; + + if (!sockunion2hostprefix(addr, &p)) + return; + + s = nhrp_shortcut_get(&p); + if (s && s->type != NHRP_CACHE_INCOMPLETE) { + s->addr = *addr; + EVENT_OFF(s->t_timer); + event_add_timer(master, nhrp_shortcut_do_purge, s, 30, + &s->t_timer); + nhrp_shortcut_send_resolution_req(s); + } +} + +void nhrp_shortcut_init(void) +{ + shortcut_rib[AFI_IP] = route_table_init(); + shortcut_rib[AFI_IP6] = route_table_init(); +} + +void nhrp_shortcut_terminate(void) +{ + route_table_finish(shortcut_rib[AFI_IP]); + route_table_finish(shortcut_rib[AFI_IP6]); +} + +void nhrp_shortcut_foreach(afi_t afi, + void (*cb)(struct nhrp_shortcut *, void *), + void *ctx) +{ + struct route_table *rt = shortcut_rib[afi]; + struct route_node *rn; + route_table_iter_t iter; + + if (!rt) + return; + + route_table_iter_init(&iter, rt); + while ((rn = route_table_iter_next(&iter)) != NULL) { + if (rn->info) + cb(rn->info, ctx); + } + route_table_iter_cleanup(&iter); +} + +struct purge_ctx { + const struct prefix *p; + int deleted; +}; + +void nhrp_shortcut_purge(struct nhrp_shortcut *s, int force) +{ + EVENT_OFF(s->t_timer); + nhrp_reqid_free(&nhrp_packet_reqid, &s->reqid); + + if (force) { + /* Immediate purge on route with draw or pending shortcut */ + event_add_timer_msec(master, nhrp_shortcut_do_purge, s, 5, + &s->t_timer); + } else { + /* Soft expire - force immediate renewal, but purge + * in few seconds to make sure stale route is not + * used too long. In practice most purges are caused + * by hub bgp change, but target usually stays same. + * This allows to keep nhrp route up, and to not + * cause temporary rerouting via hubs causing latency + * jitter. */ + event_add_timer_msec(master, nhrp_shortcut_do_purge, s, 3000, + &s->t_timer); + s->expiring = 1; + nhrp_shortcut_check_use(s); + } +} + +static void nhrp_shortcut_purge_prefix(struct nhrp_shortcut *s, void *ctx) +{ + struct purge_ctx *pctx = ctx; + + if (prefix_match(pctx->p, s->p)) + nhrp_shortcut_purge(s, pctx->deleted || !s->cache); +} + +void nhrp_shortcut_prefix_change(const struct prefix *p, int deleted) +{ + struct purge_ctx pctx = { + .p = p, .deleted = deleted, + }; + nhrp_shortcut_foreach(family2afi(PREFIX_FAMILY(p)), + nhrp_shortcut_purge_prefix, &pctx); +} diff --git a/nhrpd/nhrp_vc.c b/nhrpd/nhrp_vc.c new file mode 100644 index 0000000..2c32014 --- /dev/null +++ b/nhrpd/nhrp_vc.c @@ -0,0 +1,217 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* NHRP virtual connection + * Copyright (c) 2014-2015 Timo Teräs + */ + +#include "zebra.h" +#include "memory.h" +#include "stream.h" +#include "hash.h" +#include "frrevent.h" +#include "jhash.h" + +#include "nhrpd.h" +#include "os.h" + +DEFINE_MTYPE_STATIC(NHRPD, NHRP_VC, "NHRP virtual connection"); + +PREDECL_DLIST(childlist); + +struct child_sa { + uint32_t id; + struct nhrp_vc *vc; + struct childlist_item childlist_entry; +}; + +DECLARE_DLIST(childlist, struct child_sa, childlist_entry); + +static struct hash *nhrp_vc_hash; +static struct childlist_head childlist_head[512]; + +static unsigned int nhrp_vc_key(const void *peer_data) +{ + const struct nhrp_vc *vc = peer_data; + return jhash_2words(sockunion_hash(&vc->local.nbma), + sockunion_hash(&vc->remote.nbma), 0); +} + +static bool nhrp_vc_cmp(const void *cache_data, const void *key_data) +{ + const struct nhrp_vc *a = cache_data; + const struct nhrp_vc *b = key_data; + + return sockunion_same(&a->local.nbma, &b->local.nbma) + && sockunion_same(&a->remote.nbma, &b->remote.nbma); +} + +static void *nhrp_vc_alloc(void *data) +{ + struct nhrp_vc *vc, *key = data; + + vc = XMALLOC(MTYPE_NHRP_VC, sizeof(struct nhrp_vc)); + + *vc = (struct nhrp_vc){ + .local.nbma = key->local.nbma, + .remote.nbma = key->remote.nbma, + .notifier_list = + NOTIFIER_LIST_INITIALIZER(&vc->notifier_list), + }; + + return vc; +} + +static void nhrp_vc_free(void *data) +{ + XFREE(MTYPE_NHRP_VC, data); +} + +struct nhrp_vc *nhrp_vc_get(const union sockunion *src, + const union sockunion *dst, int create) +{ + struct nhrp_vc key; + key.local.nbma = *src; + key.remote.nbma = *dst; + return hash_get(nhrp_vc_hash, &key, create ? nhrp_vc_alloc : 0); +} + +static void nhrp_vc_check_delete(struct nhrp_vc *vc) +{ + if (vc->updating || vc->ipsec || notifier_active(&vc->notifier_list)) + return; + hash_release(nhrp_vc_hash, vc); + nhrp_vc_free(vc); +} + +static void nhrp_vc_update(struct nhrp_vc *vc, long cmd) +{ + vc->updating = 1; + notifier_call(&vc->notifier_list, cmd); + vc->updating = 0; + nhrp_vc_check_delete(vc); +} + +static void nhrp_vc_ipsec_reset(struct nhrp_vc *vc) +{ + vc->local.id[0] = 0; + vc->local.certlen = 0; + vc->remote.id[0] = 0; + vc->remote.certlen = 0; +} + +int nhrp_vc_ipsec_updown(uint32_t child_id, struct nhrp_vc *vc) +{ + struct child_sa *sa = NULL, *lsa; + uint32_t child_hash = child_id % array_size(childlist_head); + int abort_migration = 0; + + frr_each (childlist, &childlist_head[child_hash], lsa) { + if (lsa->id == child_id) { + sa = lsa; + break; + } + } + + if (!sa) { + if (!vc) + return 0; + + sa = XMALLOC(MTYPE_NHRP_VC, sizeof(struct child_sa)); + + *sa = (struct child_sa){ + .id = child_id, + .vc = NULL, + }; + childlist_add_tail(&childlist_head[child_hash], sa); + } + + if (sa->vc == vc) + return 0; + + if (vc) { + /* Attach first to new VC */ + vc->ipsec++; + nhrp_vc_update(vc, NOTIFY_VC_IPSEC_CHANGED); + } + if (sa->vc && vc) { + /* Notify old VC of migration */ + sa->vc->abort_migration = 0; + debugf(NHRP_DEBUG_COMMON, "IPsec NBMA change of %pSU to %pSU", + &sa->vc->remote.nbma, &vc->remote.nbma); + nhrp_vc_update(sa->vc, NOTIFY_VC_IPSEC_UPDATE_NBMA); + abort_migration = sa->vc->abort_migration; + } + if (sa->vc) { + /* Deattach old VC */ + sa->vc->ipsec--; + if (!sa->vc->ipsec) + nhrp_vc_ipsec_reset(sa->vc); + nhrp_vc_update(sa->vc, NOTIFY_VC_IPSEC_CHANGED); + } + + /* Update */ + sa->vc = vc; + if (!vc) { + childlist_del(&childlist_head[child_hash], sa); + XFREE(MTYPE_NHRP_VC, sa); + } + + return abort_migration; +} + +void nhrp_vc_notify_add(struct nhrp_vc *vc, struct notifier_block *n, + notifier_fn_t action) +{ + notifier_add(n, &vc->notifier_list, action); +} + +void nhrp_vc_notify_del(struct nhrp_vc *vc, struct notifier_block *n) +{ + notifier_del(n, &vc->notifier_list); + nhrp_vc_check_delete(vc); +} + + +struct nhrp_vc_iterator_ctx { + void (*cb)(struct nhrp_vc *, void *); + void *ctx; +}; + +static void nhrp_vc_iterator(struct hash_bucket *b, void *ctx) +{ + struct nhrp_vc_iterator_ctx *ic = ctx; + ic->cb(b->data, ic->ctx); +} + +void nhrp_vc_foreach(void (*cb)(struct nhrp_vc *, void *), void *ctx) +{ + struct nhrp_vc_iterator_ctx ic = { + .cb = cb, .ctx = ctx, + }; + hash_iterate(nhrp_vc_hash, nhrp_vc_iterator, &ic); +} + +void nhrp_vc_init(void) +{ + size_t i; + + nhrp_vc_hash = hash_create(nhrp_vc_key, nhrp_vc_cmp, "NHRP VC hash"); + for (i = 0; i < array_size(childlist_head); i++) + childlist_init(&childlist_head[i]); +} + +void nhrp_vc_reset(void) +{ + struct child_sa *sa; + size_t i; + + for (i = 0; i < array_size(childlist_head); i++) { + frr_each_safe (childlist, &childlist_head[i], sa) + nhrp_vc_ipsec_updown(sa->id, 0); + } +} + +void nhrp_vc_terminate(void) +{ + nhrp_vc_reset(); + hash_clean(nhrp_vc_hash, nhrp_vc_free); +} diff --git a/nhrpd/nhrp_vty.c b/nhrpd/nhrp_vty.c new file mode 100644 index 0000000..40d38c4 --- /dev/null +++ b/nhrpd/nhrp_vty.c @@ -0,0 +1,1271 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* NHRP vty handling + * Copyright (c) 2014-2015 Timo Teräs + */ + +#include "zebra.h" +#include "command.h" +#include "zclient.h" +#include "stream.h" +#include "filter.h" +#include "json.h" + +#include "nhrpd.h" +#include "netlink.h" + +static int nhrp_config_write(struct vty *vty); +static struct cmd_node zebra_node = { + .name = "zebra", + .node = ZEBRA_NODE, + .parent_node = CONFIG_NODE, + .prompt = "%s(config-router)# ", + .config_write = nhrp_config_write, +}; + +#define NHRP_DEBUG_FLAGS_CMD "" + +#define NHRP_DEBUG_FLAGS_STR \ + "All messages\n" \ + "Common messages (default)\n" \ + "Event manager messages\n" \ + "Interface messages\n" \ + "Kernel messages\n" \ + "Route messages\n" \ + "VICI messages\n" + +static const struct message debug_flags_desc[] = { + {NHRP_DEBUG_ALL, "all"}, {NHRP_DEBUG_COMMON, "common"}, + {NHRP_DEBUG_IF, "interface"}, {NHRP_DEBUG_KERNEL, "kernel"}, + {NHRP_DEBUG_ROUTE, "route"}, {NHRP_DEBUG_VICI, "vici"}, + {NHRP_DEBUG_EVENT, "event"}, {0}}; + +static const struct message interface_flags_desc[] = { + {NHRP_IFF_SHORTCUT, "shortcut"}, + {NHRP_IFF_REDIRECT, "redirect"}, + {NHRP_IFF_REG_NO_UNIQUE, "registration no-unique"}, + {0}}; + +static int nhrp_vty_return(struct vty *vty, int ret) +{ + static const char *const errmsgs[] = { + [NHRP_ERR_FAIL] = "Command failed", + [NHRP_ERR_NO_MEMORY] = "Out of memory", + [NHRP_ERR_UNSUPPORTED_INTERFACE] = + "NHRP not supported on this interface", + [NHRP_ERR_NHRP_NOT_ENABLED] = + "NHRP not enabled (set 'nhrp network-id' first)", + [NHRP_ERR_ENTRY_EXISTS] = "Entry exists already", + [NHRP_ERR_ENTRY_NOT_FOUND] = "Entry not found", + [NHRP_ERR_PROTOCOL_ADDRESS_MISMATCH] = + "Protocol address family does not match command (ip/ipv6 mismatch)", + }; + const char *str = NULL; + char buf[256]; + + if (ret == NHRP_OK) + return CMD_SUCCESS; + + if (ret > 0 && ret <= NHRP_ERR_MAX) + if (errmsgs[ret]) + str = errmsgs[ret]; + + if (!str) { + str = buf; + snprintf(buf, sizeof(buf), "Unknown error %d", ret); + } + + vty_out(vty, "%% %s\n", str); + + return CMD_WARNING_CONFIG_FAILED; + ; +} + +static int toggle_flag(struct vty *vty, const struct message *flag_desc, + const char *name, int on_off, unsigned *flags) +{ + int i; + + for (i = 0; flag_desc[i].str != NULL; i++) { + if (strcmp(flag_desc[i].str, name) != 0) + continue; + if (on_off) + *flags |= flag_desc[i].key; + else + *flags &= ~flag_desc[i].key; + return CMD_SUCCESS; + } + + vty_out(vty, "%% Invalid value %s\n", name); + return CMD_WARNING_CONFIG_FAILED; + ; +} + +#ifndef NO_DEBUG + +DEFUN_NOSH(show_debugging_nhrp, show_debugging_nhrp_cmd, + "show debugging [nhrp]", + SHOW_STR + "Debugging information\n" + "NHRP configuration\n") +{ + int i; + + vty_out(vty, "NHRP debugging status:\n"); + + for (i = 0; debug_flags_desc[i].str != NULL; i++) { + if (debug_flags_desc[i].key == NHRP_DEBUG_ALL) + continue; + if (!(debug_flags_desc[i].key & debug_flags)) + continue; + + vty_out(vty, " NHRP %s debugging is on\n", + debug_flags_desc[i].str); + } + + cmd_show_lib_debugs(vty); + + return CMD_SUCCESS; +} + +DEFUN(debug_nhrp, debug_nhrp_cmd, + "debug nhrp " NHRP_DEBUG_FLAGS_CMD, + "Enable debug messages for specific or all parts.\n" + "NHRP information\n" + NHRP_DEBUG_FLAGS_STR) +{ + return toggle_flag(vty, debug_flags_desc, argv[2]->text, 1, + &debug_flags); +} + +DEFUN(no_debug_nhrp, no_debug_nhrp_cmd, + "no debug nhrp " NHRP_DEBUG_FLAGS_CMD, + NO_STR + "Disable debug messages for specific or all parts.\n" + "NHRP information\n" + NHRP_DEBUG_FLAGS_STR) +{ + return toggle_flag(vty, debug_flags_desc, argv[3]->text, 0, + &debug_flags); +} + +#endif /* NO_DEBUG */ + +static int nhrp_config_write(struct vty *vty) +{ +#ifndef NO_DEBUG + if (debug_flags == NHRP_DEBUG_ALL) { + vty_out(vty, "debug nhrp all\n"); + } else { + int i; + + for (i = 0; debug_flags_desc[i].str != NULL; i++) { + if (debug_flags_desc[i].key == NHRP_DEBUG_ALL) + continue; + if (!(debug_flags & debug_flags_desc[i].key)) + continue; + vty_out(vty, "debug nhrp %s\n", + debug_flags_desc[i].str); + } + } + vty_out(vty, "!\n"); +#endif /* NO_DEBUG */ + + if (nhrp_event_socket_path) { + vty_out(vty, "nhrp event socket %s\n", nhrp_event_socket_path); + } + if (netlink_nflog_group) { + vty_out(vty, "nhrp nflog-group %d\n", netlink_nflog_group); + } + if (netlink_mcast_nflog_group) + vty_out(vty, "nhrp multicast-nflog-group %d\n", + netlink_mcast_nflog_group); + + return 0; +} + +#define IP_STR "IP information\n" +#define IPV6_STR "IPv6 information\n" +#define AFI_CMD "" +#define AFI_STR IP_STR IPV6_STR +#define NHRP_STR "Next Hop Resolution Protocol functions\n" + +static afi_t cmd_to_afi(const struct cmd_token *tok) +{ + return strcmp(tok->text, "ipv6") == 0 ? AFI_IP6 : AFI_IP; +} + +static const char *afi_to_cmd(afi_t afi) +{ + if (afi == AFI_IP6) + return "ipv6"; + return "ip"; +} + +DEFUN(nhrp_event_socket, nhrp_event_socket_cmd, + "nhrp event socket SOCKET", + NHRP_STR + "Event Manager commands\n" + "Event Manager unix socket path\n" + "Unix path for the socket\n") +{ + evmgr_set_socket(argv[3]->arg); + return CMD_SUCCESS; +} + +DEFUN(no_nhrp_event_socket, no_nhrp_event_socket_cmd, + "no nhrp event socket [SOCKET]", + NO_STR + NHRP_STR + "Event Manager commands\n" + "Event Manager unix socket path\n" + "Unix path for the socket\n") +{ + evmgr_set_socket(NULL); + return CMD_SUCCESS; +} + +DEFUN(nhrp_nflog_group, nhrp_nflog_group_cmd, + "nhrp nflog-group (1-65535)", + NHRP_STR + "Specify NFLOG group number\n" + "NFLOG group number\n") +{ + uint32_t nfgroup; + + nfgroup = strtoul(argv[2]->arg, NULL, 10); + netlink_set_nflog_group(nfgroup); + + return CMD_SUCCESS; +} + +DEFUN(no_nhrp_nflog_group, no_nhrp_nflog_group_cmd, + "no nhrp nflog-group [(1-65535)]", + NO_STR + NHRP_STR + "Specify NFLOG group number\n" + "NFLOG group number\n") +{ + netlink_set_nflog_group(0); + return CMD_SUCCESS; +} + +DEFUN(nhrp_multicast_nflog_group, nhrp_multicast_nflog_group_cmd, + "nhrp multicast-nflog-group (1-65535)", + NHRP_STR + "Specify NFLOG group number for Multicast Packets\n" + "NFLOG group number\n") +{ + uint32_t nfgroup; + + nfgroup = strtoul(argv[2]->arg, NULL, 10); + netlink_mcast_set_nflog_group(nfgroup); + + return CMD_SUCCESS; +} + +DEFUN(no_nhrp_multicast_nflog_group, no_nhrp_multicast_nflog_group_cmd, + "no nhrp multicast-nflog-group [(1-65535)]", + NO_STR + NHRP_STR + "Specify NFLOG group number\n" + "NFLOG group number\n") +{ + netlink_mcast_set_nflog_group(0); + return CMD_SUCCESS; +} + +DEFUN(tunnel_protection, tunnel_protection_cmd, + "tunnel protection vici profile PROFILE [fallback-profile FALLBACK]", + "NHRP/GRE integration\n" + "IPsec protection\n" + "VICI (StrongSwan)\n" + "IPsec profile\n" + "IPsec profile name\n" + "Fallback IPsec profile\n" + "Fallback IPsec profile name\n") +{ + VTY_DECLVAR_CONTEXT(interface, ifp); + + nhrp_interface_set_protection(ifp, argv[4]->arg, + argc > 6 ? argv[6]->arg : NULL); + return CMD_SUCCESS; +} + +DEFUN(no_tunnel_protection, no_tunnel_protection_cmd, + "no tunnel protection", + NO_STR + "NHRP/GRE integration\n" + "IPsec protection\n") +{ + VTY_DECLVAR_CONTEXT(interface, ifp); + + nhrp_interface_set_protection(ifp, NULL, NULL); + return CMD_SUCCESS; +} + +DEFUN(tunnel_source, tunnel_source_cmd, + "tunnel source INTERFACE", + "NHRP/GRE integration\n" + "Tunnel device binding tracking\n" + "Interface name\n") +{ + VTY_DECLVAR_CONTEXT(interface, ifp); + nhrp_interface_set_source(ifp, argv[2]->arg); + return CMD_SUCCESS; +} + +DEFUN(no_tunnel_source, no_tunnel_source_cmd, + "no tunnel source", + "NHRP/GRE integration\n" + "Tunnel device binding tracking\n" + "Interface name\n") +{ + VTY_DECLVAR_CONTEXT(interface, ifp); + nhrp_interface_set_source(ifp, NULL); + return CMD_SUCCESS; +} + +DEFUN(if_nhrp_network_id, if_nhrp_network_id_cmd, + AFI_CMD " nhrp network-id (1-4294967295)", + AFI_STR + NHRP_STR + "Enable NHRP and specify network-id\n" + "System local ID to specify interface group\n") +{ + VTY_DECLVAR_CONTEXT(interface, ifp); + struct nhrp_interface *nifp = ifp->info; + afi_t afi = cmd_to_afi(argv[0]); + + nifp->afi[afi].network_id = strtoul(argv[3]->arg, NULL, 10); + nhrp_interface_update(ifp); + + return CMD_SUCCESS; +} + +DEFUN(if_no_nhrp_network_id, if_no_nhrp_network_id_cmd, + "no " AFI_CMD " nhrp network-id [(1-4294967295)]", + NO_STR + AFI_STR + NHRP_STR + "Enable NHRP and specify network-id\n" + "System local ID to specify interface group\n") +{ + VTY_DECLVAR_CONTEXT(interface, ifp); + struct nhrp_interface *nifp = ifp->info; + afi_t afi = cmd_to_afi(argv[1]); + + nifp->afi[afi].network_id = 0; + nhrp_interface_update(ifp); + + return CMD_SUCCESS; +} + +DEFUN(if_nhrp_flags, if_nhrp_flags_cmd, + AFI_CMD " nhrp ", + AFI_STR + NHRP_STR + "Allow shortcut establishment\n" + "Send redirect notifications\n") +{ + VTY_DECLVAR_CONTEXT(interface, ifp); + struct nhrp_interface *nifp = ifp->info; + afi_t afi = cmd_to_afi(argv[0]); + + return toggle_flag(vty, interface_flags_desc, argv[2]->text, 1, + &nifp->afi[afi].flags); +} + +DEFUN(if_no_nhrp_flags, if_no_nhrp_flags_cmd, + "no " AFI_CMD " nhrp ", + NO_STR + AFI_STR + NHRP_STR + "Allow shortcut establishment\n" + "Send redirect notifications\n") +{ + VTY_DECLVAR_CONTEXT(interface, ifp); + struct nhrp_interface *nifp = ifp->info; + afi_t afi = cmd_to_afi(argv[1]); + + return toggle_flag(vty, interface_flags_desc, argv[3]->text, 0, + &nifp->afi[afi].flags); +} + +DEFUN(if_nhrp_reg_flags, if_nhrp_reg_flags_cmd, + AFI_CMD " nhrp registration no-unique", + AFI_STR + NHRP_STR + "Registration configuration\n" + "Don't set unique flag\n") +{ + VTY_DECLVAR_CONTEXT(interface, ifp); + struct nhrp_interface *nifp = ifp->info; + afi_t afi = cmd_to_afi(argv[0]); + char name[256]; + snprintf(name, sizeof(name), "registration %s", argv[3]->text); + return toggle_flag(vty, interface_flags_desc, name, 1, + &nifp->afi[afi].flags); +} + +DEFUN(if_no_nhrp_reg_flags, if_no_nhrp_reg_flags_cmd, + "no " AFI_CMD " nhrp registration no-unique", + NO_STR + AFI_STR + NHRP_STR + "Registration configuration\n" + "Don't set unique flag\n") +{ + VTY_DECLVAR_CONTEXT(interface, ifp); + struct nhrp_interface *nifp = ifp->info; + afi_t afi = cmd_to_afi(argv[1]); + char name[256]; + snprintf(name, sizeof(name), "registration %s", argv[4]->text); + return toggle_flag(vty, interface_flags_desc, name, 0, + &nifp->afi[afi].flags); +} + +DEFUN(if_nhrp_holdtime, if_nhrp_holdtime_cmd, + AFI_CMD " nhrp holdtime (1-65000)", + AFI_STR + NHRP_STR + "Specify NBMA address validity time\n" + "Time in seconds that NBMA addresses are advertised valid\n") +{ + VTY_DECLVAR_CONTEXT(interface, ifp); + struct nhrp_interface *nifp = ifp->info; + afi_t afi = cmd_to_afi(argv[0]); + + nifp->afi[afi].holdtime = strtoul(argv[3]->arg, NULL, 10); + nhrp_interface_update(ifp); + + return CMD_SUCCESS; +} + +DEFUN(if_no_nhrp_holdtime, if_no_nhrp_holdtime_cmd, + "no " AFI_CMD " nhrp holdtime [(1-65000)]", + NO_STR + AFI_STR + NHRP_STR + "Specify NBMA address validity time\n" + "Time in seconds that NBMA addresses are advertised valid\n") +{ + VTY_DECLVAR_CONTEXT(interface, ifp); + struct nhrp_interface *nifp = ifp->info; + afi_t afi = cmd_to_afi(argv[1]); + + nifp->afi[afi].holdtime = NHRPD_DEFAULT_HOLDTIME; + nhrp_interface_update(ifp); + + return CMD_SUCCESS; +} + +DEFUN(if_nhrp_mtu, if_nhrp_mtu_cmd, + "ip nhrp mtu <(576-1500)|opennhrp>", + IP_STR + NHRP_STR + "Configure NHRP advertised MTU\n" + "MTU value\n" + "Advertise bound interface MTU similar to OpenNHRP\n") +{ + VTY_DECLVAR_CONTEXT(interface, ifp); + struct nhrp_interface *nifp = ifp->info; + + if (argv[3]->arg[0] == 'o') { + nifp->afi[AFI_IP].configured_mtu = -1; + } else { + nifp->afi[AFI_IP].configured_mtu = + strtoul(argv[3]->arg, NULL, 10); + } + nhrp_interface_update_mtu(ifp, AFI_IP); + + return CMD_SUCCESS; +} + +DEFUN(if_no_nhrp_mtu, if_no_nhrp_mtu_cmd, + "no ip nhrp mtu [(576-1500)|opennhrp]", + NO_STR + IP_STR + NHRP_STR + "Configure NHRP advertised MTU\n" + "MTU value\n" + "Advertise bound interface MTU similar to OpenNHRP\n") +{ + VTY_DECLVAR_CONTEXT(interface, ifp); + struct nhrp_interface *nifp = ifp->info; + + nifp->afi[AFI_IP].configured_mtu = 0; + nhrp_interface_update_mtu(ifp, AFI_IP); + return CMD_SUCCESS; +} + +DEFUN(if_nhrp_map, if_nhrp_map_cmd, + AFI_CMD " nhrp map ", + AFI_STR + NHRP_STR + "Nexthop Server configuration\n" + "IPv4 protocol address\n" + "IPv6 protocol address\n" + "IPv4 NBMA address\n" + "Handle protocol address locally\n") +{ + VTY_DECLVAR_CONTEXT(interface, ifp); + afi_t afi = cmd_to_afi(argv[0]); + union sockunion proto_addr, nbma_addr; + struct nhrp_cache_config *cc; + struct nhrp_cache *c; + enum nhrp_cache_type type; + + if (str2sockunion(argv[3]->arg, &proto_addr) < 0 + || afi2family(afi) != sockunion_family(&proto_addr)) + return nhrp_vty_return(vty, NHRP_ERR_PROTOCOL_ADDRESS_MISMATCH); + + if (strmatch(argv[4]->text, "local")) + type = NHRP_CACHE_LOCAL; + else { + if (str2sockunion(argv[4]->arg, &nbma_addr) < 0) + return nhrp_vty_return(vty, NHRP_ERR_FAIL); + type = NHRP_CACHE_STATIC; + } + cc = nhrp_cache_config_get(ifp, &proto_addr, 1); + if (!cc) + return nhrp_vty_return(vty, NHRP_ERR_FAIL); + cc->nbma = nbma_addr; + cc->type = type; + /* gre layer not ready */ + if (ifp->ifindex == IFINDEX_INTERNAL) + return CMD_SUCCESS; + + c = nhrp_cache_get(ifp, &proto_addr, 1); + if (!c) + return nhrp_vty_return(vty, NHRP_ERR_FAIL); + + c->map = 1; + if (type == NHRP_CACHE_LOCAL) + nhrp_cache_update_binding(c, NHRP_CACHE_LOCAL, 0, NULL, 0, + NULL, NULL); + else + nhrp_cache_update_binding(c, NHRP_CACHE_STATIC, 0, + nhrp_peer_get(ifp, &nbma_addr), 0, + NULL, NULL); + return CMD_SUCCESS; +} + +DEFUN(if_no_nhrp_map, if_no_nhrp_map_cmd, + "no " AFI_CMD " nhrp map []", + NO_STR + AFI_STR + NHRP_STR + "Nexthop Server configuration\n" + "IPv4 protocol address\n" + "IPv6 protocol address\n" + "IPv4 NBMA address\n" + "Handle protocol address locally\n") +{ + VTY_DECLVAR_CONTEXT(interface, ifp); + afi_t afi = cmd_to_afi(argv[1]); + union sockunion proto_addr, nbma_addr; + struct nhrp_cache_config *cc; + struct nhrp_cache *c; + + if (str2sockunion(argv[4]->arg, &proto_addr) < 0 + || afi2family(afi) != sockunion_family(&proto_addr)) + return nhrp_vty_return(vty, NHRP_ERR_PROTOCOL_ADDRESS_MISMATCH); + + cc = nhrp_cache_config_get(ifp, &proto_addr, 0); + if (!cc) + return nhrp_vty_return(vty, NHRP_ERR_FAIL); + nhrp_cache_config_free(cc); + + c = nhrp_cache_get(ifp, &proto_addr, 0); + /* silently return */ + if (!c || !c->map) + return CMD_SUCCESS; + + nhrp_cache_update_binding(c, c->cur.type, -1, + nhrp_peer_get(ifp, &nbma_addr), 0, NULL, + NULL); + return CMD_SUCCESS; +} + +DEFUN(if_nhrp_map_multicast, if_nhrp_map_multicast_cmd, + AFI_CMD " nhrp map multicast ", + AFI_STR + NHRP_STR + "Multicast NBMA Configuration\n" + "Use this NBMA mapping for multicasts\n" + "IPv4 NBMA address\n" + "IPv6 NBMA address\n" + "Dynamically learn destinations from client registrations on hub\n") +{ + VTY_DECLVAR_CONTEXT(interface, ifp); + afi_t afi = cmd_to_afi(argv[0]); + union sockunion nbma_addr; + int ret; + + if (str2sockunion(argv[4]->arg, &nbma_addr) < 0) + sockunion_family(&nbma_addr) = AF_UNSPEC; + + ret = nhrp_multicast_add(ifp, afi, &nbma_addr); + + return nhrp_vty_return(vty, ret); +} + +DEFUN(if_no_nhrp_map_multicast, if_no_nhrp_map_multicast_cmd, + "no " AFI_CMD " nhrp map multicast ", + NO_STR + AFI_STR + NHRP_STR + "Multicast NBMA Configuration\n" + "Use this NBMA mapping for multicasts\n" + "IPv4 NBMA address\n" + "IPv6 NBMA address\n" + "Dynamically learn destinations from client registrations on hub\n") +{ + VTY_DECLVAR_CONTEXT(interface, ifp); + afi_t afi = cmd_to_afi(argv[1]); + union sockunion nbma_addr; + int ret; + + if (str2sockunion(argv[5]->arg, &nbma_addr) < 0) + sockunion_family(&nbma_addr) = AF_UNSPEC; + + ret = nhrp_multicast_del(ifp, afi, &nbma_addr); + + return nhrp_vty_return(vty, ret); +} + +DEFUN(if_nhrp_nhs, if_nhrp_nhs_cmd, + AFI_CMD " nhrp nhs nbma ", + AFI_STR + NHRP_STR + "Nexthop Server configuration\n" + "IPv4 protocol address\n" + "IPv6 protocol address\n" + "Automatic detection of protocol address\n" + "NBMA address\n" + "IPv4 NBMA address\n" + "Fully qualified domain name for NBMA address(es)\n") +{ + VTY_DECLVAR_CONTEXT(interface, ifp); + afi_t afi = cmd_to_afi(argv[0]); + union sockunion proto_addr; + int ret; + + if (str2sockunion(argv[3]->arg, &proto_addr) < 0) + sockunion_family(&proto_addr) = AF_UNSPEC; + + ret = nhrp_nhs_add(ifp, afi, &proto_addr, argv[5]->arg); + return nhrp_vty_return(vty, ret); +} + +DEFUN(if_no_nhrp_nhs, if_no_nhrp_nhs_cmd, + "no " AFI_CMD " nhrp nhs nbma ", + NO_STR + AFI_STR + NHRP_STR + "Nexthop Server configuration\n" + "IPv4 protocol address\n" + "IPv6 protocol address\n" + "Automatic detection of protocol address\n" + "NBMA address\n" + "IPv4 NBMA address\n" + "Fully qualified domain name for NBMA address(es)\n") +{ + VTY_DECLVAR_CONTEXT(interface, ifp); + afi_t afi = cmd_to_afi(argv[1]); + union sockunion proto_addr; + int ret; + + if (str2sockunion(argv[4]->arg, &proto_addr) < 0) + sockunion_family(&proto_addr) = AF_UNSPEC; + + ret = nhrp_nhs_del(ifp, afi, &proto_addr, argv[6]->arg); + return nhrp_vty_return(vty, ret); +} + +struct info_ctx { + struct vty *vty; + afi_t afi; + int count; + struct json_object *json; +}; + +static void show_ip_nhrp_cache(struct nhrp_cache *c, void *pctx) +{ + struct info_ctx *ctx = pctx; + struct vty *vty = ctx->vty; + char buf[3][SU_ADDRSTRLEN]; + struct json_object *json = NULL; + + if (ctx->afi != family2afi(sockunion_family(&c->remote_addr))) + return; + + + if (!ctx->count && !ctx->json) { + vty_out(vty, "%-8s %-8s %-24s %-24s %-24s %-6s %s\n", "Iface", + "Type", "Protocol", "NBMA", "Claimed NBMA", "Flags", + "Identity"); + } + ctx->count++; + + sockunion2str(&c->remote_addr, buf[0], sizeof(buf[0])); + if (c->cur.type == NHRP_CACHE_LOCAL) { + struct nhrp_interface *nifp = c->ifp->info; + + if (sockunion_family(&nifp->nbma) != AF_UNSPEC) { + sockunion2str(&nifp->nbma, buf[1], sizeof(buf[1])); + sockunion2str(&nifp->nbma, buf[2], sizeof(buf[2])); + } else { + snprintf(buf[1], sizeof(buf[1]), "-"); + snprintf(buf[2], sizeof(buf[2]), "-"); + } + + /* if we are behind NAT then update NBMA field */ + if (sockunion_family(&nifp->nat_nbma) != AF_UNSPEC) + sockunion2str(&nifp->nat_nbma, buf[1], sizeof(buf[1])); + } else { + if (c->cur.peer) + sockunion2str(&c->cur.peer->vc->remote.nbma, + buf[1], sizeof(buf[1])); + else + snprintf(buf[1], sizeof(buf[1]), "-"); + + if (c->cur.peer + && sockunion_family(&c->cur.remote_nbma_claimed) + != AF_UNSPEC) + sockunion2str(&c->cur.remote_nbma_claimed, + buf[2], sizeof(buf[2])); + else + snprintf(buf[2], sizeof(buf[2]), "-"); + } + + if (ctx->json) { + json = json_object_new_object(); + json_object_string_add(json, "interface", c->ifp->name); + json_object_string_add(json, "type", + nhrp_cache_type_str[c->cur.type]); + json_object_string_add(json, "protocol", buf[0]); + json_object_string_add(json, "nbma", buf[1]); + json_object_string_add(json, "claimed_nbma", buf[2]); + + if (c->used) + json_object_boolean_true_add(json, "used"); + else + json_object_boolean_false_add(json, "used"); + + if (c->t_timeout) + json_object_boolean_true_add(json, "timeout"); + else + json_object_boolean_false_add(json, "timeout"); + + if (c->t_auth) + json_object_boolean_true_add(json, "auth"); + else + json_object_boolean_false_add(json, "auth"); + + if (c->cur.peer) + json_object_string_add(json, "identity", + c->cur.peer->vc->remote.id); + else + json_object_string_add(json, "identity", "-"); + + json_object_array_add(ctx->json, json); + return; + } + vty_out(ctx->vty, "%-8s %-8s %-24s %-24s %-24s %c%c%c %s\n", + c->ifp->name, + nhrp_cache_type_str[c->cur.type], + buf[0], buf[1], buf[2], + c->used ? 'U' : ' ', c->t_timeout ? 'T' : ' ', + c->t_auth ? 'A' : ' ', + c->cur.peer ? c->cur.peer->vc->remote.id : "-"); +} + +static void show_ip_nhrp_nhs(struct nhrp_nhs *n, struct nhrp_registration *reg, + void *pctx) +{ + struct info_ctx *ctx = pctx; + struct vty *vty = ctx->vty; + char buf[2][SU_ADDRSTRLEN]; + struct json_object *json = NULL; + + if (!ctx->count && !ctx->json) { + vty_out(vty, "%-8s %-24s %-16s %-16s\n", "Iface", "FQDN", + "NBMA", "Protocol"); + } + ctx->count++; + + if (reg && reg->peer) + sockunion2str(®->peer->vc->remote.nbma, buf[0], + sizeof(buf[0])); + else + snprintf(buf[0], sizeof(buf[0]), "-"); + sockunion2str(reg ? ®->proto_addr : &n->proto_addr, buf[1], + sizeof(buf[1])); + + if (ctx->json) { + json = json_object_new_object(); + json_object_string_add(json, "interface", n->ifp->name); + json_object_string_add(json, "fqdn", n->nbma_fqdn); + json_object_string_add(json, "nbma", buf[0]); + json_object_string_add(json, "protocol", buf[1]); + + json_object_array_add(ctx->json, json); + return; + } + + vty_out(vty, "%-8s %-24s %-16s %-16s\n", n->ifp->name, n->nbma_fqdn, + buf[0], buf[1]); +} + +static void show_ip_nhrp_shortcut(struct nhrp_shortcut *s, void *pctx) +{ + struct info_ctx *ctx = pctx; + struct nhrp_cache *c; + struct vty *vty = ctx->vty; + char buf1[PREFIX_STRLEN], buf2[SU_ADDRSTRLEN]; + struct json_object *json = NULL; + + if (!ctx->count) { + vty_out(vty, "%-8s %-24s %-24s %s\n", "Type", "Prefix", "Via", + "Identity"); + } + ctx->count++; + + c = s->cache; + buf2[0] = '\0'; + if (c) + sockunion2str(&c->remote_addr, buf2, sizeof(buf2)); + prefix2str(s->p, buf1, sizeof(buf1)); + + if (ctx->json) { + json = json_object_new_object(); + json_object_string_add(json, "type", + nhrp_cache_type_str[s->type]); + json_object_string_add(json, "prefix", buf1); + + if (c) + json_object_string_add(json, "via", buf2); + + if (c && c->cur.peer) + json_object_string_add(json, "identity", + c->cur.peer->vc->remote.id); + else + json_object_string_add(json, "identity", ""); + + json_object_array_add(ctx->json, json); + return; + } + + vty_out(ctx->vty, "%-8s %-24s %-24s %s\n", + nhrp_cache_type_str[s->type], + buf1, buf2, + (c && c->cur.peer) ? c->cur.peer->vc->remote.id : ""); +} + +static void show_ip_opennhrp_cache(struct nhrp_cache *c, void *pctx) +{ + struct info_ctx *ctx = pctx; + char buf[3][SU_ADDRSTRLEN]; + struct json_object *json = NULL; + + + if (ctx->afi != family2afi(sockunion_family(&c->remote_addr))) + return; + + sockunion2str(&c->remote_addr, buf[0], sizeof(buf[0])); + if (c->cur.peer) + sockunion2str(&c->cur.peer->vc->remote.nbma, buf[1], + sizeof(buf[1])); + if (sockunion_family(&c->cur.remote_nbma_natoa) != AF_UNSPEC) + sockunion2str(&c->cur.remote_nbma_natoa, buf[2], + sizeof(buf[2])); + if (ctx->json) { + json = json_object_new_object(); + json_object_string_add(json, "type", + nhrp_cache_type_str[c->cur.type]); + + if (c->cur.peer && c->cur.peer->online) + json_object_boolean_true_add(json, "up"); + else + json_object_boolean_false_add(json, "up"); + + if (c->used) + json_object_boolean_true_add(json, "used"); + else + json_object_boolean_false_add(json, "used"); + + json_object_string_add(json, "protocolAddress", buf[0]); + json_object_int_add(json, "protocolAddressSize", + 8 * family2addrsize(sockunion_family + (&c->remote_addr))); + + if (c->cur.peer) + json_object_string_add(json, "nbmaAddress", buf[1]); + + if (sockunion_family(&c->cur.remote_nbma_natoa) != AF_UNSPEC) + json_object_string_add(json, "nbmaNatOaAddress", + buf[2]); + + json_object_array_add(ctx->json, json); + return; + } + vty_out(ctx->vty, + "Type: %s\n" + "Flags:%s%s\n" + "Protocol-Address: %s/%zu\n", + nhrp_cache_type_str[c->cur.type], + (c->cur.peer && c->cur.peer->online) ? " up" : "", + c->used ? " used" : "", + buf[0], + 8 * family2addrsize(sockunion_family(&c->remote_addr))); + + if (c->cur.peer) + vty_out(ctx->vty, "NBMA-Address: %s\n", buf[1]); + + if (sockunion_family(&c->cur.remote_nbma_natoa) != AF_UNSPEC) + vty_out(ctx->vty, "NBMA-NAT-OA-Address: %s\n", buf[2]); + + vty_out(ctx->vty, "\n\n"); +} + +DEFUN(show_ip_nhrp, show_ip_nhrp_cmd, + "show " AFI_CMD " nhrp [cache|nhs|shortcut|opennhrp] [json]", + SHOW_STR + AFI_STR + "NHRP information\n" + "Forwarding cache information\n" + "Next hop server information\n" + "Shortcut information\n" + "opennhrpctl style cache dump\n" + JSON_STR) +{ + struct vrf *vrf = vrf_lookup_by_id(VRF_DEFAULT); + struct interface *ifp; + struct info_ctx ctx = { + .vty = vty, .afi = cmd_to_afi(argv[1]), .json = NULL + }; + bool uj = use_json(argc, argv); + struct json_object *json_path = NULL; + struct json_object *json_vrf = NULL, *json_vrf_path = NULL; + int ret = CMD_SUCCESS; + + if (uj) { + json_vrf = json_object_new_object(); + json_vrf_path = json_object_new_object(); + json_path = json_object_new_array(); + ctx.json = json_path; + } + if (argc <= 3 || argv[3]->text[0] == 'c') { + FOR_ALL_INTERFACES (vrf, ifp) + nhrp_cache_foreach(ifp, show_ip_nhrp_cache, &ctx); + } else if (argv[3]->text[0] == 'n') { + FOR_ALL_INTERFACES (vrf, ifp) + nhrp_nhs_foreach(ifp, ctx.afi, show_ip_nhrp_nhs, &ctx); + } else if (argv[3]->text[0] == 's') { + nhrp_shortcut_foreach(ctx.afi, show_ip_nhrp_shortcut, &ctx); + } else { + if (!ctx.json) + vty_out(vty, "Status: ok\n\n"); + else + json_object_string_add(json_vrf, "status", "ok"); + + ctx.count++; + FOR_ALL_INTERFACES (vrf, ifp) + nhrp_cache_foreach(ifp, show_ip_opennhrp_cache, &ctx); + } + + if (uj) + json_object_int_add(json_vrf, "entriesCount", ctx.count); + if (!ctx.count) { + if (!ctx.json) + vty_out(vty, "%% No entries\n"); + ret = CMD_WARNING; + } + if (uj) { + json_object_object_add(json_vrf_path, "attr", json_vrf); + json_object_object_add(json_vrf_path, "table", ctx.json); + vty_json(vty, json_vrf_path); + } + return ret; +} + +struct dmvpn_cfg { + struct vty *vty; + struct json_object *json; +}; + +static void show_dmvpn_entry(struct nhrp_vc *vc, void *ctx) +{ + struct dmvpn_cfg *ctxt = ctx; + struct vty *vty; + struct json_object *json = NULL; + + if (!ctxt || !ctxt->vty) + return; + vty = ctxt->vty; + if (ctxt->json) { + json = json_object_new_object(); + json_object_string_addf(json, "src", "%pSU", &vc->local.nbma); + json_object_string_addf(json, "dst", "%pSU", &vc->remote.nbma); + + if (notifier_active(&vc->notifier_list)) + json_object_boolean_true_add(json, "notifierActive"); + else + json_object_boolean_false_add(json, "notifierActive"); + + json_object_int_add(json, "sas", vc->ipsec); + json_object_string_add(json, "identity", vc->remote.id); + json_object_array_add(ctxt->json, json); + } else { + vty_out(vty, "%-24pSU %-24pSU %c %-4d %-24s\n", + &vc->local.nbma, &vc->remote.nbma, + notifier_active(&vc->notifier_list) ? 'n' : ' ', + vc->ipsec, vc->remote.id); + } +} + +DEFUN(show_dmvpn, show_dmvpn_cmd, + "show dmvpn [json]", + SHOW_STR + "DMVPN information\n" + JSON_STR) +{ + bool uj = use_json(argc, argv); + struct dmvpn_cfg ctxt; + struct json_object *json_path = NULL; + + ctxt.vty = vty; + if (!uj) { + ctxt.json = NULL; + vty_out(vty, "%-24s %-24s %-6s %-4s %-24s\n", + "Src", "Dst", "Flags", "SAs", "Identity"); + } else { + json_path = json_object_new_array(); + ctxt.json = json_path; + } + nhrp_vc_foreach(show_dmvpn_entry, &ctxt); + if (uj) + vty_json(vty, json_path); + return CMD_SUCCESS; +} + +static void clear_nhrp_cache(struct nhrp_cache *c, void *data) +{ + struct info_ctx *ctx = data; + if (c->cur.type <= NHRP_CACHE_DYNAMIC) { + nhrp_cache_update_binding(c, c->cur.type, -1, NULL, 0, NULL, + NULL); + if (ctx) + ctx->count++; + } +} + +static void clear_nhrp_shortcut(struct nhrp_shortcut *s, void *data) +{ + struct info_ctx *ctx = data; + nhrp_shortcut_purge(s, 1); + ctx->count++; +} + +DEFUN(clear_nhrp, clear_nhrp_cmd, + "clear " AFI_CMD " nhrp ", + CLEAR_STR + AFI_STR + NHRP_STR + "Dynamic cache entries\n" + "Shortcut entries\n") +{ + struct vrf *vrf = vrf_lookup_by_id(VRF_DEFAULT); + struct interface *ifp; + struct info_ctx ctx = { + .vty = vty, .afi = cmd_to_afi(argv[1]), .count = 0, + }; + + if (argc <= 3 || argv[3]->text[0] == 'c') { + FOR_ALL_INTERFACES (vrf, ifp) + nhrp_cache_foreach(ifp, clear_nhrp_cache, &ctx); + } else { + nhrp_shortcut_foreach(ctx.afi, clear_nhrp_shortcut, &ctx); + /* Clear cache also because when a shortcut is cleared then its + * cache entry should be cleared as well (otherwise traffic + * continues via the shortcut path) + */ + FOR_ALL_INTERFACES (vrf, ifp) + nhrp_cache_foreach(ifp, clear_nhrp_cache, NULL); + } + + if (!ctx.count) { + vty_out(vty, "%% No entries\n"); + return CMD_WARNING; + } + + vty_out(vty, "%% %d entries cleared\n", ctx.count); + return CMD_SUCCESS; +} + +struct write_map_ctx { + struct vty *vty; + int family; + const char *aficmd; +}; + +static void interface_config_write_nhrp_map(struct nhrp_cache_config *c, + void *data) +{ + struct write_map_ctx *ctx = data; + struct vty *vty = ctx->vty; + + if (sockunion_family(&c->remote_addr) != ctx->family) + return; + + vty_out(vty, " %s nhrp map %pSU ", ctx->aficmd, &c->remote_addr); + if (c->type == NHRP_CACHE_LOCAL) + vty_out(vty, "local\n"); + else + vty_out(vty, "%pSU\n", &c->nbma); +} + +static int interface_config_write(struct vty *vty) +{ + struct vrf *vrf = vrf_lookup_by_id(VRF_DEFAULT); + struct write_map_ctx mapctx; + struct interface *ifp; + struct nhrp_interface *nifp; + struct nhrp_nhs *nhs; + struct nhrp_multicast *mcast; + const char *aficmd; + afi_t afi; + int i; + + FOR_ALL_INTERFACES (vrf, ifp) { + if_vty_config_start(vty, ifp); + if (ifp->desc) + vty_out(vty, " description %s\n", ifp->desc); + + nifp = ifp->info; + if (nifp->ipsec_profile) { + vty_out(vty, " tunnel protection vici profile %s", + nifp->ipsec_profile); + if (nifp->ipsec_fallback_profile) + vty_out(vty, " fallback-profile %s", + nifp->ipsec_fallback_profile); + vty_out(vty, "\n"); + } + if (nifp->source) + vty_out(vty, " tunnel source %s\n", nifp->source); + + for (afi = 0; afi < AFI_MAX; afi++) { + struct nhrp_afi_data *ad = &nifp->afi[afi]; + + aficmd = afi_to_cmd(afi); + + if (ad->network_id) + vty_out(vty, " %s nhrp network-id %u\n", aficmd, + ad->network_id); + + if (ad->holdtime != NHRPD_DEFAULT_HOLDTIME) + vty_out(vty, " %s nhrp holdtime %u\n", aficmd, + ad->holdtime); + + if (ad->configured_mtu < 0) + vty_out(vty, " %s nhrp mtu opennhrp\n", aficmd); + else if (ad->configured_mtu) + vty_out(vty, " %s nhrp mtu %u\n", aficmd, + ad->configured_mtu); + + for (i = 0; interface_flags_desc[i].str != NULL; i++) { + if (!(ad->flags & interface_flags_desc[i].key)) + continue; + vty_out(vty, " %s nhrp %s\n", aficmd, + interface_flags_desc[i].str); + } + + mapctx = (struct write_map_ctx){ + .vty = vty, + .family = afi2family(afi), + .aficmd = aficmd, + }; + nhrp_cache_config_foreach( + ifp, interface_config_write_nhrp_map, &mapctx); + + frr_each (nhrp_nhslist, &ad->nhslist_head, nhs) { + vty_out(vty, " %s nhrp nhs ", aficmd); + if (sockunion_family(&nhs->proto_addr) + == AF_UNSPEC) + vty_out(vty, "dynamic"); + else + vty_out(vty, "%pSU", &nhs->proto_addr); + vty_out(vty, " nbma %s\n", nhs->nbma_fqdn); + } + + frr_each (nhrp_mcastlist, &ad->mcastlist_head, mcast) { + vty_out(vty, " %s nhrp map multicast ", aficmd); + if (sockunion_family(&mcast->nbma_addr) + == AF_UNSPEC) + vty_out(vty, "dynamic\n"); + else + vty_out(vty, "%pSU\n", + &mcast->nbma_addr); + } + } + + if_vty_config_end(vty); + } + + return 0; +} + +void nhrp_config_init(void) +{ + install_node(&zebra_node); + install_default(ZEBRA_NODE); + + /* access-list commands */ + access_list_init(); + + /* global commands */ + install_element(VIEW_NODE, &show_ip_nhrp_cmd); + install_element(VIEW_NODE, &show_dmvpn_cmd); + install_element(ENABLE_NODE, &clear_nhrp_cmd); + + install_element(ENABLE_NODE, &show_debugging_nhrp_cmd); + + install_element(ENABLE_NODE, &debug_nhrp_cmd); + install_element(ENABLE_NODE, &no_debug_nhrp_cmd); + + install_element(CONFIG_NODE, &debug_nhrp_cmd); + install_element(CONFIG_NODE, &no_debug_nhrp_cmd); + + install_element(CONFIG_NODE, &nhrp_event_socket_cmd); + install_element(CONFIG_NODE, &no_nhrp_event_socket_cmd); + install_element(CONFIG_NODE, &nhrp_nflog_group_cmd); + install_element(CONFIG_NODE, &no_nhrp_nflog_group_cmd); + install_element(CONFIG_NODE, &nhrp_multicast_nflog_group_cmd); + install_element(CONFIG_NODE, &no_nhrp_multicast_nflog_group_cmd); + + vrf_cmd_init(NULL); + + /* interface specific commands */ + if_cmd_init(interface_config_write); + install_element(INTERFACE_NODE, &tunnel_protection_cmd); + install_element(INTERFACE_NODE, &no_tunnel_protection_cmd); + install_element(INTERFACE_NODE, &tunnel_source_cmd); + install_element(INTERFACE_NODE, &no_tunnel_source_cmd); + install_element(INTERFACE_NODE, &if_nhrp_network_id_cmd); + install_element(INTERFACE_NODE, &if_no_nhrp_network_id_cmd); + install_element(INTERFACE_NODE, &if_nhrp_holdtime_cmd); + install_element(INTERFACE_NODE, &if_no_nhrp_holdtime_cmd); + install_element(INTERFACE_NODE, &if_nhrp_mtu_cmd); + install_element(INTERFACE_NODE, &if_no_nhrp_mtu_cmd); + install_element(INTERFACE_NODE, &if_nhrp_flags_cmd); + install_element(INTERFACE_NODE, &if_no_nhrp_flags_cmd); + install_element(INTERFACE_NODE, &if_nhrp_reg_flags_cmd); + install_element(INTERFACE_NODE, &if_no_nhrp_reg_flags_cmd); + install_element(INTERFACE_NODE, &if_nhrp_map_cmd); + install_element(INTERFACE_NODE, &if_no_nhrp_map_cmd); + install_element(INTERFACE_NODE, &if_nhrp_map_multicast_cmd); + install_element(INTERFACE_NODE, &if_no_nhrp_map_multicast_cmd); + install_element(INTERFACE_NODE, &if_nhrp_nhs_cmd); + install_element(INTERFACE_NODE, &if_no_nhrp_nhs_cmd); +} diff --git a/nhrpd/nhrpd.h b/nhrpd/nhrpd.h new file mode 100644 index 0000000..1421f0f --- /dev/null +++ b/nhrpd/nhrpd.h @@ -0,0 +1,532 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* NHRP daemon internal structures and function prototypes + * Copyright (c) 2014-2015 Timo Teräs + */ + +#ifndef NHRPD_H +#define NHRPD_H + +#include "zbuf.h" +#include "zclient.h" +#include "debug.h" +#include "memory.h" +#include "resolver.h" + +DECLARE_MGROUP(NHRPD); + +#define NHRPD_DEFAULT_HOLDTIME 7200 + +#define NHRP_VTY_PORT 2610 +#define NHRP_DEFAULT_CONFIG "nhrpd.conf" + +extern struct event_loop *master; + +enum { NHRP_OK = 0, + NHRP_ERR_FAIL, + NHRP_ERR_NO_MEMORY, + NHRP_ERR_UNSUPPORTED_INTERFACE, + NHRP_ERR_NHRP_NOT_ENABLED, + NHRP_ERR_ENTRY_EXISTS, + NHRP_ERR_ENTRY_NOT_FOUND, + NHRP_ERR_PROTOCOL_ADDRESS_MISMATCH, + __NHRP_ERR_MAX }; +#define NHRP_ERR_MAX (__NHRP_ERR_MAX - 1) + +struct notifier_block; + +typedef void (*notifier_fn_t)(struct notifier_block *, unsigned long); + +PREDECL_DLIST(notifier_list); + +struct notifier_block { + struct notifier_list_item notifier_entry; + notifier_fn_t action; +}; + +DECLARE_DLIST(notifier_list, struct notifier_block, notifier_entry); + +struct notifier_list { + struct notifier_list_head head; +}; + +#define NOTIFIER_LIST_INITIALIZER(l) \ + { \ + .head = INIT_DLIST((l)->head) \ + } + +static inline void notifier_init(struct notifier_list *l) +{ + notifier_list_init(&l->head); +} + +static inline void notifier_add(struct notifier_block *n, + struct notifier_list *l, notifier_fn_t action) +{ + n->action = action; + notifier_list_add_tail(&l->head, n); +} + +static inline void notifier_del(struct notifier_block *n, + struct notifier_list *l) +{ + notifier_list_del(&l->head, n); +} + +static inline void notifier_call(struct notifier_list *l, int cmd) +{ + struct notifier_block *n; + + frr_each_safe (notifier_list, &l->head, n) + n->action(n, cmd); +} + +static inline int notifier_active(struct notifier_list *l) +{ + return notifier_list_count(&l->head) > 0; +} + +extern struct hash *nhrp_gre_list; + +void nhrp_zebra_init(void); +void nhrp_zebra_terminate(void); +void nhrp_send_zebra_configure_arp(struct interface *ifp, int family); +void nhrp_send_zebra_nbr(union sockunion *in, + union sockunion *out, + struct interface *ifp); + +void nhrp_send_zebra_gre_source_set(struct interface *ifp, + unsigned int link_idx, + vrf_id_t link_vrf_id); + +extern int nhrp_send_zebra_gre_request(struct interface *ifp); +extern struct nhrp_gre_info *nhrp_gre_info_alloc(struct nhrp_gre_info *p); + +struct zbuf; +struct nhrp_vc; +struct nhrp_cache; +struct nhrp_nhs; +struct nhrp_interface; + +#define MAX_ID_LENGTH 64 +#define MAX_CERT_LENGTH 2048 + +enum nhrp_notify_type { + NOTIFY_INTERFACE_UP, + NOTIFY_INTERFACE_DOWN, + NOTIFY_INTERFACE_CHANGED, + NOTIFY_INTERFACE_ADDRESS_CHANGED, + NOTIFY_INTERFACE_NBMA_CHANGED, + NOTIFY_INTERFACE_MTU_CHANGED, + NOTIFY_INTERFACE_IPSEC_CHANGED, + + NOTIFY_VC_IPSEC_CHANGED, + NOTIFY_VC_IPSEC_UPDATE_NBMA, + + NOTIFY_PEER_UP, + NOTIFY_PEER_DOWN, + NOTIFY_PEER_IFCONFIG_CHANGED, + NOTIFY_PEER_MTU_CHANGED, + NOTIFY_PEER_NBMA_CHANGING, + + NOTIFY_CACHE_UP, + NOTIFY_CACHE_DOWN, + NOTIFY_CACHE_DELETE, + NOTIFY_CACHE_USED, + NOTIFY_CACHE_BINDING_CHANGE, +}; + +struct nhrp_vc { + struct notifier_list notifier_list; + uint32_t ipsec; + uint32_t ike_uniqueid; + uint8_t updating; + uint8_t abort_migration; + + struct nhrp_vc_peer { + union sockunion nbma; + char id[MAX_ID_LENGTH]; + uint16_t certlen; + uint8_t cert[MAX_CERT_LENGTH]; + } local, remote; +}; + +enum nhrp_route_type { + NHRP_ROUTE_BLACKHOLE, + NHRP_ROUTE_LOCAL, + NHRP_ROUTE_NBMA_NEXTHOP, + NHRP_ROUTE_OFF_NBMA, +}; + +struct nhrp_peer { + unsigned int ref; + unsigned online : 1; + unsigned requested : 1; + unsigned fallback_requested : 1; + unsigned prio : 1; + struct notifier_list notifier_list; + struct interface *ifp; + struct nhrp_vc *vc; + struct event *t_fallback; + struct notifier_block vc_notifier, ifp_notifier; + struct event *t_timer; +}; + +struct nhrp_packet_parser { + struct interface *ifp; + struct nhrp_afi_data *if_ad; + struct nhrp_peer *peer; + struct zbuf *pkt; + struct zbuf payload; + struct zbuf extensions; + struct nhrp_packet_header *hdr; + enum nhrp_route_type route_type; + struct prefix route_prefix; + union sockunion src_nbma, src_proto, dst_proto; +}; + +struct nhrp_reqid_pool { + struct hash *reqid_hash; + uint32_t next_request_id; +}; + +struct nhrp_reqid { + uint32_t request_id; + void (*cb)(struct nhrp_reqid *, void *); +}; + +extern struct nhrp_reqid_pool nhrp_packet_reqid; +extern struct nhrp_reqid_pool nhrp_event_reqid; + +enum nhrp_cache_type { + NHRP_CACHE_INVALID = 0, + NHRP_CACHE_INCOMPLETE, + NHRP_CACHE_NEGATIVE, + NHRP_CACHE_CACHED, + NHRP_CACHE_DYNAMIC, + NHRP_CACHE_NHS, + NHRP_CACHE_STATIC, + NHRP_CACHE_LOCAL, + NHRP_CACHE_NUM_TYPES +}; + +extern const char *const nhrp_cache_type_str[]; +extern unsigned long nhrp_cache_counts[NHRP_CACHE_NUM_TYPES]; + +struct nhrp_cache_config { + struct interface *ifp; + union sockunion remote_addr; + enum nhrp_cache_type type; + union sockunion nbma; +}; + +struct nhrp_cache { + struct interface *ifp; + union sockunion remote_addr; + + unsigned map : 1; + unsigned used : 1; + unsigned route_installed : 1; + unsigned nhrp_route_installed : 1; + + struct notifier_block peer_notifier; + struct notifier_block newpeer_notifier; + struct notifier_list notifier_list; + struct nhrp_reqid eventid; + struct event *t_timeout; + struct event *t_auth; + + struct { + enum nhrp_cache_type type; + union sockunion remote_nbma_natoa; + union sockunion remote_nbma_claimed; + struct nhrp_peer *peer; + time_t expires; + uint32_t mtu; + int holding_time; + } cur, new; +}; + +struct nhrp_shortcut { + struct prefix *p; + union sockunion addr; + + struct nhrp_reqid reqid; + struct event *t_timer; + + enum nhrp_cache_type type; + unsigned int holding_time; + unsigned route_installed : 1; + unsigned expiring : 1; + + struct nhrp_cache *cache; + struct notifier_block cache_notifier; +}; + +PREDECL_DLIST(nhrp_nhslist); +PREDECL_DLIST(nhrp_mcastlist); +PREDECL_DLIST(nhrp_reglist); + +struct nhrp_nhs { + struct interface *ifp; + struct nhrp_nhslist_item nhslist_entry; + + unsigned hub : 1; + afi_t afi; + union sockunion proto_addr; + const char *nbma_fqdn; /* IP-address or FQDN */ + + struct event *t_resolve; + struct resolver_query dns_resolve; + struct nhrp_reglist_head reglist_head; +}; + +DECLARE_DLIST(nhrp_nhslist, struct nhrp_nhs, nhslist_entry); + +struct nhrp_multicast { + struct interface *ifp; + struct nhrp_mcastlist_item mcastlist_entry; + afi_t afi; + union sockunion nbma_addr; /* IP-address */ +}; + +DECLARE_DLIST(nhrp_mcastlist, struct nhrp_multicast, mcastlist_entry); + +struct nhrp_registration { + struct nhrp_reglist_item reglist_entry; + struct event *t_register; + struct nhrp_nhs *nhs; + struct nhrp_reqid reqid; + unsigned int timeout; + unsigned mark : 1; + union sockunion proto_addr; + struct nhrp_peer *peer; + struct notifier_block peer_notifier; +}; + +DECLARE_DLIST(nhrp_reglist, struct nhrp_registration, reglist_entry); + +#define NHRP_IFF_SHORTCUT 0x0001 +#define NHRP_IFF_REDIRECT 0x0002 +#define NHRP_IFF_REG_NO_UNIQUE 0x0100 + +struct nhrp_interface { + struct interface *ifp; + + unsigned enabled : 1; + + char *ipsec_profile, *ipsec_fallback_profile, *source; + union sockunion nbma; + union sockunion nat_nbma; + unsigned int link_idx; + unsigned int link_vrf_id; + uint32_t i_grekey; + uint32_t o_grekey; + + struct hash *peer_hash; + struct hash *cache_config_hash; + struct hash *cache_hash; + + struct notifier_list notifier_list; + + struct interface *nbmaifp; + struct notifier_block nbmanifp_notifier; + + struct nhrp_afi_data { + unsigned flags; + unsigned short configured : 1; + union sockunion addr; + uint32_t network_id; + short configured_mtu; + unsigned short mtu; + unsigned int holdtime; + struct nhrp_nhslist_head nhslist_head; + struct nhrp_mcastlist_head mcastlist_head; + } afi[AFI_MAX]; +}; + +struct nhrp_gre_info { + ifindex_t ifindex; + struct in_addr vtep_ip; /* IFLA_GRE_LOCAL */ + struct in_addr vtep_ip_remote; /* IFLA_GRE_REMOTE */ + uint32_t ikey; + uint32_t okey; + ifindex_t ifindex_link; /* Interface index of interface + * linked with GRE + */ + vrf_id_t vrfid_link; +}; + +extern struct zebra_privs_t nhrpd_privs; + +int sock_open_unix(const char *path); + +void nhrp_interface_init(void); +void nhrp_interface_update(struct interface *ifp); +void nhrp_interface_update_mtu(struct interface *ifp, afi_t afi); +void nhrp_interface_update_nbma(struct interface *ifp, + struct nhrp_gre_info *gre_info); + +int nhrp_interface_add(ZAPI_CALLBACK_ARGS); +int nhrp_interface_delete(ZAPI_CALLBACK_ARGS); +int nhrp_interface_up(ZAPI_CALLBACK_ARGS); +int nhrp_interface_down(ZAPI_CALLBACK_ARGS); +int nhrp_interface_address_add(ZAPI_CALLBACK_ARGS); +int nhrp_interface_address_delete(ZAPI_CALLBACK_ARGS); +int nhrp_neighbor_operation(ZAPI_CALLBACK_ARGS); +int nhrp_gre_update(ZAPI_CALLBACK_ARGS); + +void nhrp_interface_notify_add(struct interface *ifp, struct notifier_block *n, + notifier_fn_t fn); +void nhrp_interface_notify_del(struct interface *ifp, struct notifier_block *n); +void nhrp_interface_set_protection(struct interface *ifp, const char *profile, + const char *fallback_profile); +void nhrp_interface_set_source(struct interface *ifp, const char *ifname); +extern int nhrp_ifp_create(struct interface *ifp); +extern int nhrp_ifp_up(struct interface *ifp); +extern int nhrp_ifp_down(struct interface *ifp); +extern int nhrp_ifp_destroy(struct interface *ifp); + +int nhrp_nhs_add(struct interface *ifp, afi_t afi, union sockunion *proto_addr, + const char *nbma_fqdn); +int nhrp_nhs_del(struct interface *ifp, afi_t afi, union sockunion *proto_addr, + const char *nbma_fqdn); +int nhrp_nhs_free(struct nhrp_interface *nifp, afi_t afi, struct nhrp_nhs *nhs); +void nhrp_nhs_terminate(void); +void nhrp_nhs_foreach(struct interface *ifp, afi_t afi, + void (*cb)(struct nhrp_nhs *, struct nhrp_registration *, + void *), + void *ctx); +void nhrp_nhs_interface_del(struct interface *ifp); + +int nhrp_multicast_add(struct interface *ifp, afi_t afi, + union sockunion *nbma_addr); +int nhrp_multicast_del(struct interface *ifp, afi_t afi, + union sockunion *nbma_addr); +void nhrp_multicast_interface_del(struct interface *ifp); +void nhrp_multicast_foreach(struct interface *ifp, afi_t afi, + void (*cb)(struct nhrp_multicast *, void *), + void *ctx); +void netlink_mcast_set_nflog_group(int nlgroup); + +void nhrp_route_update_nhrp(const struct prefix *p, struct interface *ifp); +void nhrp_route_announce(int add, enum nhrp_cache_type type, + const struct prefix *p, struct interface *ifp, + const union sockunion *nexthop, uint32_t mtu); +int nhrp_route_read(ZAPI_CALLBACK_ARGS); +int nhrp_route_get_nexthop(const union sockunion *addr, struct prefix *p, + union sockunion *via, struct interface **ifp); +enum nhrp_route_type nhrp_route_address(struct interface *in_ifp, + union sockunion *addr, struct prefix *p, + struct nhrp_peer **peer); + +void nhrp_config_init(void); + +void nhrp_shortcut_init(void); +void nhrp_shortcut_terminate(void); +void nhrp_shortcut_initiate(union sockunion *addr); +void nhrp_shortcut_foreach(afi_t afi, + void (*cb)(struct nhrp_shortcut *, void *), + void *ctx); +void nhrp_shortcut_purge(struct nhrp_shortcut *s, int force); +void nhrp_shortcut_prefix_change(const struct prefix *p, int deleted); + +void nhrp_cache_interface_del(struct interface *ifp); +void nhrp_cache_config_free(struct nhrp_cache_config *c); +struct nhrp_cache_config *nhrp_cache_config_get(struct interface *ifp, + union sockunion *remote_addr, + int create); +struct nhrp_cache *nhrp_cache_get(struct interface *ifp, + union sockunion *remote_addr, int create); +void nhrp_cache_foreach(struct interface *ifp, + void (*cb)(struct nhrp_cache *, void *), void *ctx); +void nhrp_cache_config_foreach(struct interface *ifp, + void (*cb)(struct nhrp_cache_config *, void *), void *ctx); +void nhrp_cache_set_used(struct nhrp_cache *, int); +int nhrp_cache_update_binding(struct nhrp_cache *, enum nhrp_cache_type type, + int holding_time, struct nhrp_peer *p, + uint32_t mtu, union sockunion *nbma_natoa, + union sockunion *claimed_nbma); +void nhrp_cache_notify_add(struct nhrp_cache *c, struct notifier_block *, + notifier_fn_t); +void nhrp_cache_notify_del(struct nhrp_cache *c, struct notifier_block *); + +void nhrp_vc_init(void); +void nhrp_vc_terminate(void); +struct nhrp_vc *nhrp_vc_get(const union sockunion *src, + const union sockunion *dst, int create); +int nhrp_vc_ipsec_updown(uint32_t child_id, struct nhrp_vc *vc); +void nhrp_vc_notify_add(struct nhrp_vc *, struct notifier_block *, + notifier_fn_t); +void nhrp_vc_notify_del(struct nhrp_vc *, struct notifier_block *); +void nhrp_vc_foreach(void (*cb)(struct nhrp_vc *, void *), void *ctx); +void nhrp_vc_reset(void); + +void vici_init(void); +void vici_terminate(void); +void vici_terminate_vc_by_profile_name(char *profile_name); +void vici_terminate_vc_by_ike_id(unsigned int ike_id); +void vici_request_vc(const char *profile, union sockunion *src, + union sockunion *dst, int prio); + +extern const char *nhrp_event_socket_path; + +void evmgr_init(void); +void evmgr_terminate(void); +void evmgr_set_socket(const char *socket); +void evmgr_notify(const char *name, struct nhrp_cache *c, + void (*cb)(struct nhrp_reqid *, void *)); + +struct nhrp_packet_header *nhrp_packet_push(struct zbuf *zb, uint8_t type, + const union sockunion *src_nbma, + const union sockunion *src_proto, + const union sockunion *dst_proto); +void nhrp_packet_complete(struct zbuf *zb, struct nhrp_packet_header *hdr); +uint16_t nhrp_packet_calculate_checksum(const uint8_t *pdu, uint16_t len); + +struct nhrp_packet_header *nhrp_packet_pull(struct zbuf *zb, + union sockunion *src_nbma, + union sockunion *src_proto, + union sockunion *dst_proto); + +struct nhrp_cie_header *nhrp_cie_push(struct zbuf *zb, uint8_t code, + const union sockunion *nbma, + const union sockunion *proto); +struct nhrp_cie_header *nhrp_cie_pull(struct zbuf *zb, + struct nhrp_packet_header *hdr, + union sockunion *nbma, + union sockunion *proto); + +struct nhrp_extension_header * +nhrp_ext_push(struct zbuf *zb, struct nhrp_packet_header *hdr, uint16_t type); +void nhrp_ext_complete(struct zbuf *zb, struct nhrp_extension_header *ext); +struct nhrp_extension_header *nhrp_ext_pull(struct zbuf *zb, + struct zbuf *payload); +void nhrp_ext_request(struct zbuf *zb, struct nhrp_packet_header *hdr, + struct interface *); +int nhrp_ext_reply(struct zbuf *zb, struct nhrp_packet_header *hdr, + struct interface *ifp, struct nhrp_extension_header *ext, + struct zbuf *extpayload); + +uint32_t nhrp_reqid_alloc(struct nhrp_reqid_pool *, struct nhrp_reqid *r, + void (*cb)(struct nhrp_reqid *, void *)); +void nhrp_reqid_free(struct nhrp_reqid_pool *, struct nhrp_reqid *r); +struct nhrp_reqid *nhrp_reqid_lookup(struct nhrp_reqid_pool *, uint32_t reqid); + +int nhrp_packet_init(void); + +void nhrp_peer_interface_del(struct interface *ifp); +struct nhrp_peer *nhrp_peer_get(struct interface *ifp, + const union sockunion *remote_nbma); +struct nhrp_peer *nhrp_peer_ref(struct nhrp_peer *p); +void nhrp_peer_unref(struct nhrp_peer *p); +int nhrp_peer_check(struct nhrp_peer *p, int establish); +void nhrp_peer_notify_add(struct nhrp_peer *p, struct notifier_block *, + notifier_fn_t); +void nhrp_peer_notify_del(struct nhrp_peer *p, struct notifier_block *); +void nhrp_peer_recv(struct nhrp_peer *p, struct zbuf *zb); +void nhrp_peer_send(struct nhrp_peer *p, struct zbuf *zb); +void nhrp_peer_send_indication(struct interface *ifp, uint16_t, struct zbuf *); + +int nhrp_nhs_match_ip(union sockunion *in_ip, struct nhrp_interface *nifp); + +#endif diff --git a/nhrpd/os.h b/nhrpd/os.h new file mode 100644 index 0000000..2b9e07f --- /dev/null +++ b/nhrpd/os.h @@ -0,0 +1,7 @@ + +int os_socket(void); +int os_sendmsg(const uint8_t *buf, size_t len, int ifindex, const uint8_t *addr, + size_t addrlen, uint16_t protocol); +int os_recvmsg(uint8_t *buf, size_t *len, int *ifindex, uint8_t *addr, + size_t *addrlen); +int os_configure_dmvpn(unsigned int ifindex, const char *ifname, int af); diff --git a/nhrpd/reqid.c b/nhrpd/reqid.c new file mode 100644 index 0000000..738e935 --- /dev/null +++ b/nhrpd/reqid.c @@ -0,0 +1,52 @@ +#include "zebra.h" +#include "hash.h" +#include "nhrpd.h" + +static unsigned int nhrp_reqid_key(const void *data) +{ + const struct nhrp_reqid *r = data; + return r->request_id; +} + +static bool nhrp_reqid_cmp(const void *data, const void *key) +{ + const struct nhrp_reqid *a = data, *b = key; + + return a->request_id == b->request_id; +} + +uint32_t nhrp_reqid_alloc(struct nhrp_reqid_pool *p, struct nhrp_reqid *r, + void (*cb)(struct nhrp_reqid *, void *)) +{ + if (!p->reqid_hash) { + p->reqid_hash = hash_create(nhrp_reqid_key, nhrp_reqid_cmp, + "NHRP reqid Hash"); + p->next_request_id = 1; + } + + if (r->cb != cb) { + r->request_id = p->next_request_id; + if (++p->next_request_id == 0) + p->next_request_id = 1; + r->cb = cb; + (void)hash_get(p->reqid_hash, r, hash_alloc_intern); + } + return r->request_id; +} + +void nhrp_reqid_free(struct nhrp_reqid_pool *p, struct nhrp_reqid *r) +{ + if (r->cb) { + hash_release(p->reqid_hash, r); + r->cb = NULL; + } +} + +struct nhrp_reqid *nhrp_reqid_lookup(struct nhrp_reqid_pool *p, uint32_t reqid) +{ + struct nhrp_reqid key; + if (!p->reqid_hash) + return 0; + key.request_id = reqid; + return hash_lookup(p->reqid_hash, &key); +} diff --git a/nhrpd/subdir.am b/nhrpd/subdir.am new file mode 100644 index 0000000..227ff6c --- /dev/null +++ b/nhrpd/subdir.am @@ -0,0 +1,44 @@ +# +# nhrpd +# + +if NHRPD +sbin_PROGRAMS += nhrpd/nhrpd +vtysh_daemons += nhrpd +man8 += $(MANBUILD)/frr-nhrpd.8 +endif + +nhrpd_nhrpd_LDADD = lib/libfrr.la lib/libfrrcares.la $(LIBCAP) +nhrpd_nhrpd_SOURCES = \ + nhrpd/linux.c \ + nhrpd/netlink_arp.c \ + nhrpd/nhrp_cache.c \ + nhrpd/nhrp_errors.c \ + nhrpd/nhrp_event.c \ + nhrpd/nhrp_interface.c \ + nhrpd/nhrp_main.c \ + nhrpd/nhrp_nhs.c \ + nhrpd/nhrp_packet.c \ + nhrpd/nhrp_peer.c \ + nhrpd/nhrp_multicast.c \ + nhrpd/nhrp_route.c \ + nhrpd/nhrp_shortcut.c \ + nhrpd/nhrp_vc.c \ + nhrpd/nhrp_vty.c \ + nhrpd/reqid.c \ + nhrpd/vici.c \ + nhrpd/zbuf.c \ + nhrpd/znl.c \ + # end + +noinst_HEADERS += \ + nhrpd/debug.h \ + nhrpd/netlink.h \ + nhrpd/nhrp_errors.h \ + nhrpd/nhrp_protocol.h \ + nhrpd/nhrpd.h \ + nhrpd/os.h \ + nhrpd/vici.h \ + nhrpd/zbuf.h \ + nhrpd/znl.h \ + # end diff --git a/nhrpd/vici.c b/nhrpd/vici.c new file mode 100644 index 0000000..2f76362 --- /dev/null +++ b/nhrpd/vici.c @@ -0,0 +1,623 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* strongSwan VICI protocol implementation for NHRP + * Copyright (c) 2014-2015 Timo Teräs + */ + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include +#include +#include + +#include "frrevent.h" +#include "zbuf.h" +#include "log.h" +#include "lib_errors.h" + +#include "nhrpd.h" +#include "vici.h" +#include "nhrp_errors.h" + +#define ERRNO_IO_RETRY(EN) (((EN) == EAGAIN) || ((EN) == EWOULDBLOCK) || ((EN) == EINTR)) + +struct blob { + char *ptr; + int len; +}; + +static int blob_equal(const struct blob *b, const char *str) +{ + if (!b || b->len != (int)strlen(str)) + return 0; + return memcmp(b->ptr, str, b->len) == 0; +} + +static int blob2buf(const struct blob *b, char *buf, size_t n) +{ + if (!b || b->len >= (int)n) + return 0; + memcpy(buf, b->ptr, b->len); + buf[b->len] = 0; + return 1; +} + +struct vici_conn { + struct event *t_reconnect, *t_read, *t_write; + struct zbuf ibuf; + struct zbuf_queue obuf; + int fd; + uint8_t ibuf_data[VICI_MAX_MSGLEN]; +}; + +struct vici_message_ctx { + const char *sections[8]; + int nsections; +}; + +static void vici_reconnect(struct event *t); +static void vici_submit_request(struct vici_conn *vici, const char *name, ...); + +static void vici_zbuf_puts(struct zbuf *obuf, const char *str) +{ + size_t len = strlen(str); + zbuf_put8(obuf, len); + zbuf_put(obuf, str, len); +} + +static void vici_connection_error(struct vici_conn *vici) +{ + nhrp_vc_reset(); + + EVENT_OFF(vici->t_read); + EVENT_OFF(vici->t_write); + zbuf_reset(&vici->ibuf); + zbufq_reset(&vici->obuf); + + close(vici->fd); + vici->fd = -1; + event_add_timer(master, vici_reconnect, vici, 2, &vici->t_reconnect); +} + +static void vici_parse_message(struct vici_conn *vici, struct zbuf *msg, + void (*parser)(struct vici_message_ctx *ctx, + enum vici_type_t msgtype, + const struct blob *key, + const struct blob *val), + struct vici_message_ctx *ctx) +{ + uint8_t *type; + struct blob key = {0}; + struct blob val = {0}; + + while ((type = zbuf_may_pull(msg, uint8_t)) != NULL) { + switch (*type) { + case VICI_SECTION_START: + key.len = zbuf_get8(msg); + key.ptr = zbuf_pulln(msg, key.len); + debugf(NHRP_DEBUG_VICI, "VICI: Section start '%.*s'", + key.len, key.ptr); + parser(ctx, *type, &key, NULL); + ctx->nsections++; + break; + case VICI_SECTION_END: + debugf(NHRP_DEBUG_VICI, "VICI: Section end"); + parser(ctx, *type, NULL, NULL); + ctx->nsections--; + break; + case VICI_KEY_VALUE: + key.len = zbuf_get8(msg); + key.ptr = zbuf_pulln(msg, key.len); + val.len = zbuf_get_be16(msg); + val.ptr = zbuf_pulln(msg, val.len); + debugf(NHRP_DEBUG_VICI, "VICI: Key '%.*s'='%.*s'", + key.len, key.ptr, val.len, val.ptr); + parser(ctx, *type, &key, &val); + break; + case VICI_LIST_START: + key.len = zbuf_get8(msg); + key.ptr = zbuf_pulln(msg, key.len); + debugf(NHRP_DEBUG_VICI, "VICI: List start '%.*s'", + key.len, key.ptr); + break; + case VICI_LIST_ITEM: + val.len = zbuf_get_be16(msg); + val.ptr = zbuf_pulln(msg, val.len); + debugf(NHRP_DEBUG_VICI, "VICI: List item: '%.*s'", + val.len, val.ptr); + parser(ctx, *type, &key, &val); + break; + case VICI_LIST_END: + debugf(NHRP_DEBUG_VICI, "VICI: List end"); + break; + } + } +} + +struct handle_sa_ctx { + struct vici_message_ctx msgctx; + int event; + int child_ok; + int kill_ikesa; + uint32_t child_uniqueid, ike_uniqueid; + struct { + union sockunion host; + struct blob id, cert; + } local, remote; +}; + +static void parse_sa_message(struct vici_message_ctx *ctx, + enum vici_type_t msgtype, const struct blob *key, + const struct blob *val) +{ + struct handle_sa_ctx *sactx = + container_of(ctx, struct handle_sa_ctx, msgctx); + struct nhrp_vc *vc; + char buf[512]; + + switch (msgtype) { + case VICI_SECTION_START: + if (ctx->nsections == 3) { + /* Begin of child-sa section, reset child vars */ + sactx->child_uniqueid = 0; + sactx->child_ok = 0; + } + break; + case VICI_SECTION_END: + if (ctx->nsections == 3) { + /* End of child-sa section, update nhrp_vc */ + int up = sactx->child_ok || sactx->event == 1; + if (up) { + vc = nhrp_vc_get(&sactx->local.host, + &sactx->remote.host, up); + if (vc) { + blob2buf(&sactx->local.id, vc->local.id, + sizeof(vc->local.id)); + if (blob2buf(&sactx->local.cert, + (char *)vc->local.cert, + sizeof(vc->local.cert))) + vc->local.certlen = + sactx->local.cert.len; + blob2buf(&sactx->remote.id, + vc->remote.id, + sizeof(vc->remote.id)); + if (blob2buf(&sactx->remote.cert, + (char *)vc->remote.cert, + sizeof(vc->remote.cert))) + vc->remote.certlen = + sactx->remote.cert.len; + sactx->kill_ikesa |= + nhrp_vc_ipsec_updown( + sactx->child_uniqueid, + vc); + vc->ike_uniqueid = sactx->ike_uniqueid; + } + } else { + nhrp_vc_ipsec_updown(sactx->child_uniqueid, 0); + } + } + break; + case VICI_START: + case VICI_KEY_VALUE: + case VICI_LIST_START: + case VICI_LIST_ITEM: + case VICI_LIST_END: + case VICI_END: + if (!key || !key->ptr) + break; + + switch (key->ptr[0]) { + case 'l': + if (blob_equal(key, "local-host") + && ctx->nsections == 1) { + if (blob2buf(val, buf, sizeof(buf))) + if (str2sockunion(buf, + &sactx->local.host) + < 0) + flog_err( + EC_NHRP_SWAN, + "VICI: bad strongSwan local-host: %s", + buf); + } else if (blob_equal(key, "local-id") + && ctx->nsections == 1) { + sactx->local.id = *val; + } else if (blob_equal(key, "local-cert-data") + && ctx->nsections == 1) { + sactx->local.cert = *val; + } + break; + case 'r': + if (blob_equal(key, "remote-host") + && ctx->nsections == 1) { + if (blob2buf(val, buf, sizeof(buf))) + if (str2sockunion(buf, + &sactx->remote.host) + < 0) + flog_err( + EC_NHRP_SWAN, + "VICI: bad strongSwan remote-host: %s", + buf); + } else if (blob_equal(key, "remote-id") + && ctx->nsections == 1) { + sactx->remote.id = *val; + } else if (blob_equal(key, "remote-cert-data") + && ctx->nsections == 1) { + sactx->remote.cert = *val; + } + break; + case 'u': + if (blob_equal(key, "uniqueid") + && blob2buf(val, buf, sizeof(buf))) { + if (ctx->nsections == 3) + sactx->child_uniqueid = + strtoul(buf, NULL, 0); + else if (ctx->nsections == 1) + sactx->ike_uniqueid = + strtoul(buf, NULL, 0); + } + break; + case 's': + if (blob_equal(key, "state") && ctx->nsections == 3) { + sactx->child_ok = + (sactx->event == 0 + && (blob_equal(val, "INSTALLED") + || blob_equal(val, "REKEYED"))); + } + break; + } + break; + } +} + +static void parse_cmd_response(struct vici_message_ctx *ctx, + enum vici_type_t msgtype, const struct blob *key, + const struct blob *val) +{ + char buf[512]; + + switch (msgtype) { + case VICI_KEY_VALUE: + if (blob_equal(key, "errmsg") + && blob2buf(val, buf, sizeof(buf))) + flog_err(EC_NHRP_SWAN, "VICI: strongSwan: %s", buf); + break; + case VICI_START: + case VICI_SECTION_START: + case VICI_SECTION_END: + case VICI_LIST_START: + case VICI_LIST_ITEM: + case VICI_LIST_END: + case VICI_END: + break; + } +} + +static void vici_recv_sa(struct vici_conn *vici, struct zbuf *msg, int event) +{ + char buf[32]; + struct handle_sa_ctx ctx = { + .event = event, + .msgctx.nsections = 0 + }; + + vici_parse_message(vici, msg, parse_sa_message, &ctx.msgctx); + + if (ctx.kill_ikesa && ctx.ike_uniqueid) { + debugf(NHRP_DEBUG_COMMON, "VICI: Deleting IKE_SA %u", + ctx.ike_uniqueid); + snprintf(buf, sizeof(buf), "%u", ctx.ike_uniqueid); + vici_submit_request(vici, "terminate", VICI_KEY_VALUE, "ike-id", + strlen(buf), buf, VICI_END); + } +} + +static void vici_recv_message(struct vici_conn *vici, struct zbuf *msg) +{ + uint32_t msglen; + uint8_t msgtype; + struct blob name; + struct vici_message_ctx ctx = { .nsections = 0 }; + + msglen = zbuf_get_be32(msg); + msgtype = zbuf_get8(msg); + debugf(NHRP_DEBUG_VICI, "VICI: Message %d, %d bytes", msgtype, msglen); + + switch (msgtype) { + case VICI_EVENT: + name.len = zbuf_get8(msg); + name.ptr = zbuf_pulln(msg, name.len); + + debugf(NHRP_DEBUG_VICI, "VICI: Event '%.*s'", name.len, + name.ptr); + if (blob_equal(&name, "list-sa") + || blob_equal(&name, "child-updown") + || blob_equal(&name, "child-rekey")) + vici_recv_sa(vici, msg, 0); + else if (blob_equal(&name, "child-state-installed") + || blob_equal(&name, "child-state-rekeyed")) + vici_recv_sa(vici, msg, 1); + else if (blob_equal(&name, "child-state-destroying")) + vici_recv_sa(vici, msg, 2); + break; + case VICI_CMD_RESPONSE: + vici_parse_message(vici, msg, parse_cmd_response, &ctx); + break; + case VICI_EVENT_UNKNOWN: + case VICI_CMD_UNKNOWN: + flog_err( + EC_NHRP_SWAN, + "VICI: StrongSwan does not support mandatory events (unpatched?)"); + break; + case VICI_EVENT_CONFIRM: + break; + default: + zlog_notice("VICI: Unrecognized message type %d", msgtype); + break; + } +} + +static void vici_read(struct event *t) +{ + struct vici_conn *vici = EVENT_ARG(t); + struct zbuf *ibuf = &vici->ibuf; + struct zbuf pktbuf; + + if (zbuf_read(ibuf, vici->fd, (size_t)-1) < 0) { + vici_connection_error(vici); + return; + } + + /* Process all messages in buffer */ + do { + uint32_t *hdrlen = zbuf_may_pull(ibuf, uint32_t); + if (!hdrlen) + break; + if (!zbuf_may_pulln(ibuf, ntohl(*hdrlen))) { + zbuf_reset_head(ibuf, hdrlen); + break; + } + + /* Handle packet */ + zbuf_init(&pktbuf, hdrlen, htonl(*hdrlen) + 4, + htonl(*hdrlen) + 4); + vici_recv_message(vici, &pktbuf); + } while (1); + + event_add_read(master, vici_read, vici, vici->fd, &vici->t_read); +} + +static void vici_write(struct event *t) +{ + struct vici_conn *vici = EVENT_ARG(t); + int r; + + r = zbufq_write(&vici->obuf, vici->fd); + if (r > 0) { + event_add_write(master, vici_write, vici, vici->fd, + &vici->t_write); + } else if (r < 0) { + vici_connection_error(vici); + } +} + +static void vici_submit(struct vici_conn *vici, struct zbuf *obuf) +{ + if (vici->fd < 0) { + zbuf_free(obuf); + return; + } + + zbufq_queue(&vici->obuf, obuf); + event_add_write(master, vici_write, vici, vici->fd, &vici->t_write); +} + +static void vici_submit_request(struct vici_conn *vici, const char *name, ...) +{ + struct zbuf *obuf; + uint32_t *hdrlen; + va_list va; + size_t len; + int type; + + obuf = zbuf_alloc(256); + if (!obuf) + return; + + hdrlen = zbuf_push(obuf, uint32_t); + zbuf_put8(obuf, VICI_CMD_REQUEST); + vici_zbuf_puts(obuf, name); + + va_start(va, name); + for (type = va_arg(va, int); type != VICI_END; type = va_arg(va, int)) { + zbuf_put8(obuf, type); + switch (type) { + case VICI_KEY_VALUE: + vici_zbuf_puts(obuf, va_arg(va, const char *)); + len = va_arg(va, size_t); + zbuf_put_be16(obuf, len); + zbuf_put(obuf, va_arg(va, void *), len); + break; + default: + break; + } + } + va_end(va); + *hdrlen = htonl(zbuf_used(obuf) - 4); + vici_submit(vici, obuf); +} + +static void vici_register_event(struct vici_conn *vici, const char *name) +{ + struct zbuf *obuf; + uint32_t *hdrlen; + uint8_t namelen; + + namelen = strlen(name); + obuf = zbuf_alloc(4 + 1 + 1 + namelen); + if (!obuf) + return; + + hdrlen = zbuf_push(obuf, uint32_t); + zbuf_put8(obuf, VICI_EVENT_REGISTER); + zbuf_put8(obuf, namelen); + zbuf_put(obuf, name, namelen); + *hdrlen = htonl(zbuf_used(obuf) - 4); + + vici_submit(vici, obuf); +} + +static bool vici_charon_filepath_done; +static bool vici_charon_not_found; + +static char *vici_get_charon_filepath(void) +{ + static char buff[1200]; + FILE *fp; + char *ptr; + char line[1024]; + + if (vici_charon_filepath_done) + return (char *)buff; + fp = popen("ipsec --piddir", "r"); + if (!fp) { + if (!vici_charon_not_found) { + flog_err(EC_NHRP_SWAN, + "VICI: Failed to retrieve charon file path"); + vici_charon_not_found = true; + } + return NULL; + } + /* last line of output is used to get vici path */ + while (fgets(line, sizeof(line), fp) != NULL) { + ptr = strchr(line, '\n'); + if (ptr) + *ptr = '\0'; + snprintf(buff, sizeof(buff), "%s/charon.vici", line); + } + pclose(fp); + vici_charon_filepath_done = true; + return buff; +} + +static void vici_reconnect(struct event *t) +{ + struct vici_conn *vici = EVENT_ARG(t); + int fd; + char *file_path; + + if (vici->fd >= 0) + return; + + fd = sock_open_unix(VICI_SOCKET); + if (fd < 0) { + file_path = vici_get_charon_filepath(); + if (file_path) + fd = sock_open_unix(file_path); + } + if (fd < 0) { + debugf(NHRP_DEBUG_VICI, + "%s: failure connecting VICI socket: %s", __func__, + strerror(errno)); + event_add_timer(master, vici_reconnect, vici, 2, + &vici->t_reconnect); + return; + } + + debugf(NHRP_DEBUG_COMMON, "VICI: Connected"); + vici->fd = fd; + event_add_read(master, vici_read, vici, vici->fd, &vici->t_read); + + /* Send event subscribtions */ + // vici_register_event(vici, "child-updown"); + // vici_register_event(vici, "child-rekey"); + vici_register_event(vici, "child-state-installed"); + vici_register_event(vici, "child-state-rekeyed"); + vici_register_event(vici, "child-state-destroying"); + vici_register_event(vici, "list-sa"); + vici_submit_request(vici, "list-sas", VICI_END); +} + +static struct vici_conn vici_connection; + +void vici_init(void) +{ + struct vici_conn *vici = &vici_connection; + + vici->fd = -1; + zbuf_init(&vici->ibuf, vici->ibuf_data, sizeof(vici->ibuf_data), 0); + zbufq_init(&vici->obuf); + event_add_timer_msec(master, vici_reconnect, vici, 10, + &vici->t_reconnect); +} + +void vici_terminate(void) +{ +} + +void vici_terminate_vc_by_profile_name(char *profile_name) +{ + struct vici_conn *vici = &vici_connection; + + debugf(NHRP_DEBUG_VICI, "Terminate profile = %s", profile_name); + vici_submit_request(vici, "terminate", VICI_KEY_VALUE, "ike", + strlen(profile_name), profile_name, VICI_END); +} + +void vici_terminate_vc_by_ike_id(unsigned int ike_id) +{ + struct vici_conn *vici = &vici_connection; + char ike_id_str[10]; + + snprintf(ike_id_str, sizeof(ike_id_str), "%d", ike_id); + debugf(NHRP_DEBUG_VICI, "Terminate ike_id_str = %s", ike_id_str); + vici_submit_request(vici, "terminate", VICI_KEY_VALUE, "ike-id", + strlen(ike_id_str), ike_id_str, VICI_END); +} + +void vici_request_vc(const char *profile, union sockunion *src, + union sockunion *dst, int prio) +{ + struct vici_conn *vici = &vici_connection; + char buf[2][SU_ADDRSTRLEN]; + + sockunion2str(src, buf[0], sizeof(buf[0])); + sockunion2str(dst, buf[1], sizeof(buf[1])); + + vici_submit_request(vici, "initiate", VICI_KEY_VALUE, "child", + strlen(profile), profile, VICI_KEY_VALUE, "timeout", + (size_t)2, "-1", VICI_KEY_VALUE, "async", (size_t)1, + "1", VICI_KEY_VALUE, "init-limits", (size_t)1, + prio ? "0" : "1", VICI_KEY_VALUE, "my-host", + strlen(buf[0]), buf[0], VICI_KEY_VALUE, + "other-host", strlen(buf[1]), buf[1], VICI_END); +} + +int sock_open_unix(const char *path) +{ + int ret, fd; + struct sockaddr_un addr; + + fd = socket(AF_UNIX, SOCK_STREAM, 0); + if (fd < 0) + return -1; + + memset(&addr, 0, sizeof(addr)); + addr.sun_family = AF_UNIX; + strlcpy(addr.sun_path, path, sizeof(addr.sun_path)); + + ret = connect(fd, (struct sockaddr *)&addr, + sizeof(addr.sun_family) + strlen(addr.sun_path)); + if (ret < 0) { + close(fd); + return -1; + } + + ret = fcntl(fd, F_SETFL, fcntl(fd, F_GETFL, 0) | O_NONBLOCK); + if (ret < 0) { + close(fd); + return -1; + } + + return fd; +} diff --git a/nhrpd/vici.h b/nhrpd/vici.h new file mode 100644 index 0000000..f2ad3a9 --- /dev/null +++ b/nhrpd/vici.h @@ -0,0 +1,24 @@ + +enum vici_type_t { + VICI_START = 0, + VICI_SECTION_START = 1, + VICI_SECTION_END = 2, + VICI_KEY_VALUE = 3, + VICI_LIST_START = 4, + VICI_LIST_ITEM = 5, + VICI_LIST_END = 6, + VICI_END = 7 +}; + +enum vici_operation_t { + VICI_CMD_REQUEST = 0, + VICI_CMD_RESPONSE, + VICI_CMD_UNKNOWN, + VICI_EVENT_REGISTER, + VICI_EVENT_UNREGISTER, + VICI_EVENT_CONFIRM, + VICI_EVENT_UNKNOWN, + VICI_EVENT, +}; + +#define VICI_MAX_MSGLEN (512*1024) diff --git a/nhrpd/zbuf.c b/nhrpd/zbuf.c new file mode 100644 index 0000000..0eb8198 --- /dev/null +++ b/nhrpd/zbuf.c @@ -0,0 +1,237 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* Stream/packet buffer API implementation + * Copyright (c) 2014-2015 Timo Teräs + */ + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include +#include +#include +#include +#include "zbuf.h" +#include "memory.h" +#include "nhrpd.h" + +#define ERRNO_IO_RETRY(EN) (((EN) == EAGAIN) || ((EN) == EWOULDBLOCK) || ((EN) == EINTR)) + +DEFINE_MTYPE_STATIC(NHRPD, ZBUF_DATA, "NHRPD zbuf data"); + +struct zbuf *zbuf_alloc(size_t size) +{ + struct zbuf *zb; + + zb = XCALLOC(MTYPE_ZBUF_DATA, sizeof(*zb) + size); + + zbuf_init(zb, zb + 1, size, 0); + zb->allocated = 1; + + return zb; +} + +void zbuf_init(struct zbuf *zb, void *buf, size_t len, size_t datalen) +{ + *zb = (struct zbuf){ + .buf = buf, + .end = (uint8_t *)buf + len, + .head = buf, + .tail = (uint8_t *)buf + datalen, + }; +} + +void zbuf_free(struct zbuf *zb) +{ + if (zb->allocated) + XFREE(MTYPE_ZBUF_DATA, zb); +} + +void zbuf_reset(struct zbuf *zb) +{ + zb->head = zb->tail = zb->buf; + zb->error = 0; +} + +void zbuf_reset_head(struct zbuf *zb, void *ptr) +{ + assert((void *)zb->buf <= ptr && ptr <= (void *)zb->tail); + zb->head = ptr; +} + +static void zbuf_remove_headroom(struct zbuf *zb) +{ + ssize_t headroom = zbuf_headroom(zb); + if (!headroom) + return; + memmove(zb->buf, zb->head, zbuf_used(zb)); + zb->head -= headroom; + zb->tail -= headroom; +} + +ssize_t zbuf_read(struct zbuf *zb, int fd, size_t maxlen) +{ + ssize_t r; + + if (zb->error) + return -3; + + zbuf_remove_headroom(zb); + if (maxlen > zbuf_tailroom(zb)) + maxlen = zbuf_tailroom(zb); + + r = read(fd, zb->tail, maxlen); + if (r > 0) + zb->tail += r; + else if (r == 0) + r = -2; + else if (ERRNO_IO_RETRY(errno)) + r = 0; + + return r; +} + +ssize_t zbuf_write(struct zbuf *zb, int fd) +{ + ssize_t r; + + if (zb->error) + return -3; + + r = write(fd, zb->head, zbuf_used(zb)); + if (r > 0) { + zb->head += r; + if (zb->head == zb->tail) + zbuf_reset(zb); + } else if (r == 0) + r = -2; + else if (ERRNO_IO_RETRY(errno)) + r = 0; + + return r; +} + +ssize_t zbuf_recv(struct zbuf *zb, int fd) +{ + ssize_t r; + + if (zb->error) + return -3; + + zbuf_remove_headroom(zb); + r = recv(fd, zb->tail, zbuf_tailroom(zb), 0); + if (r > 0) + zb->tail += r; + else if (r == 0) + r = -2; + else if (ERRNO_IO_RETRY(errno)) + r = 0; + return r; +} + +ssize_t zbuf_send(struct zbuf *zb, int fd) +{ + ssize_t r; + + if (zb->error) + return -3; + + r = send(fd, zb->head, zbuf_used(zb), 0); + if (r >= 0) + zbuf_reset(zb); + + return r; +} + +void *zbuf_may_pull_until(struct zbuf *zb, const char *sep, struct zbuf *msg) +{ + size_t seplen = strlen(sep), len; + uint8_t *ptr; + + ptr = memmem(zb->head, zbuf_used(zb), sep, seplen); + if (!ptr) + return NULL; + + len = ptr - zb->head + seplen; + zbuf_init(msg, zbuf_pulln(zb, len), len, len); + return msg->head; +} + +void zbufq_init(struct zbuf_queue *zbq) +{ + *zbq = (struct zbuf_queue){ + .queue_head = INIT_DLIST(zbq->queue_head), + }; +} + +void zbufq_reset(struct zbuf_queue *zbq) +{ + struct zbuf *buf; + + frr_each_safe (zbuf_queue, &zbq->queue_head, buf) { + zbuf_queue_del(&zbq->queue_head, buf); + zbuf_free(buf); + } +} + +void zbufq_queue(struct zbuf_queue *zbq, struct zbuf *zb) +{ + zbuf_queue_add_tail(&zbq->queue_head, zb); +} + +int zbufq_write(struct zbuf_queue *zbq, int fd) +{ + struct iovec iov[16]; + struct zbuf *zb; + ssize_t r; + size_t iovcnt = 0; + + frr_each_safe (zbuf_queue, &zbq->queue_head, zb) { + iov[iovcnt++] = (struct iovec){ + .iov_base = zb->head, .iov_len = zbuf_used(zb), + }; + if (iovcnt >= array_size(iov)) + break; + } + + r = writev(fd, iov, iovcnt); + if (r < 0) + return r; + + frr_each_safe (zbuf_queue, &zbq->queue_head, zb) { + if (r < (ssize_t)zbuf_used(zb)) { + zb->head += r; + return 1; + } + + r -= zbuf_used(zb); + zbuf_queue_del(&zbq->queue_head, zb); + zbuf_free(zb); + } + + return 0; +} + +void zbuf_copy(struct zbuf *zdst, struct zbuf *zsrc, size_t len) +{ + const void *src; + void *dst; + + dst = zbuf_pushn(zdst, len); + src = zbuf_pulln(zsrc, len); + if (!dst || !src) + return; + memcpy(dst, src, len); +} + +void zbuf_copy_peek(struct zbuf *zdst, struct zbuf *zsrc, size_t len) +{ + const void *src; + void *dst; + + dst = zbuf_pushn(zdst, len); + src = zbuf_pulln(zsrc, 0); + if (!dst || !src) + return; + memcpy(dst, src, len); +} diff --git a/nhrpd/zbuf.h b/nhrpd/zbuf.h new file mode 100644 index 0000000..23492b4 --- /dev/null +++ b/nhrpd/zbuf.h @@ -0,0 +1,199 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* Stream/packet buffer API + * Copyright (c) 2014-2015 Timo Teräs + */ + +#ifndef ZBUF_H +#define ZBUF_H + +#include +#include +#include +#include + +#include "typesafe.h" + +PREDECL_DLIST(zbuf_queue); + +struct zbuf { + struct zbuf_queue_item queue_entry; + unsigned allocated : 1; + unsigned error : 1; + uint8_t *buf, *end; + uint8_t *head, *tail; +}; + +DECLARE_DLIST(zbuf_queue, struct zbuf, queue_entry); + +struct zbuf_queue { + struct zbuf_queue_head queue_head; +}; + +struct zbuf *zbuf_alloc(size_t size); +void zbuf_init(struct zbuf *zb, void *buf, size_t len, size_t datalen); +void zbuf_free(struct zbuf *zb); + +static inline size_t zbuf_size(struct zbuf *zb) +{ + return zb->end - zb->buf; +} + +static inline size_t zbuf_used(struct zbuf *zb) +{ + return zb->tail - zb->head; +} + +static inline size_t zbuf_tailroom(struct zbuf *zb) +{ + return zb->end - zb->tail; +} + +static inline size_t zbuf_headroom(struct zbuf *zb) +{ + return zb->head - zb->buf; +} + +void zbuf_reset(struct zbuf *zb); +void zbuf_reset_head(struct zbuf *zb, void *ptr); +ssize_t zbuf_read(struct zbuf *zb, int fd, size_t maxlen); +ssize_t zbuf_write(struct zbuf *zb, int fd); +ssize_t zbuf_recv(struct zbuf *zb, int fd); +ssize_t zbuf_send(struct zbuf *zb, int fd); + +static inline void zbuf_set_rerror(struct zbuf *zb) +{ + zb->error = 1; + zb->head = zb->tail; +} + +static inline void zbuf_set_werror(struct zbuf *zb) +{ + zb->error = 1; + zb->tail = zb->end; +} + +static inline void *__zbuf_pull(struct zbuf *zb, size_t size, int error) +{ + void *head = zb->head; + if (size > zbuf_used(zb)) { + if (error) + zbuf_set_rerror(zb); + return NULL; + } + zb->head += size; + return head; +} + +#define zbuf_pull(zb, type) ((type *)__zbuf_pull(zb, sizeof(type), 1)) +#define zbuf_pulln(zb, sz) (__zbuf_pull(zb, sz, 1)) +#define zbuf_may_pull(zb, type) ((type *)__zbuf_pull(zb, sizeof(type), 0)) +#define zbuf_may_pulln(zb, sz) (__zbuf_pull(zb, sz, 0)) + +void *zbuf_may_pull_until(struct zbuf *zb, const char *sep, struct zbuf *msg); + +static inline void zbuf_get(struct zbuf *zb, void *dst, size_t len) +{ + void *src = zbuf_pulln(zb, len); + if (src) + memcpy(dst, src, len); +} + +static inline uint8_t zbuf_get8(struct zbuf *zb) +{ + uint8_t *src = zbuf_pull(zb, uint8_t); + if (src) + return *src; + return 0; +} + +static inline uint32_t zbuf_get32(struct zbuf *zb) +{ + struct unaligned32 { + uint32_t value; + } __attribute__((packed)); + + struct unaligned32 *v = zbuf_pull(zb, struct unaligned32); + if (v) + return v->value; + return 0; +} + +static inline uint16_t zbuf_get_be16(struct zbuf *zb) +{ + struct unaligned16 { + uint16_t value; + } __attribute__((packed)); + + struct unaligned16 *v = zbuf_pull(zb, struct unaligned16); + if (v) + return be16toh(v->value); + return 0; +} + +static inline uint32_t zbuf_get_be32(struct zbuf *zb) +{ + return be32toh(zbuf_get32(zb)); +} + +static inline void *__zbuf_push(struct zbuf *zb, size_t size, int error) +{ + void *tail = zb->tail; + if (size > zbuf_tailroom(zb)) { + if (error) + zbuf_set_werror(zb); + return NULL; + } + zb->tail += size; + return tail; +} + +#define zbuf_push(zb, type) ((type *)__zbuf_push(zb, sizeof(type), 1)) +#define zbuf_pushn(zb, sz) (__zbuf_push(zb, sz, 1)) +#define zbuf_may_push(zb, type) ((type *)__zbuf_may_push(zb, sizeof(type), 0)) +#define zbuf_may_pushn(zb, sz) (__zbuf_push(zb, sz, 0)) + +static inline void zbuf_put(struct zbuf *zb, const void *src, size_t len) +{ + void *dst = zbuf_pushn(zb, len); + if (dst) + memcpy(dst, src, len); +} + +static inline void zbuf_put8(struct zbuf *zb, uint8_t val) +{ + uint8_t *dst = zbuf_push(zb, uint8_t); + if (dst) + *dst = val; +} + +static inline void zbuf_put_be16(struct zbuf *zb, uint16_t val) +{ + struct unaligned16 { + uint16_t value; + } __attribute__((packed)); + + struct unaligned16 *v = zbuf_push(zb, struct unaligned16); + if (v) + v->value = htobe16(val); +} + +static inline void zbuf_put_be32(struct zbuf *zb, uint32_t val) +{ + struct unaligned32 { + uint32_t value; + } __attribute__((packed)); + + struct unaligned32 *v = zbuf_push(zb, struct unaligned32); + if (v) + v->value = htobe32(val); +} + +void zbuf_copy(struct zbuf *zb, struct zbuf *src, size_t len); +void zbuf_copy_peek(struct zbuf *zdst, struct zbuf *zsrc, size_t len); + +void zbufq_init(struct zbuf_queue *); +void zbufq_reset(struct zbuf_queue *); +void zbufq_queue(struct zbuf_queue *, struct zbuf *); +int zbufq_write(struct zbuf_queue *, int); + +#endif diff --git a/nhrpd/znl.c b/nhrpd/znl.c new file mode 100644 index 0000000..a65282f --- /dev/null +++ b/nhrpd/znl.c @@ -0,0 +1,166 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* Netlink helpers for zbuf + * Copyright (c) 2014-2015 Timo Teräs + */ + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include +#include +#include +#include +#include +#include +#include +#include + +#include "znl.h" + +#define ZNL_ALIGN(len) (((len)+3) & ~3) + +void *znl_push(struct zbuf *zb, size_t n) +{ + return zbuf_pushn(zb, ZNL_ALIGN(n)); +} + +void *znl_pull(struct zbuf *zb, size_t n) +{ + return zbuf_pulln(zb, ZNL_ALIGN(n)); +} + +struct nlmsghdr *znl_nlmsg_push(struct zbuf *zb, uint16_t type, uint16_t flags) +{ + struct nlmsghdr *n; + + n = znl_push(zb, sizeof(*n)); + if (!n) + return NULL; + + *n = (struct nlmsghdr){ + .nlmsg_type = type, .nlmsg_flags = flags, + }; + return n; +} + +void znl_nlmsg_complete(struct zbuf *zb, struct nlmsghdr *n) +{ + n->nlmsg_len = zb->tail - (uint8_t *)n; +} + +struct nlmsghdr *znl_nlmsg_pull(struct zbuf *zb, struct zbuf *payload) +{ + struct nlmsghdr *n; + size_t plen; + + n = znl_pull(zb, sizeof(*n)); + if (!n) + return NULL; + + plen = n->nlmsg_len - sizeof(*n); + zbuf_init(payload, znl_pull(zb, plen), plen, plen); + zbuf_may_pulln(zb, ZNL_ALIGN(plen) - plen); + + return n; +} + +struct rtattr *znl_rta_push(struct zbuf *zb, uint16_t type, const void *val, + size_t len) +{ + struct rtattr *rta; + uint8_t *dst; + + rta = znl_push(zb, ZNL_ALIGN(sizeof(*rta)) + ZNL_ALIGN(len)); + if (!rta) + return NULL; + + *rta = (struct rtattr){ + .rta_type = type, .rta_len = ZNL_ALIGN(sizeof(*rta)) + len, + }; + + dst = (uint8_t *)(rta + 1); + memcpy(dst, val, len); + memset(dst + len, 0, ZNL_ALIGN(len) - len); + + return rta; +} + +struct rtattr *znl_rta_push_u32(struct zbuf *zb, uint16_t type, uint32_t val) +{ + return znl_rta_push(zb, type, &val, sizeof(val)); +} + +struct rtattr *znl_rta_nested_push(struct zbuf *zb, uint16_t type) +{ + struct rtattr *rta; + + rta = znl_push(zb, sizeof(*rta)); + if (!rta) + return NULL; + + *rta = (struct rtattr){ + .rta_type = type, + }; + return rta; +} + +void znl_rta_nested_complete(struct zbuf *zb, struct rtattr *rta) +{ + size_t len = zb->tail - (uint8_t *)rta; + size_t align = ZNL_ALIGN(len) - len; + + if (align) { + void *dst = zbuf_pushn(zb, align); + if (dst) + memset(dst, 0, align); + } + rta->rta_len = len; +} + +struct rtattr *znl_rta_pull(struct zbuf *zb, struct zbuf *payload) +{ + struct rtattr *rta; + size_t plen; + + rta = znl_pull(zb, sizeof(*rta)); + if (!rta) + return NULL; + + if (rta->rta_len > sizeof(*rta)) { + plen = rta->rta_len - sizeof(*rta); + zbuf_init(payload, znl_pull(zb, plen), plen, plen); + } else { + zbuf_init(payload, NULL, 0, 0); + } + + return rta; +} + +int znl_open(int protocol, int groups) +{ + struct sockaddr_nl addr; + int fd, buf = 128 * 1024; + + fd = socket(AF_NETLINK, SOCK_RAW, protocol); + if (fd < 0) + return -1; + + if (fcntl(fd, F_SETFL, fcntl(fd, F_GETFL, 0) | O_NONBLOCK) < 0) + goto error; + if (fcntl(fd, F_SETFD, FD_CLOEXEC) < 0) + goto error; + if (setsockopt(fd, SOL_SOCKET, SO_RCVBUF, &buf, sizeof(buf)) < 0) + goto error; + + memset(&addr, 0, sizeof(addr)); + addr.nl_family = AF_NETLINK; + addr.nl_groups = groups; + if (bind(fd, (struct sockaddr *)&addr, sizeof(addr)) < 0) + goto error; + + return fd; +error: + close(fd); + return -1; +} diff --git a/nhrpd/znl.h b/nhrpd/znl.h new file mode 100644 index 0000000..95dfae8 --- /dev/null +++ b/nhrpd/znl.h @@ -0,0 +1,25 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* Netlink helpers for zbuf + * Copyright (c) 2014-2015 Timo Teräs + */ + +#include "zbuf.h" + +#define ZNL_BUFFER_SIZE 8192 + +void *znl_push(struct zbuf *zb, size_t n); +void *znl_pull(struct zbuf *zb, size_t n); + +struct nlmsghdr *znl_nlmsg_push(struct zbuf *zb, uint16_t type, uint16_t flags); +void znl_nlmsg_complete(struct zbuf *zb, struct nlmsghdr *n); +struct nlmsghdr *znl_nlmsg_pull(struct zbuf *zb, struct zbuf *payload); + +struct rtattr *znl_rta_push(struct zbuf *zb, uint16_t type, const void *val, + size_t len); +struct rtattr *znl_rta_push_u32(struct zbuf *zb, uint16_t type, uint32_t val); +struct rtattr *znl_rta_nested_push(struct zbuf *zb, uint16_t type); +void znl_rta_nested_complete(struct zbuf *zb, struct rtattr *rta); + +struct rtattr *znl_rta_pull(struct zbuf *zb, struct zbuf *payload); + +int znl_open(int protocol, int groups); -- cgit v1.2.3