mirror of
https://github.com/signalapp/Signal-Server
synced 2026-04-19 19:48:04 +01:00
Remove obsolete turn implementations
This commit is contained in:
@@ -301,27 +301,6 @@ public class WhisperServerConfiguration extends Configuration {
|
||||
@JsonProperty
|
||||
private VirtualThreadConfiguration virtualThread = new VirtualThreadConfiguration(Duration.ofMillis(1));
|
||||
|
||||
|
||||
@Valid
|
||||
@NotNull
|
||||
@JsonProperty
|
||||
private S3ObjectMonitorFactory maxmindCityDatabase;
|
||||
|
||||
@Valid
|
||||
@NotNull
|
||||
@JsonProperty
|
||||
private S3ObjectMonitorFactory callingTurnDnsRecords;
|
||||
|
||||
@Valid
|
||||
@NotNull
|
||||
@JsonProperty
|
||||
private S3ObjectMonitorFactory callingTurnPerformanceTable;
|
||||
|
||||
@Valid
|
||||
@NotNull
|
||||
@JsonProperty
|
||||
private S3ObjectMonitorFactory callingTurnManualTable;
|
||||
|
||||
@Valid
|
||||
@NotNull
|
||||
@JsonProperty
|
||||
@@ -539,22 +518,6 @@ public class WhisperServerConfiguration extends Configuration {
|
||||
return virtualThread;
|
||||
}
|
||||
|
||||
public S3ObjectMonitorFactory getMaxmindCityDatabase() {
|
||||
return maxmindCityDatabase;
|
||||
}
|
||||
|
||||
public S3ObjectMonitorFactory getCallingTurnDnsRecords() {
|
||||
return callingTurnDnsRecords;
|
||||
}
|
||||
|
||||
public S3ObjectMonitorFactory getCallingTurnPerformanceTable() {
|
||||
return callingTurnPerformanceTable;
|
||||
}
|
||||
|
||||
public S3ObjectMonitorFactory getCallingTurnManualTable() {
|
||||
return callingTurnManualTable;
|
||||
}
|
||||
|
||||
public NoiseWebSocketTunnelConfiguration getNoiseWebSocketTunnelConfiguration() {
|
||||
return noiseTunnel;
|
||||
}
|
||||
|
||||
@@ -85,7 +85,6 @@ import org.whispersystems.textsecuregcm.auth.ExternalServiceCredentialsGenerator
|
||||
import org.whispersystems.textsecuregcm.auth.IdlePrimaryDeviceAuthenticatedWebSocketUpgradeFilter;
|
||||
import org.whispersystems.textsecuregcm.auth.PhoneVerificationTokenManager;
|
||||
import org.whispersystems.textsecuregcm.auth.RegistrationLockVerificationManager;
|
||||
import org.whispersystems.textsecuregcm.auth.TurnTokenGenerator;
|
||||
import org.whispersystems.textsecuregcm.auth.WebsocketRefreshApplicationEventListener;
|
||||
import org.whispersystems.textsecuregcm.auth.grpc.ProhibitAuthenticationInterceptor;
|
||||
import org.whispersystems.textsecuregcm.auth.grpc.RequireAuthenticationInterceptor;
|
||||
@@ -667,7 +666,6 @@ public class WhisperServerService extends Application<WhisperServerConfiguration
|
||||
|
||||
final MessageSender messageSender = new MessageSender(messagesManager, pushNotificationManager);
|
||||
final ReceiptSender receiptSender = new ReceiptSender(accountsManager, messageSender, receiptSenderExecutor);
|
||||
final TurnTokenGenerator turnTokenGenerator = new TurnTokenGenerator(config.getTurnConfiguration().secret().value());
|
||||
final CloudflareTurnCredentialsManager cloudflareTurnCredentialsManager = new CloudflareTurnCredentialsManager(
|
||||
config.getTurnConfiguration().cloudflare().apiToken().value(),
|
||||
config.getTurnConfiguration().cloudflare().endpoint(),
|
||||
|
||||
@@ -15,8 +15,8 @@ import java.net.Inet6Address;
|
||||
import java.net.URI;
|
||||
import java.net.http.HttpRequest;
|
||||
import java.net.http.HttpResponse;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.Optional;
|
||||
import java.util.concurrent.CompletionException;
|
||||
import java.util.concurrent.ExecutorService;
|
||||
import java.util.concurrent.ScheduledExecutorService;
|
||||
@@ -127,11 +127,11 @@ public class CloudflareTurnCredentialsManager {
|
||||
final CloudflareTurnResponse cloudflareTurnResponse = SystemMapper.jsonMapper()
|
||||
.readValue(response.body(), CloudflareTurnResponse.class);
|
||||
|
||||
return TurnTokenGenerator.from(
|
||||
return new TurnToken(
|
||||
cloudflareTurnResponse.iceServers().username(),
|
||||
cloudflareTurnResponse.iceServers().credential(),
|
||||
Optional.ofNullable(cloudflareTurnUrls),
|
||||
Optional.ofNullable(cloudflareTurnComposedUrls),
|
||||
cloudflareTurnUrls == null ? Collections.emptyList() : cloudflareTurnUrls,
|
||||
cloudflareTurnComposedUrls,
|
||||
cloudflareTurnHostname
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1,87 +0,0 @@
|
||||
/*
|
||||
* Copyright 2013-2020 Signal Messenger, LLC
|
||||
* SPDX-License-Identifier: AGPL-3.0-only
|
||||
*/
|
||||
|
||||
package org.whispersystems.textsecuregcm.auth;
|
||||
|
||||
import java.security.InvalidKeyException;
|
||||
import java.security.NoSuchAlgorithmException;
|
||||
import java.security.SecureRandom;
|
||||
import java.time.Duration;
|
||||
import java.time.Instant;
|
||||
import java.util.Base64;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.Optional;
|
||||
import javax.crypto.Mac;
|
||||
import javax.crypto.spec.SecretKeySpec;
|
||||
import org.whispersystems.textsecuregcm.calls.routing.TurnServerOptions;
|
||||
import org.whispersystems.textsecuregcm.util.Util;
|
||||
|
||||
public class TurnTokenGenerator {
|
||||
|
||||
private final byte[] turnSecret;
|
||||
|
||||
private static final String ALGORITHM = "HmacSHA1";
|
||||
|
||||
private static final String WithUrlsProtocol = "00";
|
||||
|
||||
private static final String WithIpsProtocol = "01";
|
||||
|
||||
public TurnTokenGenerator(final byte[] turnSecret) {
|
||||
this.turnSecret = turnSecret;
|
||||
}
|
||||
|
||||
public TurnToken generateWithTurnServerOptions(TurnServerOptions options) {
|
||||
return generateToken(options.hostname(), options.urlsWithIps(), options.urlsWithHostname());
|
||||
}
|
||||
|
||||
@SuppressWarnings("OptionalUsedAsFieldOrParameterType")
|
||||
private TurnToken generateToken(
|
||||
String hostname,
|
||||
Optional<List<String>> urlsWithIps,
|
||||
Optional<List<String>> urlsWithHostname
|
||||
) {
|
||||
try {
|
||||
final Mac mac = Mac.getInstance(ALGORITHM);
|
||||
final long validUntilSeconds = Instant.now().plus(Duration.ofDays(1)).getEpochSecond();
|
||||
final long user = Util.ensureNonNegativeInt(new SecureRandom().nextInt());
|
||||
final String userTime = validUntilSeconds + ":" + user;
|
||||
final String protocol = urlsWithIps.isEmpty() || urlsWithIps.get().isEmpty()
|
||||
? WithUrlsProtocol
|
||||
: WithIpsProtocol;
|
||||
final String protocolUserTime = userTime + "#" + protocol;
|
||||
|
||||
mac.init(new SecretKeySpec(turnSecret, ALGORITHM));
|
||||
final String password = Base64.getEncoder().encodeToString(mac.doFinal(protocolUserTime.getBytes()));
|
||||
|
||||
return from(
|
||||
protocolUserTime,
|
||||
password,
|
||||
urlsWithHostname,
|
||||
urlsWithIps,
|
||||
hostname
|
||||
);
|
||||
} catch (final NoSuchAlgorithmException | InvalidKeyException e) {
|
||||
throw new AssertionError(e);
|
||||
}
|
||||
}
|
||||
|
||||
@SuppressWarnings("OptionalUsedAsFieldOrParameterType")
|
||||
public static TurnToken from(
|
||||
String username,
|
||||
String password,
|
||||
Optional<List<String>> urls,
|
||||
Optional<List<String>> urlsWithIps,
|
||||
String hostname
|
||||
) {
|
||||
return new TurnToken(
|
||||
username,
|
||||
password,
|
||||
urls.orElse(Collections.emptyList()),
|
||||
urlsWithIps.orElse(Collections.emptyList()),
|
||||
hostname
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -1,74 +0,0 @@
|
||||
/*
|
||||
* Copyright 2024 Signal Messenger, LLC
|
||||
* SPDX-License-Identifier: AGPL-3.0-only
|
||||
*/
|
||||
|
||||
package org.whispersystems.textsecuregcm.calls.routing;
|
||||
|
||||
import org.whispersystems.textsecuregcm.configuration.TurnUriConfiguration;
|
||||
import org.whispersystems.textsecuregcm.configuration.dynamic.DynamicConfiguration;
|
||||
import org.whispersystems.textsecuregcm.configuration.dynamic.DynamicTurnConfiguration;
|
||||
import org.whispersystems.textsecuregcm.storage.DynamicConfigurationManager;
|
||||
import org.whispersystems.textsecuregcm.util.Pair;
|
||||
import org.whispersystems.textsecuregcm.util.WeightedRandomSelect;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.Optional;
|
||||
import java.util.Random;
|
||||
import java.util.UUID;
|
||||
|
||||
/** Uses DynamicConfig to help route a turn request */
|
||||
public class DynamicConfigTurnRouter {
|
||||
|
||||
private static final Random rng = new Random();
|
||||
|
||||
public static final long RANDOMIZE_RATE_BASIS = 100_000;
|
||||
|
||||
private final DynamicConfigurationManager<DynamicConfiguration> dynamicConfigurationManager;
|
||||
|
||||
public DynamicConfigTurnRouter(final DynamicConfigurationManager<DynamicConfiguration> dynamicConfigurationManager) {
|
||||
this.dynamicConfigurationManager = dynamicConfigurationManager;
|
||||
}
|
||||
|
||||
public List<String> targetedUrls(final UUID aci) {
|
||||
final DynamicTurnConfiguration turnConfig = dynamicConfigurationManager.getConfiguration().getTurnConfiguration();
|
||||
|
||||
final Optional<TurnUriConfiguration> enrolled = turnConfig.getUriConfigs().stream()
|
||||
.filter(config -> config.getEnrolledAcis().contains(aci))
|
||||
.findFirst();
|
||||
|
||||
return enrolled
|
||||
.map(turnUriConfiguration -> turnUriConfiguration.getUris().stream().toList())
|
||||
.orElse(Collections.emptyList());
|
||||
}
|
||||
|
||||
public List<String> randomUrls() {
|
||||
final DynamicTurnConfiguration turnConfig = dynamicConfigurationManager.getConfiguration().getTurnConfiguration();
|
||||
|
||||
// select from turn server sets by weighted choice
|
||||
return WeightedRandomSelect.select(turnConfig
|
||||
.getUriConfigs()
|
||||
.stream()
|
||||
.map(c -> new Pair<>(c.getUris(), c.getWeight())).toList());
|
||||
}
|
||||
|
||||
public String getHostname() {
|
||||
final DynamicTurnConfiguration turnConfig = dynamicConfigurationManager.getConfiguration().getTurnConfiguration();
|
||||
return turnConfig.getHostname();
|
||||
}
|
||||
|
||||
public long getRandomizeRate() {
|
||||
final DynamicTurnConfiguration turnConfig = dynamicConfigurationManager.getConfiguration().getTurnConfiguration();
|
||||
return turnConfig.getRandomizeRate();
|
||||
}
|
||||
|
||||
public int getDefaultInstanceIpCount() {
|
||||
final DynamicTurnConfiguration turnConfig = dynamicConfigurationManager.getConfiguration().getTurnConfiguration();
|
||||
return turnConfig.getDefaultInstanceIpCount();
|
||||
}
|
||||
|
||||
public boolean shouldRandomize() {
|
||||
long rate = getRandomizeRate();
|
||||
return rate >= RANDOMIZE_RATE_BASIS || rng.nextLong(0, DynamicConfigTurnRouter.RANDOMIZE_RATE_BASIS) < rate;
|
||||
}
|
||||
}
|
||||
@@ -1,190 +0,0 @@
|
||||
/*
|
||||
* Copyright 2023 Signal Messenger, LLC
|
||||
* SPDX-License-Identifier: AGPL-3.0-only
|
||||
*/
|
||||
|
||||
package org.whispersystems.textsecuregcm.calls.routing;
|
||||
|
||||
import com.maxmind.geoip2.DatabaseReader;
|
||||
import com.maxmind.geoip2.exception.GeoIp2Exception;
|
||||
import com.maxmind.geoip2.model.CityResponse;
|
||||
import org.apache.commons.lang3.tuple.Triple;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import org.whispersystems.textsecuregcm.util.Util;
|
||||
import javax.annotation.Nonnull;
|
||||
import java.io.IOException;
|
||||
import java.net.InetAddress;
|
||||
import java.util.*;
|
||||
import java.util.function.Supplier;
|
||||
import java.util.stream.IntStream;
|
||||
import java.util.stream.Stream;
|
||||
|
||||
/**
|
||||
* Returns routes based on performance tables, manually routing tables, and target routing. Falls back to a random Turn
|
||||
* instance that the server knows about.
|
||||
*/
|
||||
public class TurnCallRouter {
|
||||
|
||||
private final Logger logger = LoggerFactory.getLogger(TurnCallRouter.class);
|
||||
|
||||
private final Supplier<CallDnsRecords> callDnsRecords;
|
||||
private final Supplier<CallRoutingTable> performanceRouting;
|
||||
private final Supplier<CallRoutingTable> manualRouting;
|
||||
private final DynamicConfigTurnRouter configTurnRouter;
|
||||
private final Supplier<DatabaseReader> geoIp;
|
||||
// controls whether instance IPs are shuffled. using if & boolean is ~5x faster than a function pointer
|
||||
private final boolean stableSelect;
|
||||
|
||||
public TurnCallRouter(
|
||||
@Nonnull Supplier<CallDnsRecords> callDnsRecords,
|
||||
@Nonnull Supplier<CallRoutingTable> performanceRouting,
|
||||
@Nonnull Supplier<CallRoutingTable> manualRouting,
|
||||
@Nonnull DynamicConfigTurnRouter configTurnRouter,
|
||||
@Nonnull Supplier<DatabaseReader> geoIp,
|
||||
boolean stableSelect
|
||||
) {
|
||||
this.performanceRouting = performanceRouting;
|
||||
this.callDnsRecords = callDnsRecords;
|
||||
this.manualRouting = manualRouting;
|
||||
this.configTurnRouter = configTurnRouter;
|
||||
this.geoIp = geoIp;
|
||||
this.stableSelect = stableSelect;
|
||||
}
|
||||
|
||||
public TurnServerOptions getRoutingFor(
|
||||
@Nonnull final UUID aci,
|
||||
@Nonnull final Optional<InetAddress> clientAddress
|
||||
) {
|
||||
return getRoutingFor(aci, clientAddress, this.configTurnRouter.getDefaultInstanceIpCount());
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets Turn Instance addresses. Returns both the IPv4 and IPv6 addresses. Prefers to match the IP protocol of the
|
||||
* client address in datacenter selection. Returns 2 instance options of the preferred protocol for every one instance
|
||||
* of the other.
|
||||
* @param aci aci of client
|
||||
* @param clientAddress IP address to base routing on
|
||||
* @param instanceLimit max instances to return options for
|
||||
* @return Up to two * instanceLimit options, half in ipv4, half in ipv6
|
||||
*/
|
||||
public TurnServerOptions getRoutingFor(
|
||||
@Nonnull final UUID aci,
|
||||
@Nonnull final Optional<InetAddress> clientAddress,
|
||||
final int instanceLimit
|
||||
) {
|
||||
try {
|
||||
return getRoutingForInner(aci, clientAddress, instanceLimit);
|
||||
} catch(Exception e) {
|
||||
logger.error("Failed to perform routing", e);
|
||||
return new TurnServerOptions(this.configTurnRouter.getHostname(), null, Optional.of(this.configTurnRouter.randomUrls()));
|
||||
}
|
||||
}
|
||||
|
||||
TurnServerOptions getRoutingForInner(
|
||||
@Nonnull final UUID aci,
|
||||
@Nonnull final Optional<InetAddress> clientAddress,
|
||||
final int instanceLimit
|
||||
) {
|
||||
String hostname = this.configTurnRouter.getHostname();
|
||||
|
||||
List<String> targetedUrls = this.configTurnRouter.targetedUrls(aci);
|
||||
if(!targetedUrls.isEmpty()) {
|
||||
return new TurnServerOptions(hostname, Optional.empty(), Optional.ofNullable(targetedUrls));
|
||||
}
|
||||
|
||||
if(clientAddress.isEmpty() || this.configTurnRouter.shouldRandomize() || instanceLimit < 1) {
|
||||
return new TurnServerOptions(hostname, Optional.empty(), Optional.ofNullable(this.configTurnRouter.randomUrls()));
|
||||
}
|
||||
|
||||
CityResponse geoInfo;
|
||||
try {
|
||||
geoInfo = geoIp.get().city(clientAddress.get());
|
||||
} catch (IOException | GeoIp2Exception e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
Optional<String> subdivision = !geoInfo.getSubdivisions().isEmpty()
|
||||
? Optional.of(geoInfo.getSubdivisions().getFirst().getIsoCode())
|
||||
: Optional.empty();
|
||||
|
||||
List<String> datacenters = this.manualRouting.get().getDatacentersFor(
|
||||
clientAddress.get(),
|
||||
geoInfo.getContinent().getCode(),
|
||||
geoInfo.getCountry().getIsoCode(),
|
||||
subdivision
|
||||
);
|
||||
|
||||
if (datacenters.isEmpty()){
|
||||
datacenters = this.performanceRouting.get().getDatacentersFor(
|
||||
clientAddress.get(),
|
||||
geoInfo.getContinent().getCode(),
|
||||
geoInfo.getCountry().getIsoCode(),
|
||||
subdivision
|
||||
);
|
||||
}
|
||||
|
||||
List<String> urlsWithIps = getUrlsForInstances(
|
||||
selectInstances(
|
||||
datacenters,
|
||||
instanceLimit
|
||||
));
|
||||
return new TurnServerOptions(hostname, Optional.of(urlsWithIps), Optional.of(minimalRandomUrls()));
|
||||
}
|
||||
|
||||
// Includes only the udp options in the randomUrls
|
||||
private List<String> minimalRandomUrls(){
|
||||
return this.configTurnRouter.randomUrls().stream()
|
||||
.filter(s -> s.startsWith("turn:") && !s.endsWith("transport=tcp"))
|
||||
.toList();
|
||||
}
|
||||
|
||||
// returns balanced number of instances across provided datacenters, prioritizing the datacenters earlier in the list
|
||||
private List<String> selectInstances(List<String> datacenters, int instanceLimit) {
|
||||
if(datacenters.isEmpty() || instanceLimit == 0) {
|
||||
return Collections.emptyList();
|
||||
}
|
||||
|
||||
CallDnsRecords dnsRecords = this.callDnsRecords.get();
|
||||
List<List<InetAddress>> ipv4Options = datacenters.stream()
|
||||
.map(dc -> randomNOf(dnsRecords.aByRegion().get(dc), instanceLimit, stableSelect))
|
||||
.toList();
|
||||
List<List<InetAddress>> ipv6Options = datacenters.stream()
|
||||
.map(dc -> randomNOf(dnsRecords.aaaaByRegion().get(dc), instanceLimit, stableSelect))
|
||||
.toList();
|
||||
|
||||
List<InetAddress> ipv4Selection = selectFromOptions(ipv4Options, instanceLimit);
|
||||
List<InetAddress> ipv6Selection = selectFromOptions(ipv6Options, instanceLimit);
|
||||
|
||||
return Stream.concat(
|
||||
ipv4Selection.stream().map(InetAddress::getHostAddress),
|
||||
// map ipv6 to RFC3986 format i.e. surrounded by brackets
|
||||
ipv6Selection.stream().map(i -> String.format("[%s]", i.getHostAddress()))
|
||||
).toList();
|
||||
}
|
||||
|
||||
private static List<InetAddress> selectFromOptions(List<List<InetAddress>> recordsByDc, int instanceLimit) {
|
||||
return IntStream.range(0, recordsByDc.size())
|
||||
.mapToObj(dcIndex -> IntStream.range(0, recordsByDc.get(dcIndex).size())
|
||||
.mapToObj(addressIndex -> Triple.of(addressIndex, dcIndex, recordsByDc.get(dcIndex).get(addressIndex))))
|
||||
.flatMap(i -> i)
|
||||
.sorted(Comparator.comparingInt((Triple<Integer, Integer, InetAddress> t) -> t.getLeft())
|
||||
.thenComparingInt(Triple::getMiddle))
|
||||
.limit(instanceLimit)
|
||||
.sorted(Comparator.comparingInt(Triple::getMiddle))
|
||||
.map(Triple::getRight)
|
||||
.toList();
|
||||
}
|
||||
|
||||
private static <E> List<E> randomNOf(List<E> values, int n, boolean stableSelect) {
|
||||
return stableSelect ? Util.randomNOfStable(values, n) : Util.randomNOfShuffled(values, n);
|
||||
}
|
||||
|
||||
private static List<String> getUrlsForInstances(List<String> instanceIps) {
|
||||
return instanceIps.stream().flatMap(ip -> Stream.of(
|
||||
String.format("turn:%s", ip),
|
||||
String.format("turn:%s:80?transport=tcp", ip),
|
||||
String.format("turns:%s:443?transport=tcp", ip)
|
||||
)
|
||||
).toList();
|
||||
}
|
||||
}
|
||||
@@ -7,6 +7,7 @@ package org.whispersystems.textsecuregcm.configuration;
|
||||
|
||||
import jakarta.validation.Valid;
|
||||
import jakarta.validation.constraints.NotBlank;
|
||||
import jakarta.validation.constraints.NotEmpty;
|
||||
import jakarta.validation.constraints.NotNull;
|
||||
import java.util.List;
|
||||
import jakarta.validation.constraints.Positive;
|
||||
@@ -15,8 +16,8 @@ import org.whispersystems.textsecuregcm.configuration.secrets.SecretString;
|
||||
public record CloudflareTurnConfiguration(@NotNull SecretString apiToken,
|
||||
@NotBlank String endpoint,
|
||||
@NotBlank long ttl,
|
||||
@NotBlank List<String> urls,
|
||||
@NotBlank List<String> urlsWithIps,
|
||||
@NotNull @NotEmpty @Valid List<@NotBlank String> urls,
|
||||
@NotNull @NotEmpty @Valid List<@NotBlank String> urlsWithIps,
|
||||
@NotNull @Valid CircuitBreakerConfiguration circuitBreaker,
|
||||
@NotNull @Valid RetryConfiguration retry,
|
||||
@NotBlank String hostname,
|
||||
|
||||
@@ -5,7 +5,5 @@
|
||||
|
||||
package org.whispersystems.textsecuregcm.configuration;
|
||||
|
||||
import org.whispersystems.textsecuregcm.configuration.secrets.SecretBytes;
|
||||
|
||||
public record TurnConfiguration(SecretBytes secret, CloudflareTurnConfiguration cloudflare) {
|
||||
public record TurnConfiguration(CloudflareTurnConfiguration cloudflare) {
|
||||
}
|
||||
|
||||
@@ -42,10 +42,6 @@ public class DynamicConfiguration {
|
||||
@Valid
|
||||
private DynamicCaptchaConfiguration captcha = new DynamicCaptchaConfiguration();
|
||||
|
||||
@JsonProperty
|
||||
@Valid
|
||||
private DynamicTurnConfiguration turn = new DynamicTurnConfiguration();
|
||||
|
||||
@JsonProperty
|
||||
@Valid
|
||||
DynamicMessagePersisterConfiguration messagePersister = new DynamicMessagePersisterConfiguration();
|
||||
@@ -104,10 +100,6 @@ public class DynamicConfiguration {
|
||||
return captcha;
|
||||
}
|
||||
|
||||
public DynamicTurnConfiguration getTurnConfiguration() {
|
||||
return turn;
|
||||
}
|
||||
|
||||
public DynamicMessagePersisterConfiguration getMessagePersisterConfiguration() {
|
||||
return messagePersister;
|
||||
}
|
||||
|
||||
@@ -1,50 +0,0 @@
|
||||
/*
|
||||
* Copyright 2013-2020 Signal Messenger, LLC
|
||||
* SPDX-License-Identifier: AGPL-3.0-only
|
||||
*/
|
||||
|
||||
package org.whispersystems.textsecuregcm.configuration.dynamic;
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonProperty;
|
||||
import jakarta.validation.Valid;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import org.whispersystems.textsecuregcm.configuration.TurnUriConfiguration;
|
||||
|
||||
public class DynamicTurnConfiguration {
|
||||
|
||||
@JsonProperty
|
||||
private String hostname;
|
||||
|
||||
/**
|
||||
* Rate at which to prioritize a random turn URL to exercise all endpoints.
|
||||
* Based on a 100,000 basis, where 100,000 == 100%.
|
||||
*/
|
||||
@JsonProperty
|
||||
private long randomizeRate = 5_000;
|
||||
|
||||
/**
|
||||
* Number of instance ips to return in TURN routing request
|
||||
*/
|
||||
@JsonProperty
|
||||
private int defaultInstanceIpCount = 0;
|
||||
|
||||
@JsonProperty
|
||||
private List<@Valid TurnUriConfiguration> uriConfigs = Collections.emptyList();
|
||||
|
||||
public List<TurnUriConfiguration> getUriConfigs() {
|
||||
return uriConfigs;
|
||||
}
|
||||
|
||||
public long getRandomizeRate() {
|
||||
return randomizeRate;
|
||||
}
|
||||
|
||||
public int getDefaultInstanceIpCount() {
|
||||
return defaultInstanceIpCount;
|
||||
}
|
||||
|
||||
public String getHostname() {
|
||||
return hostname;
|
||||
}
|
||||
}
|
||||
@@ -1,102 +0,0 @@
|
||||
/*
|
||||
* Copyright 2023 Signal Messenger, LLC
|
||||
* SPDX-License-Identifier: AGPL-3.0-only
|
||||
*/
|
||||
|
||||
package org.whispersystems.textsecuregcm.geo;
|
||||
|
||||
import com.maxmind.db.CHMCache;
|
||||
import com.maxmind.geoip2.DatabaseReader;
|
||||
import io.dropwizard.lifecycle.Managed;
|
||||
import io.micrometer.core.instrument.Metrics;
|
||||
import io.micrometer.core.instrument.Timer;
|
||||
import java.io.BufferedInputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.util.concurrent.ScheduledExecutorService;
|
||||
import java.util.concurrent.atomic.AtomicReference;
|
||||
import java.util.function.Supplier;
|
||||
import org.apache.commons.compress.archivers.ArchiveEntry;
|
||||
import org.apache.commons.compress.archivers.tar.TarArchiveInputStream;
|
||||
import org.apache.commons.compress.compressors.gzip.GzipCompressorInputStream;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import org.whispersystems.textsecuregcm.configuration.S3ObjectMonitorFactory;
|
||||
import org.whispersystems.textsecuregcm.metrics.MetricsUtil;
|
||||
import org.whispersystems.textsecuregcm.s3.S3ObjectMonitor;
|
||||
import software.amazon.awssdk.auth.credentials.AwsCredentialsProvider;
|
||||
|
||||
public class MaxMindDatabaseManager implements Supplier<DatabaseReader>, Managed {
|
||||
|
||||
private final S3ObjectMonitor databaseMonitor;
|
||||
|
||||
private final AtomicReference<DatabaseReader> databaseReader = new AtomicReference<>();
|
||||
|
||||
private final String databaseTag;
|
||||
|
||||
private final Timer refreshTimer;
|
||||
|
||||
private static final Logger log = LoggerFactory.getLogger(MaxMindDatabaseManager.class);
|
||||
|
||||
public MaxMindDatabaseManager(final ScheduledExecutorService executorService,
|
||||
final AwsCredentialsProvider awsCredentialsProvider, final S3ObjectMonitorFactory configuration,
|
||||
final String databaseTag) {
|
||||
|
||||
this.databaseMonitor = configuration.build(awsCredentialsProvider, executorService);
|
||||
this.databaseTag = databaseTag;
|
||||
this.refreshTimer = Metrics.timer(MetricsUtil.name(MaxMindDatabaseManager.class, "refresh"), "db", databaseTag);
|
||||
}
|
||||
|
||||
private void handleDatabaseChanged(final InputStream inputStream) {
|
||||
refreshTimer.record(() -> {
|
||||
boolean foundDatabaseEntry = false;
|
||||
|
||||
try (final InputStream bufferedInputStream = new BufferedInputStream(inputStream);
|
||||
final GzipCompressorInputStream gzipInputStream = new GzipCompressorInputStream(bufferedInputStream);
|
||||
final TarArchiveInputStream tarInputStream = new TarArchiveInputStream(gzipInputStream)) {
|
||||
|
||||
ArchiveEntry nextEntry;
|
||||
|
||||
while ((nextEntry = tarInputStream.getNextEntry()) != null) {
|
||||
if (nextEntry.getName().toLowerCase().endsWith(".mmdb")) {
|
||||
foundDatabaseEntry = true;
|
||||
|
||||
final DatabaseReader oldReader = databaseReader.getAndSet(
|
||||
new DatabaseReader.Builder(tarInputStream).withCache(new CHMCache()).build()
|
||||
);
|
||||
if (oldReader != null) {
|
||||
oldReader.close();
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
} catch (final IOException e) {
|
||||
log.error(String.format("Failed to load MaxMind database, tag %s", databaseTag));
|
||||
}
|
||||
|
||||
if (!foundDatabaseEntry) {
|
||||
log.warn(String.format("No .mmdb entry loaded from input stream, tag %s", databaseTag));
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
public void start() throws Exception {
|
||||
databaseMonitor.start(this::handleDatabaseChanged);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void stop() throws Exception {
|
||||
databaseMonitor.stop();
|
||||
|
||||
final DatabaseReader reader = databaseReader.getAndSet(null);
|
||||
if (reader != null) {
|
||||
reader.close();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public DatabaseReader get() {
|
||||
return this.databaseReader.get();
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user