diff options
author | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-28 09:53:30 +0000 |
---|---|---|
committer | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-28 09:53:30 +0000 |
commit | 2c7cac91ed6e7db0f6937923d2b57f97dbdbc337 (patch) | |
tree | c05dc0f8e6aa3accc84e3e5cffc933ed94941383 /zebra/connected.c | |
parent | Initial commit. (diff) | |
download | frr-2c7cac91ed6e7db0f6937923d2b57f97dbdbc337.tar.xz frr-2c7cac91ed6e7db0f6937923d2b57f97dbdbc337.zip |
Adding upstream version 8.4.4.upstream/8.4.4upstream
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'zebra/connected.c')
-rw-r--r-- | zebra/connected.c | 639 |
1 files changed, 639 insertions, 0 deletions
diff --git a/zebra/connected.c b/zebra/connected.c new file mode 100644 index 0000000..c01be58 --- /dev/null +++ b/zebra/connected.c @@ -0,0 +1,639 @@ +/* + * Address linked list routine. + * Copyright (C) 1997, 98 Kunihiro Ishiguro + * + * This file is part of GNU Zebra. + * + * GNU Zebra is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the + * Free Software Foundation; either version 2, or (at your option) any + * later version. + * + * GNU Zebra is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; see the file COPYING; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#include <zebra.h> + +#include "prefix.h" +#include "linklist.h" +#include "if.h" +#include "table.h" +#include "rib.h" +#include "table.h" +#include "log.h" +#include "memory.h" + +#include "vty.h" +#include "zebra/debug.h" +#include "zebra/zserv.h" +#include "zebra/redistribute.h" +#include "zebra/interface.h" +#include "zebra/connected.h" +#include "zebra/rtadv.h" +#include "zebra/zebra_mpls.h" +#include "zebra/zebra_errors.h" +#include "zebra/zebra_router.h" + +/* communicate the withdrawal of a connected address */ +static void connected_withdraw(struct connected *ifc) +{ + if (!ifc) + return; + + /* Update interface address information to protocol daemon. */ + if (CHECK_FLAG(ifc->conf, ZEBRA_IFC_REAL)) { + zebra_interface_address_delete_update(ifc->ifp, ifc); + + if (ifc->address->family == AF_INET) + if_subnet_delete(ifc->ifp, ifc); + + connected_down(ifc->ifp, ifc); + + UNSET_FLAG(ifc->conf, ZEBRA_IFC_REAL); + } + + /* The address is not in the kernel anymore, so clear the flag */ + UNSET_FLAG(ifc->conf, ZEBRA_IFC_QUEUED); + + if (!CHECK_FLAG(ifc->conf, ZEBRA_IFC_CONFIGURED)) { + listnode_delete(ifc->ifp->connected, ifc); + connected_free(&ifc); + } +} + +static void connected_announce(struct interface *ifp, struct connected *ifc) +{ + if (!ifc) + return; + + if (!if_is_loopback(ifp) && ifc->address->family == AF_INET) { + if (ifc->address->prefixlen == IPV4_MAX_BITLEN) + SET_FLAG(ifc->flags, ZEBRA_IFA_UNNUMBERED); + else + UNSET_FLAG(ifc->flags, ZEBRA_IFA_UNNUMBERED); + } + + listnode_add(ifp->connected, ifc); + + /* Update interface address information to protocol daemon. */ + if (ifc->address->family == AF_INET) + if_subnet_add(ifp, ifc); + + zebra_interface_address_add_update(ifp, ifc); + + if (if_is_operative(ifp)) { + connected_up(ifp, ifc); + } +} + +/* If same interface address is already exist... */ +struct connected *connected_check(struct interface *ifp, + union prefixconstptr pu) +{ + const struct prefix *p = pu.p; + struct connected *ifc; + struct listnode *node; + + for (ALL_LIST_ELEMENTS_RO(ifp->connected, node, ifc)) + if (prefix_same(ifc->address, p)) + return ifc; + + return NULL; +} + +/* same, but with peer address */ +struct connected *connected_check_ptp(struct interface *ifp, + union prefixconstptr pu, + union prefixconstptr du) +{ + const struct prefix *p = pu.p; + const struct prefix *d = du.p; + struct connected *ifc; + struct listnode *node; + + for (ALL_LIST_ELEMENTS_RO(ifp->connected, node, ifc)) { + if (!prefix_same(ifc->address, p)) + continue; + if (!CONNECTED_PEER(ifc) && !d) + return ifc; + if (CONNECTED_PEER(ifc) && d + && prefix_same(ifc->destination, d)) + return ifc; + } + + return NULL; +} + +/* Check if two ifc's describe the same address in the same state */ +static int connected_same(struct connected *ifc1, struct connected *ifc2) +{ + if (ifc1->ifp != ifc2->ifp) + return 0; + + if (ifc1->flags != ifc2->flags) + return 0; + + if (ifc1->conf != ifc2->conf) + return 0; + + if (ifc1->destination) + if (!ifc2->destination) + return 0; + if (ifc2->destination) + if (!ifc1->destination) + return 0; + + if (ifc1->destination && ifc2->destination) + if (!prefix_same(ifc1->destination, ifc2->destination)) + return 0; + + return 1; +} + +/* Handle changes to addresses and send the neccesary announcements + * to clients. */ +static void connected_update(struct interface *ifp, struct connected *ifc) +{ + struct connected *current; + + /* Check same connected route. */ + current = connected_check_ptp(ifp, ifc->address, ifc->destination); + if (current) { + if (CHECK_FLAG(current->conf, ZEBRA_IFC_CONFIGURED)) + SET_FLAG(ifc->conf, ZEBRA_IFC_CONFIGURED); + + /* Avoid spurious withdraws, this might be just the kernel + * 'reflecting' + * back an address we have already added. + */ + if (connected_same(current, ifc)) { + /* nothing to do */ + connected_free(&ifc); + return; + } + + /* Clear the configured flag on the old ifc, so it will be freed + * by + * connected withdraw. */ + UNSET_FLAG(current->conf, ZEBRA_IFC_CONFIGURED); + connected_withdraw( + current); /* implicit withdraw - freebsd does this */ + } + + /* If the connected is new or has changed, announce it, if it is usable + */ + if (CHECK_FLAG(ifc->conf, ZEBRA_IFC_REAL)) + connected_announce(ifp, ifc); +} + +/* Called from if_up(). */ +void connected_up(struct interface *ifp, struct connected *ifc) +{ + afi_t afi; + struct prefix p; + struct nexthop nh = { + .type = NEXTHOP_TYPE_IFINDEX, + .ifindex = ifp->ifindex, + .vrf_id = ifp->vrf->vrf_id, + }; + struct zebra_vrf *zvrf; + uint32_t metric; + uint32_t flags = 0; + uint32_t count = 0; + struct listnode *cnode; + struct connected *c; + + zvrf = ifp->vrf->info; + if (!zvrf) { + flog_err( + EC_ZEBRA_VRF_NOT_FOUND, + "%s: Received Up for interface but no associated zvrf: %s(%d)", + __func__, ifp->vrf->name, ifp->vrf->vrf_id); + return; + } + if (!CHECK_FLAG(ifc->conf, ZEBRA_IFC_REAL)) + return; + + /* Ensure 'down' flag is cleared */ + UNSET_FLAG(ifc->conf, ZEBRA_IFC_DOWN); + + prefix_copy(&p, CONNECTED_PREFIX(ifc)); + + /* Apply mask to the network. */ + apply_mask(&p); + + afi = family2afi(p.family); + + switch (afi) { + case AFI_IP: + /* + * In case of connected address is 0.0.0.0/0 we treat it tunnel + * address. + */ + if (prefix_ipv4_any((struct prefix_ipv4 *)&p)) + return; + break; + case AFI_IP6: +#ifndef GNU_LINUX + /* XXX: It is already done by rib_bogus_ipv6 within rib_add */ + if (IN6_IS_ADDR_UNSPECIFIED(&p.u.prefix6)) + return; +#endif + break; + default: + flog_warn(EC_ZEBRA_CONNECTED_AFI_UNKNOWN, + "Received unknown AFI: %s", afi2str(afi)); + return; + break; + } + + metric = (ifc->metric < (uint32_t)METRIC_MAX) ? + ifc->metric : ifp->metric; + + /* + * Since we are hand creating the connected routes + * in our main routing table, *if* we are working + * in an offloaded environment then we need to + * pretend like the route is offloaded so everything + * else will work + */ + if (zrouter.asic_offloaded) + flags |= ZEBRA_FLAG_OFFLOADED; + + /* + * It's possible to add the same network and mask + * to an interface over and over. This would + * result in an equivalent number of connected + * routes. Just add one connected route in + * for all the addresses on an interface that + * resolve to the same network and mask + */ + for (ALL_LIST_ELEMENTS_RO(ifp->connected, cnode, c)) { + struct prefix cp; + + prefix_copy(&cp, CONNECTED_PREFIX(c)); + apply_mask(&cp); + + if (prefix_same(&cp, &p) && + !CHECK_FLAG(c->conf, ZEBRA_IFC_DOWN)) + count++; + + if (count >= 2) + return; + } + + rib_add(afi, SAFI_UNICAST, zvrf->vrf->vrf_id, ZEBRA_ROUTE_CONNECT, 0, + flags, &p, NULL, &nh, 0, zvrf->table_id, metric, 0, 0, 0, + false); + + rib_add(afi, SAFI_MULTICAST, zvrf->vrf->vrf_id, ZEBRA_ROUTE_CONNECT, 0, + flags, &p, NULL, &nh, 0, zvrf->table_id, metric, 0, 0, 0, + false); + + /* Schedule LSP forwarding entries for processing, if appropriate. */ + if (zvrf->vrf->vrf_id == VRF_DEFAULT) { + if (IS_ZEBRA_DEBUG_MPLS) + zlog_debug( + "%u: IF %s IP %pFX address add/up, scheduling MPLS processing", + zvrf->vrf->vrf_id, ifp->name, &p); + mpls_mark_lsps_for_processing(zvrf, &p); + } +} + +/* Add connected IPv4 route to the interface. */ +void connected_add_ipv4(struct interface *ifp, int flags, + const struct in_addr *addr, uint16_t prefixlen, + const struct in_addr *dest, const char *label, + uint32_t metric) +{ + struct prefix_ipv4 *p; + struct connected *ifc; + + if (ipv4_martian(addr)) + return; + + /* Make connected structure. */ + ifc = connected_new(); + ifc->ifp = ifp; + ifc->flags = flags; + ifc->metric = metric; + /* If we get a notification from the kernel, + * we can safely assume the address is known to the kernel */ + SET_FLAG(ifc->conf, ZEBRA_IFC_QUEUED); + if (!if_is_operative(ifp)) + SET_FLAG(ifc->conf, ZEBRA_IFC_DOWN); + + /* Allocate new connected address. */ + p = prefix_ipv4_new(); + p->family = AF_INET; + p->prefix = *addr; + p->prefixlen = + CHECK_FLAG(flags, ZEBRA_IFA_PEER) ? IPV4_MAX_BITLEN : prefixlen; + ifc->address = (struct prefix *)p; + + /* If there is a peer address. */ + if (CONNECTED_PEER(ifc)) { + /* validate the destination address */ + if (dest) { + p = prefix_ipv4_new(); + p->family = AF_INET; + p->prefix = *dest; + p->prefixlen = prefixlen; + ifc->destination = (struct prefix *)p; + + if (IPV4_ADDR_SAME(addr, dest)) + flog_warn( + EC_ZEBRA_IFACE_SAME_LOCAL_AS_PEER, + "interface %s has same local and peer address %pI4, routing protocols may malfunction", + ifp->name, addr); + } else { + zlog_debug( + "%s called for interface %s with peer flag set, but no peer address supplied", + __func__, ifp->name); + UNSET_FLAG(ifc->flags, ZEBRA_IFA_PEER); + } + } + + /* no destination address was supplied */ + if (!dest && (prefixlen == IPV4_MAX_BITLEN) && if_is_pointopoint(ifp)) + zlog_debug( + "PtP interface %s with addr %pI4/%d needs a peer address", + ifp->name, addr, prefixlen); + + /* Label of this address. */ + if (label) + ifc->label = XSTRDUP(MTYPE_CONNECTED_LABEL, label); + + /* For all that I know an IPv4 address is always ready when we receive + * the notification. So it should be safe to set the REAL flag here. */ + SET_FLAG(ifc->conf, ZEBRA_IFC_REAL); + + connected_update(ifp, ifc); +} + +void connected_down(struct interface *ifp, struct connected *ifc) +{ + afi_t afi; + struct prefix p; + struct nexthop nh = { + .type = NEXTHOP_TYPE_IFINDEX, + .ifindex = ifp->ifindex, + .vrf_id = ifp->vrf->vrf_id, + }; + struct zebra_vrf *zvrf; + uint32_t count = 0; + struct listnode *cnode; + struct connected *c; + + zvrf = ifp->vrf->info; + if (!zvrf) { + flog_err( + EC_ZEBRA_VRF_NOT_FOUND, + "%s: Received Down for interface but no associated zvrf: %s(%d)", + __func__, ifp->vrf->name, ifp->vrf->vrf_id); + return; + } + + if (!CHECK_FLAG(ifc->conf, ZEBRA_IFC_REAL)) + return; + + /* Skip if we've already done this; this can happen if we have a + * config change that takes an interface down, then we receive kernel + * notifications about the downed interface and its addresses. + */ + if (CHECK_FLAG(ifc->conf, ZEBRA_IFC_DOWN)) { + if (IS_ZEBRA_DEBUG_RIB) + zlog_debug("%s: ifc %p, %pFX already DOWN", + __func__, ifc, ifc->address); + return; + } + + prefix_copy(&p, CONNECTED_PREFIX(ifc)); + + /* Apply mask to the network. */ + apply_mask(&p); + + afi = family2afi(p.family); + + switch (afi) { + case AFI_IP: + /* + * In case of connected address is 0.0.0.0/0 we treat it tunnel + * address. + */ + if (prefix_ipv4_any((struct prefix_ipv4 *)&p)) + return; + break; + case AFI_IP6: + if (IN6_IS_ADDR_UNSPECIFIED(&p.u.prefix6)) + return; + break; + default: + zlog_warn("Unknown AFI: %s", afi2str(afi)); + break; + } + + /* Mark the address as 'down' */ + SET_FLAG(ifc->conf, ZEBRA_IFC_DOWN); + + /* + * It's possible to have X number of addresses + * on a interface that all resolve to the same + * network and mask. Find them and just + * allow the deletion when are removing the last + * one. + */ + for (ALL_LIST_ELEMENTS_RO(ifp->connected, cnode, c)) { + struct prefix cp; + + prefix_copy(&cp, CONNECTED_PREFIX(c)); + apply_mask(&cp); + + if (prefix_same(&p, &cp) && + !CHECK_FLAG(c->conf, ZEBRA_IFC_DOWN)) + count++; + + if (count >= 1) + return; + } + + /* + * Same logic as for connected_up(): push the changes into the + * head. + */ + rib_delete(afi, SAFI_UNICAST, zvrf->vrf->vrf_id, ZEBRA_ROUTE_CONNECT, 0, + 0, &p, NULL, &nh, 0, zvrf->table_id, 0, 0, false); + + rib_delete(afi, SAFI_MULTICAST, zvrf->vrf->vrf_id, ZEBRA_ROUTE_CONNECT, + 0, 0, &p, NULL, &nh, 0, zvrf->table_id, 0, 0, false); + + /* Schedule LSP forwarding entries for processing, if appropriate. */ + if (zvrf->vrf->vrf_id == VRF_DEFAULT) { + if (IS_ZEBRA_DEBUG_MPLS) + zlog_debug( + "%u: IF %s IP %pFX address down, scheduling MPLS processing", + zvrf->vrf->vrf_id, ifp->name, &p); + mpls_mark_lsps_for_processing(zvrf, &p); + } +} + +static void connected_delete_helper(struct connected *ifc, struct prefix *p) +{ + struct interface *ifp; + + if (!ifc) + return; + ifp = ifc->ifp; + + connected_withdraw(ifc); + + /* Schedule LSP forwarding entries for processing, if appropriate. */ + if (ifp->vrf->vrf_id == VRF_DEFAULT) { + if (IS_ZEBRA_DEBUG_MPLS) + zlog_debug( + "%u: IF %s IP %pFX address delete, scheduling MPLS processing", + ifp->vrf->vrf_id, ifp->name, p); + mpls_mark_lsps_for_processing(ifp->vrf->info, p); + } +} + +/* Delete connected IPv4 route to the interface. */ +void connected_delete_ipv4(struct interface *ifp, int flags, + const struct in_addr *addr, uint16_t prefixlen, + const struct in_addr *dest) +{ + struct prefix p, d; + struct connected *ifc; + + memset(&p, 0, sizeof(p)); + p.family = AF_INET; + p.u.prefix4 = *addr; + p.prefixlen = + CHECK_FLAG(flags, ZEBRA_IFA_PEER) ? IPV4_MAX_BITLEN : prefixlen; + + if (dest) { + memset(&d, 0, sizeof(d)); + d.family = AF_INET; + d.u.prefix4 = *dest; + d.prefixlen = prefixlen; + ifc = connected_check_ptp(ifp, &p, &d); + } else + ifc = connected_check_ptp(ifp, &p, NULL); + + connected_delete_helper(ifc, &p); +} + +/* Add connected IPv6 route to the interface. */ +void connected_add_ipv6(struct interface *ifp, int flags, + const struct in6_addr *addr, + const struct in6_addr *dest, uint16_t prefixlen, + const char *label, uint32_t metric) +{ + struct prefix_ipv6 *p; + struct connected *ifc; + + if (ipv6_martian(addr)) + return; + + /* Make connected structure. */ + ifc = connected_new(); + ifc->ifp = ifp; + ifc->flags = flags; + ifc->metric = metric; + /* If we get a notification from the kernel, + * we can safely assume the address is known to the kernel */ + SET_FLAG(ifc->conf, ZEBRA_IFC_QUEUED); + if (!if_is_operative(ifp)) + SET_FLAG(ifc->conf, ZEBRA_IFC_DOWN); + + /* Allocate new connected address. */ + p = prefix_ipv6_new(); + p->family = AF_INET6; + IPV6_ADDR_COPY(&p->prefix, addr); + p->prefixlen = prefixlen; + ifc->address = (struct prefix *)p; + + /* Add global ipv6 address to the RA prefix list */ + if (!IN6_IS_ADDR_LINKLOCAL(&p->prefix)) + rtadv_add_prefix(ifp->info, p); + + if (dest) { + p = prefix_ipv6_new(); + p->family = AF_INET6; + IPV6_ADDR_COPY(&p->prefix, dest); + p->prefixlen = prefixlen; + ifc->destination = (struct prefix *)p; + } else { + if (CHECK_FLAG(ifc->flags, ZEBRA_IFA_PEER)) { + zlog_debug( + "%s called for interface %s with peer flag set, but no peer address supplied", + __func__, ifp->name); + UNSET_FLAG(ifc->flags, ZEBRA_IFA_PEER); + } + } + + /* Label of this address. */ + if (label) + ifc->label = XSTRDUP(MTYPE_CONNECTED_LABEL, label); + + /* On Linux, we only get here when DAD is complete, therefore we can set + * ZEBRA_IFC_REAL. + * + * On BSD, there currently doesn't seem to be a way to check for + * completion of + * DAD, so we replicate the old behaviour and set ZEBRA_IFC_REAL, + * although DAD + * might still be running. + */ + SET_FLAG(ifc->conf, ZEBRA_IFC_REAL); + connected_update(ifp, ifc); +} + +void connected_delete_ipv6(struct interface *ifp, + const struct in6_addr *address, + const struct in6_addr *dest, uint16_t prefixlen) +{ + struct prefix p, d; + struct connected *ifc; + + memset(&p, 0, sizeof(p)); + p.family = AF_INET6; + memcpy(&p.u.prefix6, address, sizeof(struct in6_addr)); + p.prefixlen = prefixlen; + + /* Delete global ipv6 address from RA prefix list */ + if (!IN6_IS_ADDR_LINKLOCAL(&p.u.prefix6)) + rtadv_delete_prefix(ifp->info, &p); + + if (dest) { + memset(&d, 0, sizeof(d)); + d.family = AF_INET6; + IPV6_ADDR_COPY(&d.u.prefix6, dest); + d.prefixlen = prefixlen; + ifc = connected_check_ptp(ifp, &p, &d); + } else + ifc = connected_check_ptp(ifp, &p, NULL); + + connected_delete_helper(ifc, &p); +} + +int connected_is_unnumbered(struct interface *ifp) +{ + struct connected *connected; + struct listnode *node; + + for (ALL_LIST_ELEMENTS_RO(ifp->connected, node, connected)) { + if (CHECK_FLAG(connected->conf, ZEBRA_IFC_REAL) + && connected->address->family == AF_INET) + return CHECK_FLAG(connected->flags, + ZEBRA_IFA_UNNUMBERED); + } + return 0; +} |