You are viewing a plain text version of this content. The canonical link for it is here.
Posted to issues@trafficcontrol.apache.org by GitBox <gi...@apache.org> on 2018/05/14 17:22:30 UTC

[GitHub] elsloo closed pull request #2184: Anonymous IP TR Implementation

elsloo closed pull request #2184: Anonymous IP TR Implementation
URL: https://github.com/apache/incubator-trafficcontrol/pull/2184
 
 
   

This is a PR merged from a forked repository.
As GitHub hides the original diff on merge, it is displayed below for
the sake of provenance:

As this is a foreign pull request (from a fork), the diff is supplied
below (as it won't show otherwise due to GitHub magic):

diff --git a/traffic_router/core/src/main/java/com/comcast/cdn/traffic_control/traffic_router/core/config/ConfigHandler.java b/traffic_router/core/src/main/java/com/comcast/cdn/traffic_control/traffic_router/core/config/ConfigHandler.java
index ea17db058..2c33409e1 100644
--- a/traffic_router/core/src/main/java/com/comcast/cdn/traffic_control/traffic_router/core/config/ConfigHandler.java
+++ b/traffic_router/core/src/main/java/com/comcast/cdn/traffic_control/traffic_router/core/config/ConfigHandler.java
@@ -60,6 +60,9 @@
 import com.comcast.cdn.traffic_control.traffic_router.core.router.StatTracker;
 import com.comcast.cdn.traffic_control.traffic_router.geolocation.Geolocation;
 import com.comcast.cdn.traffic_control.traffic_router.core.request.HTTPRequest;
+import com.comcast.cdn.traffic_control.traffic_router.core.loc.AnonymousIp;
+import com.comcast.cdn.traffic_control.traffic_router.core.loc.AnonymousIpConfigUpdater;
+import com.comcast.cdn.traffic_control.traffic_router.core.loc.AnonymousIpDatabaseUpdater;
 
 @SuppressWarnings("PMD.TooManyFields")
 public class ConfigHandler {
@@ -80,6 +83,8 @@
 	private DeepNetworkUpdater deepNetworkUpdater;
 	private FederationsWatcher federationsWatcher;
 	private RegionalGeoUpdater regionalGeoUpdater;
+	private AnonymousIpConfigUpdater anonymousIpConfigUpdater;
+	private AnonymousIpDatabaseUpdater anonymousIpDatabaseUpdater;
 	private SteeringWatcher steeringWatcher;
 	private CertificatesPoller certificatesPoller;
 	private CertificatesPublisher certificatesPublisher;
@@ -112,6 +117,14 @@ public RegionalGeoUpdater getRegionalGeoUpdater() {
 		return regionalGeoUpdater;
 	}
 
+	public AnonymousIpConfigUpdater getAnonymousIpConfigUpdater() {
+		return anonymousIpConfigUpdater;
+	}
+
+	public AnonymousIpDatabaseUpdater getAnonymousIpDatabaseUpdater() {
+		return anonymousIpDatabaseUpdater;
+	}
+
 	@SuppressWarnings({"PMD.CyclomaticComplexity", "PMD.NPathComplexity", "PMD.AvoidCatchingThrowable"})
 	public boolean processConfig(final String jsonStr) throws JsonUtilsException, IOException  {
 		isProcessing.set(true);
@@ -148,6 +161,7 @@ public boolean processConfig(final String jsonStr) throws JsonUtilsException, IO
 				parseCoverageZoneNetworkConfig(config);
 				parseDeepCoverageZoneNetworkConfig(config);
 				parseRegionalGeoConfig(jo);
+				parseAnonymousIpConfig(jo);
 
 				final CacheRegister cacheRegister = new CacheRegister();
 				final JsonNode deliveryServicesJson = JsonUtils.getJsonNode(jo, "deliveryServices");
@@ -270,6 +284,14 @@ public void setRegionalGeoUpdater(final RegionalGeoUpdater regionalGeoUpdater) {
 		this.regionalGeoUpdater = regionalGeoUpdater;
 	}
 
+	public void setAnonymousIpConfigUpdater(final AnonymousIpConfigUpdater anonymousIpConfigUpdater) {
+		this.anonymousIpConfigUpdater = anonymousIpConfigUpdater;
+	}
+	
+	public void setAnonymousIpDatabaseUpdater(final AnonymousIpDatabaseUpdater anonymousIpDatabaseUpdater) {
+		this.anonymousIpDatabaseUpdater = anonymousIpDatabaseUpdater;
+	}
+
 	/**
 	 * Parses the Traffic Ops config
 	 * @param config
@@ -540,6 +562,52 @@ private void parseCertificatesConfig(final JsonNode config) {
 		}
 	}
 
+	private void parseAnonymousIpConfig(final JsonNode jo) throws JsonUtilsException {
+		final String anonymousPollingUrl = "anonymousip.polling.url";
+		final String anonymousPollingInterval = "anonymousip.polling.interval";
+		final String anonymousPolicyConfiguration = "anonymousip.policy.configuration";
+
+		final JsonNode config = JsonUtils.getJsonNode(jo,"config");
+		final String configUrl = JsonUtils.optString(config, anonymousPolicyConfiguration, null);
+		final String databaseUrl = JsonUtils.optString(config, anonymousPollingUrl, null);
+
+		if (configUrl == null) {
+			LOGGER.info(anonymousPolicyConfiguration + " not configured; stopping service updater and disabling feature");
+			getAnonymousIpConfigUpdater().stopServiceUpdater();
+			AnonymousIp.getCurrentConfig().enabled = false;
+			return;
+		}
+		
+		if (databaseUrl == null) {
+			LOGGER.info(anonymousPollingUrl + " not configured; stopping service updater and disabling feature");
+			getAnonymousIpDatabaseUpdater().stopServiceUpdater();
+			AnonymousIp.getCurrentConfig().enabled = false;
+			return;
+		}
+
+		if (jo.has(deliveryServicesKey)) {
+			final JsonNode dss = JsonUtils.getJsonNode(jo, deliveryServicesKey);
+			final Iterator<String> dsNames = dss.fieldNames();
+			while (dsNames.hasNext()) {
+				final String ds = dsNames.next();
+				final JsonNode dsNode = JsonUtils.getJsonNode(dss, ds);
+				if (JsonUtils.optString(dsNode, "anonymousBlockingEnabled").equals("true")) {
+					final long interval = JsonUtils.optLong(config, anonymousPollingInterval);
+					getAnonymousIpConfigUpdater().setDataBaseURL(configUrl, interval);
+					getAnonymousIpDatabaseUpdater().setDataBaseURL(databaseUrl, interval);
+					AnonymousIp.getCurrentConfig().enabled = true;
+					LOGGER.debug("Anonymous Blocking in use, scheduling service updaters and enabling feature");
+					return;
+				}
+			}
+		}
+
+		LOGGER.debug("No DS using anonymous ip blocking - disabling feature");
+		getAnonymousIpConfigUpdater().cancelServiceUpdater();
+		getAnonymousIpDatabaseUpdater().cancelServiceUpdater();
+		AnonymousIp.getCurrentConfig().enabled = false;
+	}
+
 	/**
 	 * Parses the ConverageZoneNetwork database configuration and updates the database if the URL has
 	 * changed.
diff --git a/traffic_router/core/src/main/java/com/comcast/cdn/traffic_control/traffic_router/core/ds/DeliveryService.java b/traffic_router/core/src/main/java/com/comcast/cdn/traffic_control/traffic_router/core/ds/DeliveryService.java
index 4aadd70d2..441378c16 100644
--- a/traffic_router/core/src/main/java/com/comcast/cdn/traffic_control/traffic_router/core/ds/DeliveryService.java
+++ b/traffic_router/core/src/main/java/com/comcast/cdn/traffic_control/traffic_router/core/ds/DeliveryService.java
@@ -33,8 +33,6 @@
 import java.util.Iterator;
 import java.util.concurrent.atomic.AtomicInteger;
 
-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.annotation.JsonIgnore;
 import com.fasterxml.jackson.databind.JsonNode;
 import org.apache.log4j.Logger;
@@ -49,9 +47,11 @@
 import com.comcast.cdn.traffic_control.traffic_router.core.router.StatTracker.Track;
 import com.comcast.cdn.traffic_control.traffic_router.core.router.StatTracker.Track.ResultType;
 import com.comcast.cdn.traffic_control.traffic_router.core.router.StatTracker.Track.ResultDetails;
+import com.comcast.cdn.traffic_control.traffic_router.core.util.JsonUtils;
+import com.comcast.cdn.traffic_control.traffic_router.core.util.JsonUtilsException;
 import com.comcast.cdn.traffic_control.traffic_router.core.util.StringProtector;
 
-@SuppressWarnings({"PMD.TooManyFields","PMD.CyclomaticComplexity"})
+@SuppressWarnings({"PMD.TooManyFields","PMD.CyclomaticComplexity", "PMD.AvoidDuplicateLiterals"})
 public class DeliveryService {
 	protected static final Logger LOGGER = Logger.getLogger(DeliveryService.class);
 	private final String id;
@@ -86,6 +86,7 @@
 	private final Set<String> requestHeaders = new HashSet<String>();
 	private final boolean regionalGeoEnabled;
 	private final String geolocationProvider;
+	private final boolean anonymousIpEnabled;
 	private final boolean sslEnabled;
 	private static final int STANDARD_HTTP_PORT = 80;
 	private static final int STANDARD_HTTPS_PORT = 443;
@@ -145,6 +146,7 @@ public DeliveryService(final String id, final JsonNode dsJo) throws JsonUtilsExc
 			LOGGER.info("DeliveryService '" + id + "' will use default geolocation provider Maxmind");
 		}
 		sslEnabled = JsonUtils.optBoolean(dsJo, "sslEnabled");
+		this.anonymousIpEnabled = JsonUtils.optBoolean(dsJo, "anonymousBlockingEnabled");
 
 		final JsonNode protocol = dsJo.get("protocol");
 		acceptHttp = JsonUtils.optBoolean(protocol, "acceptHttp", true);
@@ -617,6 +619,10 @@ public String getGeolocationProvider() {
 		return geolocationProvider;
 	}
 
+	public boolean isAnonymousIpEnabled() {
+		return anonymousIpEnabled;
+	}
+
 	public List<CacheLocation> filterAvailableLocations(final Collection<CacheLocation> cacheLocations) {
 		final List<CacheLocation> locations = new ArrayList<CacheLocation>();
 
diff --git a/traffic_router/core/src/main/java/com/comcast/cdn/traffic_control/traffic_router/core/loc/AnonymousIp.java b/traffic_router/core/src/main/java/com/comcast/cdn/traffic_control/traffic_router/core/loc/AnonymousIp.java
new file mode 100644
index 000000000..26f8fe9bd
--- /dev/null
+++ b/traffic_router/core/src/main/java/com/comcast/cdn/traffic_control/traffic_router/core/loc/AnonymousIp.java
@@ -0,0 +1,252 @@
+/*
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.comcast.cdn.traffic_control.traffic_router.core.loc;
+
+import java.io.File;
+import java.net.InetAddress;
+import java.net.MalformedURLException;
+import java.net.URL;
+
+import com.fasterxml.jackson.databind.JsonNode;
+import com.fasterxml.jackson.databind.ObjectMapper;
+import com.google.common.net.InetAddresses;
+import com.maxmind.geoip2.model.AnonymousIpResponse;
+import org.apache.log4j.Logger;
+
+import com.comcast.cdn.traffic_control.traffic_router.core.cache.Cache;
+import com.comcast.cdn.traffic_control.traffic_router.core.ds.DeliveryService;
+import com.comcast.cdn.traffic_control.traffic_router.core.request.HTTPRequest;
+import com.comcast.cdn.traffic_control.traffic_router.core.request.Request;
+import com.comcast.cdn.traffic_control.traffic_router.core.router.HTTPRouteResult;
+import com.comcast.cdn.traffic_control.traffic_router.core.router.StatTracker.Track;
+import com.comcast.cdn.traffic_control.traffic_router.core.router.StatTracker.Track.ResultType;
+import com.comcast.cdn.traffic_control.traffic_router.core.router.TrafficRouter;
+import com.comcast.cdn.traffic_control.traffic_router.core.util.JsonUtils;
+import com.comcast.cdn.traffic_control.traffic_router.core.util.JsonUtilsException;
+
+public final class AnonymousIp {
+
+	private static final Logger LOGGER = Logger.getLogger(AnonymousIp.class);
+
+	private static AnonymousIp currentConfig = new AnonymousIp();
+
+	// Feature flipper
+	// This is set to true if the CRConfig parameters containing the MMDB URL
+	// and the config url are present AND any delivery service has the feature
+	// enabled
+	public boolean enabled = false;
+
+	private boolean blockAnonymousIp = true;
+	private boolean blockHostingProvider = true;
+	private boolean blockPublicProxy = true;
+	private boolean blockTorExitNode = true;
+
+	private AnonymousIpWhitelist ipv4Whitelist;
+	private AnonymousIpWhitelist ipv6Whitelist;
+
+	private String redirectUrl;
+
+	public final static int BLOCK_CODE = 403;
+	public final static String WHITE_LIST_LOC = "w";
+
+	private AnonymousIp() {
+		try {
+			ipv4Whitelist = new AnonymousIpWhitelist();
+			ipv6Whitelist = new AnonymousIpWhitelist();
+		} catch (NetworkNodeException e) {
+			LOGGER.error("AnonymousIp ERR: Network node exception ", e);
+		}
+	}
+
+	/*
+	 * Returns the current anonymous ip object
+	 */
+	public static AnonymousIp getCurrentConfig() {
+		return currentConfig;
+	}
+
+	/*
+	 * Returns the list of subnets in the IPv4 whitelist
+	 */
+	public AnonymousIpWhitelist getIPv4Whitelist() {
+		return ipv4Whitelist;
+	}
+
+	/*
+	 * Returns the list of subnets in the IPv6 whitelist
+	 */
+	public AnonymousIpWhitelist getIPv6Whitelist() {
+		return ipv6Whitelist;
+	}
+
+	private static void parseIPv4Whitelist(final JsonNode config, final AnonymousIp anonymousIp) throws JsonUtilsException {
+		if (config.has("ip4Whitelist")) {
+			try {
+				anonymousIp.ipv4Whitelist = new AnonymousIpWhitelist();
+				anonymousIp.ipv4Whitelist.init(JsonUtils.getJsonNode(config, "ip4Whitelist"));
+			} catch (NetworkNodeException e) {
+				LOGGER.error("Anonymous Ip ERR: Network node err ", e);
+			}
+		}
+	}
+
+	private static void parseIPv6Whitelist(final JsonNode config, final AnonymousIp anonymousIp) throws JsonUtilsException {
+		if (config.has("ip6Whitelist")) {
+			try {
+				anonymousIp.ipv4Whitelist = new AnonymousIpWhitelist();
+				anonymousIp.ipv4Whitelist.init(JsonUtils.getJsonNode(config, "ip6Whitelist"));
+			} catch (NetworkNodeException e) {
+				LOGGER.error("Anonymous Ip ERR: Network node err ", e);
+			}
+		}
+	}
+
+	@SuppressWarnings({ "PMD.NPathComplexity", "PMD.CyclomaticComplexity" })
+	private static AnonymousIp parseConfigJson(final JsonNode config) {
+		final AnonymousIp anonymousIp = new AnonymousIp();
+		try {
+			final JsonNode blockingTypes = JsonUtils.getJsonNode(config, "anonymousIp");
+
+			anonymousIp.blockAnonymousIp = JsonUtils.getBoolean(blockingTypes, "blockAnonymousVPN");
+			anonymousIp.blockHostingProvider = JsonUtils.getBoolean(blockingTypes, "blockHostingProvider");
+			anonymousIp.blockPublicProxy = JsonUtils.getBoolean(blockingTypes, "blockPublicProxy");
+			anonymousIp.blockTorExitNode = JsonUtils.getBoolean(blockingTypes, "blockTorExitNode");
+
+			anonymousIp.enabled = AnonymousIp.currentConfig.enabled;
+
+			parseIPv4Whitelist(config, anonymousIp);
+			parseIPv6Whitelist(config, anonymousIp);
+
+			if (config.has("redirectUrl")) {
+				anonymousIp.redirectUrl = JsonUtils.getString(config, "redirectUrl");
+			}
+
+			return anonymousIp;
+		} catch (Exception e) {
+			LOGGER.error("AnonymousIp ERR: parsing config file failed", e);
+		}
+
+		return null;
+	}
+
+	@SuppressWarnings({ "PMD.NPathComplexity" })
+	public static boolean parseConfigFile(final File f, final boolean verifyOnly) {
+		JsonNode json = null;
+		try {
+			final ObjectMapper mapper = new ObjectMapper();
+			json = mapper.readTree(f);
+		} catch (Exception e) {
+			LOGGER.error("AnonymousIp ERR: json file exception " + f, e);
+			return false;
+		}
+
+		final AnonymousIp anonymousIp = parseConfigJson(json);
+
+		if (anonymousIp == null) {
+			return false;
+		}
+
+		if (!verifyOnly) {
+			currentConfig = anonymousIp; // point to the new parsed object
+		}
+
+		return true;
+	}
+
+	private static boolean inWhitelist(final String address) {
+		// If the address is ipv4 check against the ipv4whitelist
+		if (address.indexOf(':') == -1) {
+			if (currentConfig.ipv4Whitelist.contains(address)) {
+				return true;
+			}
+		}
+
+		// If the address is ipv6 check against the ipv6whitelist
+		else {
+			if (currentConfig.ipv6Whitelist.contains(address)) {
+				return true;
+			}
+		}
+
+		return false;
+	}
+
+	@SuppressWarnings({ "PMD.CyclomaticComplexity", "PMD.NPathComplexity" })
+	public static boolean enforce(final TrafficRouter trafficRouter, final String dsvcId, final String url, final String ip) {
+
+		final InetAddress address = InetAddresses.forString(ip);
+
+		if (inWhitelist(ip)) {
+			return false;
+		}
+
+		final AnonymousIpResponse response = trafficRouter.getAnonymousIpDatabaseService().lookupIp(address);
+
+		if (response == null) {
+			return false;
+		}
+
+		// Check if the ip should be blocked by checking if the ip falls into a
+		// specific policy
+		if (AnonymousIp.getCurrentConfig().blockAnonymousIp && response.isAnonymousVpn()) {
+			return true;
+		}
+
+		if (AnonymousIp.getCurrentConfig().blockHostingProvider && response.isHostingProvider()) {
+			return true;
+		}
+
+		if (AnonymousIp.getCurrentConfig().blockPublicProxy && response.isPublicProxy()) {
+			return true;
+		}
+
+		if (AnonymousIp.getCurrentConfig().blockTorExitNode && response.isTorExitNode()) {
+			return true;
+		}
+
+		return false;
+	}
+
+	@SuppressWarnings({ "PMD.CyclomaticComplexity" })
+	/*
+	 * Enforces the anonymous ip blocking policies
+	 * 
+	 * If the Delivery Service has anonymous ip blocking enabled And the ip is
+	 * in the anonymous ip database The ip will be blocked if it matches a
+	 * policy defined in the config file
+	 */
+	public static void enforce(final TrafficRouter trafficRouter, final Request request, final DeliveryService deliveryService, final Cache cache, 
+		final HTTPRouteResult routeResult, final Track track) throws MalformedURLException {
+
+		final HTTPRequest httpRequest = HTTPRequest.class.cast(request);
+
+		// If the database isn't initialized dont block
+		if (!trafficRouter.getAnonymousIpDatabaseService().isInitialized()) {
+			return;
+		}
+
+		// Check if the ip is allowed
+		final boolean block = enforce(trafficRouter, deliveryService.getId(), httpRequest.getRequestedUrl(), httpRequest.getClientIP());
+
+		// Block the ip if it is not allowed
+		if (block) {
+			routeResult.setResponseCode(AnonymousIp.BLOCK_CODE);
+			track.setResult(ResultType.ANON_BLOCK);
+			if (AnonymousIp.getCurrentConfig().redirectUrl != null) {
+				routeResult.setUrl(new URL(AnonymousIp.getCurrentConfig().redirectUrl));
+			}
+		}
+	}
+}
diff --git a/traffic_router/core/src/main/java/com/comcast/cdn/traffic_control/traffic_router/core/loc/AnonymousIpConfigUpdater.java b/traffic_router/core/src/main/java/com/comcast/cdn/traffic_control/traffic_router/core/loc/AnonymousIpConfigUpdater.java
new file mode 100644
index 000000000..ede90bc4e
--- /dev/null
+++ b/traffic_router/core/src/main/java/com/comcast/cdn/traffic_control/traffic_router/core/loc/AnonymousIpConfigUpdater.java
@@ -0,0 +1,51 @@
+/*
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.comcast.cdn.traffic_control.traffic_router.core.loc;
+
+import java.io.File;
+import java.io.IOException;
+
+import org.apache.log4j.Logger;
+
+public class AnonymousIpConfigUpdater extends AbstractServiceUpdater {
+    private static final Logger LOGGER = Logger.getLogger(AnonymousIpConfigUpdater.class);
+
+    public AnonymousIpConfigUpdater() {
+        LOGGER.debug("init...");
+        sourceCompressed = false;
+        tmpPrefix = "anonymousip";
+        tmpSuffix = ".json";
+    }
+    
+    @Override
+    /*
+     * Loads the anonymous ip config file
+     */
+    public boolean loadDatabase() throws IOException {
+    	LOGGER.debug("AnonymousIpConfigUodater loading config");
+        final File existingDB = databasesDirectory.resolve(databaseName).toFile();
+        return AnonymousIp.parseConfigFile(existingDB, false);
+    }
+
+    @Override
+    /*
+     * Verifies the anonymous ip config file
+     */
+    public boolean verifyDatabase(final File dbFile) throws IOException {
+    	LOGGER.debug("AnonymousIpConfigUpdater verifying config");
+        return AnonymousIp.parseConfigFile(dbFile, true);
+    }
+
+}
diff --git a/traffic_router/core/src/main/java/com/comcast/cdn/traffic_control/traffic_router/core/loc/AnonymousIpDatabaseService.java b/traffic_router/core/src/main/java/com/comcast/cdn/traffic_control/traffic_router/core/loc/AnonymousIpDatabaseService.java
new file mode 100644
index 000000000..b70846b46
--- /dev/null
+++ b/traffic_router/core/src/main/java/com/comcast/cdn/traffic_control/traffic_router/core/loc/AnonymousIpDatabaseService.java
@@ -0,0 +1,131 @@
+/*
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.comcast.cdn.traffic_control.traffic_router.core.loc;
+
+import java.io.File;
+import java.io.IOException;
+import java.net.InetAddress;
+
+import org.apache.log4j.Logger;
+
+import com.maxmind.geoip2.DatabaseReader;
+import com.maxmind.geoip2.exception.GeoIp2Exception;
+import com.maxmind.geoip2.model.AnonymousIpResponse;
+
+@SuppressWarnings({ "PMD.AvoidDuplicateLiterals" })
+public class AnonymousIpDatabaseService {
+	private static final Logger LOGGER = Logger.getLogger(AnonymousIpDatabaseService.class);
+
+	private boolean initialized = false;
+	private File databaseFile;
+	private DatabaseReader databaseReader;
+
+	/*
+	 * Reloads the anonymous ip database
+	 */
+	public void reloadDatabase() throws IOException {
+		if (databaseReader != null) {
+			databaseReader.close();
+		}
+
+		if (databaseFile != null) {
+			final DatabaseReader reader = createDatabaseReader(databaseFile);
+			if (reader != null) {
+				databaseReader = reader;
+				initialized = true;
+			} else {
+				throw new IOException("Could not create database reader");
+			}
+		}
+	}
+
+	public void setDatabaseFile(final File databaseFile) {
+		this.databaseFile = databaseFile;
+	}
+
+	/*
+	 * Verifies the database by attempting to recreate it
+	 */
+	public boolean verifyDatabase(final File databaseFile) throws IOException {
+		return createDatabaseReader(databaseFile) != null;
+	}
+
+	/*
+	 * Creates a DatabaseReader object using an input database file
+	 */
+	private DatabaseReader createDatabaseReader(final File databaseFile) throws IOException {
+		if (!databaseFile.exists()) {
+			LOGGER.warn(databaseFile.getAbsolutePath() + " does not exist yet!");
+			return null;
+		}
+
+		if (databaseFile.isDirectory()) {
+			LOGGER.error(databaseFile + " is a directory, need a file");
+			return null;
+		}
+
+		LOGGER.info("Loading Anonymous IP db: " + databaseFile.getAbsolutePath());
+
+		try {
+			final DatabaseReader reader = new DatabaseReader.Builder(databaseFile).build();
+			return reader;
+		} catch (Exception e) {
+			LOGGER.error(databaseFile.getAbsolutePath() + " is not a valid Anonymous IP data file", e);
+			return null;
+		}
+	}
+
+	/*
+	 * Returns an AnonymousIpResponse from looking an ip up in the database
+	 */
+	public AnonymousIpResponse lookupIp(final InetAddress ipAddress) {
+		if (initialized) {
+			// Return an anonymousIp object after looking up the ip in the
+			// database
+			try {
+				return databaseReader.anonymousIp(ipAddress);
+			} catch (GeoIp2Exception e) {
+				LOGGER.debug(String.format("AnonymousIP: IP %s not found in anonymous ip database", ipAddress.getHostAddress()));
+				return null;
+			} catch (IOException e) {
+				LOGGER.error("AnonymousIp ERR: IO Error during lookup of ip in anonymous ip database", e);
+				return null;
+			}
+		} else {
+			return null;
+		}
+	}
+
+	public boolean isInitialized() {
+		return initialized;
+	}
+
+	/*
+	 * Closes the database when the object is destroyed
+	 */
+	@Override
+	protected void finalize() throws Throwable {
+		if (databaseReader != null) {
+			try {
+				databaseReader.close();
+				databaseReader = null;
+			} catch (IOException e) {
+				LOGGER.warn("Caught exception while trying to close anonymous ip database reader: ", e);
+			}
+		}
+		super.finalize();
+	}
+
+}
diff --git a/traffic_router/core/src/main/java/com/comcast/cdn/traffic_control/traffic_router/core/loc/AnonymousIpDatabaseUpdater.java b/traffic_router/core/src/main/java/com/comcast/cdn/traffic_control/traffic_router/core/loc/AnonymousIpDatabaseUpdater.java
new file mode 100644
index 000000000..2d63f2d19
--- /dev/null
+++ b/traffic_router/core/src/main/java/com/comcast/cdn/traffic_control/traffic_router/core/loc/AnonymousIpDatabaseUpdater.java
@@ -0,0 +1,63 @@
+/*
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.comcast.cdn.traffic_control.traffic_router.core.loc;
+
+import java.io.File;
+import java.io.IOException;
+
+import org.apache.log4j.Logger;
+
+@SuppressWarnings({ "PMD.AvoidDuplicateLiterals" })
+public class AnonymousIpDatabaseUpdater extends AbstractServiceUpdater {
+    private static final Logger LOGGER = Logger.getLogger(AnonymousIpDatabaseUpdater.class);
+
+	private AnonymousIpDatabaseService anonymousIpDatabaseService;
+
+	@Override
+	/*
+	 * Verifies the anonymous ip database
+	 */
+	public boolean verifyDatabase(final File dbFile) throws IOException {
+		LOGGER.debug("Verifying Anonymous IP Database");
+		return anonymousIpDatabaseService.verifyDatabase(dbFile);
+	}
+
+	/*
+	 * Sets the anonymous ip database file and reloads the database
+	 */
+	public boolean loadDatabase() throws IOException {
+		LOGGER.debug("Loading Anonymous IP Database");
+		anonymousIpDatabaseService.setDatabaseFile(databasesDirectory.resolve(databaseName).toFile());
+		anonymousIpDatabaseService.reloadDatabase();
+		return true;
+	}
+
+	@Override
+	/*
+	 * Returns a boolean with the initialization state of the database
+	 */
+	public boolean isLoaded() {
+		if (anonymousIpDatabaseService != null) {
+			return anonymousIpDatabaseService.isInitialized();
+		}
+
+		return loaded;
+	}
+	
+	public void setAnonymousIpDatabaseService(final AnonymousIpDatabaseService anonymousIpDatabaseService) {
+		this.anonymousIpDatabaseService = anonymousIpDatabaseService;
+	}
+
+}
diff --git a/traffic_router/core/src/main/java/com/comcast/cdn/traffic_control/traffic_router/core/loc/AnonymousIpWhitelist.java b/traffic_router/core/src/main/java/com/comcast/cdn/traffic_control/traffic_router/core/loc/AnonymousIpWhitelist.java
new file mode 100644
index 000000000..b05aaafc4
--- /dev/null
+++ b/traffic_router/core/src/main/java/com/comcast/cdn/traffic_control/traffic_router/core/loc/AnonymousIpWhitelist.java
@@ -0,0 +1,64 @@
+/*
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.comcast.cdn.traffic_control.traffic_router.core.loc;
+
+import org.apache.log4j.Logger;
+import com.comcast.cdn.traffic_control.traffic_router.core.util.JsonUtilsException;
+import com.fasterxml.jackson.databind.JsonNode;
+
+public class AnonymousIpWhitelist {
+	private static final Logger LOGGER = Logger.getLogger(AnonymousIpWhitelist.class);
+
+	final private NetworkNode.SuperNode whitelist;
+
+	public AnonymousIpWhitelist() throws NetworkNodeException {
+		whitelist = new NetworkNode.SuperNode();
+	}
+
+	public void init(final JsonNode config) throws JsonUtilsException, NetworkNodeException {
+		if (config.isArray()) {
+			for (final JsonNode node : config) {
+				final String network = node.asText();
+				this.add(network);
+			}
+		}
+	}
+
+	public void add(final String network) throws NetworkNodeException {
+		final NetworkNode node = new NetworkNode(network, AnonymousIp.WHITE_LIST_LOC);
+		if (network.indexOf(':') == -1) {
+			whitelist.add(node);
+		} else {
+			whitelist.add6(node);
+		}
+	}
+
+	public boolean contains(final String address) {
+		if (whitelist == null) {
+			return false;
+		}
+
+		try {
+			final NetworkNode nn = whitelist.getNetwork(address);
+			if (nn.getLoc() == AnonymousIp.WHITE_LIST_LOC) {
+				return true;
+			}
+		} catch (NetworkNodeException e) {
+			LOGGER.warn("AnonymousIp: exception", e);
+		}
+
+		return false;
+	}
+}
diff --git a/traffic_router/core/src/main/java/com/comcast/cdn/traffic_control/traffic_router/core/router/StatTracker.java b/traffic_router/core/src/main/java/com/comcast/cdn/traffic_control/traffic_router/core/router/StatTracker.java
index b444cd82a..ccc675cb9 100644
--- a/traffic_router/core/src/main/java/com/comcast/cdn/traffic_control/traffic_router/core/router/StatTracker.java
+++ b/traffic_router/core/src/main/java/com/comcast/cdn/traffic_control/traffic_router/core/router/StatTracker.java
@@ -111,7 +111,7 @@ public void setRegionalAlternateCount(final int regionalAlternateCount) {
 		}
 
 		public static enum ResultType {
-			ERROR, CZ, GEO, MISS, STATIC_ROUTE, DS_REDIRECT, DS_MISS, INIT, FED, RGDENY, RGALT, GEO_REDIRECT, DEEP_CZ
+			ERROR, CZ, GEO, MISS, STATIC_ROUTE, DS_REDIRECT, DS_MISS, INIT, FED, RGDENY, RGALT, GEO_REDIRECT, DEEP_CZ, ANON_BLOCK
 		}
 
 		public enum ResultDetails {
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 9662fd8ca..78ec478a8 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
@@ -67,6 +67,8 @@
 import com.comcast.cdn.traffic_control.traffic_router.core.util.TrafficOpsUtils;
 import com.comcast.cdn.traffic_control.traffic_router.core.util.CidrAddress;
 import com.comcast.cdn.traffic_control.traffic_router.core.router.StatTracker.Track.ResultDetails;
+import com.comcast.cdn.traffic_control.traffic_router.core.loc.AnonymousIp;
+import com.comcast.cdn.traffic_control.traffic_router.core.loc.AnonymousIpDatabaseService;
 
 public class TrafficRouter {
 	public static final Logger LOGGER = Logger.getLogger(TrafficRouter.class);
@@ -76,6 +78,7 @@
 	private final ZoneManager zoneManager;
 	private final GeolocationService geolocationService;
 	private final GeolocationService geolocationService6;
+	private final AnonymousIpDatabaseService anonymousIpService;
 	private final FederationRegistry federationRegistry;
 	private final boolean consistentDNSRouting;
 
@@ -91,7 +94,8 @@
 
 	public TrafficRouter(final CacheRegister cr, 
 			final GeolocationService geolocationService, 
-			final GeolocationService geolocationService6, 
+			final GeolocationService geolocationService6,
+			final AnonymousIpDatabaseService anonymousIpService,
 			final StatTracker statTracker,
 			final TrafficOpsUtils trafficOpsUtils,
 			final FederationRegistry federationRegistry,
@@ -99,6 +103,7 @@ public TrafficRouter(final CacheRegister cr,
 		this.cacheRegister = cr;
 		this.geolocationService = geolocationService;
 		this.geolocationService6 = geolocationService6;
+		this.anonymousIpService = anonymousIpService;
 		this.federationRegistry = federationRegistry;
 		this.consistentDNSRouting = JsonUtils.optBoolean(cr.getConfig(), "consistent.dns.routing");
 		this.zoneManager = new ZoneManager(this, statTracker, trafficOpsUtils, trafficRouterManager);
@@ -199,6 +204,10 @@ public GeolocationService getGeolocationService() {
 		return geolocationService;
 	}
 
+	public AnonymousIpDatabaseService getAnonymousIpDatabaseService() {
+		return anonymousIpService;
+	}
+
 	public Geolocation getLocation(final String clientIP) throws GeolocationException {
 		return clientIP.contains(":") ? geolocationService6.location(clientIP) : geolocationService.location(clientIP);
 	}
@@ -532,6 +541,7 @@ public HTTPRouteResult multiRoute(final HTTPRequest request, final Track track)
 		return routeResult;
 	}
 
+	@SuppressWarnings({ "PMD.CyclomaticComplexity", "PMD.NPathComplexity" })
 	public HTTPRouteResult route(final HTTPRequest request, final Track track) throws MalformedURLException, GeolocationException {
 		track.setRouteType(RouteType.HTTP, request.getHostname());
 
@@ -542,6 +552,7 @@ public HTTPRouteResult route(final HTTPRequest request, final Track track) throw
 		}
 	}
 
+	@SuppressWarnings({ "PMD.CyclomaticComplexity", "PMD.NPathComplexity" })
 	public HTTPRouteResult singleRoute(final HTTPRequest request, final Track track) throws MalformedURLException, GeolocationException {
 		final DeliveryService deliveryService = getDeliveryService(request, track);
 
@@ -574,6 +585,16 @@ public HTTPRouteResult singleRoute(final HTTPRequest request, final Track track)
 
 		final Cache cache = consistentHasher.selectHashable(caches, deliveryService.getDispersion(), request.getPath());
 
+		// Enforce anonymous IP blocking if a DS has anonymous blocking enabled
+		// and the feature is enabled
+		if (deliveryService.isAnonymousIpEnabled() && AnonymousIp.getCurrentConfig().enabled) {
+			AnonymousIp.enforce(this, request, deliveryService, cache, routeResult, track);
+
+			if (routeResult.getResponseCode() == AnonymousIp.BLOCK_CODE) {
+				return routeResult;
+			}
+		}
+
 		if (deliveryService.isRegionalGeoEnabled()) {
 			RegionalGeo.enforce(this, request, deliveryService, cache, routeResult, track);
 			return routeResult;
diff --git a/traffic_router/core/src/main/java/com/comcast/cdn/traffic_control/traffic_router/core/router/TrafficRouterManager.java b/traffic_router/core/src/main/java/com/comcast/cdn/traffic_control/traffic_router/core/router/TrafficRouterManager.java
index 26b20c250..941ba5125 100644
--- a/traffic_router/core/src/main/java/com/comcast/cdn/traffic_control/traffic_router/core/router/TrafficRouterManager.java
+++ b/traffic_router/core/src/main/java/com/comcast/cdn/traffic_control/traffic_router/core/router/TrafficRouterManager.java
@@ -32,6 +32,7 @@
 import org.springframework.context.ApplicationContext;
 import org.springframework.context.ApplicationListener;
 import org.springframework.context.event.ContextRefreshedEvent;
+import com.comcast.cdn.traffic_control.traffic_router.core.loc.AnonymousIpDatabaseService;
 
 public class TrafficRouterManager implements ApplicationListener<ContextRefreshedEvent> {
 	private static final Logger LOGGER = Logger.getLogger(TrafficRouterManager.class);
@@ -42,6 +43,7 @@
 	private TrafficRouter trafficRouter;
 	private GeolocationService geolocationService;
 	private GeolocationService geolocationService6;
+	private AnonymousIpDatabaseService anonymousIpService;
 	private StatTracker statTracker;
 	private static final Map<String, Long> timeTracker = new ConcurrentHashMap<String, Long>();
 	private NameServer nameServer;
@@ -98,7 +100,7 @@ public void setCacheRegister(final CacheRegister cacheRegister) throws IOExcepti
 			return;
 		}
 
-		final TrafficRouter tr = new TrafficRouter(cacheRegister, geolocationService, geolocationService6, statTracker, trafficOpsUtils, federationRegistry, this);
+		final TrafficRouter tr = new TrafficRouter(cacheRegister, geolocationService, geolocationService6, anonymousIpService, statTracker, trafficOpsUtils, federationRegistry, this);
 		tr.setSteeringRegistry(steeringRegistry);
 		synchronized(this) {
 			if (state != null) {
@@ -126,6 +128,10 @@ public void setGeolocationService6(final GeolocationService geolocationService)
 		this.geolocationService6 = geolocationService;
 	}
 
+	public void setAnonymousIpService(final AnonymousIpDatabaseService anonymousIpService) {
+		this.anonymousIpService = anonymousIpService;
+	}
+
 	public void setStatTracker(final StatTracker statTracker) {
 		this.statTracker = statTracker;
 	}
diff --git a/traffic_router/core/src/main/webapp/WEB-INF/applicationContext.xml b/traffic_router/core/src/main/webapp/WEB-INF/applicationContext.xml
index fb5cca3b2..a46fdf729 100644
--- a/traffic_router/core/src/main/webapp/WEB-INF/applicationContext.xml
+++ b/traffic_router/core/src/main/webapp/WEB-INF/applicationContext.xml
@@ -62,6 +62,7 @@
 		<property name="federationRegistry" ref="federationsRegistry" />
 		<property name="geolocationService" ref="maxmindGeolocationService" />
 		<property name="geolocationService6" ref="maxmindGeolocationService" />
+		<property name="anonymousIpService" ref="anonymousIpDatabaseService" />
 		<property name="steeringRegistry" ref="steeringRegistry" />
 	</bean>
 
@@ -111,6 +112,8 @@
 		<property name="networkUpdater" ref="networkUpdater" />
 		<property name="deepNetworkUpdater" ref="deepNetworkUpdater" />
 		<property name="regionalGeoUpdater" ref="regionalGeoUpdater" />
+		<property name="anonymousIpConfigUpdater" ref="anonymousIpConfigUpdater" />
+		<property name="anonymousIpDatabaseUpdater" ref="anonymousIpDatabaseUpdater" />
 		<property name="statTracker" ref="statTracker" />
 		<property name="configDir" value="/opt/traffic_router/conf" />
 		<property name="federationsWatcher" ref="federationsWatcher" />
@@ -127,6 +130,7 @@
 	</bean>
 
 	<bean id="maxmindGeolocationService" class="com.comcast.cdn.traffic_control.traffic_router.core.loc.MaxmindGeolocationService"/>
+	<bean id="anonymousIpDatabaseService" class="com.comcast.cdn.traffic_control.traffic_router.core.loc.AnonymousIpDatabaseService"/>
 	
 	<bean id="geolocationDatabaseUpdater" class="com.comcast.cdn.traffic_control.traffic_router.core.loc.GeolocationDatabaseUpdater" init-method="init">
 		<property name="databasesDirectory" ref="databasesDir"/>
@@ -162,6 +166,25 @@
 		<property name="trafficRouterManager" ref="trafficRouterManager" />
 	</bean>
 
+	<bean id="anonymousIpConfigUpdater" class="com.comcast.cdn.traffic_control.traffic_router.core.loc.AnonymousIpConfigUpdater"
+		init-method="init">
+		<property name="executorService" ref="ScheduledExecutorService" />
+		<property name="databasesDirectory" ref="databasesDir" />
+		<property name="databaseName" value="$[cache.anonymousip.database:anonymous_ip.json]" />
+		<property name="pollingInterval" value="$[cache.anonymousip.database.refresh.period:10800000]" />
+		<property name="trafficRouterManager" ref="trafficRouterManager" />
+	</bean>
+
+	<bean id="anonymousIpDatabaseUpdater" class="com.comcast.cdn.traffic_control.traffic_router.core.loc.AnonymousIpDatabaseUpdater"
+		init-method="init">
+		<property name="executorService" ref="ScheduledExecutorService" />
+		<property name="databasesDirectory" ref="databasesDir" />
+		<property name="databaseName" value="$[cache.anonymousip.database:GeoIP2-Anonymous-IP.mmdb]" />
+		<property name="pollingInterval" value="$[cache.anonymousip.database.refresh.period:10800000]" />
+		<property name="trafficRouterManager" ref="trafficRouterManager" />
+		<property name="anonymousIpDatabaseService" ref="anonymousIpDatabaseService" />
+	</bean>
+
 	<bean id="ScheduledExecutorService" class="java.util.concurrent.Executors"
 		factory-method="newSingleThreadScheduledExecutor" />
 
diff --git a/traffic_router/core/src/test/java/com/comcast/cdn/traffic_control/traffic_router/core/loc/AnonymousIpDatabaseServiceTest.java b/traffic_router/core/src/test/java/com/comcast/cdn/traffic_control/traffic_router/core/loc/AnonymousIpDatabaseServiceTest.java
new file mode 100644
index 000000000..c68276094
--- /dev/null
+++ b/traffic_router/core/src/test/java/com/comcast/cdn/traffic_control/traffic_router/core/loc/AnonymousIpDatabaseServiceTest.java
@@ -0,0 +1,95 @@
+/*
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.comcast.cdn.traffic_control.traffic_router.core.loc;
+
+import static org.hamcrest.CoreMatchers.equalTo;
+import static org.hamcrest.CoreMatchers.notNullValue;
+import static org.hamcrest.MatcherAssert.assertThat;
+
+import java.io.File;
+import java.io.IOException;
+import java.net.InetAddress;
+import java.net.UnknownHostException;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+
+import com.maxmind.geoip2.exception.GeoIp2Exception;
+
+public class AnonymousIpDatabaseServiceTest {
+
+	private AnonymousIpDatabaseService anonymousIpService;
+	private final static String mmdb = "src/test/resources/GeoIP2-Anonymous-IP.mmdb";
+
+	@Before
+	public void setup() throws Exception {
+		// ignore the test if there is no mmdb file
+		File mmdbFile = new File(mmdb);
+		org.junit.Assume.assumeTrue(mmdbFile.exists());
+
+		anonymousIpService = new AnonymousIpDatabaseService();
+		File databaseFile = new File(mmdb);
+		anonymousIpService.setDatabaseFile(databaseFile);
+		anonymousIpService.reloadDatabase();
+		assert anonymousIpService.isInitialized();
+	}
+
+	@Test
+	public void testIpInDatabase() throws Exception {
+		assertThat(anonymousIpService.lookupIp(InetAddress.getByName("223.26.48.248")), notNullValue());
+		assertThat(anonymousIpService.lookupIp(InetAddress.getByName("223.26.48.248")), notNullValue());
+		assertThat(anonymousIpService.lookupIp(InetAddress.getByName("1.1.205.152")), notNullValue());
+		assertThat(anonymousIpService.lookupIp(InetAddress.getByName("18.85.22.204")), notNullValue());
+	}
+
+	@Test
+	public void testIpNotInDatabase() throws Exception {
+		assertThat(anonymousIpService.lookupIp(InetAddress.getByName("192.168.0.1")), equalTo(null));
+	}
+
+	@Test
+	public void testDatabaseNotLoaded() throws UnknownHostException, IOException, GeoIp2Exception {
+		AnonymousIpDatabaseService anonymousIpService = new AnonymousIpDatabaseService();
+		assertThat(anonymousIpService.isInitialized(), equalTo(false));
+		assertThat(anonymousIpService.lookupIp(InetAddress.getByName("223.26.48.248")), equalTo(null));
+		assertThat(anonymousIpService.lookupIp(InetAddress.getByName("192.168.0.1")), equalTo(null));
+	}
+
+	@Test
+	public void testLookupTime() throws IOException {
+		final InetAddress ipAddress = InetAddress.getByName("223.26.48.248");
+		final long start = System.nanoTime();
+
+		long total = 100000;
+
+		for (int i = 0; i <= total; i++) {
+			anonymousIpService.lookupIp(ipAddress);
+		}
+
+		long duration = System.nanoTime() - start;
+		
+		System.out.println(String.format("Anonymous IP database average lookup: %s nanoseconds", Long.toString(duration / total)));
+	}
+
+	@After
+	public void tearDown() throws Exception {
+		try {
+			anonymousIpService.finalize();
+		} catch (Throwable e) {
+		}
+	}
+
+}
diff --git a/traffic_router/core/src/test/java/com/comcast/cdn/traffic_control/traffic_router/core/loc/AnonymousIpTest.java b/traffic_router/core/src/test/java/com/comcast/cdn/traffic_control/traffic_router/core/loc/AnonymousIpTest.java
new file mode 100644
index 000000000..7fe97f7ec
--- /dev/null
+++ b/traffic_router/core/src/test/java/com/comcast/cdn/traffic_control/traffic_router/core/loc/AnonymousIpTest.java
@@ -0,0 +1,272 @@
+/*
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.comcast.cdn.traffic_control.traffic_router.core.loc;
+
+import static org.hamcrest.CoreMatchers.equalTo;
+import static org.hamcrest.CoreMatchers.notNullValue;
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.when;
+
+import java.io.File;
+import java.io.IOException;
+
+import org.junit.Before;
+import org.junit.Test;
+
+import com.comcast.cdn.traffic_control.traffic_router.core.router.TrafficRouter;
+
+public class AnonymousIpTest {
+	private TrafficRouter trafficRouter;
+
+	final File configFile = new File("src/test/resources/anonymous_ip.json");
+	final File configNoWhitelist = new File("src/test/resources/anonymous_ip_no_whitelist.json");
+
+	final String mmdb = "src/test/resources/GeoIP2-Anonymous-IP.mmdb";
+	File databaseFile = new File(mmdb);
+
+	@Before
+	public void setUp() throws Exception {
+		// ignore the test if there is no mmdb file
+		File mmdbFile = new File(mmdb);
+		org.junit.Assume.assumeTrue(mmdbFile.exists());
+
+		AnonymousIp.parseConfigFile(configFile, false);
+		assert (AnonymousIp.getCurrentConfig().getIPv4Whitelist() != null);
+		assert (AnonymousIp.getCurrentConfig().getIPv6Whitelist() != null);
+
+		// Set up a mock traffic router with real database
+		AnonymousIpDatabaseService anonymousIpService = new AnonymousIpDatabaseService();
+		anonymousIpService.setDatabaseFile(databaseFile);
+		anonymousIpService.reloadDatabase();
+		assert anonymousIpService.isInitialized();
+		trafficRouter = mock(TrafficRouter.class);
+		when(trafficRouter.getAnonymousIpDatabaseService()).thenReturn(anonymousIpService);
+		assert (trafficRouter.getAnonymousIpDatabaseService() != null);
+	}
+
+	@Test
+	public void testConfigFileParsingIpv4() {
+		AnonymousIp currentConfig = AnonymousIp.getCurrentConfig();
+		assertThat(currentConfig, notNullValue());
+		AnonymousIpWhitelist whitelist = currentConfig.getIPv4Whitelist();
+		assertThat(whitelist, notNullValue());
+	}
+
+	@Test
+	public void testConfigFileParsingIpv6() {
+		AnonymousIp currentConfig = AnonymousIp.getCurrentConfig();
+		assertThat(currentConfig, notNullValue());
+		AnonymousIpWhitelist whitelist = currentConfig.getIPv6Whitelist();
+		assertThat(whitelist, notNullValue());
+	}
+
+	@Test
+	public void testIpInWhitelistIsAllowed() {
+		final String dsvcId = "dsID";
+		final String url = "http://ds1.example.com/live1";
+		final String ip = "5.34.32.79";
+
+		final boolean result = AnonymousIp.enforce(trafficRouter, dsvcId, url, ip);
+
+		assertThat(result, equalTo(false));
+	}
+
+	@Test
+	public void testFallsUnderManyPolicies() {
+		final String dsvcId = "dsID";
+		final String url = "http://ds1.example.com/live1";
+		final String ip = "2.38.158.142";
+
+		final boolean result = AnonymousIp.enforce(trafficRouter, dsvcId, url, ip);
+
+		assertThat(result, equalTo(true));
+	}
+
+	@Test
+	public void testAllowNotCheckingPolicy() {
+		final String dsvcId = "dsID";
+		final String url = "http://ds1.example.com/live1";
+		final String ip = "2.36.248.52";
+
+		final boolean result = AnonymousIp.enforce(trafficRouter, dsvcId, url, ip);
+
+		assertThat(result, equalTo(false));
+	}
+
+	@Test
+	public void testEnforceAllowed() throws IOException {
+		final String dsvcId = "dsID";
+		final String url = "http://ds1.example.com/live1";
+		final String ip = "10.0.0.1";
+
+		final boolean result = AnonymousIp.enforce(trafficRouter, dsvcId, url, ip);
+
+		assertThat(result, equalTo(false));
+	}
+
+	@Test
+	public void testEnforceAllowedIpInWhitelist() throws IOException {
+		final String dsvcId = "dsID";
+		final String url = "http://ds1.example.com/live1";
+		final String ip = "10.0.2.1";
+
+		final boolean result = AnonymousIp.enforce(trafficRouter, dsvcId, url, ip);
+
+		assertThat(result, equalTo(false));
+	}
+
+	@Test
+	public void testEnforceBlocked() {
+		final String dsvcId = "dsID";
+		final String url = "http://ds1.example.com/live1";
+		final String ip = "223.26.48.248";
+
+		final boolean result = AnonymousIp.enforce(trafficRouter, dsvcId, url, ip);
+
+		assertThat(result, equalTo(true));
+	}
+
+	@Test
+	public void testEnforceNotInWhitelistNotInDB() {
+		final String dsvcId = "dsID";
+		final String url = "http://ds1.example.com/live1";
+		final String ip = "192.168.0.1";
+
+		final boolean result = AnonymousIp.enforce(trafficRouter, dsvcId, url, ip);
+
+		assertThat(result, equalTo(false));
+	}
+
+	/* IPv4 no whitelist */
+
+	@Test
+	public void testEnforceNoWhitelistAllowed() {
+		final String dsvcId = "dsID";
+		final String url = "http://ds1.example.com/live1";
+		final String ip = "192.168.0.1";
+		AnonymousIp.parseConfigFile(configNoWhitelist, false);
+
+		final boolean result = AnonymousIp.enforce(trafficRouter, dsvcId, url, ip);
+		assertThat(result, equalTo(false));
+	}
+
+	@Test
+	public void testEnforceNoWhitelistBlocked() {
+		final String dsvcId = "dsID";
+		final String url = "http://ds1.example.com/live1";
+		final String ip = "223.26.48.248";
+		AnonymousIp.parseConfigFile(configNoWhitelist, false);
+
+		final boolean result = AnonymousIp.enforce(trafficRouter, dsvcId, url, ip);
+		assertThat(result, equalTo(true));
+	}
+
+	@Test
+	public void testEnforceNoWhitelistNotEnforcePolicy() {
+		final String dsvcId = "dsID";
+		final String url = "http://ds1.example.com/live1";
+		final String ip = "2.36.248.52";
+		AnonymousIp.parseConfigFile(configNoWhitelist, false);
+
+		final boolean result = AnonymousIp.enforce(trafficRouter, dsvcId, url, ip);
+		assertThat(result, equalTo(false));
+	}
+
+	/* IPv6 Testing */
+
+	@Test
+	public void testIpv6EnforceBlock() {
+		final String dsvcId = "dsID";
+		final String url = "http://ds1.example.com/live1";
+		final String ip = "2001:418:9807::1";
+
+		final boolean result = AnonymousIp.enforce(trafficRouter, dsvcId, url, ip);
+		assertThat(result, equalTo(true));
+	}
+
+	@Test
+	public void testIpv6EnforceNotBlock() {
+		final String dsvcId = "dsID";
+		final String url = "http://ds1.example.com/live1";
+		final String ip = "2001:418::1";
+
+		final boolean result = AnonymousIp.enforce(trafficRouter, dsvcId, url, ip);
+		assertThat(result, equalTo(false));
+	}
+
+	@Test
+	public void testIpv6EnforceNotBlockWhitelisted() {
+		final String dsvcId = "dsID";
+		final String url = "http://ds1.example.com/live1";
+		final String ip = "2001:550:90a:0:0:0:0:1";
+
+		final boolean result = AnonymousIp.enforce(trafficRouter, dsvcId, url, ip);
+		assertThat(result, equalTo(false));
+	}
+
+	@Test
+	public void testIpv6EnforceNotBlockOnWhitelist() {
+		final String dsvcId = "dsID";
+		final String url = "http://ds1.example.com/live1";
+		final String ip = "::1";
+
+		final boolean result = AnonymousIp.enforce(trafficRouter, dsvcId, url, ip);
+		assertThat(result, equalTo(false));
+	}
+
+	/* IPv6 tests no whitelist */
+
+	@Test
+	public void testIpv6NoWhitelistEnforceBlock() {
+		final String dsvcId = "dsID";
+		final String url = "http://ds1.example.com/live1";
+		final String ip = "2001:418:9807::1";
+		AnonymousIp.parseConfigFile(configNoWhitelist, false);
+
+		final boolean result = AnonymousIp.enforce(trafficRouter, dsvcId, url, ip);
+		assertThat(result, equalTo(true));
+	}
+
+	@Test
+	public void testIpv6NoWhitelistNoBlock() {
+		final String dsvcId = "dsID";
+		final String url = "http://ds1.example.com/live1";
+		final String ip = "::1";
+		AnonymousIp.parseConfigFile(configNoWhitelist, false);
+
+		final boolean result = AnonymousIp.enforce(trafficRouter, dsvcId, url, ip);
+		assertThat(result, equalTo(false));
+	}
+
+	@Test
+	public void testAnonymousIpPerformance() {
+		final String dsvcId = "dsID";
+		final String url = "http://ds1.example.com/live1";
+		final String ip = "2.36.248.52";
+
+		long total = 100000;
+
+		long start = System.nanoTime();
+
+		for (int i = 0; i <= total; i++) {
+			final boolean result = AnonymousIp.enforce(trafficRouter, dsvcId, url, ip);
+		}
+
+		long duration = System.nanoTime() - start;
+
+		System.out.println(String.format("Anonymous IP blocking average took %s nanoseconds", Long.toString(duration / total)));
+	}
+}
diff --git a/traffic_router/core/src/test/java/com/comcast/cdn/traffic_control/traffic_router/core/loc/AnonymousIpWhitelistTest.java b/traffic_router/core/src/test/java/com/comcast/cdn/traffic_control/traffic_router/core/loc/AnonymousIpWhitelistTest.java
new file mode 100644
index 000000000..2a80bdc16
--- /dev/null
+++ b/traffic_router/core/src/test/java/com/comcast/cdn/traffic_control/traffic_router/core/loc/AnonymousIpWhitelistTest.java
@@ -0,0 +1,252 @@
+/*
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.comcast.cdn.traffic_control.traffic_router.core.loc;
+
+import static org.hamcrest.CoreMatchers.equalTo;
+import static org.hamcrest.MatcherAssert.assertThat;
+
+import com.comcast.cdn.traffic_control.traffic_router.core.util.JsonUtilsException;
+import com.fasterxml.jackson.databind.ObjectMapper;
+import org.junit.Before;
+import org.junit.Test;
+
+import java.io.IOException;
+
+public class AnonymousIpWhitelistTest {
+
+	AnonymousIpWhitelist ip4whitelist;
+	AnonymousIpWhitelist ip6whitelist;
+
+	@Before
+	public void setup() throws IOException, JsonUtilsException, NetworkNodeException {
+		final ObjectMapper mapper = new ObjectMapper();
+
+		ip4whitelist = new AnonymousIpWhitelist();
+		ip4whitelist.init(mapper.readTree("[\"192.168.30.0/24\", \"10.0.2.0/24\", \"10.0.0.0/16\"]"));
+
+		ip6whitelist = new AnonymousIpWhitelist();
+		ip6whitelist.init(mapper.readTree("[\"::1/32\", \"2001::/64\"]"));
+	}
+
+	@Test
+	public void testAnonymousIpWhitelistConstructor() {
+		// final InetAddress address = InetAddresses.forString("192.168.30.1");
+		assertThat(ip4whitelist.contains("192.168.30.1"), equalTo(true));
+	}
+
+	@Test
+	public void testIPsInWhitelist() {
+		assertThat(ip4whitelist.contains("192.168.30.1"), equalTo(true));
+
+		assertThat(ip4whitelist.contains("192.168.30.254"), equalTo(true));
+
+		assertThat(ip4whitelist.contains("10.0.2.1"), equalTo(true));
+
+		assertThat(ip4whitelist.contains("10.0.2.254"), equalTo(true));
+
+		assertThat(ip4whitelist.contains("10.0.1.1"), equalTo(true));
+
+		assertThat(ip4whitelist.contains("10.0.254.254"), equalTo(true));
+	}
+
+	@Test
+	public void testIPsNotInWhitelist() {
+		assertThat(ip4whitelist.contains("192.168.31.1"), equalTo(false));
+
+		assertThat(ip4whitelist.contains("192.167.30.1"), equalTo(false));
+
+		assertThat(ip4whitelist.contains("10.1.1.1"), equalTo(false));
+
+		assertThat(ip4whitelist.contains("10.10.1.1"), equalTo(false));
+	}
+
+	/* IPv6 Testing */
+
+	@Test
+	public void testIPv6AddressInWhitelist() {
+		assertThat(ip6whitelist.contains("::1"), equalTo(true));
+	}
+
+	@Test
+	public void testIPv6AddressInWhitelistInSubnet() {
+		assertThat(ip6whitelist.contains("2001::"), equalTo(true));
+
+		assertThat(ip6whitelist.contains("2001:0:0:0:0:0:0:1"), equalTo(true));
+
+		assertThat(ip6whitelist.contains("2001:0:0:0:0:0:1:1"), equalTo(true));
+
+		assertThat(ip6whitelist.contains("2001:0:0:0:a:a:a:a"), equalTo(true));
+
+		assertThat(ip6whitelist.contains("2001:0:0:0:ffff:ffff:ffff:ffff"), equalTo(true));
+	}
+
+	@Test
+	public void testIpv6AddressNotInWhitelist() {
+		assertThat(ip6whitelist.contains("2001:1:0:0:0:0:0:0"), equalTo(false));
+
+		assertThat(ip6whitelist.contains("2001:0:1::"), equalTo(false));
+
+		assertThat(ip6whitelist.contains("2002:0:0:0:0:0:0:1"), equalTo(false));
+
+		assertThat(ip6whitelist.contains("2001:0:0:1:ffff:ffff:ffff:ffff"), equalTo(false));
+	}
+
+	@Test
+	public void testWhitelistCreationLeafFirst() throws IOException, JsonUtilsException, NetworkNodeException {
+		final ObjectMapper mapper = new ObjectMapper();
+
+		ip4whitelist.init(mapper.readTree("[\"10.0.2.0/24\", \"10.0.0.0/16\"]"));
+
+		assertThat(ip4whitelist.contains("10.0.2.1"), equalTo(true));
+
+		assertThat(ip4whitelist.contains("10.0.10.1"), equalTo(true));
+	}
+
+	@Test
+	public void testWhitelistCreationParentFirst() throws IOException, JsonUtilsException, NetworkNodeException {
+		final ObjectMapper mapper = new ObjectMapper();
+
+		ip4whitelist.init(mapper.readTree("[\"10.0.0.0/16\"], \"10.0.2.0/24\""));
+
+		assertThat(ip4whitelist.contains("10.0.2.1"), equalTo(true));
+
+		assertThat(ip4whitelist.contains("10.0.10.1"), equalTo(true));
+	}
+
+	/* IPv4 validation */
+
+	@Test(expected = IOException.class)
+	public void badIPv4Input1() throws IOException, JsonUtilsException, NetworkNodeException {
+		final ObjectMapper mapper = new ObjectMapper();
+		AnonymousIpWhitelist badlist = new AnonymousIpWhitelist();
+		badlist.init(mapper.readTree("[\"\"192.168.1/24\"]"));
+		assertThat(badlist.contains("192.168.0.1"), equalTo(false));
+	}
+
+	@Test(expected = IOException.class)
+	public void badIPv4Input2() throws IOException, JsonUtilsException, NetworkNodeException {
+		final ObjectMapper mapper = new ObjectMapper();
+		AnonymousIpWhitelist badlist = new AnonymousIpWhitelist();
+		badlist.init(mapper.readTree("[\"\"256.168.0.1/24\"]"));
+		assertThat(badlist.contains("192.168.0.1"), equalTo(false));
+	}
+
+	@Test(expected = IOException.class)
+	public void badNetmaskInput1() throws IOException, JsonUtilsException, NetworkNodeException {
+		final ObjectMapper mapper = new ObjectMapper();
+		AnonymousIpWhitelist badlist = new AnonymousIpWhitelist();
+		badlist.init(mapper.readTree("[\"\"192.168.0.1/33\"]"));
+		assertThat(badlist.contains("192.168.0.1"), equalTo(false));
+	}
+
+	@Test(expected = IOException.class)
+	public void badNetmaskInput2() throws IOException, JsonUtilsException, NetworkNodeException {
+		final ObjectMapper mapper = new ObjectMapper();
+		AnonymousIpWhitelist badlist = new AnonymousIpWhitelist();
+		badlist.init(mapper.readTree("[\"\"::1/129\"]"));
+		assertThat(badlist.contains("::1"), equalTo(false));
+	}
+
+	@Test(expected = IOException.class)
+	public void badNetmaskInput3() throws IOException, JsonUtilsException, NetworkNodeException {
+		final ObjectMapper mapper = new ObjectMapper();
+		AnonymousIpWhitelist badlist = new AnonymousIpWhitelist();
+		badlist.init(mapper.readTree("[\"\"192.168.0.1/-1\"]"));
+		assertThat(badlist.contains("192.168.0.1"), equalTo(false));
+	}
+
+	@Test(expected = IOException.class)
+	public void validIPv4Input() throws IOException, JsonUtilsException, NetworkNodeException {
+		final ObjectMapper mapper = new ObjectMapper();
+		AnonymousIpWhitelist badlist = new AnonymousIpWhitelist();
+		badlist.init(mapper.readTree("[\"\"192.168.0.1/32\"]"));
+		assertThat(badlist.contains("192.168.0.1"), equalTo(false));
+	}
+
+	@Test(expected = IOException.class)
+	public void validIPv6Input() throws IOException, JsonUtilsException, NetworkNodeException {
+		final ObjectMapper mapper = new ObjectMapper();
+		AnonymousIpWhitelist badlist = new AnonymousIpWhitelist();
+		badlist.init(mapper.readTree("[\"\"::1/128\"]"));
+		assertThat(badlist.contains("::1"), equalTo(false));
+	}
+
+	/* NetworkNode takes forever to create Tree - commented out until it is needed
+	@Test
+	public void testAnonymousIpWhitelistPerformance65000() throws NetworkNodeException {
+		AnonymousIpWhitelist whitelist = new AnonymousIpWhitelist();
+		List<String> tempList = new ArrayList<>();
+		// add a bunch of ips to the whitelist
+
+		for (int i = 0; i < 255; i++) {
+			for (int j = 0; j < 255; j++) {
+				int a = ThreadLocalRandom.current().nextInt(1, 254 + 1);
+				int b = ThreadLocalRandom.current().nextInt(1, 254 + 1);
+				int c = ThreadLocalRandom.current().nextInt(1, 254 + 1);
+				int d = ThreadLocalRandom.current().nextInt(1, 254 + 1);
+				tempList.add(String.format("%s.%s.%s.%s", a, b, c, d));
+			}
+		}
+
+		long startTime = System.nanoTime();
+
+		for (int i = 0; i < tempList.size(); i++) {
+			whitelist.add(tempList.get(i) + "/32");
+		}
+
+		long durationTime = System.nanoTime() - startTime;
+
+		System.out.println(String.format("Anonymous IP Whitelist creation took %s nanoseconds to create tree of %d subnets", Long.toString(durationTime),
+				tempList.size()));
+
+		int total = 1000;
+
+		long start = System.nanoTime();
+
+		for (int i = 0; i <= total; i++) {
+			whitelist.contains("192.168.30.1");
+		}
+
+		long duration = System.nanoTime() - start;
+
+		System.out.println(
+				String.format("Anonymous IP Whitelist average lookup took %s nanoseconds for %d ips", Long.toString(duration / total), tempList.size()));
+	}
+	*/
+	@Test
+	public void testAddSubnets() throws NetworkNodeException {
+		AnonymousIpWhitelist whitelist = new AnonymousIpWhitelist();
+
+		whitelist.add("192.168.1.1/32");
+		assertThat(whitelist.contains("192.168.1.1"), equalTo(true));
+
+		whitelist.add("192.168.1.0/24");
+		assertThat(whitelist.contains("192.168.1.255"), equalTo(true));
+		assertThat(whitelist.contains("192.168.1.167"), equalTo(true));
+
+		whitelist.add("192.168.1.0/27");
+		assertThat(whitelist.contains("192.168.1.255"), equalTo(true));
+		assertThat(whitelist.contains("192.168.1.167"), equalTo(true));
+
+		whitelist.add("10.0.0.1/32");
+		assertThat(whitelist.contains("10.0.0.1"), equalTo(true));
+		assertThat(whitelist.contains("10.0.0.2"), equalTo(false));
+		assertThat(whitelist.contains("192.168.2.1"), equalTo(false));
+		assertThat(whitelist.contains("192.168.2.255"), equalTo(false));
+		assertThat(whitelist.contains("192.167.1.1"), equalTo(false));
+		assertThat(whitelist.contains("192.169.1.1"), equalTo(false));
+		assertThat(whitelist.contains("10.0.0.0"), equalTo(false));
+	}
+}
diff --git a/traffic_router/core/src/test/resources/anonymous_ip.json b/traffic_router/core/src/test/resources/anonymous_ip.json
new file mode 100644
index 000000000..05f686aa1
--- /dev/null
+++ b/traffic_router/core/src/test/resources/anonymous_ip.json
@@ -0,0 +1,18 @@
+{
+
+   "customer": "Cisco",
+   "version": "1",
+   "date" : "2017-05-23 03:28:25",
+   "name": "Anonymous IP Blocking Policy",
+
+   "anonymousIp": { "blockAnonymousVPN": true,
+                    "blockHostingProvider": true, 
+                    "blockPublicProxy": true,
+                    "blockTorExitNode": false},
+                    
+   "ip4Whitelist": ["192.168.30.0/24", "10.0.2.0/24", "5.34.32.0/24"],
+   
+   "ip6Whitelist": ["2001:550:90a::/48", "::1/128"],
+   
+   "redirectUrl": "http://youvebeenblocked.com"
+}
\ No newline at end of file
diff --git a/traffic_router/core/src/test/resources/anonymous_ip_no_whitelist.json b/traffic_router/core/src/test/resources/anonymous_ip_no_whitelist.json
new file mode 100644
index 000000000..1e0ba93b9
--- /dev/null
+++ b/traffic_router/core/src/test/resources/anonymous_ip_no_whitelist.json
@@ -0,0 +1,13 @@
+{
+
+   "customer": "Cisco",
+   "version": "1",
+   "date" : "2017-05-23 03:28:25",
+   "name": "Anonymous IP Blocking Policy",
+
+   "anonymousIp": { "blockAnonymousVPN": true,
+                    "blockHostingProvider": true, 
+                    "blockPublicProxy": true,
+                    "blockTorExitNode": false},
+   "redirectUrl": "http://youvebeenblocked.com"
+}
\ No newline at end of file


 

----------------------------------------------------------------
This is an automated message from the Apache Git Service.
To respond to the message, please log on GitHub and use the
URL above to go to the specific comment.
 
For queries about this service, please contact Infrastructure at:
users@infra.apache.org


With regards,
Apache Git Services