// SPDX-License-Identifier: GPL-2.0-or-later /* * Zebra connect code. * Copyright (C) 2018 Cumulus Networks, Inc. * Donald Sharp */ #include #include "frrevent.h" #include "command.h" #include "network.h" #include "prefix.h" #include "routemap.h" #include "table.h" #include "srcdest_table.h" #include "stream.h" #include "memory.h" #include "zclient.h" #include "filter.h" #include "plist.h" #include "log.h" #include "nexthop.h" #include "nexthop_group.h" #include "hash.h" #include "jhash.h" #include "static_vrf.h" #include "static_routes.h" #include "static_zebra.h" #include "static_nht.h" #include "static_vty.h" #include "static_debug.h" DEFINE_MTYPE_STATIC(STATIC, STATIC_NHT_DATA, "Static Nexthop tracking data"); PREDECL_HASH(static_nht_hash); struct static_nht_data { struct static_nht_hash_item itm; struct prefix nh; safi_t safi; vrf_id_t nh_vrf_id; uint32_t refcount; uint8_t nh_num; bool registered; }; static int static_nht_data_cmp(const struct static_nht_data *nhtd1, const struct static_nht_data *nhtd2) { if (nhtd1->nh_vrf_id != nhtd2->nh_vrf_id) return numcmp(nhtd1->nh_vrf_id, nhtd2->nh_vrf_id); if (nhtd1->safi != nhtd2->safi) return numcmp(nhtd1->safi, nhtd2->safi); return prefix_cmp(&nhtd1->nh, &nhtd2->nh); } static unsigned int static_nht_data_hash(const struct static_nht_data *nhtd) { unsigned int key = 0; key = prefix_hash_key(&nhtd->nh); return jhash_2words(nhtd->nh_vrf_id, nhtd->safi, key); } DECLARE_HASH(static_nht_hash, struct static_nht_data, itm, static_nht_data_cmp, static_nht_data_hash); static struct static_nht_hash_head static_nht_hash[1]; /* Zebra structure to hold current status. */ struct zclient *zclient; uint32_t zebra_ecmp_count = MULTIPATH_NUM; /* Interface addition message from zebra. */ static int static_ifp_create(struct interface *ifp) { static_ifindex_update(ifp, true); return 0; } static int static_ifp_destroy(struct interface *ifp) { static_ifindex_update(ifp, false); return 0; } static int interface_address_add(ZAPI_CALLBACK_ARGS) { zebra_interface_address_read(cmd, zclient->ibuf, vrf_id); return 0; } static int interface_address_delete(ZAPI_CALLBACK_ARGS) { struct connected *c; c = zebra_interface_address_read(cmd, zclient->ibuf, vrf_id); if (!c) return 0; connected_free(&c); return 0; } static int static_ifp_up(struct interface *ifp) { static_ifindex_update(ifp, true); return 0; } static int static_ifp_down(struct interface *ifp) { static_ifindex_update(ifp, false); return 0; } static int route_notify_owner(ZAPI_CALLBACK_ARGS) { struct prefix p; enum zapi_route_notify_owner note; uint32_t table_id; safi_t safi; if (!zapi_route_notify_decode(zclient->ibuf, &p, &table_id, ¬e, NULL, &safi)) return -1; switch (note) { case ZAPI_ROUTE_FAIL_INSTALL: static_nht_mark_state(&p, safi, vrf_id, STATIC_NOT_INSTALLED); zlog_warn("%s: Route %pFX failed to install for table: %u", __func__, &p, table_id); break; case ZAPI_ROUTE_BETTER_ADMIN_WON: static_nht_mark_state(&p, safi, vrf_id, STATIC_NOT_INSTALLED); zlog_warn( "%s: Route %pFX over-ridden by better route for table: %u", __func__, &p, table_id); break; case ZAPI_ROUTE_INSTALLED: static_nht_mark_state(&p, safi, vrf_id, STATIC_INSTALLED); break; case ZAPI_ROUTE_REMOVED: static_nht_mark_state(&p, safi, vrf_id, STATIC_NOT_INSTALLED); break; case ZAPI_ROUTE_REMOVE_FAIL: static_nht_mark_state(&p, safi, vrf_id, STATIC_INSTALLED); zlog_warn("%s: Route %pFX failure to remove for table: %u", __func__, &p, table_id); break; } return 0; } static void zebra_connected(struct zclient *zclient) { struct vrf *vrf; zebra_route_notify_send(ZEBRA_ROUTE_NOTIFY_REQUEST, zclient, true); zclient_send_reg_requests(zclient, VRF_DEFAULT); vrf = vrf_lookup_by_id(VRF_DEFAULT); assert(vrf); static_fixup_vrf_ids(vrf); } /* API to check whether the configured nexthop address is * one of its local connected address or not. */ static bool static_nexthop_is_local(vrf_id_t vrfid, struct prefix *addr, int family) { if (family == AF_INET) { if (if_address_is_local(&addr->u.prefix4, AF_INET, vrfid)) return true; } else if (family == AF_INET6) { if (if_address_is_local(&addr->u.prefix6, AF_INET6, vrfid)) return true; } return false; } static void static_zebra_nexthop_update(struct vrf *vrf, struct prefix *matched, struct zapi_route *nhr) { struct static_nht_data *nhtd, lookup; afi_t afi = AFI_IP; if (zclient->bfd_integration) bfd_nht_update(matched, nhr); if (matched->family == AF_INET6) afi = AFI_IP6; if (nhr->type == ZEBRA_ROUTE_CONNECT) { if (static_nexthop_is_local(vrf->vrf_id, matched, nhr->prefix.family)) nhr->nexthop_num = 0; } memset(&lookup, 0, sizeof(lookup)); lookup.nh = *matched; lookup.nh_vrf_id = vrf->vrf_id; lookup.safi = nhr->safi; nhtd = static_nht_hash_find(static_nht_hash, &lookup); if (nhtd) { nhtd->nh_num = nhr->nexthop_num; static_nht_reset_start(matched, afi, nhr->safi, nhtd->nh_vrf_id); static_nht_update(NULL, matched, nhr->nexthop_num, afi, nhr->safi, nhtd->nh_vrf_id); } else zlog_err("No nhtd?"); } static void static_zebra_capabilities(struct zclient_capabilities *cap) { mpls_enabled = cap->mpls_enabled; zebra_ecmp_count = cap->ecmp; } static struct static_nht_data * static_nht_hash_getref(const struct static_nht_data *ref) { struct static_nht_data *nhtd; nhtd = static_nht_hash_find(static_nht_hash, ref); if (!nhtd) { nhtd = XCALLOC(MTYPE_STATIC_NHT_DATA, sizeof(*nhtd)); prefix_copy(&nhtd->nh, &ref->nh); nhtd->nh_vrf_id = ref->nh_vrf_id; nhtd->safi = ref->safi; static_nht_hash_add(static_nht_hash, nhtd); } nhtd->refcount++; return nhtd; } static bool static_nht_hash_decref(struct static_nht_data **nhtd_p) { struct static_nht_data *nhtd = *nhtd_p; *nhtd_p = NULL; if (--nhtd->refcount > 0) return true; static_nht_hash_del(static_nht_hash, nhtd); XFREE(MTYPE_STATIC_NHT_DATA, nhtd); return false; } static void static_nht_hash_clear(void) { struct static_nht_data *nhtd; while ((nhtd = static_nht_hash_pop(static_nht_hash))) XFREE(MTYPE_STATIC_NHT_DATA, nhtd); } static bool static_zebra_nht_get_prefix(const struct static_nexthop *nh, struct prefix *p) { switch (nh->type) { case STATIC_IFNAME: case STATIC_BLACKHOLE: p->family = AF_UNSPEC; return false; case STATIC_IPV4_GATEWAY: case STATIC_IPV4_GATEWAY_IFNAME: p->family = AF_INET; p->prefixlen = IPV4_MAX_BITLEN; p->u.prefix4 = nh->addr.ipv4; return true; case STATIC_IPV6_GATEWAY: case STATIC_IPV6_GATEWAY_IFNAME: p->family = AF_INET6; p->prefixlen = IPV6_MAX_BITLEN; p->u.prefix6 = nh->addr.ipv6; return true; } assertf(0, "BUG: someone forgot to add nexthop type %u", nh->type); return false; } void static_zebra_nht_register(struct static_nexthop *nh, bool reg) { struct static_path *pn = nh->pn; struct route_node *rn = pn->rn; struct static_route_info *si = static_route_info_from_rnode(rn); struct static_nht_data *nhtd, lookup = {}; uint32_t cmd; if (!static_zebra_nht_get_prefix(nh, &lookup.nh)) return; lookup.nh_vrf_id = nh->nh_vrf_id; lookup.safi = si->safi; if (nh->nh_registered) { /* nh->nh_registered means we own a reference on the nhtd */ nhtd = static_nht_hash_find(static_nht_hash, &lookup); assertf(nhtd, "BUG: NH %pFX registered but not in hashtable", &lookup.nh); } else if (reg) { nhtd = static_nht_hash_getref(&lookup); if (nhtd->refcount > 1) DEBUGD(&static_dbg_route, "Reusing registered nexthop(%pFX) for %pRN %d", &lookup.nh, rn, nhtd->nh_num); } else { /* !reg && !nh->nh_registered */ zlog_warn("trying to unregister nexthop %pFX twice", &lookup.nh); return; } nh->nh_registered = reg; if (reg) { if (nhtd->nh_num) { /* refresh with existing data */ afi_t afi = prefix_afi(&lookup.nh); if (nh->state == STATIC_NOT_INSTALLED) nh->state = STATIC_START; static_nht_update(&rn->p, &nhtd->nh, nhtd->nh_num, afi, si->safi, nh->nh_vrf_id); return; } if (nhtd->registered) /* have no data, but did send register */ return; cmd = ZEBRA_NEXTHOP_REGISTER; DEBUGD(&static_dbg_route, "Registering nexthop(%pFX) for %pRN", &lookup.nh, rn); } else { bool was_zebra_registered; was_zebra_registered = nhtd->registered; if (static_nht_hash_decref(&nhtd)) /* still got references alive */ return; /* NB: nhtd is now NULL. */ if (!was_zebra_registered) return; cmd = ZEBRA_NEXTHOP_UNREGISTER; DEBUGD(&static_dbg_route, "Unregistering nexthop(%pFX) for %pRN", &lookup.nh, rn); } if (zclient_send_rnh(zclient, cmd, &lookup.nh, si->safi, false, false, nh->nh_vrf_id) == ZCLIENT_SEND_FAILURE) zlog_warn("%s: Failure to send nexthop %pFX for %pRN to zebra", __func__, &lookup.nh, rn); else if (reg) nhtd->registered = true; } extern void static_zebra_route_add(struct static_path *pn, bool install) { struct route_node *rn = pn->rn; struct static_route_info *si = rn->info; struct static_nexthop *nh; const struct prefix *p, *src_pp; struct zapi_nexthop *api_nh; struct zapi_route api; uint32_t nh_num = 0; if (!si->svrf->vrf || si->svrf->vrf->vrf_id == VRF_UNKNOWN) return; p = src_pp = NULL; srcdest_rnode_prefixes(rn, &p, &src_pp); memset(&api, 0, sizeof(api)); api.vrf_id = si->svrf->vrf->vrf_id; api.type = ZEBRA_ROUTE_STATIC; api.safi = si->safi; memcpy(&api.prefix, p, sizeof(api.prefix)); if (src_pp) { SET_FLAG(api.message, ZAPI_MESSAGE_SRCPFX); memcpy(&api.src_prefix, src_pp, sizeof(api.src_prefix)); } SET_FLAG(api.flags, ZEBRA_FLAG_RR_USE_DISTANCE); SET_FLAG(api.flags, ZEBRA_FLAG_ALLOW_RECURSION); SET_FLAG(api.message, ZAPI_MESSAGE_NEXTHOP); if (pn->distance) { SET_FLAG(api.message, ZAPI_MESSAGE_DISTANCE); api.distance = pn->distance; } if (pn->tag) { SET_FLAG(api.message, ZAPI_MESSAGE_TAG); api.tag = pn->tag; } if (pn->table_id != 0) { SET_FLAG(api.message, ZAPI_MESSAGE_TABLEID); api.tableid = pn->table_id; } frr_each(static_nexthop_list, &pn->nexthop_list, nh) { /* Don't overrun the nexthop array */ if (nh_num == zebra_ecmp_count) break; api_nh = &api.nexthops[nh_num]; if (nh->nh_vrf_id == VRF_UNKNOWN) continue; /* Skip next hop which peer is down. */ if (nh->path_down) continue; api_nh->vrf_id = nh->nh_vrf_id; if (nh->onlink) SET_FLAG(api_nh->flags, ZAPI_NEXTHOP_FLAG_ONLINK); if (nh->color != 0) { SET_FLAG(api.message, ZAPI_MESSAGE_SRTE); api_nh->srte_color = nh->color; } nh->state = STATIC_SENT_TO_ZEBRA; switch (nh->type) { case STATIC_IFNAME: if (nh->ifindex == IFINDEX_INTERNAL) continue; api_nh->ifindex = nh->ifindex; api_nh->type = NEXTHOP_TYPE_IFINDEX; break; case STATIC_IPV4_GATEWAY: if (!nh->nh_valid) continue; api_nh->type = NEXTHOP_TYPE_IPV4; api_nh->gate = nh->addr; break; case STATIC_IPV4_GATEWAY_IFNAME: if (nh->ifindex == IFINDEX_INTERNAL) continue; api_nh->ifindex = nh->ifindex; api_nh->type = NEXTHOP_TYPE_IPV4_IFINDEX; api_nh->gate = nh->addr; break; case STATIC_IPV6_GATEWAY: if (!nh->nh_valid) continue; api_nh->type = NEXTHOP_TYPE_IPV6; api_nh->gate = nh->addr; break; case STATIC_IPV6_GATEWAY_IFNAME: if (nh->ifindex == IFINDEX_INTERNAL) continue; api_nh->type = NEXTHOP_TYPE_IPV6_IFINDEX; api_nh->ifindex = nh->ifindex; api_nh->gate = nh->addr; break; case STATIC_BLACKHOLE: api_nh->type = NEXTHOP_TYPE_BLACKHOLE; switch (nh->bh_type) { case STATIC_BLACKHOLE_DROP: case STATIC_BLACKHOLE_NULL: api_nh->bh_type = BLACKHOLE_NULL; break; case STATIC_BLACKHOLE_REJECT: api_nh->bh_type = BLACKHOLE_REJECT; } break; } if (nh->snh_label.num_labels) { int i; SET_FLAG(api_nh->flags, ZAPI_NEXTHOP_FLAG_LABEL); api_nh->label_num = nh->snh_label.num_labels; for (i = 0; i < api_nh->label_num; i++) api_nh->labels[i] = nh->snh_label.label[i]; } if (nh->snh_seg.num_segs) { int i; api_nh->seg6local_action = ZEBRA_SEG6_LOCAL_ACTION_UNSPEC; SET_FLAG(api_nh->flags, ZAPI_NEXTHOP_FLAG_SEG6); SET_FLAG(api.flags, ZEBRA_FLAG_ALLOW_RECURSION); api.safi = SAFI_UNICAST; api_nh->seg_num = nh->snh_seg.num_segs; for (i = 0; i < api_nh->seg_num; i++) memcpy(&api_nh->seg6_segs[i], &nh->snh_seg.seg[i], sizeof(struct in6_addr)); } nh_num++; } api.nexthop_num = nh_num; /* * If we have been given an install but nothing is valid * go ahead and delete the route for double plus fun */ if (!nh_num && install) install = false; zclient_route_send(install ? ZEBRA_ROUTE_ADD : ZEBRA_ROUTE_DELETE, zclient, &api); } static zclient_handler *const static_handlers[] = { [ZEBRA_INTERFACE_ADDRESS_ADD] = interface_address_add, [ZEBRA_INTERFACE_ADDRESS_DELETE] = interface_address_delete, [ZEBRA_ROUTE_NOTIFY_OWNER] = route_notify_owner, }; void static_zebra_init(void) { hook_register_prio(if_real, 0, static_ifp_create); hook_register_prio(if_up, 0, static_ifp_up); hook_register_prio(if_down, 0, static_ifp_down); hook_register_prio(if_unreal, 0, static_ifp_destroy); zclient = zclient_new(master, &zclient_options_default, static_handlers, array_size(static_handlers)); zclient_init(zclient, ZEBRA_ROUTE_STATIC, 0, &static_privs); zclient->zebra_capabilities = static_zebra_capabilities; zclient->zebra_connected = zebra_connected; zclient->nexthop_update = static_zebra_nexthop_update; static_nht_hash_init(static_nht_hash); static_bfd_initialize(zclient, master); } /* static_zebra_stop used by tests/lib/test_grpc.cpp */ void static_zebra_stop(void) { static_nht_hash_clear(); static_nht_hash_fini(static_nht_hash); if (!zclient) return; zclient_stop(zclient); zclient_free(zclient); zclient = NULL; } void static_zebra_vrf_register(struct vrf *vrf) { if (vrf->vrf_id == VRF_DEFAULT) return; zclient_send_reg_requests(zclient, vrf->vrf_id); } void static_zebra_vrf_unregister(struct vrf *vrf) { if (vrf->vrf_id == VRF_DEFAULT) return; zclient_send_dereg_requests(zclient, vrf->vrf_id); }