aboutsummaryrefslogtreecommitdiff
path: root/sys/netinet/ip_carp.c
diff options
context:
space:
mode:
authorJosh Paetzel <jpaetzel@FreeBSD.org>2016-12-30 18:46:21 +0000
committerJosh Paetzel <jpaetzel@FreeBSD.org>2016-12-30 18:46:21 +0000
commit8151740c881f1f400dab7a3f4c7bb23439c11b66 (patch)
tree024c850c8dce673e03f336fd205e003b79f158f3 /sys/netinet/ip_carp.c
parent8f87df16d414c7b09739ba95030e1e5650207b73 (diff)
downloadsrc-8151740c881f1f400dab7a3f4c7bb23439c11b66.tar.gz
src-8151740c881f1f400dab7a3f4c7bb23439c11b66.zip
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. Submitted by: torek Obtained from: FreeNAS MFC after: 2 weeks Sponsored by: iXsystems
Notes
Notes: svn path=/head/; revision=310847
Diffstat (limited to 'sys/netinet/ip_carp.c')
-rw-r--r--sys/netinet/ip_carp.c120
1 files changed, 104 insertions, 16 deletions
diff --git a/sys/netinet/ip_carp.c b/sys/netinet/ip_carp.c
index 7855af2e430f..ae04d5914a0d 100644
--- a/sys/netinet/ip_carp.c
+++ b/sys/netinet/ip_carp.c
@@ -581,27 +581,90 @@ 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)
+{
+ struct ip *ip4;
+ struct in_addr in4;
+ struct ip6_hdr *ip6;
+ struct in6_addr in6;
+
+ switch (af) {
+ case AF_INET:
+ ip4 = mtod(m, struct ip *);
+ in4 = ifatoia(ifa)->ia_addr.sin_addr;
+ return (in4.s_addr == ip4->ip_src.s_addr);
+
+ 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);
+
+ default: /* how did this happen? */
+ 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 +850,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;
@@ -841,9 +933,7 @@ carp_send_ad_locked(struct carp_softc *sc)
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 +977,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));