summaryrefslogtreecommitdiffstats
path: root/pimd/pim_zlookup.c
diff options
context:
space:
mode:
Diffstat (limited to 'pimd/pim_zlookup.c')
-rw-r--r--pimd/pim_zlookup.c574
1 files changed, 574 insertions, 0 deletions
diff --git a/pimd/pim_zlookup.c b/pimd/pim_zlookup.c
new file mode 100644
index 0000000..6a026f9
--- /dev/null
+++ b/pimd/pim_zlookup.c
@@ -0,0 +1,574 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * PIM for Quagga
+ * Copyright (C) 2008 Everton da Silva Marques
+ */
+
+#include <zebra.h>
+
+#include "log.h"
+#include "prefix.h"
+#include "zclient.h"
+#include "stream.h"
+#include "network.h"
+#include "frrevent.h"
+#include "prefix.h"
+#include "vty.h"
+#include "lib_errors.h"
+
+#include "pimd.h"
+#include "pim_instance.h"
+#include "pim_iface.h"
+#include "pim_neighbor.h"
+#include "pim_pim.h"
+#include "pim_str.h"
+#include "pim_oil.h"
+#include "pim_zlookup.h"
+#include "pim_addr.h"
+
+static struct zclient *zlookup = NULL;
+struct event *zlookup_read;
+
+static void zclient_lookup_sched(struct zclient *zlookup, int delay);
+static void zclient_lookup_read_pipe(struct event *thread);
+
+/* Connect to zebra for nexthop lookup. */
+static void zclient_lookup_connect(struct event *t)
+{
+ struct zclient *zlookup;
+
+ zlookup = EVENT_ARG(t);
+
+ if (zlookup->sock >= 0) {
+ return;
+ }
+
+ if (zclient_socket_connect(zlookup) < 0) {
+ ++zlookup->fail;
+ zlog_warn("%s: failure connecting zclient socket: failures=%d",
+ __func__, zlookup->fail);
+ } else {
+ zlookup->fail = 0; /* reset counter on connection */
+ }
+
+ if (zclient_send_hello(zlookup) == ZCLIENT_SEND_FAILURE) {
+ if (close(zlookup->sock)) {
+ zlog_warn("%s: closing fd=%d: errno=%d %s", __func__,
+ zlookup->sock, errno, safe_strerror(errno));
+ }
+ zlookup->sock = -1;
+ }
+
+ if (zlookup->sock < 0) {
+ /* Since last connect failed, retry within 10 secs */
+ zclient_lookup_sched(zlookup, 10);
+ return;
+ }
+
+ event_add_timer(router->master, zclient_lookup_read_pipe, zlookup, 60,
+ &zlookup_read);
+}
+
+/* Schedule connection with delay. */
+static void zclient_lookup_sched(struct zclient *zlookup, int delay)
+{
+ event_add_timer(router->master, zclient_lookup_connect, zlookup, delay,
+ &zlookup->t_connect);
+
+ zlog_notice("%s: zclient lookup connection scheduled for %d seconds",
+ __func__, delay);
+}
+
+/* Schedule connection for now. */
+static void zclient_lookup_sched_now(struct zclient *zlookup)
+{
+ event_add_event(router->master, zclient_lookup_connect, zlookup, 0,
+ &zlookup->t_connect);
+
+ zlog_notice("%s: zclient lookup immediate connection scheduled",
+ __func__);
+}
+
+/* Schedule reconnection, if needed. */
+static void zclient_lookup_reconnect(struct zclient *zlookup)
+{
+ if (zlookup->t_connect) {
+ return;
+ }
+
+ zclient_lookup_sched_now(zlookup);
+}
+
+static void zclient_lookup_failed(struct zclient *zlookup)
+{
+ if (zlookup->sock >= 0) {
+ if (close(zlookup->sock)) {
+ zlog_warn("%s: closing fd=%d: errno=%d %s", __func__,
+ zlookup->sock, errno, safe_strerror(errno));
+ }
+ zlookup->sock = -1;
+ }
+
+ zclient_lookup_reconnect(zlookup);
+}
+
+void zclient_lookup_free(void)
+{
+ EVENT_OFF(zlookup_read);
+ zclient_stop(zlookup);
+ zclient_free(zlookup);
+ zlookup = NULL;
+}
+
+void zclient_lookup_new(void)
+{
+ struct zclient_options options = zclient_options_default;
+ options.synchronous = true;
+
+ zlookup = zclient_new(router->master, &options, NULL, 0);
+ if (!zlookup) {
+ flog_err(EC_LIB_ZAPI_SOCKET, "%s: zclient_new() failure",
+ __func__);
+ return;
+ }
+
+ zlookup->sock = -1;
+ zlookup->t_connect = NULL;
+ zlookup->privs = &pimd_privs;
+
+ zclient_lookup_sched_now(zlookup);
+
+ zlog_notice("%s: zclient lookup socket initialized", __func__);
+}
+
+static int zclient_read_nexthop(struct pim_instance *pim,
+ struct zclient *zlookup,
+ struct pim_zlookup_nexthop nexthop_tab[],
+ const int tab_size, pim_addr addr)
+{
+ int num_ifindex = 0;
+ struct stream *s;
+ uint16_t length;
+ uint8_t marker;
+ uint8_t version;
+ vrf_id_t vrf_id;
+ uint16_t command = 0;
+ struct ipaddr raddr;
+ uint8_t distance;
+ uint32_t metric;
+ int nexthop_num;
+ int i, err;
+
+ if (PIM_DEBUG_PIM_NHT_DETAIL)
+ zlog_debug("%s: addr=%pPAs(%s)", __func__, &addr,
+ pim->vrf->name);
+
+ s = zlookup->ibuf;
+
+ while (command != ZEBRA_NEXTHOP_LOOKUP_MRIB) {
+ stream_reset(s);
+ err = zclient_read_header(s, zlookup->sock, &length, &marker,
+ &version, &vrf_id, &command);
+ if (err < 0) {
+ flog_err(EC_LIB_ZAPI_MISSMATCH,
+ "%s: zclient_read_header() failed", __func__);
+ zclient_lookup_failed(zlookup);
+ return -1;
+ }
+
+ if (command == ZEBRA_ERROR) {
+ enum zebra_error_types error;
+
+ zapi_error_decode(s, &error);
+ /* Do nothing with it for now */
+ return -1;
+ }
+ }
+
+ stream_get_ipaddr(s, &raddr);
+
+ if (raddr.ipa_type != PIM_IPADDR ||
+ pim_addr_cmp(raddr.ipaddr_pim, addr)) {
+ zlog_warn("%s: address mismatch: addr=%pPA(%s) raddr=%pIA",
+ __func__, &addr, pim->vrf->name, &raddr);
+ /* warning only */
+ }
+
+ distance = stream_getc(s);
+ metric = stream_getl(s);
+ nexthop_num = stream_getc(s);
+
+ if (nexthop_num < 1 || nexthop_num > router->multipath) {
+ if (PIM_DEBUG_PIM_NHT_DETAIL)
+ zlog_debug("%s: socket %d bad nexthop_num=%d", __func__,
+ zlookup->sock, nexthop_num);
+ return -6;
+ }
+
+ for (i = 0; i < nexthop_num; ++i) {
+ vrf_id_t nexthop_vrf_id;
+ enum nexthop_types_t nexthop_type;
+ struct in_addr nh_ip4;
+ struct in6_addr nh_ip6;
+ ifindex_t nh_ifi;
+
+ nexthop_vrf_id = stream_getl(s);
+ nexthop_type = stream_getc(s);
+ if (num_ifindex >= tab_size) {
+ zlog_warn(
+ "%s: found too many nexthop ifindexes (%d > %d) for address %pPAs(%s)",
+ __func__, (num_ifindex + 1), tab_size, &addr,
+ pim->vrf->name);
+ return num_ifindex;
+ }
+ nexthop_tab[num_ifindex].protocol_distance = distance;
+ nexthop_tab[num_ifindex].route_metric = metric;
+ nexthop_tab[num_ifindex].vrf_id = nexthop_vrf_id;
+ switch (nexthop_type) {
+ case NEXTHOP_TYPE_IFINDEX:
+ nexthop_tab[num_ifindex].ifindex = stream_getl(s);
+ /*
+ * Connected route (i.e. no nexthop), use
+ * address passed in as PIM nexthop. This will
+ * allow us to work in cases where we are
+ * trying to find a route for this box.
+ */
+ nexthop_tab[num_ifindex].nexthop_addr = addr;
+ ++num_ifindex;
+ break;
+ case NEXTHOP_TYPE_IPV4_IFINDEX:
+ case NEXTHOP_TYPE_IPV4:
+ nh_ip4.s_addr = stream_get_ipv4(s);
+ nh_ifi = stream_getl(s);
+#if PIM_IPV == 4
+ nexthop_tab[num_ifindex].nexthop_addr = nh_ip4;
+ nexthop_tab[num_ifindex].ifindex = nh_ifi;
+ ++num_ifindex;
+#else
+ zlog_warn(
+ "cannot use IPv4 nexthop %pI4(%d) for IPv6 %pPA",
+ &nh_ip4, nh_ifi, &addr);
+#endif
+ break;
+ case NEXTHOP_TYPE_IPV6:
+ case NEXTHOP_TYPE_IPV6_IFINDEX:
+ stream_get(&nh_ip6, s, sizeof(nh_ip6));
+ nh_ifi = stream_getl(s);
+
+#if PIM_IPV == 6
+ nexthop_tab[num_ifindex].nexthop_addr = nh_ip6;
+ nexthop_tab[num_ifindex].ifindex = nh_ifi;
+ ++num_ifindex;
+#else
+ /* RFC 5549 v4-over-v6 nexthop handling */
+
+ /*
+ * If we are sending v6 secondary assume we receive v6
+ * secondary
+ */
+ struct interface *ifp = if_lookup_by_index(
+ nh_ifi,
+ nexthop_vrf_id);
+
+ if (!ifp)
+ break;
+
+ struct pim_neighbor *nbr;
+
+ if (pim->send_v6_secondary) {
+ struct prefix p;
+
+ p.family = AF_INET6;
+ p.prefixlen = IPV6_MAX_BITLEN;
+ p.u.prefix6 = nh_ip6;
+
+ nbr = pim_neighbor_find_by_secondary(ifp, &p);
+ } else
+ nbr = pim_neighbor_find_if(ifp);
+
+ if (!nbr)
+ break;
+
+ nexthop_tab[num_ifindex].nexthop_addr =
+ nbr->source_addr;
+ nexthop_tab[num_ifindex].ifindex = nh_ifi;
+ ++num_ifindex;
+#endif
+ break;
+ case NEXTHOP_TYPE_BLACKHOLE:
+ /* do nothing */
+ zlog_warn(
+ "%s: found non-ifindex nexthop type=%d for address %pPAs(%s)",
+ __func__, nexthop_type, &addr, pim->vrf->name);
+ break;
+ }
+ }
+
+ return num_ifindex;
+}
+
+static int zclient_lookup_nexthop_once(struct pim_instance *pim,
+ struct pim_zlookup_nexthop nexthop_tab[],
+ const int tab_size, pim_addr addr)
+{
+ struct stream *s;
+ int ret;
+ struct ipaddr ipaddr;
+
+ if (PIM_DEBUG_PIM_NHT_DETAIL)
+ zlog_debug("%s: addr=%pPAs(%s)", __func__, &addr,
+ pim->vrf->name);
+
+ /* Check socket. */
+ if (zlookup->sock < 0) {
+ flog_err(EC_LIB_ZAPI_SOCKET,
+ "%s: zclient lookup socket is not connected",
+ __func__);
+ zclient_lookup_failed(zlookup);
+ return -1;
+ }
+
+ if (pim->vrf->vrf_id == VRF_UNKNOWN) {
+ zlog_notice(
+ "%s: VRF: %s does not fully exist yet, delaying lookup",
+ __func__, pim->vrf->name);
+ return -1;
+ }
+
+ ipaddr.ipa_type = PIM_IPADDR;
+ ipaddr.ipaddr_pim = addr;
+
+ s = zlookup->obuf;
+ stream_reset(s);
+ zclient_create_header(s, ZEBRA_NEXTHOP_LOOKUP_MRIB, pim->vrf->vrf_id);
+ stream_put_ipaddr(s, &ipaddr);
+ stream_putw_at(s, 0, stream_get_endp(s));
+
+ ret = writen(zlookup->sock, s->data, stream_get_endp(s));
+ if (ret < 0) {
+ flog_err(
+ EC_LIB_SOCKET,
+ "%s: writen() failure: %d writing to zclient lookup socket",
+ __func__, errno);
+ zclient_lookup_failed(zlookup);
+ return -2;
+ }
+ if (ret == 0) {
+ flog_err_sys(EC_LIB_SOCKET,
+ "%s: connection closed on zclient lookup socket",
+ __func__);
+ zclient_lookup_failed(zlookup);
+ return -3;
+ }
+
+ return zclient_read_nexthop(pim, zlookup, nexthop_tab, tab_size, addr);
+}
+
+void zclient_lookup_read_pipe(struct event *thread)
+{
+ struct zclient *zlookup = EVENT_ARG(thread);
+ struct pim_instance *pim = pim_get_pim_instance(VRF_DEFAULT);
+ struct pim_zlookup_nexthop nexthop_tab[10];
+ pim_addr l = PIMADDR_ANY;
+
+ if (!pim) {
+ if (PIM_DEBUG_PIM_NHT_DETAIL)
+ zlog_debug("%s: Unable to find pim instance", __func__);
+ return;
+ }
+
+ zclient_lookup_nexthop_once(pim, nexthop_tab, 10, l);
+ event_add_timer(router->master, zclient_lookup_read_pipe, zlookup, 60,
+ &zlookup_read);
+}
+
+int zclient_lookup_nexthop(struct pim_instance *pim,
+ struct pim_zlookup_nexthop nexthop_tab[],
+ const int tab_size, pim_addr addr,
+ int max_lookup)
+{
+ int lookup;
+ uint32_t route_metric = 0xFFFFFFFF;
+ uint8_t protocol_distance = 0xFF;
+
+ pim->nexthop_lookups++;
+
+ for (lookup = 0; lookup < max_lookup; ++lookup) {
+ int num_ifindex;
+ int first_ifindex;
+ pim_addr nexthop_addr;
+
+ num_ifindex = zclient_lookup_nexthop_once(pim, nexthop_tab,
+ tab_size, addr);
+ if (num_ifindex < 1) {
+ if (PIM_DEBUG_PIM_NHT_DETAIL)
+ zlog_debug(
+ "%s: lookup=%d/%d: could not find nexthop ifindex for address %pPA(%s)",
+ __func__, lookup, max_lookup, &addr,
+ pim->vrf->name);
+ return -1;
+ }
+
+ if (lookup < 1) {
+ /* this is the non-recursive lookup - save original
+ * metric/distance */
+ route_metric = nexthop_tab[0].route_metric;
+ protocol_distance = nexthop_tab[0].protocol_distance;
+ }
+
+ /*
+ * FIXME: Non-recursive nexthop ensured only for first ifindex.
+ * However, recursive route lookup should really be fixed in
+ * zebra daemon.
+ * See also TODO T24.
+ *
+ * So Zebra for NEXTHOP_TYPE_IPV4 returns the ifindex now since
+ * it was being stored. This Doesn't solve all cases of
+ * recursive lookup but for the most common types it does.
+ */
+ first_ifindex = nexthop_tab[0].ifindex;
+ nexthop_addr = nexthop_tab[0].nexthop_addr;
+ if (first_ifindex > 0) {
+ /* found: first ifindex is non-recursive nexthop */
+
+ if (lookup > 0) {
+ /* Report non-recursive success after first
+ * lookup */
+ if (PIM_DEBUG_PIM_NHT)
+ zlog_debug(
+ "%s: lookup=%d/%d: found non-recursive ifindex=%d for address %pPA(%s) dist=%d met=%d",
+ __func__, lookup, max_lookup,
+ first_ifindex, &addr,
+ pim->vrf->name,
+ nexthop_tab[0]
+ .protocol_distance,
+ nexthop_tab[0].route_metric);
+
+ /* use last address as nexthop address */
+ nexthop_tab[0].nexthop_addr = addr;
+
+ /* report original route metric/distance */
+ nexthop_tab[0].route_metric = route_metric;
+ nexthop_tab[0].protocol_distance =
+ protocol_distance;
+ }
+
+ return num_ifindex;
+ }
+
+ if (PIM_DEBUG_PIM_NHT)
+ zlog_debug(
+ "%s: lookup=%d/%d: zebra returned recursive nexthop %pPAs for address %pPA(%s) dist=%d met=%d",
+ __func__, lookup, max_lookup, &nexthop_addr,
+ &addr, pim->vrf->name,
+ nexthop_tab[0].protocol_distance,
+ nexthop_tab[0].route_metric);
+
+ addr = nexthop_addr; /* use nexthop
+ addr for recursive lookup */
+
+ } /* for (max_lookup) */
+
+ if (PIM_DEBUG_PIM_NHT)
+ zlog_warn(
+ "%s: lookup=%d/%d: failure searching recursive nexthop ifindex for address %pPA(%s)",
+ __func__, lookup, max_lookup, &addr, pim->vrf->name);
+
+ return -2;
+}
+
+void pim_zlookup_show_ip_multicast(struct vty *vty)
+{
+ vty_out(vty, "Zclient lookup socket: ");
+ if (zlookup) {
+ vty_out(vty, "%d failures=%d\n", zlookup->sock, zlookup->fail);
+ } else {
+ vty_out(vty, "<null zclient>\n");
+ }
+}
+
+int pim_zlookup_sg_statistics(struct channel_oil *c_oil)
+{
+ struct stream *s = zlookup->obuf;
+ uint16_t command = 0;
+ unsigned long long lastused;
+ pim_sgaddr sg;
+ int count = 0;
+ int ret;
+ pim_sgaddr more = {};
+ struct interface *ifp =
+ pim_if_find_by_vif_index(c_oil->pim, *oil_incoming_vif(c_oil));
+
+ if (PIM_DEBUG_ZEBRA) {
+ more.src = *oil_origin(c_oil);
+ more.grp = *oil_mcastgrp(c_oil);
+ zlog_debug("Sending Request for New Channel Oil Information%pSG VIIF %d(%s:%s)",
+ &more, *oil_incoming_vif(c_oil),
+ ifp ? ifp->name : "Unknown", c_oil->pim->vrf->name);
+ }
+
+ if (!ifp)
+ return -1;
+
+ stream_reset(s);
+ zclient_create_header(s, ZEBRA_IPMR_ROUTE_STATS,
+ c_oil->pim->vrf->vrf_id);
+ stream_putl(s, PIM_AF);
+ stream_write(s, oil_origin(c_oil), sizeof(pim_addr));
+ stream_write(s, oil_mcastgrp(c_oil), sizeof(pim_addr));
+ stream_putl(s, ifp->ifindex);
+ stream_putw_at(s, 0, stream_get_endp(s));
+
+ count = stream_get_endp(s);
+ ret = writen(zlookup->sock, s->data, count);
+ if (ret <= 0) {
+ flog_err(
+ EC_LIB_SOCKET,
+ "%s: writen() failure: %d writing to zclient lookup socket",
+ __func__, errno);
+ return -1;
+ }
+
+ s = zlookup->ibuf;
+
+ while (command != ZEBRA_IPMR_ROUTE_STATS) {
+ int err;
+ uint16_t length = 0;
+ vrf_id_t vrf_id;
+ uint8_t marker;
+ uint8_t version;
+
+ stream_reset(s);
+ err = zclient_read_header(s, zlookup->sock, &length, &marker,
+ &version, &vrf_id, &command);
+ if (err < 0) {
+ flog_err(EC_LIB_ZAPI_MISSMATCH,
+ "%s: zclient_read_header() failed", __func__);
+ zclient_lookup_failed(zlookup);
+ return -1;
+ }
+ }
+
+ stream_get(&sg.src, s, sizeof(pim_addr));
+ stream_get(&sg.grp, s, sizeof(pim_addr));
+
+ more.src = *oil_origin(c_oil);
+ more.grp = *oil_mcastgrp(c_oil);
+ if (pim_sgaddr_cmp(sg, more)) {
+ if (PIM_DEBUG_ZEBRA)
+ flog_err(
+ EC_LIB_ZAPI_MISSMATCH,
+ "%s: Received wrong %pSG(%s) information requested",
+ __func__, &more, c_oil->pim->vrf->name);
+ zclient_lookup_failed(zlookup);
+ return -3;
+ }
+
+ stream_get(&lastused, s, sizeof(lastused));
+ /* signed success value from netlink_talk; currently unused */
+ (void)stream_getl(s);
+
+ c_oil->cc.lastused = lastused;
+
+ return 0;
+}