This is an automated email from the ASF dual-hosted git repository. elsloo pushed a commit to branch master in repository https://gitbox.apache.org/repos/asf/incubator-trafficcontrol.git
commit f2542e62394fe93d67367c3411c9c6751e51b0dd Author: PeterRyder <peter.w.ry...@gmail.com> AuthorDate: Tue Jul 18 13:24:04 2017 -0400 Initial commit of open source anonymous ip blocking --- .../traffic_router/core/config/ConfigHandler.java | 66 ++++++ .../traffic_router/core/ds/DeliveryService.java | 8 +- .../traffic_router/core/loc/AnonymousIp.java | 241 +++++++++++++++++++ .../core/loc/AnonymousIpConfigUpdater.java | 53 +++++ .../core/loc/AnonymousIpDatabaseService.java | 117 ++++++++++ .../core/loc/AnonymousIpDatabaseUpdater.java | 65 ++++++ .../core/loc/AnonymousIpWhitelist.java | 48 ++++ .../traffic_router/core/router/StatTracker.java | 2 +- .../traffic_router/core/router/TrafficRouter.java | 22 +- .../core/router/TrafficRouterManager.java | 8 +- .../src/main/webapp/WEB-INF/applicationContext.xml | 23 ++ .../core/loc/AnonymousIpDatabaseServiceTest.java | 81 +++++++ .../traffic_router/core/loc/AnonymousIpTest.java | 258 +++++++++++++++++++++ .../core/loc/AnonymousIpWhitelistTest.java | 227 ++++++++++++++++++ .../core/src/test/resources/anonymous_ip.json | 16 ++ .../test/resources/anonymous_ip_no_whitelist.json | 12 + 16 files changed, 1243 insertions(+), 4 deletions(-) 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 ea17db0..aa55f0a 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.util.TrafficOpsUtils; 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 @@ public class ConfigHandler { 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 class ConfigHandler { 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 class ConfigHandler { 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 class ConfigHandler { 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,50 @@ public class ConfigHandler { } } + private void parseAnonymousIpConfig(final JSONObject jo) throws JSONException { + final String anonymousPollingUrl = "anonymousip.polling.url"; + final String anonymousPollingInterval = "anonymousip.polling.interval"; + final String anonymousPolicyConfiguration = "anonymousip.policy.configuration"; + + final JSONObject config = jo.getJSONObject("config"); + final String configUrl = config.optString(anonymousPolicyConfiguration, null); + final String databaseUrl = config.optString(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 JSONObject dss = jo.getJSONObject(deliveryServicesKey); + for (final String ds : JSONObject.getNames(dss)) { + if (dss.getJSONObject(ds).has("anonymousBlockingEnabled") && + dss.getJSONObject(ds).getString("anonymousBlockingEnabled").equals("true")) { + final long interval = config.optLong(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 4aadd70..41637b3 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 @@ -51,7 +51,7 @@ import com.comcast.cdn.traffic_control.traffic_router.core.router.StatTracker.Tr import com.comcast.cdn.traffic_control.traffic_router.core.router.StatTracker.Track.ResultDetails; 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 @@ public class DeliveryService { 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 class DeliveryService { 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 class DeliveryService { 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 0000000..bc1452e --- /dev/null +++ b/traffic_router/core/src/main/java/com/comcast/cdn/traffic_control/traffic_router/core/loc/AnonymousIp.java @@ -0,0 +1,241 @@ +/* + * 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.FileReader; +import java.net.InetAddress; +import java.net.MalformedURLException; + +import org.apache.log4j.Logger; +import org.apache.wicket.ajax.json.JSONException; +import org.apache.wicket.ajax.json.JSONObject; +import org.apache.wicket.ajax.json.JSONTokener; + +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.google.common.net.InetAddresses; +import com.maxmind.geoip2.model.AnonymousIpResponse; + +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; + + 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 JSONObject config, final AnonymousIp anonymousIp) throws JSONException { + if (config.optJSONArray("ip4Whitelist") != null) { + try { + anonymousIp.ipv4Whitelist = new AnonymousIpWhitelist(); + anonymousIp.ipv4Whitelist.init(config.optJSONArray("ip4Whitelist")); + } catch (NetworkNodeException e) { + LOGGER.error("Anonymous Ip ERR: Network node err ", e); + } + } + } + + private static void parseIPv6Whitelist(final JSONObject config, final AnonymousIp anonymousIp) throws JSONException { + if (config.optJSONArray("ip6Whitelist") != null) { + try { + anonymousIp.ipv6Whitelist = new AnonymousIpWhitelist(); + anonymousIp.ipv6Whitelist.init(config.optJSONArray("ip6Whitelist")); + } catch (NetworkNodeException e) { + LOGGER.error("Anonymous Ip ERR: Network node err ", e); + } + } + } + + @SuppressWarnings({ "PMD.NPathComplexity", "PMD.CyclomaticComplexity" }) + private static AnonymousIp parseConfigJson(final JSONObject config) { + final AnonymousIp anonymousIp = new AnonymousIp(); + try { + final JSONObject blockingTypes = config.getJSONObject("anonymousIp"); + + anonymousIp.blockAnonymousIp = blockingTypes.getBoolean("blockAnonymousVPN"); + anonymousIp.blockHostingProvider = blockingTypes.getBoolean("blockHostingProvider"); + anonymousIp.blockPublicProxy = blockingTypes.getBoolean("blockPublicProxy"); + anonymousIp.blockTorExitNode = blockingTypes.getBoolean("blockTorExitNode"); + + anonymousIp.enabled = AnonymousIp.currentConfig.enabled; + + parseIPv4Whitelist(config, anonymousIp); + parseIPv6Whitelist(config, anonymousIp); + + 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) { + JSONObject json = null; + try { + json = new JSONObject(new JSONTokener(new FileReader(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); + } + } +} 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 0000000..324bb32 --- /dev/null +++ b/traffic_router/core/src/main/java/com/comcast/cdn/traffic_control/traffic_router/core/loc/AnonymousIpConfigUpdater.java @@ -0,0 +1,53 @@ +/* + * Copyright 2017 Cisco Systems, Inc. + * + * 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 0000000..7fe368a --- /dev/null +++ b/traffic_router/core/src/main/java/com/comcast/cdn/traffic_control/traffic_router/core/loc/AnonymousIpDatabaseService.java @@ -0,0 +1,117 @@ +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 0000000..17aa623 --- /dev/null +++ b/traffic_router/core/src/main/java/com/comcast/cdn/traffic_control/traffic_router/core/loc/AnonymousIpDatabaseUpdater.java @@ -0,0 +1,65 @@ +/* + * Copyright 2017 Cisco Systems, Inc. + * + * 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 0000000..d8bc3fc --- /dev/null +++ b/traffic_router/core/src/main/java/com/comcast/cdn/traffic_control/traffic_router/core/loc/AnonymousIpWhitelist.java @@ -0,0 +1,48 @@ +package com.comcast.cdn.traffic_control.traffic_router.core.loc; + +import org.apache.log4j.Logger; +import org.apache.wicket.ajax.json.JSONArray; +import org.apache.wicket.ajax.json.JSONException; + +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 JSONArray config) throws JSONException, NetworkNodeException { + for (int i = 0; i < config.length(); i++) { + final String network = config.getString(i); + 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 b444cd8..ccc675c 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 class StatTracker { } 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 9662fd8..43a94ad 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.router.StatTracker.Tr 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 @@ public class TrafficRouter { 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 class TrafficRouter { 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 class TrafficRouter { 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 class TrafficRouter { 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 class TrafficRouter { 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()); @@ -574,6 +584,16 @@ public class TrafficRouter { 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 26b20c2..941ba51 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 com.comcast.cdn.traffic_control.traffic_router.core.util.TrafficOpsUtils; 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 @@ public class TrafficRouterManager implements ApplicationListener<ContextRefreshe 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 class TrafficRouterManager implements ApplicationListener<ContextRefreshe 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 class TrafficRouterManager implements ApplicationListener<ContextRefreshe 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 fb5cca3..a46fdf7 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 0000000..1333894 --- /dev/null +++ b/traffic_router/core/src/test/java/com/comcast/cdn/traffic_control/traffic_router/core/loc/AnonymousIpDatabaseServiceTest.java @@ -0,0 +1,81 @@ +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 0000000..271c62d --- /dev/null +++ b/traffic_router/core/src/test/java/com/comcast/cdn/traffic_control/traffic_router/core/loc/AnonymousIpTest.java @@ -0,0 +1,258 @@ +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 0000000..1dfe57c --- /dev/null +++ b/traffic_router/core/src/test/java/com/comcast/cdn/traffic_control/traffic_router/core/loc/AnonymousIpWhitelistTest.java @@ -0,0 +1,227 @@ +package com.comcast.cdn.traffic_control.traffic_router.core.loc; + +import static org.hamcrest.CoreMatchers.equalTo; +import static org.hamcrest.MatcherAssert.assertThat; + +import java.util.ArrayList; +import java.util.List; +import java.util.concurrent.ThreadLocalRandom; + +import org.apache.wicket.ajax.json.JSONArray; +import org.apache.wicket.ajax.json.JSONException; +import org.junit.Before; +import org.junit.Test; + +public class AnonymousIpWhitelistTest { + + AnonymousIpWhitelist ip4whitelist; + AnonymousIpWhitelist ip6whitelist; + + @Before + public void setup() throws JSONException, NetworkNodeException { + ip4whitelist = new AnonymousIpWhitelist(); + ip4whitelist.init(new JSONArray("[\"192.168.30.0/24\", \"10.0.2.0/24\", \"10.0.0.0/16\"]")); + + ip6whitelist = new AnonymousIpWhitelist(); + ip6whitelist.init(new JSONArray("[\"::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 JSONException, NetworkNodeException { + ip4whitelist.init(new JSONArray("[\"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 JSONException, NetworkNodeException { + ip4whitelist.init(new JSONArray("[\"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 = JSONException.class) + public void badIPv4Input1() throws JSONException, NetworkNodeException { + AnonymousIpWhitelist badlist = new AnonymousIpWhitelist(); + badlist.init(new JSONArray("[\"\"192.168.1/24\"]")); + assertThat(badlist.contains("192.168.0.1"), equalTo(false)); + } + + @Test(expected = JSONException.class) + public void badIPv4Input2() throws JSONException, NetworkNodeException { + AnonymousIpWhitelist badlist = new AnonymousIpWhitelist(); + badlist.init(new JSONArray("[\"\"256.168.0.1/24\"]")); + assertThat(badlist.contains("192.168.0.1"), equalTo(false)); + } + + @Test(expected = JSONException.class) + public void badNetmaskInput1() throws JSONException, NetworkNodeException { + AnonymousIpWhitelist badlist = new AnonymousIpWhitelist(); + badlist.init(new JSONArray("[\"\"192.168.0.1/33\"]")); + assertThat(badlist.contains("192.168.0.1"), equalTo(false)); + } + + @Test(expected = JSONException.class) + public void badNetmaskInput2() throws JSONException, NetworkNodeException { + AnonymousIpWhitelist badlist = new AnonymousIpWhitelist(); + badlist.init(new JSONArray("[\"\"::1/129\"]")); + assertThat(badlist.contains("::1"), equalTo(false)); + } + + @Test(expected = JSONException.class) + public void badNetmaskInput3() throws JSONException, NetworkNodeException { + AnonymousIpWhitelist badlist = new AnonymousIpWhitelist(); + badlist.init(new JSONArray("[\"\"192.168.0.1/-1\"]")); + assertThat(badlist.contains("192.168.0.1"), equalTo(false)); + } + + @Test(expected = JSONException.class) + public void validIPv4Input() throws JSONException, NetworkNodeException { + AnonymousIpWhitelist badlist = new AnonymousIpWhitelist(); + badlist.init(new JSONArray("[\"\"192.168.0.1/32\"]")); + assertThat(badlist.contains("192.168.0.1"), equalTo(false)); + } + + @Test(expected = JSONException.class) + public void validIPv6Input() throws JSONException, NetworkNodeException { + AnonymousIpWhitelist badlist = new AnonymousIpWhitelist(); + badlist.init(new JSONArray("[\"\"::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 0000000..e64d0a5 --- /dev/null +++ b/traffic_router/core/src/test/resources/anonymous_ip.json @@ -0,0 +1,16 @@ +{ + + "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"] +} \ 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 0000000..2f0f18e --- /dev/null +++ b/traffic_router/core/src/test/resources/anonymous_ip_no_whitelist.json @@ -0,0 +1,12 @@ +{ + + "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}, +} \ No newline at end of file -- To stop receiving notification emails like this one, please contact els...@apache.org.