summaryrefslogtreecommitdiffstats
path: root/zebra/connected.c
diff options
context:
space:
mode:
Diffstat (limited to 'zebra/connected.c')
-rw-r--r--zebra/connected.c628
1 files changed, 628 insertions, 0 deletions
diff --git a/zebra/connected.c b/zebra/connected.c
new file mode 100644
index 0000000..ee0823f
--- /dev/null
+++ b/zebra/connected.c
@@ -0,0 +1,628 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * Address linked list routine.
+ * Copyright (C) 1997, 98 Kunihiro Ishiguro
+ */
+
+#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;
+ case AFI_UNSPEC:
+ case AFI_L2VPN:
+ case AFI_MAX:
+ 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;
+ case AFI_UNSPEC:
+ case AFI_L2VPN:
+ case AFI_MAX:
+ 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;
+}