You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@trafficcontrol.apache.org by ma...@apache.org on 2020/11/02 22:02:48 UTC

[trafficcontrol] branch master updated: Check cache availability by IP version instead of filtering later (#5186)

This is an automated email from the ASF dual-hosted git repository.

mattjackson pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/trafficcontrol.git


The following commit(s) were added to refs/heads/master by this push:
     new d0c9699  Check cache availability by IP version instead of filtering later (#5186)
d0c9699 is described below

commit d0c9699e586d9c4fcf3d94407d9028aebc8766df
Author: Rawlin Peters <ra...@apache.org>
AuthorDate: Mon Nov 2 15:02:32 2020 -0700

    Check cache availability by IP version instead of filtering later (#5186)
    
    * Check cache availability by IP version instead of filtering later
    
    If the returned caches turn out to all be unavailable for the client's
    IP version, we don't want to 503. Instead, we want to follow the regular
    cache availability logic (falling back to closest available location,
    etc).
    
    * Add changelog
    
    * De-duplicate line in test
---
 CHANGELOG.md                                       |   2 +
 .../api/controllers/CoverageZoneController.java    |   6 +-
 .../controllers/DeepCoverageZoneController.java    |   4 +-
 .../traffic_router/core/dns/ZoneManager.java       |   3 +-
 .../traffic_router/core/edge/Node.java             |  74 +++-------
 .../traffic_router/core/router/TrafficRouter.java  | 153 ++++++++++-----------
 .../traffic_router/core/dns/ZoneManagerTest.java   |   5 +-
 .../traffic_router/core/external/SteeringTest.java |   4 +-
 .../traffic_router/core/loc/CoverageZoneTest.java  |  11 +-
 .../core/router/DNSRoutingMissesTest.java          |   7 +-
 .../core/router/TrafficRouterTest.java             |  69 ++++++++--
 11 files changed, 173 insertions(+), 165 deletions(-)

diff --git a/CHANGELOG.md b/CHANGELOG.md
index 6a8ee96..086d8c0 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -9,6 +9,7 @@ The format is based on [Keep a Changelog](http://keepachangelog.com/en/1.0.0/).
 - Fixed an issue where the jobs and servers table in Traffic Portal would not clear a column's filter when it's hidden
 - Fixed an issue with Traffic Router failing to authenticate if secrets are changed
 - Fixed validation error message for Traffic Ops `POST /api/x/profileparameters` route
+- Fixed an issue where Traffic Router would erroneously return 503s or NXDOMAINs if the caches in a cachegroup were all unavailable for a client's requested IP version, rather than selecting caches from the next closest available cachegroup.
 
 ### Added
 - Added If-Match and If-Unmodified-Since Support in Server and Clients.
@@ -483,6 +484,7 @@ will be returned indicating that overlap exists.
 ### Changed
 - Reformatted this CHANGELOG file to the keep-a-changelog format
 
+[unreleased]: https://github.com/apache/trafficcontrol/compare/RELEASE-5.0.0...HEAD
 [5.0.0]: https://github.com/apache/trafficcontrol/compare/RELEASE-4.1.0...RELEASE-5.0.0
 [4.1.0]: https://github.com/apache/trafficcontrol/compare/RELEASE-4.0.0...RELEASE-4.1.0
 [4.0.0]: https://github.com/apache/trafficcontrol/compare/RELEASE-3.0.0...RELEASE-4.0.0
diff --git a/traffic_router/core/src/main/java/com/comcast/cdn/traffic_control/traffic_router/api/controllers/CoverageZoneController.java b/traffic_router/core/src/main/java/com/comcast/cdn/traffic_control/traffic_router/api/controllers/CoverageZoneController.java
index 918158f..53e60bf 100644
--- a/traffic_router/core/src/main/java/com/comcast/cdn/traffic_control/traffic_router/api/controllers/CoverageZoneController.java
+++ b/traffic_router/core/src/main/java/com/comcast/cdn/traffic_control/traffic_router/api/controllers/CoverageZoneController.java
@@ -17,6 +17,7 @@ package com.comcast.cdn.traffic_control.traffic_router.api.controllers;
 
 import com.comcast.cdn.traffic_control.traffic_router.core.edge.Cache;
 import com.comcast.cdn.traffic_control.traffic_router.core.edge.CacheLocation;
+import com.comcast.cdn.traffic_control.traffic_router.core.edge.Node.IPVersions;
 import com.comcast.cdn.traffic_control.traffic_router.core.router.TrafficRouterManager;
 import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.http.HttpStatus;
@@ -38,7 +39,8 @@ public class CoverageZoneController {
 	public @ResponseBody
 	ResponseEntity<CacheLocation> getCacheLocationForIp(@RequestParam(name = "ip") final String ip,
 	                                    @RequestParam(name = "deliveryServiceId") final String deliveryServiceId) {
-		final CacheLocation cacheLocation = trafficRouterManager.getTrafficRouter().getCoverageZoneCacheLocation(ip, deliveryServiceId);
+		final IPVersions requestVersion = ip.contains(":") ? IPVersions.IPV6ONLY : IPVersions.IPV4ONLY;
+		final CacheLocation cacheLocation = trafficRouterManager.getTrafficRouter().getCoverageZoneCacheLocation(ip, deliveryServiceId, requestVersion);
 		if (cacheLocation == null) {
 			return ResponseEntity.status(HttpStatus.NOT_FOUND).body(null);
 		}
@@ -50,7 +52,7 @@ public class CoverageZoneController {
 	public @ResponseBody
 	ResponseEntity<List<Cache>> getCachesForDeliveryService(@RequestParam(name = "deliveryServiceId") final String deliveryServiceId,
 	                                                        @RequestParam(name = "cacheLocationId") final String cacheLocationId) {
-		final List<Cache> caches = trafficRouterManager.getTrafficRouter().selectCachesByCZ(deliveryServiceId, cacheLocationId, null);
+		final List<Cache> caches = trafficRouterManager.getTrafficRouter().selectCachesByCZ(deliveryServiceId, cacheLocationId, null, IPVersions.ANY);
 		if (caches == null || caches.isEmpty()) {
 			return ResponseEntity.status(HttpStatus.NOT_FOUND).body(null);
 		}
diff --git a/traffic_router/core/src/main/java/com/comcast/cdn/traffic_control/traffic_router/api/controllers/DeepCoverageZoneController.java b/traffic_router/core/src/main/java/com/comcast/cdn/traffic_control/traffic_router/api/controllers/DeepCoverageZoneController.java
index c45f119..71f9fa4 100644
--- a/traffic_router/core/src/main/java/com/comcast/cdn/traffic_control/traffic_router/api/controllers/DeepCoverageZoneController.java
+++ b/traffic_router/core/src/main/java/com/comcast/cdn/traffic_control/traffic_router/api/controllers/DeepCoverageZoneController.java
@@ -16,6 +16,7 @@
 package com.comcast.cdn.traffic_control.traffic_router.api.controllers;
 
 import com.comcast.cdn.traffic_control.traffic_router.core.edge.CacheLocation;
+import com.comcast.cdn.traffic_control.traffic_router.core.edge.Node.IPVersions;
 import com.comcast.cdn.traffic_control.traffic_router.core.router.TrafficRouterManager;
 import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.http.HttpStatus;
@@ -35,7 +36,8 @@ public class DeepCoverageZoneController {
     public @ResponseBody
     ResponseEntity<CacheLocation> getCacheLocationForIp(@RequestParam(name = "ip") final String ip,
                                                         @RequestParam(name = "deliveryServiceId") final String deliveryServiceId) {
-        final CacheLocation cacheLocation = trafficRouterManager.getTrafficRouter().getCoverageZoneCacheLocation(ip, deliveryServiceId, true, null);
+        final IPVersions requestVersion = ip.contains(":") ? IPVersions.IPV6ONLY : IPVersions.IPV4ONLY;
+        final CacheLocation cacheLocation = trafficRouterManager.getTrafficRouter().getCoverageZoneCacheLocation(ip, deliveryServiceId, true, null, requestVersion);
         if (cacheLocation == null) {
             return ResponseEntity.status(HttpStatus.NOT_FOUND).body(null);
         }
diff --git a/traffic_router/core/src/main/java/com/comcast/cdn/traffic_control/traffic_router/core/dns/ZoneManager.java b/traffic_router/core/src/main/java/com/comcast/cdn/traffic_control/traffic_router/core/dns/ZoneManager.java
index fdbcf53..64ee8b8 100644
--- a/traffic_router/core/src/main/java/com/comcast/cdn/traffic_control/traffic_router/core/dns/ZoneManager.java
+++ b/traffic_router/core/src/main/java/com/comcast/cdn/traffic_control/traffic_router/core/dns/ZoneManager.java
@@ -46,6 +46,7 @@ import java.util.concurrent.ScheduledExecutorService;
 import java.util.concurrent.TimeUnit;
 import java.util.stream.Collectors;
 
+import com.comcast.cdn.traffic_control.traffic_router.core.edge.Node.IPVersions;
 import com.comcast.cdn.traffic_control.traffic_router.core.util.JsonUtils;
 import com.comcast.cdn.traffic_control.traffic_router.core.util.JsonUtilsException;
 import com.fasterxml.jackson.databind.JsonNode;
@@ -589,7 +590,7 @@ public class ZoneManager extends Resolver {
 		request.setHostname(edgeName.toString(true)); // Name.toString(true) - omit the trailing dot
 
 		for (final CacheLocation cacheLocation : data.getCacheLocations()) {
-			final List<Cache> caches = tr.selectCachesByCZ(ds, cacheLocation);
+			final List<Cache> caches = tr.selectCachesByCZ(ds, cacheLocation, IPVersions.ANY);
 
 			if (caches == null) {
 				continue;
diff --git a/traffic_router/core/src/main/java/com/comcast/cdn/traffic_control/traffic_router/core/edge/Node.java b/traffic_router/core/src/main/java/com/comcast/cdn/traffic_control/traffic_router/core/edge/Node.java
index a50cb99..0c92dab 100644
--- a/traffic_router/core/src/main/java/com/comcast/cdn/traffic_control/traffic_router/core/edge/Node.java
+++ b/traffic_router/core/src/main/java/com/comcast/cdn/traffic_control/traffic_router/core/edge/Node.java
@@ -37,14 +37,18 @@ public class Node extends DefaultHashable {
 	private static final Logger LOGGER = Logger.getLogger(Node.class);
 	private static final int REPLICAS = 1000;
 
-	public enum IpVersions {
-		IPV4ONLY, IPV6ONLY, BOTH
+	public enum IPVersions {
+		IPV4ONLY, IPV6ONLY, ANY
 	}
-	private IpVersions ipAvailableVersions;
 	protected final String id;
 	private String fqdn;
 	private List<InetRecord> ipAddresses;
-	private List<InetRecord> unavailableIpAddresses;
+	private InetAddress ip4;
+	private InetAddress ip6;
+	private boolean isAvailable = false;
+	private boolean ipv4Available = true;
+	private boolean ipv6Available = true;
+	private boolean hasAuthority = false;
 	private int port;
 	private final Map<String, Cache.DeliveryServiceReference> deliveryServices = new HashMap<>();
 	private final Set<String> capabilities = new HashSet<>();
@@ -158,8 +162,6 @@ public class Node extends DefaultHashable {
 		return "Node [id=" + id + "] ";
 	}
 
-	boolean isAvailable = false;
-	boolean hasAuthority = false;
 	public void setIsAvailable(final boolean isAvailable) {
 		this.hasAuthority = true;
 		this.isAvailable = isAvailable;
@@ -170,11 +172,18 @@ public class Node extends DefaultHashable {
 	public boolean isAvailable() {
 		return isAvailable;
 	}
-	InetAddress ip4;
-	InetAddress ip6;
+	public boolean isAvailable(final IPVersions requestVersion) {
+	    switch (requestVersion) {
+			case IPV4ONLY:
+			    return isAvailable && ipv4Available;
+			case IPV6ONLY:
+			    return isAvailable && ipv6Available;
+			default:
+			    return isAvailable;
+		}
+	}
 	public void setIpAddress(final String ip, final String ip6, final long ttl) throws UnknownHostException {
 		this.ipAddresses = new ArrayList<InetRecord>();
-		this.unavailableIpAddresses = new ArrayList<InetRecord>();
 
 		if (ip != null && !ip.isEmpty()) {
 			this.ip4 = InetAddress.getByName(ip);
@@ -200,8 +209,6 @@ public class Node extends DefaultHashable {
 
 	@SuppressWarnings({"PMD.CyclomaticComplexity", "PMD.NPathComplexity"})
 	public void setState(final JsonNode state) {
-		final boolean ipv4Available;
-		final boolean ipv6Available;
 		if (state == null) {
 			LOGGER.warn("got null health state for " + fqdn + ". Setting it to unavailable!");
 			isAvailable = false;
@@ -212,44 +219,7 @@ public class Node extends DefaultHashable {
 			ipv4Available = JsonUtils.optBoolean(state, "ipv4Available", true);
 			ipv6Available = JsonUtils.optBoolean(state, "ipv6Available", true);
 		}
-
-		final List<InetRecord> newlyAvailable = new ArrayList<>();
-		final List<InetRecord> newlyUnavailable = new ArrayList<>();
-
-		if (ipv4Available && !ipv6Available) {
-			this.ipAvailableVersions = IpVersions.IPV4ONLY;
-		} else if (ipv6Available && !ipv4Available) {
-			this.ipAvailableVersions = IpVersions.IPV6ONLY;
-		} else {
-			this.ipAvailableVersions = IpVersions.BOTH;
-		}
-
-		for (final InetRecord record : ipAddresses) {
-			if (record.getAddress().equals(ip4) && !ipv4Available) {
-				newlyUnavailable.add(record);
-			}
-			if (record.getAddress().equals(ip6) && !ipv6Available) {
-				newlyUnavailable.add(record);
-			}
-		}
-
-		for (final InetRecord record : unavailableIpAddresses) {
-			if (record.getAddress().equals(ip4) && ipv4Available) {
-				newlyAvailable.add(record);
-			}
-			if (record.getAddress().equals(ip6) && ipv6Available) {
-				newlyAvailable.add(record);
-			}
-		}
-
-		ipAddresses.addAll(newlyAvailable);
-		ipAddresses.removeAll(newlyUnavailable);
-		unavailableIpAddresses.addAll(newlyUnavailable);
-		unavailableIpAddresses.removeAll(newlyAvailable);
-
 		this.setIsAvailable(isAvailable);
-
-
 	}
 
 	public int getHttpsPort() {
@@ -259,12 +229,4 @@ public class Node extends DefaultHashable {
 	public void setHttpsPort(final int httpsPort) {
 		this.httpsPort = httpsPort;
 	}
-
-	public IpVersions getIpAvailableVersions() {
-		return ipAvailableVersions;
-	}
-
-	public void setIpAvailableVersions(final IpVersions ipAvailableVersions) {
-		this.ipAvailableVersions = ipAvailableVersions;
-	}
 }
diff --git a/traffic_router/core/src/main/java/com/comcast/cdn/traffic_control/traffic_router/core/router/TrafficRouter.java b/traffic_router/core/src/main/java/com/comcast/cdn/traffic_control/traffic_router/core/router/TrafficRouter.java
index d5ee972..67cb266 100644
--- a/traffic_router/core/src/main/java/com/comcast/cdn/traffic_control/traffic_router/core/router/TrafficRouter.java
+++ b/traffic_router/core/src/main/java/com/comcast/cdn/traffic_control/traffic_router/core/router/TrafficRouter.java
@@ -31,6 +31,7 @@ import com.comcast.cdn.traffic_control.traffic_router.core.edge.CacheRegister;
 import com.comcast.cdn.traffic_control.traffic_router.core.edge.InetRecord;
 import com.comcast.cdn.traffic_control.traffic_router.core.edge.Location;
 import com.comcast.cdn.traffic_control.traffic_router.core.edge.Node;
+import com.comcast.cdn.traffic_control.traffic_router.core.edge.Node.IPVersions;
 import com.comcast.cdn.traffic_control.traffic_router.core.edge.TrafficRouterLocation;
 import com.comcast.cdn.traffic_control.traffic_router.core.hash.ConsistentHasher;
 import com.comcast.cdn.traffic_control.traffic_router.core.loc.AnonymousIp;
@@ -194,7 +195,7 @@ public class TrafficRouter {
 	 *            the DeliveryService to check
 	 * @return collection of supported caches
 	 */
-	public List<Cache> getSupportingCaches(final List<Cache> caches, final DeliveryService ds) {
+	public List<Cache> getSupportingCaches(final List<Cache> caches, final DeliveryService ds, final IPVersions requestVersion) {
 		final List<Cache> supportingCaches = new ArrayList<Cache>();
 
 		for (final Cache cache : caches) {
@@ -202,7 +203,7 @@ public class TrafficRouter {
 				continue;
 			}
 
-			if (cache.hasAuthority() ? cache.isAvailable() : true) {
+			if (!cache.hasAuthority() || (cache.isAvailable(requestVersion))) {
 				supportingCaches.add(cache);
 			}
 		}
@@ -324,7 +325,7 @@ public class TrafficRouter {
 	 * only "Maxmind" and "Neustar" are supported)
 	 * @param deliveryServiceId Currently only used for logging error information, should be an
 	 * identifier for a Delivery Service
-	 * @return A {@link #GeolocationService} that can be used to geo-locate clients <em>or</em>
+	 * @return A {@link GeolocationService} that can be used to geo-locate clients <em>or</em>
 	 * {@code null} if an error occurs.
 	 */
 	private GeolocationService getGeolocationService(final String geolocationProvider, final String deliveryServiceId) {
@@ -379,7 +380,7 @@ public class TrafficRouter {
 	}
 
 	/**
-	 * Gets a {@link #List} of {@link Cache}s that are capabable of serving a given Delivery Service.
+	 * Gets a {@link List} of {@link Cache}s that are capabable of serving a given Delivery Service.
 	 * <p>
 	 * The caches chosen are from the closest, non-empty, cache location to the client's physical
 	 * location, up to the Location Limit ({@link DeliveryService#getLocationLimit()}) of the
@@ -387,11 +388,11 @@ public class TrafficRouter {
 	 * </p>
 	 * @param ds The Delivery Service being served.
 	 * @param clientLocation The physical location of the requesting client.
-	 * @param track The {@link #Track} object on which a result location shall be set, should one be found
-	 * @return A {@link #List} of {@link Cache}s that should be used to service a request should such a collection be found, or
+	 * @param track The {@link Track} object on which a result location shall be set, should one be found
+	 * @return A {@link List} of {@link Cache}s that should be used to service a request should such a collection be found, or
 	 * {@code null} if the no applicable {@link Cache}s could be found.
 	 */
-	public List<Cache> getCachesByGeo(final DeliveryService ds, final Geolocation clientLocation, final Track track) throws GeolocationException {
+	public List<Cache> getCachesByGeo(final DeliveryService ds, final Geolocation clientLocation, final Track track, final IPVersions requestVersion) throws GeolocationException {
 		int locationsTested = 0;
 
 		final int locationLimit = ds.getLocationLimit();
@@ -401,7 +402,7 @@ public class TrafficRouter {
 		final List<CacheLocation> cacheLocations = (List<CacheLocation>) orderLocations(cacheLocations1, clientLocation);
 
 		for (final CacheLocation location : cacheLocations) {
-			final List<Cache> caches = selectCaches(location, ds);
+			final List<Cache> caches = selectCaches(location, ds, requestVersion);
 			if (caches != null) {
 				track.setResultLocation(location.getGeolocation());
 				if (track.getResultLocation().equals(GEO_ZERO_ZERO)) {
@@ -427,7 +428,7 @@ public class TrafficRouter {
 	 * </p>
 	 * @param request The HTTP request made by the client.
 	 * @param ds The Delivery Service being served.
-	 * @param track The {@link #Track} object that tracks how requests are served
+	 * @param track The {@link Track} object that tracks how requests are served
 	 */
 	protected List<Cache> selectCaches(final HTTPRequest request, final DeliveryService ds, final Track track) throws GeolocationException {
 		return selectCaches(request, ds, track, true);
@@ -437,7 +438,7 @@ public class TrafficRouter {
 	 * Selects {@link Cache}s to serve a request for a Delivery Service.
 	 * @param request The HTTP request made by the client.
 	 * @param ds The Delivery Service being served.
-	 * @param track The {@link #Track} object that tracks how requests are served
+	 * @param track The {@link Track} object that tracks how requests are served
 	 * @param enableDeep Sets whether or not "Deep Caching" may be used.
 	 */
 	@SuppressWarnings("PMD.CyclomaticComplexity")
@@ -445,23 +446,24 @@ public class TrafficRouter {
 		CacheLocation cacheLocation;
 		ResultType result = ResultType.CZ;
 		final boolean useDeep = enableDeep && (ds.getDeepCache() == DeliveryService.DeepCachingType.ALWAYS);
+		final IPVersions requestVersion = request.getClientIP().contains(":") ? IPVersions.IPV6ONLY : IPVersions.IPV4ONLY;
 
 		if (useDeep) {
 			// Deep caching is enabled. See if there are deep caches available
-			cacheLocation = getDeepCoverageZoneCacheLocation(request.getClientIP(), ds);
+			cacheLocation = getDeepCoverageZoneCacheLocation(request.getClientIP(), ds, requestVersion);
 			if (cacheLocation != null && cacheLocation.getCaches().size() != 0) {
 				// Found deep caches for this client, and there are caches that might be available there.
 				result = ResultType.DEEP_CZ;
 			} else {
 				// No deep caches for this client, would have used them if there were any. Fallback to regular CZ
-				cacheLocation = getCoverageZoneCacheLocation(request.getClientIP(), ds);
+				cacheLocation = getCoverageZoneCacheLocation(request.getClientIP(), ds, requestVersion);
 			}
 		} else {
 			// Deep caching not enabled for this Delivery Service; use the regular CZ
-			cacheLocation = getCoverageZoneCacheLocation(request.getClientIP(), ds, useDeep, track);
+			cacheLocation = getCoverageZoneCacheLocation(request.getClientIP(), ds, false, track, requestVersion);
 		}
 
-		List<Cache>caches = selectCachesByCZ(ds, cacheLocation, track, result);
+		List<Cache>caches = selectCachesByCZ(ds, cacheLocation, track, result, requestVersion);
 
 		if (caches != null) {
 			return caches;
@@ -470,14 +472,14 @@ public class TrafficRouter {
 		if (ds.isCoverageZoneOnly()) {
 			if (ds.getGeoRedirectUrl() != null) {
 				//use the NGB redirect
-				caches = enforceGeoRedirect(track, ds, request.getClientIP(), null);
+				caches = enforceGeoRedirect(track, ds, request.getClientIP(), null, requestVersion);
 			} else {
 				track.setResult(ResultType.MISS);
 				track.setResultDetails(ResultDetails.DS_CZ_ONLY);
 			}
 		} else if (track.continueGeo) {
 			// continue Geo can be disabled when backup group is used -- ended up an empty cache list if reach here
-			caches = selectCachesByGeo(request.getClientIP(), ds, cacheLocation, track);
+			caches = selectCachesByGeo(request.getClientIP(), ds, cacheLocation, track, requestVersion);
 		}
 
 		return caches;
@@ -500,9 +502,9 @@ public class TrafficRouter {
 	 * @param deliveryService The Delivery Service being served.
 	 * @param cacheLocation A selected {@link CacheLocation} from which {@link Cache}s will be
 	 * extracted based on the client's location.
-	 * @param track The {@link #Track} object that tracks how requests are served
+	 * @param track The {@link Track} object that tracks how requests are served
 	 */
-	public List<Cache> selectCachesByGeo(final String clientIp, final DeliveryService deliveryService, final CacheLocation cacheLocation, final Track track) throws GeolocationException {
+	public List<Cache> selectCachesByGeo(final String clientIp, final DeliveryService deliveryService, final CacheLocation cacheLocation, final Track track, final IPVersions requestVersion) throws GeolocationException {
 		Geolocation clientLocation = null;
 
 		try {
@@ -517,7 +519,7 @@ public class TrafficRouter {
 				LOGGER.debug(String
 						.format("client is blocked by geolimit, use the NGB redirect url: %s",
 								deliveryService.getGeoRedirectUrl()));
-				return enforceGeoRedirect(track, deliveryService, clientIp, track.getClientGeolocation());
+				return enforceGeoRedirect(track, deliveryService, clientIp, track.getClientGeolocation(), requestVersion);
 			} else {
 				track.setResultDetails(ResultDetails.DS_CLIENT_GEO_UNSUPPORTED);
 				return null;
@@ -534,7 +536,7 @@ public class TrafficRouter {
 			}
 		}
 
-		final List<Cache> caches = getCachesByGeo(deliveryService, clientLocation, track);
+		final List<Cache> caches = getCachesByGeo(deliveryService, clientLocation, track, requestVersion);
 
 		if (caches == null || caches.isEmpty()) {
 			track.setResultDetails(ResultDetails.GEO_NO_CACHE_FOUND);
@@ -737,8 +739,9 @@ public class TrafficRouter {
 			return result;
 		}
 
-		final CacheLocation cacheLocation = getCoverageZoneCacheLocation(request.getClientIP(), ds, false, track);
-		List<Cache> caches = selectCachesByCZ(ds, cacheLocation, track);
+		final IPVersions requestVersion = request.getQueryType() == Type.AAAA ? IPVersions.IPV6ONLY : IPVersions.IPV4ONLY;
+		final CacheLocation cacheLocation = getCoverageZoneCacheLocation(request.getClientIP(), ds, false, track, requestVersion);
+		List<Cache> caches = selectCachesByCZ(ds, cacheLocation, track, requestVersion);
 
 		if (caches != null) {
 			track.setResult(ResultType.CZ);
@@ -769,7 +772,7 @@ public class TrafficRouter {
 		}
 
 		if (track.continueGeo) {
-			caches = selectCachesByGeo(request.getClientIP(), ds, cacheLocation, track);
+			caches = selectCachesByGeo(request.getClientIP(), ds, cacheLocation, track, requestVersion);
 		}
 
 		if (caches != null) {
@@ -934,15 +937,15 @@ public class TrafficRouter {
 	 * Selects caches to service requests for a Delivery Service from a cache location based on
 	 * Coverage Zone configuration.
 	 * <p>
-	 * This is equivalent to calling {@link #selectCachesByCZ(DeliveryService, CacheLocation, Track)}
+	 * This is equivalent to calling {@link #selectCachesByCZ(DeliveryService, CacheLocation, Track, IPVersions)}
 	 * with a 'null' "track" argument.
 	 * </p>
 	 * @param ds The Delivery Service being served.
 	 * @param cacheLocation The location from which caches will be selected.
 	 * @return All of the caches in the given location capable of serving ds.
 	 */
-	public List<Cache> selectCachesByCZ(final DeliveryService ds, final CacheLocation cacheLocation) {
-		return selectCachesByCZ(ds, cacheLocation, null);
+	public List<Cache> selectCachesByCZ(final DeliveryService ds, final CacheLocation cacheLocation, final IPVersions requestVersion) {
+		return selectCachesByCZ(ds, cacheLocation, null, requestVersion);
 	}
 
 	/**
@@ -954,23 +957,23 @@ public class TrafficRouter {
 	 * @return All of the caches in the given location capable of serving the identified Delivery
 	 * Service.
 	 */
-	public List<Cache> selectCachesByCZ(final String deliveryServiceId, final String cacheLocationId, final Track track) {
-		return selectCachesByCZ(cacheRegister.getDeliveryService(deliveryServiceId), cacheRegister.getCacheLocation(cacheLocationId), track);
+	public List<Cache> selectCachesByCZ(final String deliveryServiceId, final String cacheLocationId, final Track track, final IPVersions requestVersion) {
+		return selectCachesByCZ(cacheRegister.getDeliveryService(deliveryServiceId), cacheRegister.getCacheLocation(cacheLocationId), track, requestVersion);
 	}
 
 	/**
 	 * Selects caches to service requests for a Delivery Service from a cache location based on
 	 * Coverage Zone Configuration.
 	 * <p>
-	 * This is equivalent to calling {@link #selectCachesByCZ(DeliveryService, CacheLocation, Track, ResultType)}
-	 * with the "result" argument set to {@link #ResultType.CZ}.
+	 * This is equivalent to calling {@link #selectCachesByCZ(DeliveryService, CacheLocation, Track, ResultType, IPVersions)}
+	 * with the "result" argument set to {@link ResultType#CZ}.
 	 * </p>
 	 * @param ds The Delivery Service being served.
 	 * @param cacheLocation The location from which caches will be selected
 	 * @return All of the caches in the given location capable of serving ds.
 	 */
-	private List<Cache> selectCachesByCZ(final DeliveryService ds, final CacheLocation cacheLocation, final Track track) {
-		return selectCachesByCZ(ds, cacheLocation, track, ResultType.CZ); // ResultType.CZ was the original default before DDC
+	private List<Cache> selectCachesByCZ(final DeliveryService ds, final CacheLocation cacheLocation, final Track track, final IPVersions requestVersion) {
+		return selectCachesByCZ(ds, cacheLocation, track, ResultType.CZ, requestVersion); // ResultType.CZ was the original default before DDC
 	}
 
 	/**
@@ -980,7 +983,7 @@ public class TrafficRouter {
 	 * Obviously, at this point, the location from which to select caches must already be known.
 	 * So it's totally possible that that decision wasn't made based on Coverage Zones at all,
 	 * that's just the default routing result chosen by a common caller of this method
-	 * ({@link #selectCachesByCZ(DeliveryService, CacheLocation, Track)}).
+	 * ({@link #selectCachesByCZ(DeliveryService, CacheLocation, Track, IPVersions)}).
 	 * </p>
 	 * @param ds The Delivery Service being served.
 	 * @param cacheLocation The location from which caches will be selected.
@@ -988,12 +991,12 @@ public class TrafficRouter {
 	 * This is used for tracking routing results.
 	 * @return All of the caches in the given location capable of serving ds.
 	 */
-	private List<Cache> selectCachesByCZ(final DeliveryService ds, final CacheLocation cacheLocation, final Track track, final ResultType result) {
+	private List<Cache> selectCachesByCZ(final DeliveryService ds, final CacheLocation cacheLocation, final Track track, final ResultType result, final IPVersions requestVersion) {
 		if (cacheLocation == null || ds == null || !ds.isLocationAvailable(cacheLocation)) {
 			return null;
 		}
 
-		final List<Cache> caches = selectCaches(cacheLocation, ds);
+		final List<Cache> caches = selectCaches(cacheLocation, ds, requestVersion);
 
 		if (caches != null && track != null) {
 			track.setResult(result);
@@ -1043,9 +1046,6 @@ public class TrafficRouter {
 			final DeliveryService ds = steeringResult.getDeliveryService();
 			List<Cache> caches = selectCaches(request, ds, track);
 
-			if (caches != null) {
-				caches = editCacheListForIpVersion(!request.getClientIP().contains(":"), caches);
-			}
 			// child Delivery Services can use their query parameters
 			final String pathToHash = steeringHash + ds.extractSignificantQueryParams(request);
 
@@ -1193,11 +1193,7 @@ public class TrafficRouter {
 
 		routeResult.setDeliveryService(deliveryService);
 
-		List<Cache> caches = selectCaches(request, deliveryService, track);
-		if (caches != null) {
-			caches = editCacheListForIpVersion(!request.getClientIP().contains(":"), caches);
-		}
-
+		final List<Cache> caches = selectCaches(request, deliveryService, track);
 		if (caches == null || caches.isEmpty()) {
 			if (track.getResult() == ResultType.GEO_REDIRECT) {
 				routeResult.setUrl(new URL(deliveryService.getGeoRedirectUrl()));
@@ -1235,18 +1231,6 @@ public class TrafficRouter {
 		return routeResult;
 	}
 
-	private List<Cache> editCacheListForIpVersion(final boolean requestIsIpv4, final List<Cache> caches) {
-		final List<Cache> removeCaches = new ArrayList<>();
-		for (final Cache cache : caches) {
-			if ((!requestIsIpv4 && cache.getIpAvailableVersions() == Cache.IpVersions.IPV4ONLY) ||
-					requestIsIpv4 && cache.getIpAvailableVersions() == Cache.IpVersions.IPV6ONLY) {
-				removeCaches.add(cache);
-			}
-		}
-		caches.removeAll(removeCaches);
-		return caches;
-	}
-
 	/**
 	 * Gets all the possible steering results for a request to a Delivery Service.
 	 * @param request The client HTTP request.
@@ -1361,12 +1345,12 @@ public class TrafficRouter {
 		return null;
 	}
 
-	public CacheLocation getCoverageZoneCacheLocation(final String ip, final String deliveryServiceId) {
-		return getCoverageZoneCacheLocation(ip, deliveryServiceId, false, null); // default is not deep
+	public CacheLocation getCoverageZoneCacheLocation(final String ip, final String deliveryServiceId, final IPVersions requestVersion) {
+		return getCoverageZoneCacheLocation(ip, deliveryServiceId, false, null, requestVersion); // default is not deep
 	}
 
 	@SuppressWarnings({"PMD.CyclomaticComplexity", "PMD.NPathComplexity"})
-	public CacheLocation getCoverageZoneCacheLocation(final String ip, final String deliveryServiceId, final boolean useDeep, final Track track) {
+	public CacheLocation getCoverageZoneCacheLocation(final String ip, final String deliveryServiceId, final boolean useDeep, final Track track, final IPVersions requestVersion) {
 		final NetworkNode networkNode = useDeep ? getDeepNetworkNode(ip) : getNetworkNode(ip);
 		final LocalizationMethod localizationMethod = useDeep ? LocalizationMethod.DEEP_CZ : LocalizationMethod.CZ;
 
@@ -1386,7 +1370,7 @@ public class TrafficRouter {
 			return null;
 		}
 
-		if (cacheLocation != null && !getSupportingCaches(cacheLocation.getCaches(), deliveryService).isEmpty()) {
+		if (cacheLocation != null && !getSupportingCaches(cacheLocation.getCaches(), deliveryService, requestVersion).isEmpty()) {
 			return cacheLocation;
 		}
 
@@ -1406,7 +1390,7 @@ public class TrafficRouter {
 			return null;
 		}
 
-		if (cacheLocation != null && !getSupportingCaches(cacheLocation.getCaches(), deliveryService).isEmpty()) {
+		if (cacheLocation != null && !getSupportingCaches(cacheLocation.getCaches(), deliveryService, requestVersion).isEmpty()) {
 			// lazy loading in case a CacheLocation has not yet been associated with this NetworkNode
 			networkNode.setLocation(cacheLocation);
 			return cacheLocation;
@@ -1418,7 +1402,7 @@ public class TrafficRouter {
 				if (bkCacheLocation != null && !bkCacheLocation.isEnabledFor(localizationMethod)) {
 					continue;
 				}
-				if (bkCacheLocation != null && !getSupportingCaches(bkCacheLocation.getCaches(), deliveryService).isEmpty()) {
+				if (bkCacheLocation != null && !getSupportingCaches(bkCacheLocation.getCaches(), deliveryService, requestVersion).isEmpty()) {
 					LOGGER.debug("Got backup CZ cache group " + bkCacheLocation.getId() + " for " + ip + ", ds " + deliveryServiceId);
 					if (track != null) {
 						track.setFromBackupCzGroup(true);
@@ -1439,7 +1423,7 @@ public class TrafficRouter {
 		// Check whether the CZF entry has a geolocation and use it if so.
 		List<CacheLocation> availableLocations = cacheRegister.filterAvailableCacheLocations(deliveryServiceId);
 		availableLocations = filterEnabledLocations(availableLocations, localizationMethod);
-		final CacheLocation closestCacheLocation = getClosestCacheLocation(availableLocations, networkNode.getGeolocation(), cacheRegister.getDeliveryService(deliveryServiceId));
+		final CacheLocation closestCacheLocation = getClosestCacheLocation(availableLocations, networkNode.getGeolocation(), cacheRegister.getDeliveryService(deliveryServiceId), requestVersion);
 
 		if (closestCacheLocation != null) {
 			LOGGER.debug("Got closest CZ cache group " + closestCacheLocation.getId() + " for " + ip + ", ds " + deliveryServiceId);
@@ -1456,16 +1440,16 @@ public class TrafficRouter {
 				.collect(Collectors.toList());
 	}
 
-	public CacheLocation getDeepCoverageZoneCacheLocation(final String ip, final DeliveryService deliveryService) {
-		return getCoverageZoneCacheLocation(ip, deliveryService, true, null);
+	public CacheLocation getDeepCoverageZoneCacheLocation(final String ip, final DeliveryService deliveryService, final IPVersions requestVersion) {
+		return getCoverageZoneCacheLocation(ip, deliveryService, true, null, requestVersion);
 	}
 
-	public CacheLocation getCoverageZoneCacheLocation(final String ip, final DeliveryService deliveryService, final boolean useDeep, final Track track) {
-		return getCoverageZoneCacheLocation(ip, deliveryService.getId(), useDeep, track);
+	public CacheLocation getCoverageZoneCacheLocation(final String ip, final DeliveryService deliveryService, final boolean useDeep, final Track track, final IPVersions requestVersion) {
+		return getCoverageZoneCacheLocation(ip, deliveryService.getId(), useDeep, track, requestVersion);
 	}
 
-	public CacheLocation getCoverageZoneCacheLocation(final String ip, final DeliveryService deliveryService) {
-		return getCoverageZoneCacheLocation(ip, deliveryService.getId());
+	public CacheLocation getCoverageZoneCacheLocation(final String ip, final DeliveryService deliveryService, final IPVersions requestVersion) {
+		return getCoverageZoneCacheLocation(ip, deliveryService.getId(), requestVersion);
 	}
 
 	/**
@@ -1516,8 +1500,9 @@ public class TrafficRouter {
 			return null;
 		}
 
-		final CacheLocation coverageZoneCacheLocation = getCoverageZoneCacheLocation(ip, deliveryService, useDeep, null);
-		final List<Cache> caches = selectCachesByCZ(deliveryService, coverageZoneCacheLocation);
+		final IPVersions requestVersion = ip.contains(":") ? IPVersions.IPV6ONLY : IPVersions.IPV4ONLY;
+		final CacheLocation coverageZoneCacheLocation = getCoverageZoneCacheLocation(ip, deliveryService, useDeep, null, requestVersion);
+		final List<Cache> caches = selectCachesByCZ(deliveryService, coverageZoneCacheLocation, requestVersion);
 
 		if (caches == null || caches.isEmpty()) {
 			return null;
@@ -1558,15 +1543,16 @@ public class TrafficRouter {
 			return null;
 		}
 
+		final IPVersions requestVersion = ip.contains(":") ? IPVersions.IPV6ONLY : IPVersions.IPV4ONLY;
 		List<Cache> caches = null;
 		if (deliveryService.isCoverageZoneOnly() && deliveryService.getGeoRedirectUrl() != null) {
 			//use the NGB redirect
-			caches = enforceGeoRedirect(StatTracker.getTrack(), deliveryService, ip, null);
+			caches = enforceGeoRedirect(StatTracker.getTrack(), deliveryService, ip, null, requestVersion);
 		} else {
-			final CacheLocation cacheLocation = getCoverageZoneCacheLocation(ip, deliveryServiceId);
+			final CacheLocation cacheLocation = getCoverageZoneCacheLocation(ip, deliveryServiceId, requestVersion);
 
 			try {
-				caches = selectCachesByGeo(ip, deliveryService, cacheLocation, StatTracker.getTrack());
+				caches = selectCachesByGeo(ip, deliveryService, cacheLocation, StatTracker.getTrack(), requestVersion);
 			} catch (GeolocationException e) {
 				LOGGER.warn("Failed gettting list of caches by geolocation for ip " + ip + " delivery service id '" + deliveryServiceId + "'");
 			}
@@ -1712,7 +1698,7 @@ public class TrafficRouter {
 	 * File given a clients IP and request.
 	 * @param ip The client's IP address
 	 * @param deliveryServiceId The "xml_id" of a Delivery Service being routed
-	 * @param requestPath The client's HTTP request
+	 * @param request The client's HTTP request
 	 * @return A cache object chosen to serve the client's request
 	 */
 	public Cache consistentHashSteeringForCoverageZone(final String ip, final String deliveryServiceId, final HTTPRequest request) {
@@ -1722,8 +1708,9 @@ public class TrafficRouter {
 			return null;
 		}
 
-		final CacheLocation coverageZoneCacheLocation = getCoverageZoneCacheLocation(ip, deliveryService, false, null);
-		final List<Cache> caches = selectCachesByCZ(deliveryService, coverageZoneCacheLocation);
+		final IPVersions requestVersion = ip.contains(":") ? IPVersions.IPV6ONLY : IPVersions.IPV4ONLY;
+		final CacheLocation coverageZoneCacheLocation = getCoverageZoneCacheLocation(ip, deliveryService, false, null, requestVersion);
+		final List<Cache> caches = selectCachesByCZ(deliveryService, coverageZoneCacheLocation, requestVersion);
 
 		if (caches == null || caches.isEmpty()) {
 			return null;
@@ -1763,7 +1750,7 @@ public class TrafficRouter {
 	 * Chooses a target Delivery Service of a given Delivery Service to service a given request and
 	 * {@link #XTC_STEERING_OPTION} value.
 	 *
-	 * @param deliveryServiceId The "xml_id" of the Delivery Service being requested
+	 * @param deliveryService The DeliveryService being requested
 	 * @param request The client's HTTP request
 	 * @param xtcSteeringOption The value of the client's {@link #XTC_STEERING_OPTION} HTTP Header.
 	 * @return The chosen target Delivery Service, or null if one could not be determined.
@@ -1822,7 +1809,7 @@ public class TrafficRouter {
 		return locations;
 	}
 
-	private CacheLocation getClosestCacheLocation(final List<CacheLocation> cacheLocations, final Geolocation clientLocation, final DeliveryService deliveryService) {
+	private CacheLocation getClosestCacheLocation(final List<CacheLocation> cacheLocations, final Geolocation clientLocation, final DeliveryService deliveryService, final IPVersions requestVersion) {
 		if (clientLocation == null) {
 			return null;
 		}
@@ -1830,7 +1817,7 @@ public class TrafficRouter {
 	    final List<CacheLocation> orderedLocations = (List<CacheLocation>) orderLocations(cacheLocations, clientLocation);
 
 		for (final CacheLocation cacheLocation : orderedLocations) {
-			if (!getSupportingCaches(cacheLocation.getCaches(), deliveryService).isEmpty()) {
+			if (!getSupportingCaches(cacheLocation.getCaches(), deliveryService, requestVersion).isEmpty()) {
 				return cacheLocation;
 			}
 		}
@@ -1847,12 +1834,12 @@ public class TrafficRouter {
 	 *            the delivery service for the request
 	 * @return the selected cache or null if none can be found
 	 */
-	private List<Cache> selectCaches(final CacheLocation location, final DeliveryService ds) {
+	private List<Cache> selectCaches(final CacheLocation location, final DeliveryService ds, final IPVersions requestVersion) {
 		if (LOGGER.isDebugEnabled()) {
 			LOGGER.debug("Trying location: " + location.getId());
 		}
 
-		final List<Cache> caches = getSupportingCaches(location.getCaches(), ds);
+		final List<Cache> caches = getSupportingCaches(location.getCaches(), ds, requestVersion);
 		if (caches.isEmpty()) {
 			if (LOGGER.isDebugEnabled()) {
 				LOGGER.debug("No online, supporting caches were found at location: "
@@ -1902,7 +1889,7 @@ public class TrafficRouter {
 		return dnssecZoneDiffingEnabled;
 	}
 
-	private List<Cache> enforceGeoRedirect(final Track track, final DeliveryService ds, final String clientIp, final Geolocation queriedClientLocation) {
+	private List<Cache> enforceGeoRedirect(final Track track, final DeliveryService ds, final String clientIp, final Geolocation queriedClientLocation, final IPVersions requestVersion) {
 		final String urlType = ds.getGeoRedirectUrlType();
 		track.setResult(ResultType.GEO_REDIRECT);
 
@@ -1944,7 +1931,7 @@ public class TrafficRouter {
 		List<Cache> caches = null;
 
 		try {
-			caches = getCachesByGeo(ds, clientLocation, track);
+			caches = getCachesByGeo(ds, clientLocation, track, requestVersion);
 		} catch (GeolocationException e) {
 			LOGGER.error("Failed getting caches by geolocation " + e.getMessage());
 		}
diff --git a/traffic_router/core/src/test/java/com/comcast/cdn/traffic_control/traffic_router/core/dns/ZoneManagerTest.java b/traffic_router/core/src/test/java/com/comcast/cdn/traffic_control/traffic_router/core/dns/ZoneManagerTest.java
index fbaa618..a82025b 100644
--- a/traffic_router/core/src/test/java/com/comcast/cdn/traffic_control/traffic_router/core/dns/ZoneManagerTest.java
+++ b/traffic_router/core/src/test/java/com/comcast/cdn/traffic_control/traffic_router/core/dns/ZoneManagerTest.java
@@ -51,6 +51,7 @@ import com.comcast.cdn.traffic_control.traffic_router.core.TestBase;
 import com.comcast.cdn.traffic_control.traffic_router.core.edge.Cache;
 import com.comcast.cdn.traffic_control.traffic_router.core.edge.CacheLocation;
 import com.comcast.cdn.traffic_control.traffic_router.core.edge.CacheRegister;
+import com.comcast.cdn.traffic_control.traffic_router.core.edge.Node.IPVersions;
 import com.comcast.cdn.traffic_control.traffic_router.core.ds.DeliveryService;
 import com.comcast.cdn.traffic_control.traffic_router.core.router.TrafficRouter;
 import com.comcast.cdn.traffic_control.traffic_router.core.router.TrafficRouterManager;
@@ -108,8 +109,8 @@ public class ZoneManagerTest {
 			final Name edgeName = new Name(ds.getRoutingName() + "." + domain + ".");
 
 			for (InetAddress source : netMap.values()) {
-				final CacheLocation location = trafficRouter.getCoverageZoneCacheLocation(source.getHostAddress(), ds);
-				final List<Cache> caches = trafficRouter.selectCachesByCZ(ds, location);
+				final CacheLocation location = trafficRouter.getCoverageZoneCacheLocation(source.getHostAddress(), ds, IPVersions.IPV4ONLY);
+				final List<Cache> caches = trafficRouter.selectCachesByCZ(ds, location, IPVersions.IPV4ONLY);
 
 				if (caches == null) {
 					continue;
diff --git a/traffic_router/core/src/test/java/com/comcast/cdn/traffic_control/traffic_router/core/external/SteeringTest.java b/traffic_router/core/src/test/java/com/comcast/cdn/traffic_control/traffic_router/core/external/SteeringTest.java
index a72f18d..e365ef8 100644
--- a/traffic_router/core/src/test/java/com/comcast/cdn/traffic_control/traffic_router/core/external/SteeringTest.java
+++ b/traffic_router/core/src/test/java/com/comcast/cdn/traffic_control/traffic_router/core/external/SteeringTest.java
@@ -326,8 +326,8 @@ public class SteeringTest {
 		HttpPost httpPost = new HttpPost("http://localhost:" + testHttpPort + "/steering");
 		httpClient.execute(httpPost).close();
 
-		// steering is checked every 15 seconds by default.
-		Thread.sleep(30 * 1000);
+		// a polling interval of 60 seconds is common
+		Thread.sleep(90 * 1000);
 
 		Map<String, List<String>> rehashedPaths = new HashMap<>();
 		rehashedPaths.put(smallerTarget, new ArrayList<String>());
diff --git a/traffic_router/core/src/test/java/com/comcast/cdn/traffic_control/traffic_router/core/loc/CoverageZoneTest.java b/traffic_router/core/src/test/java/com/comcast/cdn/traffic_control/traffic_router/core/loc/CoverageZoneTest.java
index 9be34b9..4d90f67 100644
--- a/traffic_router/core/src/test/java/com/comcast/cdn/traffic_control/traffic_router/core/loc/CoverageZoneTest.java
+++ b/traffic_router/core/src/test/java/com/comcast/cdn/traffic_control/traffic_router/core/loc/CoverageZoneTest.java
@@ -19,6 +19,7 @@ import com.comcast.cdn.traffic_control.traffic_router.core.edge.Cache;
 import com.comcast.cdn.traffic_control.traffic_router.core.edge.CacheLocation;
 import com.comcast.cdn.traffic_control.traffic_router.core.edge.CacheLocation.LocalizationMethod;
 import com.comcast.cdn.traffic_control.traffic_router.core.edge.CacheRegister;
+import com.comcast.cdn.traffic_control.traffic_router.core.edge.Node.IPVersions;
 import com.comcast.cdn.traffic_control.traffic_router.core.ds.DeliveryService;
 import com.comcast.cdn.traffic_control.traffic_router.core.router.TrafficRouter;
 import com.comcast.cdn.traffic_control.traffic_router.geolocation.Geolocation;
@@ -100,19 +101,19 @@ public class CoverageZoneTest {
 
 		trafficRouter = PowerMockito.mock(TrafficRouter.class);
 		Whitebox.setInternalState(trafficRouter, "cacheRegister", cacheRegister);
-		when(trafficRouter.getCoverageZoneCacheLocation("12.23.34.45", "delivery-service-1")).thenCallRealMethod();
-		when(trafficRouter.getCoverageZoneCacheLocation("12.23.34.45", "delivery-service-1", false, null)).thenCallRealMethod();
+		when(trafficRouter.getCoverageZoneCacheLocation("12.23.34.45", "delivery-service-1", IPVersions.IPV4ONLY)).thenCallRealMethod();
+		when(trafficRouter.getCoverageZoneCacheLocation("12.23.34.45", "delivery-service-1", false, null, IPVersions.IPV4ONLY)).thenCallRealMethod();
 		when(trafficRouter.getCacheRegister()).thenReturn(cacheRegister);
 		when(trafficRouter.orderLocations(anyListOf(CacheLocation.class),any(Geolocation.class))).thenCallRealMethod();
-		when(trafficRouter.getSupportingCaches(anyListOf(Cache.class), eq(deliveryService))).thenCallRealMethod();
+		when(trafficRouter.getSupportingCaches(anyListOf(Cache.class), eq(deliveryService), any(IPVersions.class))).thenCallRealMethod();
 		when(trafficRouter.filterEnabledLocations(anyListOf(CacheLocation.class), any(CacheLocation.LocalizationMethod.class))).thenCallRealMethod();
 		PowerMockito.when(trafficRouter, "getNetworkNode", "12.23.34.45").thenReturn(eastNetworkNode);
-		PowerMockito.when(trafficRouter, "getClosestCacheLocation", anyListOf(CacheLocation.class), any(CacheLocation.class), any(DeliveryService.class)).thenCallRealMethod();
+		PowerMockito.when(trafficRouter, "getClosestCacheLocation", anyListOf(CacheLocation.class), any(CacheLocation.class), any(DeliveryService.class), any(IPVersions.class)).thenCallRealMethod();
 	}
 
 	@Test
 	public void trafficRouterReturnsNearestCacheGroupForDeliveryService() throws Exception {
-		CacheLocation cacheLocation = trafficRouter.getCoverageZoneCacheLocation("12.23.34.45", "delivery-service-1");
+		CacheLocation cacheLocation = trafficRouter.getCoverageZoneCacheLocation("12.23.34.45", "delivery-service-1", IPVersions.IPV4ONLY);
 		assertThat(cacheLocation.getId(), equalTo("west-cache-group"));
 		// NOTE: far-east-cache-group is actually closer to the client but isn't enabled for CZ-localization and must be filtered out
 	}
diff --git a/traffic_router/core/src/test/java/com/comcast/cdn/traffic_control/traffic_router/core/router/DNSRoutingMissesTest.java b/traffic_router/core/src/test/java/com/comcast/cdn/traffic_control/traffic_router/core/router/DNSRoutingMissesTest.java
index 34ba482..9361630 100644
--- a/traffic_router/core/src/test/java/com/comcast/cdn/traffic_control/traffic_router/core/router/DNSRoutingMissesTest.java
+++ b/traffic_router/core/src/test/java/com/comcast/cdn/traffic_control/traffic_router/core/router/DNSRoutingMissesTest.java
@@ -17,6 +17,7 @@ package com.comcast.cdn.traffic_control.traffic_router.core.router;
 
 import com.comcast.cdn.traffic_control.traffic_router.core.edge.CacheLocation;
 import com.comcast.cdn.traffic_control.traffic_router.core.edge.CacheRegister;
+import com.comcast.cdn.traffic_control.traffic_router.core.edge.Node.IPVersions;
 import com.comcast.cdn.traffic_control.traffic_router.core.ds.DeliveryService;
 import com.comcast.cdn.traffic_control.traffic_router.core.loc.FederationRegistry;
 import com.comcast.cdn.traffic_control.traffic_router.core.request.DNSRequest;
@@ -63,7 +64,7 @@ public class DNSRoutingMissesTest {
         trafficRouter = mock(TrafficRouter.class);
         when(trafficRouter.getCacheRegister()).thenReturn(mock(CacheRegister.class));
         Whitebox.setInternalState(trafficRouter, "federationRegistry", federationRegistry);
-        when(trafficRouter.selectCachesByGeo(anyString(), any(DeliveryService.class), any(CacheLocation.class), any(Track.class))).thenCallRealMethod();
+        when(trafficRouter.selectCachesByGeo(anyString(), any(DeliveryService.class), any(CacheLocation.class), any(Track.class), any(IPVersions.class))).thenCallRealMethod();
 
         track = spy(StatTracker.getTrack());
         doCallRealMethod().when(trafficRouter).route(request, track);
@@ -156,7 +157,7 @@ public class DNSRoutingMissesTest {
 
     @Test
     public void itSetsDetailsWhenCacheNotFoundByGeolocation() throws Exception {
-        doCallRealMethod().when(trafficRouter).selectCachesByGeo(anyString(), any(DeliveryService.class), any(CacheLocation.class), any(Track.class));
+        doCallRealMethod().when(trafficRouter).selectCachesByGeo(anyString(), any(DeliveryService.class), any(CacheLocation.class), any(Track.class), any(IPVersions.class));
         CacheLocation cacheLocation = mock(CacheLocation.class);
         CacheRegister cacheRegister = mock(CacheRegister.class);
 
@@ -168,7 +169,7 @@ public class DNSRoutingMissesTest {
         when(deliveryService.isDns()).thenReturn(true);
 
         doReturn(deliveryService).when(trafficRouter).selectDeliveryService(request);
-        doReturn(cacheLocation).when(trafficRouter).getCoverageZoneCacheLocation("192.168.34.56", deliveryService);
+        doReturn(cacheLocation).when(trafficRouter).getCoverageZoneCacheLocation("192.168.34.56", deliveryService, IPVersions.IPV4ONLY);
         doReturn(cacheRegister).when(trafficRouter).getCacheRegister();
 
         trafficRouter.route(request, track);
diff --git a/traffic_router/core/src/test/java/com/comcast/cdn/traffic_control/traffic_router/core/router/TrafficRouterTest.java b/traffic_router/core/src/test/java/com/comcast/cdn/traffic_control/traffic_router/core/router/TrafficRouterTest.java
index 52286d8..77815c6 100644
--- a/traffic_router/core/src/test/java/com/comcast/cdn/traffic_control/traffic_router/core/router/TrafficRouterTest.java
+++ b/traffic_router/core/src/test/java/com/comcast/cdn/traffic_control/traffic_router/core/router/TrafficRouterTest.java
@@ -20,6 +20,7 @@ import com.comcast.cdn.traffic_control.traffic_router.core.edge.CacheLocation;
 import com.comcast.cdn.traffic_control.traffic_router.core.edge.CacheLocation.LocalizationMethod;
 import com.comcast.cdn.traffic_control.traffic_router.core.edge.CacheRegister;
 import com.comcast.cdn.traffic_control.traffic_router.core.edge.InetRecord;
+import com.comcast.cdn.traffic_control.traffic_router.core.edge.Node.IPVersions;
 import com.comcast.cdn.traffic_control.traffic_router.core.ds.DeliveryService;
 import com.comcast.cdn.traffic_control.traffic_router.core.ds.Dispersion;
 import com.comcast.cdn.traffic_control.traffic_router.core.ds.SteeringRegistry;
@@ -50,7 +51,7 @@ import static org.hamcrest.Matchers.equalTo;
 import static org.mockito.Matchers.any;
 import static org.mockito.Matchers.anyBoolean;
 import static org.mockito.Matchers.anyString;
-import static org.mockito.Matchers.eq;
+import static org.mockito.Mockito.doCallRealMethod;
 import static org.mockito.Mockito.mock;
 import static org.mockito.Mockito.spy;
 import static org.mockito.Mockito.verify;
@@ -144,9 +145,9 @@ public class TrafficRouterTest {
         List<Cache> caches = new ArrayList<Cache>();
         caches.add(cache);
         when(trafficRouter.selectCaches(any(HTTPRequest.class), any(DeliveryService.class), any(Track.class))).thenReturn(caches);
-        when(trafficRouter.selectCachesByGeo(anyString(), any(DeliveryService.class), any(CacheLocation.class), any(Track.class))).thenCallRealMethod();
+        when(trafficRouter.selectCachesByGeo(anyString(), any(DeliveryService.class), any(CacheLocation.class), any(Track.class), any(IPVersions.class))).thenCallRealMethod();
         when(trafficRouter.getClientLocation(anyString(), any(DeliveryService.class), any(CacheLocation.class), any(Track.class))).thenReturn(new Geolocation(40, -100));
-        when(trafficRouter.getCachesByGeo(any(DeliveryService.class), any(Geolocation.class), any(Track.class))).thenCallRealMethod();
+        when(trafficRouter.getCachesByGeo(any(DeliveryService.class), any(Geolocation.class), any(Track.class), any(IPVersions.class))).thenCallRealMethod();
         when(trafficRouter.getCacheRegister()).thenReturn(cacheRegister);
         when(trafficRouter.orderLocations(any(List.class), any(Geolocation.class))).thenCallRealMethod();
 
@@ -156,6 +157,54 @@ public class TrafficRouterTest {
     }
 
     @Test
+    public void itFiltersByIPAvailability() throws Exception {
+
+        DeliveryService ds = mock(DeliveryService.class);
+
+        Cache cacheIPv4 = mock(Cache.class);
+        when(cacheIPv4.hasDeliveryService(anyString())).thenReturn(true);
+        when(cacheIPv4.hasAuthority()).thenReturn(true);
+        when(cacheIPv4.isAvailable(any(IPVersions.class))).thenCallRealMethod();
+        doCallRealMethod().when(cacheIPv4).setIsAvailable(anyBoolean());
+        setInternalState(cacheIPv4, "ipv4Available", true);
+        setInternalState(cacheIPv4, "ipv6Available", false);
+        cacheIPv4.setIsAvailable(true);
+        when(cacheIPv4.getId()).thenReturn("cache IPv4");
+
+        Cache cacheIPv6 = mock(Cache.class);
+        when(cacheIPv6.hasDeliveryService(anyString())).thenReturn(true);
+        when(cacheIPv6.hasAuthority()).thenReturn(true);
+        when(cacheIPv6.isAvailable(any(IPVersions.class))).thenCallRealMethod();
+        doCallRealMethod().when(cacheIPv6).setIsAvailable(anyBoolean());
+        setInternalState(cacheIPv6, "ipv4Available", false);
+        setInternalState(cacheIPv6, "ipv6Available", true);
+        cacheIPv6.setIsAvailable(true);
+        when(cacheIPv6.getId()).thenReturn("cache IPv6");
+
+        List<Cache> caches = new ArrayList<Cache>();
+        caches.add(cacheIPv4);
+        caches.add(cacheIPv6);
+
+        when(trafficRouter.getSupportingCaches(any(List.class), any(DeliveryService.class), any(IPVersions.class))).thenCallRealMethod();
+
+        List<Cache> supportingIPv4Caches = trafficRouter.getSupportingCaches(caches, ds, IPVersions.IPV4ONLY);
+        assertThat(supportingIPv4Caches.size(), equalTo(1));
+        assertThat(supportingIPv4Caches.get(0).getId(), equalTo("cache IPv4"));
+
+        List<Cache> supportingIPv6Caches = trafficRouter.getSupportingCaches(caches, ds, IPVersions.IPV6ONLY);
+        assertThat(supportingIPv6Caches.size(), equalTo(1));
+        assertThat(supportingIPv6Caches.get(0).getId(), equalTo("cache IPv6"));
+
+        List<Cache> supportingEitherCaches = trafficRouter.getSupportingCaches(caches, ds, IPVersions.ANY);
+        assertThat(supportingEitherCaches.size(), equalTo(2));
+
+        cacheIPv6.setIsAvailable(false);
+        List<Cache> supportingAvailableCaches = trafficRouter.getSupportingCaches(caches, ds, IPVersions.ANY);
+        assertThat(supportingAvailableCaches.size(), equalTo(1));
+        assertThat(supportingAvailableCaches.get(0).getId(), equalTo("cache IPv4"));
+    }
+
+    @Test
     public void itChecksDefaultLocation() throws Exception {
         String ip = "1.2.3.4";
         Track track = new Track();
@@ -172,11 +221,11 @@ public class TrafficRouterTest {
         List<Cache> list = new ArrayList<>();
         list.add(cache);
         when(deliveryService.getMissLocation()).thenReturn(defaultUSLocation);
-        when(trafficRouter.getCachesByGeo(deliveryService, deliveryService.getMissLocation(), track)).thenReturn(list);
-        when(trafficRouter.selectCachesByGeo(ip, deliveryService, null, track)).thenCallRealMethod();
+        when(trafficRouter.getCachesByGeo(deliveryService, deliveryService.getMissLocation(), track, IPVersions.IPV4ONLY)).thenReturn(list);
+        when(trafficRouter.selectCachesByGeo(ip, deliveryService, null, track, IPVersions.IPV4ONLY)).thenCallRealMethod();
         when(trafficRouter.isValidMissLocation(deliveryService)).thenCallRealMethod();
-        List<Cache> result = trafficRouter.selectCachesByGeo(ip, deliveryService, null, track);
-        verify(trafficRouter).getCachesByGeo(deliveryService, deliveryService.getMissLocation(), track);
+        List<Cache> result = trafficRouter.selectCachesByGeo(ip, deliveryService, null, track, IPVersions.IPV4ONLY);
+        verify(trafficRouter).getCachesByGeo(deliveryService, deliveryService.getMissLocation(), track, IPVersions.IPV4ONLY);
         assertThat(result.size(), equalTo(1));
         assertThat(result.get(0), equalTo(cache));
         assertThat(track.getResult(), equalTo(Track.ResultType.GEO_DS));
@@ -215,15 +264,15 @@ public class TrafficRouterTest {
 
         when(trafficRouter.selectCaches(any(HTTPRequest.class), any(DeliveryService.class), any(Track.class))).thenCallRealMethod();
         when(trafficRouter.selectCaches(any(HTTPRequest.class), any(DeliveryService.class), any(Track.class), anyBoolean())).thenCallRealMethod();
-        when(trafficRouter.selectCachesByGeo(anyString(), any(DeliveryService.class), any(CacheLocation.class), any(Track.class))).thenCallRealMethod();
+        when(trafficRouter.selectCachesByGeo(anyString(), any(DeliveryService.class), any(CacheLocation.class), any(Track.class), any(IPVersions.class))).thenCallRealMethod();
 
         Geolocation clientLocation = new Geolocation(40, -100);
         when(trafficRouter.getClientLocation(anyString(), any(DeliveryService.class), any(CacheLocation.class), any(Track.class))).thenReturn(clientLocation);
 
-        when(trafficRouter.getCachesByGeo(any(DeliveryService.class), any(Geolocation.class), any(Track.class))).thenCallRealMethod();
+        when(trafficRouter.getCachesByGeo(any(DeliveryService.class), any(Geolocation.class), any(Track.class), any(IPVersions.class))).thenCallRealMethod();
         when(trafficRouter.filterEnabledLocations(any(List.class), any(LocalizationMethod.class))).thenCallRealMethod();
         when(trafficRouter.orderLocations(any(List.class), any(Geolocation.class))).thenCallRealMethod();
-        when(trafficRouter.getSupportingCaches(any(List.class), any(DeliveryService.class))).thenCallRealMethod();
+        when(trafficRouter.getSupportingCaches(any(List.class), any(DeliveryService.class), any(IPVersions.class))).thenCallRealMethod();
 
         HTTPRequest httpRequest = new HTTPRequest();
         httpRequest.setClientIP("192.168.10.11");