Introduce an ASN-to-IP manager.
This commit is contained in:
		
							parent
							
								
									1160af9522
								
							
						
					
					
						commit
						f8c623074b
					
				|  | @ -40,7 +40,7 @@ import org.whispersystems.textsecuregcm.configuration.RemoteConfigConfiguration; | ||||||
| import org.whispersystems.textsecuregcm.configuration.SecureBackupServiceConfiguration; | import org.whispersystems.textsecuregcm.configuration.SecureBackupServiceConfiguration; | ||||||
| import org.whispersystems.textsecuregcm.configuration.SecureStorageServiceConfiguration; | import org.whispersystems.textsecuregcm.configuration.SecureStorageServiceConfiguration; | ||||||
| import org.whispersystems.textsecuregcm.configuration.TestDeviceConfiguration; | import org.whispersystems.textsecuregcm.configuration.TestDeviceConfiguration; | ||||||
| import org.whispersystems.textsecuregcm.configuration.TorExitNodeConfiguration; | import org.whispersystems.textsecuregcm.configuration.MonitoredS3ObjectConfiguration; | ||||||
| import org.whispersystems.textsecuregcm.configuration.TurnConfiguration; | import org.whispersystems.textsecuregcm.configuration.TurnConfiguration; | ||||||
| import org.whispersystems.textsecuregcm.configuration.TwilioConfiguration; | import org.whispersystems.textsecuregcm.configuration.TwilioConfiguration; | ||||||
| import org.whispersystems.textsecuregcm.configuration.UnidentifiedDeliveryConfiguration; | import org.whispersystems.textsecuregcm.configuration.UnidentifiedDeliveryConfiguration; | ||||||
|  | @ -259,7 +259,12 @@ public class WhisperServerConfiguration extends Configuration { | ||||||
|   @Valid |   @Valid | ||||||
|   @NotNull |   @NotNull | ||||||
|   @JsonProperty |   @JsonProperty | ||||||
|   private TorExitNodeConfiguration tor; |   private MonitoredS3ObjectConfiguration torExitNodeList; | ||||||
|  | 
 | ||||||
|  |   @Valid | ||||||
|  |   @NotNull | ||||||
|  |   @JsonProperty | ||||||
|  |   private MonitoredS3ObjectConfiguration asnTable; | ||||||
| 
 | 
 | ||||||
|   @Valid |   @Valid | ||||||
|   @NotNull |   @NotNull | ||||||
|  | @ -450,8 +455,12 @@ public class WhisperServerConfiguration extends Configuration { | ||||||
|     return reportMessageDynamoDb; |     return reportMessageDynamoDb; | ||||||
|   } |   } | ||||||
| 
 | 
 | ||||||
|   public TorExitNodeConfiguration getTorExitNodeConfiguration() { |   public MonitoredS3ObjectConfiguration getTorExitNodeListConfiguration() { | ||||||
|     return tor; |     return torExitNodeList; | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |   public MonitoredS3ObjectConfiguration getAsnTableConfiguration() { | ||||||
|  |     return asnTable; | ||||||
|   } |   } | ||||||
| 
 | 
 | ||||||
|   public DonationConfiguration getDonationConfiguration() { |   public DonationConfiguration getDonationConfiguration() { | ||||||
|  |  | ||||||
|  | @ -186,6 +186,7 @@ import org.whispersystems.textsecuregcm.storage.ReportMessageManager; | ||||||
| import org.whispersystems.textsecuregcm.storage.ReservedUsernames; | import org.whispersystems.textsecuregcm.storage.ReservedUsernames; | ||||||
| import org.whispersystems.textsecuregcm.storage.Usernames; | import org.whispersystems.textsecuregcm.storage.Usernames; | ||||||
| import org.whispersystems.textsecuregcm.storage.UsernamesManager; | import org.whispersystems.textsecuregcm.storage.UsernamesManager; | ||||||
|  | import org.whispersystems.textsecuregcm.util.AsnManager; | ||||||
| import org.whispersystems.textsecuregcm.util.Constants; | import org.whispersystems.textsecuregcm.util.Constants; | ||||||
| import org.whispersystems.textsecuregcm.util.TorExitNodeManager; | import org.whispersystems.textsecuregcm.util.TorExitNodeManager; | ||||||
| import org.whispersystems.textsecuregcm.websocket.AuthenticatedConnectListener; | import org.whispersystems.textsecuregcm.websocket.AuthenticatedConnectListener; | ||||||
|  | @ -436,7 +437,8 @@ public class WhisperServerService extends Application<WhisperServerConfiguration | ||||||
|     GCMSender                  gcmSender                  = new GCMSender(gcmSenderExecutor, accountsManager, config.getGcmConfiguration().getApiKey()); |     GCMSender                  gcmSender                  = new GCMSender(gcmSenderExecutor, accountsManager, config.getGcmConfiguration().getApiKey()); | ||||||
|     RateLimiters               rateLimiters               = new RateLimiters(config.getLimitsConfiguration(), dynamicConfigurationManager, rateLimitersCluster); |     RateLimiters               rateLimiters               = new RateLimiters(config.getLimitsConfiguration(), dynamicConfigurationManager, rateLimitersCluster); | ||||||
|     ProvisioningManager        provisioningManager        = new ProvisioningManager(pubSubManager); |     ProvisioningManager        provisioningManager        = new ProvisioningManager(pubSubManager); | ||||||
|     TorExitNodeManager         torExitNodeManager         = new TorExitNodeManager(recurringJobExecutor, config.getTorExitNodeConfiguration()); |     TorExitNodeManager         torExitNodeManager         = new TorExitNodeManager(recurringJobExecutor, config.getTorExitNodeListConfiguration()); | ||||||
|  |     AsnManager                 asnManager                 = new AsnManager(recurringJobExecutor, config.getAsnTableConfiguration()); | ||||||
| 
 | 
 | ||||||
|     AccountAuthenticator                  accountAuthenticator                  = new AccountAuthenticator(accountsManager); |     AccountAuthenticator                  accountAuthenticator                  = new AccountAuthenticator(accountsManager); | ||||||
|     DisabledPermittedAccountAuthenticator disabledPermittedAccountAuthenticator = new DisabledPermittedAccountAuthenticator(accountsManager); |     DisabledPermittedAccountAuthenticator disabledPermittedAccountAuthenticator = new DisabledPermittedAccountAuthenticator(accountsManager); | ||||||
|  | @ -489,6 +491,7 @@ public class WhisperServerService extends Application<WhisperServerConfiguration | ||||||
|     environment.lifecycle().manage(clientPresenceManager); |     environment.lifecycle().manage(clientPresenceManager); | ||||||
|     environment.lifecycle().manage(currencyManager); |     environment.lifecycle().manage(currencyManager); | ||||||
|     environment.lifecycle().manage(torExitNodeManager); |     environment.lifecycle().manage(torExitNodeManager); | ||||||
|  |     environment.lifecycle().manage(asnManager); | ||||||
| 
 | 
 | ||||||
|     AWSCredentials         credentials               = new BasicAWSCredentials(config.getCdnConfiguration().getAccessKey(), config.getCdnConfiguration().getAccessSecret()); |     AWSCredentials         credentials               = new BasicAWSCredentials(config.getCdnConfiguration().getAccessKey(), config.getCdnConfiguration().getAccessSecret()); | ||||||
|     AWSCredentialsProvider credentialsProvider       = new AWSStaticCredentialsProvider(credentials); |     AWSCredentialsProvider credentialsProvider       = new AWSStaticCredentialsProvider(credentials); | ||||||
|  |  | ||||||
|  | @ -11,7 +11,7 @@ import javax.validation.Valid; | ||||||
| import javax.validation.constraints.NotBlank; | import javax.validation.constraints.NotBlank; | ||||||
| import java.time.Duration; | import java.time.Duration; | ||||||
| 
 | 
 | ||||||
| public class TorExitNodeConfiguration { | public class MonitoredS3ObjectConfiguration { | ||||||
| 
 | 
 | ||||||
|   @JsonProperty |   @JsonProperty | ||||||
|   @NotBlank |   @NotBlank | ||||||
|  | @ -0,0 +1,98 @@ | ||||||
|  | /* | ||||||
|  |  * Copyright 2013-2021 Signal Messenger, LLC | ||||||
|  |  * SPDX-License-Identifier: AGPL-3.0-only | ||||||
|  |  */ | ||||||
|  | 
 | ||||||
|  | package org.whispersystems.textsecuregcm.util; | ||||||
|  | 
 | ||||||
|  | import static com.codahale.metrics.MetricRegistry.name; | ||||||
|  | 
 | ||||||
|  | import com.amazonaws.services.s3.model.S3Object; | ||||||
|  | import com.google.common.annotations.VisibleForTesting; | ||||||
|  | import io.dropwizard.lifecycle.Managed; | ||||||
|  | import io.micrometer.core.instrument.Counter; | ||||||
|  | import io.micrometer.core.instrument.Metrics; | ||||||
|  | import io.micrometer.core.instrument.Timer; | ||||||
|  | import java.io.IOException; | ||||||
|  | import java.io.InputStream; | ||||||
|  | import java.io.InputStreamReader; | ||||||
|  | import java.net.Inet4Address; | ||||||
|  | import java.net.UnknownHostException; | ||||||
|  | import java.util.Optional; | ||||||
|  | import java.util.concurrent.ScheduledExecutorService; | ||||||
|  | import java.util.concurrent.atomic.AtomicReference; | ||||||
|  | import java.util.zip.GZIPInputStream; | ||||||
|  | import org.slf4j.Logger; | ||||||
|  | import org.slf4j.LoggerFactory; | ||||||
|  | import org.whispersystems.textsecuregcm.configuration.MonitoredS3ObjectConfiguration; | ||||||
|  | 
 | ||||||
|  | public class AsnManager implements Managed { | ||||||
|  | 
 | ||||||
|  |   private final S3ObjectMonitor asnTableMonitor; | ||||||
|  | 
 | ||||||
|  |   private final AtomicReference<AsnTable> asnTable = new AtomicReference<>(AsnTable.EMPTY); | ||||||
|  | 
 | ||||||
|  |   private static final Timer REFRESH_TIMER = Metrics.timer(name(AsnManager.class, "refresh")); | ||||||
|  |   private static final Counter REFRESH_ERRORS = Metrics.counter(name(AsnManager.class, "refreshErrors")); | ||||||
|  | 
 | ||||||
|  |   private static final Logger log = LoggerFactory.getLogger(AsnManager.class); | ||||||
|  | 
 | ||||||
|  |   public AsnManager( | ||||||
|  |       final ScheduledExecutorService scheduledExecutorService, | ||||||
|  |       final MonitoredS3ObjectConfiguration configuration) { | ||||||
|  | 
 | ||||||
|  |     this.asnTableMonitor = new S3ObjectMonitor( | ||||||
|  |         configuration.getS3Region(), | ||||||
|  |         configuration.getS3Bucket(), | ||||||
|  |         configuration.getObjectKey(), | ||||||
|  |         configuration.getMaxSize(), | ||||||
|  |         scheduledExecutorService, | ||||||
|  |         configuration.getRefreshInterval(), | ||||||
|  |         this::handleAsnTableChanged); | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |   @Override | ||||||
|  |   public void start() throws Exception { | ||||||
|  |     try { | ||||||
|  |       handleAsnTableChanged(asnTableMonitor.getObject()); | ||||||
|  |     } catch (final Exception e) { | ||||||
|  |       log.warn("Failed to load initial IP-to-ASN map", e); | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     asnTableMonitor.start(); | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |   @Override | ||||||
|  |   public void stop() throws Exception { | ||||||
|  |     asnTableMonitor.stop(); | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |   public Optional<Long> getAsn(final String address) { | ||||||
|  |     try { | ||||||
|  |       return asnTable.get().getAsn((Inet4Address) Inet4Address.getByName(address)); | ||||||
|  |     } catch (final UnknownHostException e) { | ||||||
|  |       log.warn("Could not parse \"{}\" as an Inet4Address", address); | ||||||
|  |       return Optional.empty(); | ||||||
|  |     } | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |   private void handleAsnTableChanged(final S3Object asnTableObject) { | ||||||
|  |     REFRESH_TIMER.record(() -> { | ||||||
|  |       try { | ||||||
|  |         handleAsnTableChanged(new GZIPInputStream(asnTableObject.getObjectContent())); | ||||||
|  |       } catch (final IOException e) { | ||||||
|  |         log.error("Retrieved object was not a gzip archive", e); | ||||||
|  |       } | ||||||
|  |     }); | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |   @VisibleForTesting | ||||||
|  |   void handleAsnTableChanged(final InputStream inputStream) { | ||||||
|  |     try (final InputStreamReader reader = new InputStreamReader(inputStream)) { | ||||||
|  |       asnTable.set(new AsnTable(reader)); | ||||||
|  |     } catch (final Exception e) { | ||||||
|  |       REFRESH_ERRORS.increment(); | ||||||
|  |       log.warn("Failed to refresh IP-to-ASN table", e); | ||||||
|  |     } | ||||||
|  |   } | ||||||
|  | } | ||||||
|  | @ -44,6 +44,8 @@ class AsnTable { | ||||||
|     } |     } | ||||||
|   } |   } | ||||||
| 
 | 
 | ||||||
|  |   public static final AsnTable EMPTY = new AsnTable(); | ||||||
|  | 
 | ||||||
|   public AsnTable(final Reader tsvReader) throws IOException { |   public AsnTable(final Reader tsvReader) throws IOException { | ||||||
|     final TreeMap<Long, AsnRange> treeMap = new TreeMap<>(); |     final TreeMap<Long, AsnRange> treeMap = new TreeMap<>(); | ||||||
| 
 | 
 | ||||||
|  | @ -60,6 +62,10 @@ class AsnTable { | ||||||
|     asnBlocksByFirstIp = treeMap; |     asnBlocksByFirstIp = treeMap; | ||||||
|   } |   } | ||||||
| 
 | 
 | ||||||
|  |   private AsnTable() { | ||||||
|  |     asnBlocksByFirstIp = new TreeMap<>(); | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|   public Optional<Long> getAsn(final Inet4Address address) { |   public Optional<Long> getAsn(final Inet4Address address) { | ||||||
|     final long addressAsLong = ipToLong(address); |     final long addressAsLong = ipToLong(address); | ||||||
| 
 | 
 | ||||||
|  |  | ||||||
|  | @ -24,7 +24,7 @@ import java.util.concurrent.atomic.AtomicReference; | ||||||
| import java.util.stream.Collectors; | import java.util.stream.Collectors; | ||||||
| import org.slf4j.Logger; | import org.slf4j.Logger; | ||||||
| import org.slf4j.LoggerFactory; | import org.slf4j.LoggerFactory; | ||||||
| import org.whispersystems.textsecuregcm.configuration.TorExitNodeConfiguration; | import org.whispersystems.textsecuregcm.configuration.MonitoredS3ObjectConfiguration; | ||||||
| 
 | 
 | ||||||
| /** | /** | ||||||
|  * A utility for checking whether IP addresses belong to Tor exit nodes using the "bulk exit list." |  * A utility for checking whether IP addresses belong to Tor exit nodes using the "bulk exit list." | ||||||
|  | @ -44,7 +44,7 @@ public class TorExitNodeManager implements Managed { | ||||||
| 
 | 
 | ||||||
|   public TorExitNodeManager( |   public TorExitNodeManager( | ||||||
|       final ScheduledExecutorService scheduledExecutorService, |       final ScheduledExecutorService scheduledExecutorService, | ||||||
|       final TorExitNodeConfiguration configuration) { |       final MonitoredS3ObjectConfiguration configuration) { | ||||||
| 
 | 
 | ||||||
|     this.exitListMonitor = new S3ObjectMonitor( |     this.exitListMonitor = new S3ObjectMonitor( | ||||||
|         configuration.getS3Region(), |         configuration.getS3Region(), | ||||||
|  |  | ||||||
|  | @ -0,0 +1,45 @@ | ||||||
|  | /* | ||||||
|  |  * Copyright 2013-2021 Signal Messenger, LLC | ||||||
|  |  * SPDX-License-Identifier: AGPL-3.0-only | ||||||
|  |  */ | ||||||
|  | 
 | ||||||
|  | package org.whispersystems.textsecuregcm.util; | ||||||
|  | 
 | ||||||
|  | import org.junit.jupiter.api.Test; | ||||||
|  | import org.whispersystems.textsecuregcm.configuration.MonitoredS3ObjectConfiguration; | ||||||
|  | 
 | ||||||
|  | import java.io.ByteArrayInputStream; | ||||||
|  | import java.io.IOException; | ||||||
|  | import java.io.InputStream; | ||||||
|  | import java.io.InputStreamReader; | ||||||
|  | import java.net.Inet4Address; | ||||||
|  | import java.nio.charset.StandardCharsets; | ||||||
|  | import java.util.Optional; | ||||||
|  | import java.util.concurrent.ScheduledExecutorService; | ||||||
|  | 
 | ||||||
|  | import static org.junit.Assert.assertFalse; | ||||||
|  | import static org.junit.Assert.assertTrue; | ||||||
|  | import static org.junit.jupiter.api.Assertions.*; | ||||||
|  | import static org.mockito.Mockito.mock; | ||||||
|  | 
 | ||||||
|  | class AsnManagerTest { | ||||||
|  | 
 | ||||||
|  |   @Test | ||||||
|  |   void getAsn() throws IOException { | ||||||
|  |     final MonitoredS3ObjectConfiguration configuration = new MonitoredS3ObjectConfiguration(); | ||||||
|  |     configuration.setS3Region("ap-northeast-3"); | ||||||
|  | 
 | ||||||
|  |     final AsnManager asnManager = new AsnManager(mock(ScheduledExecutorService.class), configuration); | ||||||
|  | 
 | ||||||
|  |     assertEquals(Optional.empty(), asnManager.getAsn("10.0.0.1")); | ||||||
|  | 
 | ||||||
|  |     try (final InputStream tableInputStream = getClass().getResourceAsStream("ip2asn-test.tsv")) { | ||||||
|  |       asnManager.handleAsnTableChanged(tableInputStream); | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     assertEquals(Optional.of(7922L), asnManager.getAsn("50.79.54.1")); | ||||||
|  |     assertEquals(Optional.of(7552L), asnManager.getAsn("27.79.32.1")); | ||||||
|  |     assertEquals(Optional.empty(), asnManager.getAsn("32.79.117.1")); | ||||||
|  |     assertEquals(Optional.empty(), asnManager.getAsn("10.0.0.1")); | ||||||
|  |   } | ||||||
|  | } | ||||||
|  | @ -13,14 +13,14 @@ import java.io.ByteArrayInputStream; | ||||||
| import java.nio.charset.StandardCharsets; | import java.nio.charset.StandardCharsets; | ||||||
| import java.util.concurrent.ScheduledExecutorService; | import java.util.concurrent.ScheduledExecutorService; | ||||||
| import org.junit.Test; | import org.junit.Test; | ||||||
| import org.whispersystems.textsecuregcm.configuration.TorExitNodeConfiguration; | import org.whispersystems.textsecuregcm.configuration.MonitoredS3ObjectConfiguration; | ||||||
| import org.whispersystems.textsecuregcm.redis.AbstractRedisClusterTest; | import org.whispersystems.textsecuregcm.redis.AbstractRedisClusterTest; | ||||||
| 
 | 
 | ||||||
| public class TorExitNodeManagerTest extends AbstractRedisClusterTest { | public class TorExitNodeManagerTest extends AbstractRedisClusterTest { | ||||||
| 
 | 
 | ||||||
|   @Test |   @Test | ||||||
|   public void testIsTorExitNode() { |   public void testIsTorExitNode() { | ||||||
|     final TorExitNodeConfiguration configuration = new TorExitNodeConfiguration(); |     final MonitoredS3ObjectConfiguration configuration = new MonitoredS3ObjectConfiguration(); | ||||||
|     configuration.setS3Region("ap-northeast-3"); |     configuration.setS3Region("ap-northeast-3"); | ||||||
| 
 | 
 | ||||||
|     final TorExitNodeManager torExitNodeManager = |     final TorExitNodeManager torExitNodeManager = | ||||||
|  |  | ||||||
		Loading…
	
		Reference in New Issue
	
	 Jon Chambers
						Jon Chambers