Skip site navigation (1)Skip section navigation (2)
Date:      Thu, 19 Jan 2017 23:38:31 +0000 (UTC)
From:      Josh Paetzel <jpaetzel@FreeBSD.org>
To:        src-committers@freebsd.org, svn-src-all@freebsd.org, svn-src-stable@freebsd.org, svn-src-stable-11@freebsd.org
Subject:   svn commit: r312439 - stable/11/sys/netinet
Message-ID:  <201701192338.v0JNcVM5099732@repo.freebsd.org>

next in thread | raw e-mail | index | archive | help
Author: jpaetzel
Date: Thu Jan 19 23:38:31 2017
New Revision: 312439
URL: https://svnweb.freebsd.org/changeset/base/312439

Log:
  MFC 310847 310864
  
  Harden CARP against network loops.
  
  If there is a loop in the network a CARP that is in MASTER state will see it's
  own broadcasts, which will then cause it to assume BACKUP state.  When it
  assumes BACKUP it will stop sending advertisements.  In that state it will no
  longer see advertisements and will assume MASTER...
  
  We can't catch all the cases where we are seeing our own CARP broadcast, but
  we can catch the obvious case.
  
  Unbreak ip_carp with WITHOUT_INET6 enabled by conditionalizing all IPv6
  structs under the INET6 #ifdef. Similarly (even though it doesn't seem
  to affect the build), conditionalize all IPv4 structs under the INET
  #ifdef
  
  This also unbreaks the LINT-NOINET6 tinderbox target on amd64; I have not
  verified other MACHINE/TARGET pairs (e.g. armv6/arm).
  
  Submitted by:	torek
  Obtained from:	FreeNAS
  Pointyhat fix:	ngie

Modified:
  stable/11/sys/netinet/ip_carp.c
Directory Properties:
  stable/11/   (props changed)

Modified: stable/11/sys/netinet/ip_carp.c
==============================================================================
--- stable/11/sys/netinet/ip_carp.c	Thu Jan 19 22:07:21 2017	(r312438)
+++ stable/11/sys/netinet/ip_carp.c	Thu Jan 19 23:38:31 2017	(r312439)
@@ -581,27 +581,96 @@ carp6_input(struct mbuf **mp, int *offp,
 }
 #endif /* INET6 */
 
+/*
+ * This routine should not be necessary at all, but some switches
+ * (VMWare ESX vswitches) can echo our own packets back at us,
+ * and we must ignore them or they will cause us to drop out of
+ * MASTER mode.
+ *
+ * We cannot catch all cases of network loops.  Instead, what we
+ * do here is catch any packet that arrives with a carp header
+ * with a VHID of 0, that comes from an address that is our own.
+ * These packets are by definition "from us" (even if they are from
+ * a misconfigured host that is pretending to be us).
+ *
+ * The VHID test is outside this mini-function.
+ */
+static int
+carp_source_is_self(struct mbuf *m, struct ifaddr *ifa, sa_family_t af)
+{
+#ifdef INET
+	struct ip *ip4;
+	struct in_addr in4;
+#endif
+#ifdef INET6
+	struct ip6_hdr *ip6;
+	struct in6_addr in6;
+#endif
+
+	switch (af) {
+#ifdef INET
+	case AF_INET:
+		ip4 = mtod(m, struct ip *);
+		in4 = ifatoia(ifa)->ia_addr.sin_addr;
+		return (in4.s_addr == ip4->ip_src.s_addr);
+#endif
+#ifdef INET6
+	case AF_INET6:
+		ip6 = mtod(m, struct ip6_hdr *);
+		in6 = ifatoia6(ifa)->ia_addr.sin6_addr;
+		return (memcmp(&in6, &ip6->ip6_src, sizeof(in6)) == 0);
+#endif
+	default:
+		break;
+	}
+	return (0);
+}
+
 static void
 carp_input_c(struct mbuf *m, struct carp_header *ch, sa_family_t af)
 {
 	struct ifnet *ifp = m->m_pkthdr.rcvif;
-	struct ifaddr *ifa;
+	struct ifaddr *ifa, *match;
 	struct carp_softc *sc;
 	uint64_t tmp_counter;
 	struct timeval sc_tv, ch_tv;
+	int error;
 
-	/* verify that the VHID is valid on the receiving interface */
+	/*
+	 * Verify that the VHID is valid on the receiving interface.
+	 *
+	 * There should be just one match.  If there are none
+	 * the VHID is not valid and we drop the packet.  If
+	 * there are multiple VHID matches, take just the first
+	 * one, for compatibility with previous code.  While we're
+	 * scanning, check for obvious loops in the network topology
+	 * (these should never happen, and as noted above, we may
+	 * miss real loops; this is just a double-check).
+	 */
 	IF_ADDR_RLOCK(ifp);
-	IFNET_FOREACH_IFA(ifp, ifa)
-		if (ifa->ifa_addr->sa_family == af &&
-		    ifa->ifa_carp->sc_vhid == ch->carp_vhid) {
-			ifa_ref(ifa);
-			break;
-		}
+	error = 0;
+	match = NULL;
+	IFNET_FOREACH_IFA(ifp, ifa) {
+		if (match == NULL && ifa->ifa_carp != NULL &&
+		    ifa->ifa_addr->sa_family == af &&
+		    ifa->ifa_carp->sc_vhid == ch->carp_vhid)
+			match = ifa;
+		if (ch->carp_vhid == 0 && carp_source_is_self(m, ifa, af))
+			error = ELOOP;
+	}
+	ifa = error ? NULL : match;
+	if (ifa != NULL)
+		ifa_ref(ifa);
 	IF_ADDR_RUNLOCK(ifp);
 
 	if (ifa == NULL) {
-		CARPSTATS_INC(carps_badvhid);
+		if (error == ELOOP) {
+			CARP_DEBUG("dropping looped packet on interface %s\n",
+			    ifp->if_xname);
+			CARPSTATS_INC(carps_badif);	/* ??? */
+		} else {
+			CARPSTATS_INC(carps_badvhid);
+		}
 		m_freem(m);
 		return;
 	}
@@ -787,12 +856,41 @@ carp_send_ad_error(struct carp_softc *sc
 	}
 }
 
+/*
+ * Pick the best ifaddr on the given ifp for sending CARP
+ * advertisements.
+ *
+ * "Best" here is defined by ifa_preferred().  This function is much
+ * much like ifaof_ifpforaddr() except that we just use ifa_preferred().
+ *
+ * (This could be simplified to return the actual address, except that
+ * it has a different format in AF_INET and AF_INET6.)
+ */
+static struct ifaddr *
+carp_best_ifa(int af, struct ifnet *ifp)
+{
+	struct ifaddr *ifa, *best;
+
+	if (af >= AF_MAX)
+		return (NULL);
+	best = NULL;
+	IF_ADDR_RLOCK(ifp);
+	TAILQ_FOREACH(ifa, &ifp->if_addrhead, ifa_link) {
+		if (ifa->ifa_addr->sa_family == af &&
+		    (best == NULL || ifa_preferred(best, ifa)))
+			best = ifa;
+	}
+	IF_ADDR_RUNLOCK(ifp);
+	if (best != NULL)
+		ifa_ref(best);
+	return (best);
+}
+
 static void
 carp_send_ad_locked(struct carp_softc *sc)
 {
 	struct carp_header ch;
 	struct timeval tv;
-	struct sockaddr sa;
 	struct ifaddr *ifa;
 	struct carp_header *ch_ptr;
 	struct mbuf *m;
@@ -841,9 +939,7 @@ carp_send_ad_locked(struct carp_softc *s
 		ip->ip_sum = 0;
 		ip_fillid(ip);
 
-		bzero(&sa, sizeof(sa));
-		sa.sa_family = AF_INET;
-		ifa = ifaof_ifpforaddr(&sa, sc->sc_carpdev);
+		ifa = carp_best_ifa(AF_INET, sc->sc_carpdev);
 		if (ifa != NULL) {
 			ip->ip_src.s_addr =
 			    ifatoia(ifa)->ia_addr.sin_addr.s_addr;
@@ -887,11 +983,9 @@ carp_send_ad_locked(struct carp_softc *s
 		ip6->ip6_vfc |= IPV6_VERSION;
 		ip6->ip6_hlim = CARP_DFLTTL;
 		ip6->ip6_nxt = IPPROTO_CARP;
-		bzero(&sa, sizeof(sa));
 
 		/* set the source address */
-		sa.sa_family = AF_INET6;
-		ifa = ifaof_ifpforaddr(&sa, sc->sc_carpdev);
+		ifa = carp_best_ifa(AF_INET6, sc->sc_carpdev);
 		if (ifa != NULL) {
 			bcopy(IFA_IN6(ifa), &ip6->ip6_src,
 			    sizeof(struct in6_addr));



Want to link to this message? Use this URL: <https://mail-archive.FreeBSD.org/cgi/mid.cgi?201701192338.v0JNcVM5099732>