summaryrefslogtreecommitdiffstats
path: root/sys/netinet/ip_carp.c
diff options
context:
space:
mode:
authorjpaetzel <jpaetzel@FreeBSD.org>2017-01-19 23:42:26 +0000
committerjpaetzel <jpaetzel@FreeBSD.org>2017-01-19 23:42:26 +0000
commit3d43d47e2dfb79e5d3abb61fc33c636d37e8b842 (patch)
tree7237e9071c03e76acb4656994debc267fbec4932 /sys/netinet/ip_carp.c
parent34f0e7816bb1543e5e1380573b253e859cdb816d (diff)
downloadFreeBSD-src-3d43d47e2dfb79e5d3abb61fc33c636d37e8b842.zip
FreeBSD-src-3d43d47e2dfb79e5d3abb61fc33c636d37e8b842.tar.gz
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
Diffstat (limited to 'sys/netinet/ip_carp.c')
-rw-r--r--sys/netinet/ip_carp.c126
1 files changed, 110 insertions, 16 deletions
diff --git a/sys/netinet/ip_carp.c b/sys/netinet/ip_carp.c
index c5fea16..6e72202 100644
--- a/sys/netinet/ip_carp.c
+++ b/sys/netinet/ip_carp.c
@@ -573,27 +573,96 @@ carp6_input(struct mbuf **mp, int *offp, int proto)
}
#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;
}
@@ -789,12 +858,41 @@ carp_send_ad_error(struct carp_softc *sc, int error)
}
}
+/*
+ * 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;
@@ -843,9 +941,7 @@ carp_send_ad_locked(struct carp_softc *sc)
ip->ip_p = IPPROTO_CARP;
ip->ip_sum = 0;
- 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;
@@ -889,11 +985,9 @@ carp_send_ad_locked(struct carp_softc *sc)
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));
OpenPOWER on IntegriCloud