From d4322a2ed45cfeccbe9524d27eb7f1082baacbc1 Mon Sep 17 00:00:00 2001 From: Adel Lahlou Date: Wed, 25 Jun 2025 15:08:11 -0700 Subject: [PATCH] Remove latency based 1:1 call routing --- .../calls/routing/CallDnsRecords.java | 34 --- .../calls/routing/CallDnsRecordsManager.java | 80 -------- .../calls/routing/CallRoutingTable.java | 193 ------------------ .../routing/CallRoutingTableManager.java | 73 ------- .../calls/routing/CallRoutingTableParser.java | 185 ----------------- .../calls/routing/CidrBlock.java | 137 ------------- .../calls/routing/TurnServerOptions.java | 16 -- 7 files changed, 718 deletions(-) delete mode 100644 service/src/main/java/org/whispersystems/textsecuregcm/calls/routing/CallDnsRecords.java delete mode 100644 service/src/main/java/org/whispersystems/textsecuregcm/calls/routing/CallDnsRecordsManager.java delete mode 100644 service/src/main/java/org/whispersystems/textsecuregcm/calls/routing/CallRoutingTable.java delete mode 100644 service/src/main/java/org/whispersystems/textsecuregcm/calls/routing/CallRoutingTableManager.java delete mode 100644 service/src/main/java/org/whispersystems/textsecuregcm/calls/routing/CallRoutingTableParser.java delete mode 100644 service/src/main/java/org/whispersystems/textsecuregcm/calls/routing/CidrBlock.java delete mode 100644 service/src/main/java/org/whispersystems/textsecuregcm/calls/routing/TurnServerOptions.java diff --git a/service/src/main/java/org/whispersystems/textsecuregcm/calls/routing/CallDnsRecords.java b/service/src/main/java/org/whispersystems/textsecuregcm/calls/routing/CallDnsRecords.java deleted file mode 100644 index 278beb2c6..000000000 --- a/service/src/main/java/org/whispersystems/textsecuregcm/calls/routing/CallDnsRecords.java +++ /dev/null @@ -1,34 +0,0 @@ -/* - * Copyright 2024 Signal Messenger, LLC - * SPDX-License-Identifier: AGPL-3.0-only - */ - -package org.whispersystems.textsecuregcm.calls.routing; - -import jakarta.validation.constraints.NotNull; -import java.net.InetAddress; -import java.util.List; -import java.util.Map; - -public record CallDnsRecords( - @NotNull - Map> aByRegion, - @NotNull - Map> aaaaByRegion -) { - public String getSummary() { - int numARecords = aByRegion.values().stream().mapToInt(List::size).sum(); - int numAAAARecords = aaaaByRegion.values().stream().mapToInt(List::size).sum(); - return String.format( - "(A records, %s regions, %s records), (AAAA records, %s regions, %s records)", - aByRegion.size(), - numARecords, - aaaaByRegion.size(), - numAAAARecords - ); - } - - public static CallDnsRecords empty() { - return new CallDnsRecords(Map.of(), Map.of()); - } -} diff --git a/service/src/main/java/org/whispersystems/textsecuregcm/calls/routing/CallDnsRecordsManager.java b/service/src/main/java/org/whispersystems/textsecuregcm/calls/routing/CallDnsRecordsManager.java deleted file mode 100644 index 5660d0a17..000000000 --- a/service/src/main/java/org/whispersystems/textsecuregcm/calls/routing/CallDnsRecordsManager.java +++ /dev/null @@ -1,80 +0,0 @@ -/* - * Copyright 2023 Signal Messenger, LLC - * SPDX-License-Identifier: AGPL-3.0-only - */ - -package org.whispersystems.textsecuregcm.calls.routing; - -import com.fasterxml.jackson.core.StreamReadFeature; -import com.fasterxml.jackson.databind.ObjectMapper; -import com.fasterxml.jackson.databind.json.JsonMapper; -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.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 CallDnsRecordsManager implements Supplier, Managed { - - private final S3ObjectMonitor objectMonitor; - - private final AtomicReference callDnsRecords = new AtomicReference<>(); - - private final Timer refreshTimer; - - private static final Logger log = LoggerFactory.getLogger(CallDnsRecordsManager.class); - - private static final ObjectMapper objectMapper = JsonMapper.builder() - .enable(StreamReadFeature.INCLUDE_SOURCE_IN_LOCATION) - .build(); - - public CallDnsRecordsManager(final ScheduledExecutorService executorService, - final AwsCredentialsProvider awsCredentialsProvider, final S3ObjectMonitorFactory configuration) { - - this.objectMonitor = configuration.build(awsCredentialsProvider, executorService); - this.callDnsRecords.set(CallDnsRecords.empty()); - this.refreshTimer = Metrics.timer(MetricsUtil.name(CallDnsRecordsManager.class, "refresh")); - } - - private void handleDatabaseChanged(final InputStream inputStream) { - refreshTimer.record(() -> { - try (final InputStream bufferedInputStream = new BufferedInputStream(inputStream)) { - final CallDnsRecords newRecords = parseRecords(bufferedInputStream); - final CallDnsRecords oldRecords = callDnsRecords.getAndSet(newRecords); - log.info("Replaced dns records, old summary=[{}], new summary=[{}]", oldRecords != null ? oldRecords.getSummary() : "null", newRecords); - } catch (final IOException e) { - log.error("Failed to load Call DNS Records"); - } - }); - } - - static CallDnsRecords parseRecords(InputStream inputStream) throws IOException { - return objectMapper.readValue(inputStream, CallDnsRecords.class); - } - - @Override - public void start() throws Exception { - objectMonitor.start(this::handleDatabaseChanged); - } - - @Override - public void stop() throws Exception { - objectMonitor.stop(); - callDnsRecords.getAndSet(null); - } - - @Override - public CallDnsRecords get() { - return this.callDnsRecords.get(); - } -} diff --git a/service/src/main/java/org/whispersystems/textsecuregcm/calls/routing/CallRoutingTable.java b/service/src/main/java/org/whispersystems/textsecuregcm/calls/routing/CallRoutingTable.java deleted file mode 100644 index 88d5f1541..000000000 --- a/service/src/main/java/org/whispersystems/textsecuregcm/calls/routing/CallRoutingTable.java +++ /dev/null @@ -1,193 +0,0 @@ -/* - * Copyright 2024 Signal Messenger, LLC - * SPDX-License-Identifier: AGPL-3.0-only - */ - -package org.whispersystems.textsecuregcm.calls.routing; - -import jakarta.validation.constraints.NotBlank; -import jakarta.validation.constraints.NotNull; -import java.math.BigInteger; -import java.net.Inet4Address; -import java.net.Inet6Address; -import java.net.InetAddress; -import java.util.Collections; -import java.util.HashMap; -import java.util.List; -import java.util.Map; -import java.util.Objects; -import java.util.Optional; -import java.util.TreeMap; -import java.util.function.Function; -import java.util.stream.Stream; - -public class CallRoutingTable { - private final TreeMap>> ipv4Map; - private final TreeMap>> ipv6Map; - private final Map> geoToDatacenter; - - public CallRoutingTable( - Map> ipv4SubnetToDatacenter, - Map> ipv6SubnetToDatacenter, - Map> geoToDatacenter - ) { - this.ipv4Map = new TreeMap<>(); - for (Map.Entry> t : ipv4SubnetToDatacenter.entrySet()) { - if (!this.ipv4Map.containsKey(t.getKey().cidrBlockSize())) { - this.ipv4Map.put(t.getKey().cidrBlockSize(), new HashMap<>()); - } - this.ipv4Map - .get(t.getKey().cidrBlockSize()) - .put(t.getKey().subnet(), t.getValue()); - } - - this.ipv6Map = new TreeMap<>(); - for (Map.Entry> t : ipv6SubnetToDatacenter.entrySet()) { - if (!this.ipv6Map.containsKey(t.getKey().cidrBlockSize())) { - this.ipv6Map.put(t.getKey().cidrBlockSize(), new HashMap<>()); - } - this.ipv6Map - .get(t.getKey().cidrBlockSize()) - .put(t.getKey().subnet(), t.getValue()); - } - - this.geoToDatacenter = geoToDatacenter; - } - - public static CallRoutingTable empty() { - return new CallRoutingTable(Map.of(), Map.of(), Map.of()); - } - - public enum Protocol { - v4, - v6 - } - - public record GeoKey( - @NotBlank String continent, - @NotBlank String country, - @NotNull Optional subdivision, - @NotBlank Protocol protocol - ) {} - - /** - * Returns ordered list of fastest datacenters based on IP & Geo info. Prioritize the results based on subnet. - * Returns at most three, 2 by subnet and 1 by geo. Takes more from either bucket to hit 3. - */ - public List getDatacentersFor( - InetAddress address, - String continent, - String country, - Optional subdivision - ) { - final int NUM_DATACENTERS = 3; - - if(this.isEmpty()) { - return Collections.emptyList(); - } - - List dcsBySubnet = getDatacentersBySubnet(address); - List dcsByGeo = getDatacentersByGeo(continent, country, subdivision).stream() - .limit(NUM_DATACENTERS) - .filter(dc -> - (dcsBySubnet.isEmpty() || !dc.equals(dcsBySubnet.getFirst())) - && (dcsBySubnet.size() < 2 || !dc.equals(dcsBySubnet.get(1))) - ).toList(); - - return Stream.concat( - dcsBySubnet.stream().limit(dcsByGeo.isEmpty() ? NUM_DATACENTERS : NUM_DATACENTERS - 1), - dcsByGeo.stream()) - .limit(NUM_DATACENTERS) - .toList(); - } - - public boolean isEmpty() { - return this.ipv4Map.isEmpty() && this.ipv6Map.isEmpty() && this.geoToDatacenter.isEmpty(); - } - - /** - * Returns ordered list of fastest datacenters based on ip info. Prioritizes V4 connections. - */ - public List getDatacentersBySubnet(InetAddress address) throws IllegalArgumentException { - if(address instanceof Inet4Address) { - for(Map.Entry>> t: this.ipv4Map.descendingMap().entrySet()) { - int maskedIp = CidrBlock.IpV4CidrBlock.maskToSize((Inet4Address) address, t.getKey()); - if(t.getValue().containsKey(maskedIp)) { - return t.getValue().get(maskedIp); - } - } - } else if (address instanceof Inet6Address) { - for(Map.Entry>> t: this.ipv6Map.descendingMap().entrySet()) { - BigInteger maskedIp = CidrBlock.IpV6CidrBlock.maskToSize((Inet6Address) address, t.getKey()); - if(t.getValue().containsKey(maskedIp)) { - return t.getValue().get(maskedIp); - } - } - } else { - throw new IllegalArgumentException("Expected either an Inet4Address or Inet6Address"); - } - - return Collections.emptyList(); - } - - /** - * Returns ordered list of fastest datacenters based on geo info. Attempts to match based on subdivision, falls back - * to country based lookup. Does not attempt to look for nearby subdivisions. Prioritizes V4 connections. - */ - public List getDatacentersByGeo( - String continent, - String country, - Optional subdivision - ) { - GeoKey v4Key = new GeoKey(continent, country, subdivision, Protocol.v4); - List v4Options = this.geoToDatacenter.getOrDefault(v4Key, Collections.emptyList()); - List v4OptionsBackup = v4Options.isEmpty() && subdivision.isPresent() ? - this.geoToDatacenter.getOrDefault( - new GeoKey(continent, country, Optional.empty(), Protocol.v4), - Collections.emptyList()) - : Collections.emptyList(); - - GeoKey v6Key = new GeoKey(continent, country, subdivision, Protocol.v6); - List v6Options = this.geoToDatacenter.getOrDefault(v6Key, Collections.emptyList()); - List v6OptionsBackup = v6Options.isEmpty() && subdivision.isPresent() ? - this.geoToDatacenter.getOrDefault( - new GeoKey(continent, country, Optional.empty(), Protocol.v6), - Collections.emptyList()) - : Collections.emptyList(); - - return Stream.of( - v4Options.stream(), - v6Options.stream(), - v4OptionsBackup.stream(), - v6OptionsBackup.stream() - ) - .flatMap(Function.identity()) - .distinct() - .toList(); - } - - public String toSummaryString() { - return String.format( - "[Ipv4Table=%s rows, Ipv6Table=%s rows, GeoTable=%s rows]", - ipv4Map.size(), - ipv6Map.size(), - geoToDatacenter.size() - ); - } - - @Override - public boolean equals(final Object o) { - if (this == o) - return true; - if (o == null || getClass() != o.getClass()) - return false; - CallRoutingTable that = (CallRoutingTable) o; - return Objects.equals(ipv4Map, that.ipv4Map) && Objects.equals(ipv6Map, that.ipv6Map) && Objects.equals( - geoToDatacenter, that.geoToDatacenter); - } - - @Override - public int hashCode() { - return Objects.hash(ipv4Map, ipv6Map, geoToDatacenter); - } -} diff --git a/service/src/main/java/org/whispersystems/textsecuregcm/calls/routing/CallRoutingTableManager.java b/service/src/main/java/org/whispersystems/textsecuregcm/calls/routing/CallRoutingTableManager.java deleted file mode 100644 index ff3db32b7..000000000 --- a/service/src/main/java/org/whispersystems/textsecuregcm/calls/routing/CallRoutingTableManager.java +++ /dev/null @@ -1,73 +0,0 @@ -/* - * Copyright 2023 Signal Messenger, LLC - * SPDX-License-Identifier: AGPL-3.0-only - */ - -package org.whispersystems.textsecuregcm.calls.routing; - -import io.dropwizard.lifecycle.Managed; -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.util.concurrent.ScheduledExecutorService; -import java.util.concurrent.atomic.AtomicReference; -import java.util.function.Supplier; -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 CallRoutingTableManager implements Supplier, Managed { - - private final S3ObjectMonitor objectMonitor; - - private final AtomicReference routingTable = new AtomicReference<>(); - - private final String tableTag; - - private final Timer refreshTimer; - - private static final Logger log = LoggerFactory.getLogger(CallRoutingTableManager.class); - - public CallRoutingTableManager(final ScheduledExecutorService executorService, - final AwsCredentialsProvider awsCredentialsProvider, final S3ObjectMonitorFactory configuration, - final String tableTag) { - - this.objectMonitor = configuration.build(awsCredentialsProvider, executorService); - this.tableTag = tableTag; - this.routingTable.set(CallRoutingTable.empty()); - this.refreshTimer = Metrics.timer(MetricsUtil.name(CallRoutingTableManager.class, tableTag)); - } - - private void handleDatabaseChanged(final InputStream inputStream) { - refreshTimer.record(() -> { - try(InputStreamReader reader = new InputStreamReader(inputStream)) { - CallRoutingTable newTable = CallRoutingTableParser.fromJson(reader); - this.routingTable.set(newTable); - log.info("Replaced {} call routing table: {}", tableTag, newTable.toSummaryString()); - } catch (final IOException e) { - log.error("Failed to parse and update {} call routing table", tableTag); - } - }); - } - - @Override - public void start() throws Exception { - objectMonitor.start(this::handleDatabaseChanged); - } - - @Override - public void stop() throws Exception { - objectMonitor.stop(); - routingTable.getAndSet(null); - } - - @Override - public CallRoutingTable get() { - return this.routingTable.get(); - } -} diff --git a/service/src/main/java/org/whispersystems/textsecuregcm/calls/routing/CallRoutingTableParser.java b/service/src/main/java/org/whispersystems/textsecuregcm/calls/routing/CallRoutingTableParser.java deleted file mode 100644 index a3538b6dc..000000000 --- a/service/src/main/java/org/whispersystems/textsecuregcm/calls/routing/CallRoutingTableParser.java +++ /dev/null @@ -1,185 +0,0 @@ -/* - * Copyright 2024 Signal Messenger, LLC - * SPDX-License-Identifier: AGPL-3.0-only - */ - -package org.whispersystems.textsecuregcm.calls.routing; - -import com.fasterxml.jackson.core.StreamReadFeature; -import com.fasterxml.jackson.databind.ObjectMapper; -import com.fasterxml.jackson.databind.json.JsonMapper; -import java.io.BufferedReader; -import java.io.IOException; -import java.io.Reader; -import java.util.Arrays; -import java.util.HashMap; -import java.util.List; -import java.util.Map; -import java.util.Optional; -import java.util.stream.Collectors; -import java.util.stream.Stream; - - -final class CallRoutingTableParser { - - private final static int IPV4_DEFAULT_BLOCK_SIZE = 24; - private final static int IPV6_DEFAULT_BLOCK_SIZE = 48; - private static final ObjectMapper objectMapper = JsonMapper.builder() - .enable(StreamReadFeature.INCLUDE_SOURCE_IN_LOCATION) - .build(); - - /** Used for parsing JSON */ - private static class RawCallRoutingTable { - public Map> ipv4GeoToDataCenters = Map.of(); - public Map> ipv6GeoToDataCenters = Map.of(); - public Map> ipv4SubnetsToDatacenters = Map.of(); - public Map> ipv6SubnetsToDatacenters = Map.of(); - } - - private final static String WHITESPACE_REGEX = "\\s+"; - - public static CallRoutingTable fromJson(final Reader inputReader) throws IOException { - try (final BufferedReader reader = new BufferedReader(inputReader)) { - RawCallRoutingTable rawTable = objectMapper.readValue(reader, RawCallRoutingTable.class); - - Map> ipv4SubnetToDatacenter = rawTable.ipv4SubnetsToDatacenters - .entrySet() - .stream() - .collect(Collectors.toUnmodifiableMap( - e -> (CidrBlock.IpV4CidrBlock) CidrBlock.parseCidrBlock(e.getKey(), IPV4_DEFAULT_BLOCK_SIZE), - Map.Entry::getValue - )); - - Map> ipv6SubnetToDatacenter = rawTable.ipv6SubnetsToDatacenters - .entrySet() - .stream() - .collect(Collectors.toUnmodifiableMap( - e -> (CidrBlock.IpV6CidrBlock) CidrBlock.parseCidrBlock(e.getKey(), IPV6_DEFAULT_BLOCK_SIZE), - Map.Entry::getValue - )); - - Map> geoToDatacenter = Stream.concat( - rawTable.ipv4GeoToDataCenters - .entrySet() - .stream() - .map(e -> Map.entry(parseRawGeoKey(e.getKey(), CallRoutingTable.Protocol.v4), e.getValue())), - rawTable.ipv6GeoToDataCenters - .entrySet() - .stream() - .map(e -> Map.entry(parseRawGeoKey(e.getKey(), CallRoutingTable.Protocol.v6), e.getValue())) - ).collect(Collectors.toUnmodifiableMap(Map.Entry::getKey, Map.Entry::getValue)); - - return new CallRoutingTable( - ipv4SubnetToDatacenter, - ipv6SubnetToDatacenter, - geoToDatacenter - ); - } - } - - private static CallRoutingTable.GeoKey parseRawGeoKey(String rawKey, CallRoutingTable.Protocol protocol) { - String[] splits = rawKey.split("-"); - if (splits.length < 2 || splits.length > 3) { - throw new IllegalArgumentException("Invalid raw key"); - } - - Optional subdivision = splits.length < 3 ? Optional.empty() : Optional.of(splits[2]); - return new CallRoutingTable.GeoKey(splits[0], splits[1], subdivision, protocol); - } - - /** - * Parses a call routing table in TSV format. Example below - see tests for more examples: - 192.0.2.0/24 northamerica-northeast1 - 198.51.100.0/24 us-south1 - 203.0.113.0/24 asia-southeast1 - - 2001:db8:b0a9::/48 us-east4 - 2001:db8:b0f5::/48 us-central1 northamerica-northeast1 us-east4 - 2001:db8:9406::/48 us-east1 us-central1 - - SA-SR-v4 us-east1 us-east4 - SA-SR-v6 us-east1 us-south1 - SA-UY-v4 southamerica-west1 southamerica-east1 europe-west3 - SA-UY-v6 southamerica-west1 europe-west4 - SA-VE-v4 us-east1 us-east4 us-south1 - SA-VE-v6 us-east1 northamerica-northeast1 us-east4 - ZZ-ZZ-v4 asia-south1 europe-southwest1 australia-southeast1 - */ - public static CallRoutingTable fromTsv(final Reader inputReader) throws IOException { - try (final BufferedReader reader = new BufferedReader(inputReader)) { - // use maps to silently dedupe CidrBlocks - Map> ipv4Map = new HashMap<>(); - Map> ipv6Map = new HashMap<>(); - Map> ipGeoTable = new HashMap<>(); - String line; - while((line = reader.readLine()) != null) { - if(line.isBlank()) { - continue; - } - - List splits = Arrays.stream(line.split(WHITESPACE_REGEX)).filter(s -> !s.isBlank()).toList(); - if (splits.size() < 2) { - throw new IllegalStateException("Invalid row, expected some key and list of values"); - } - - List datacenters = splits.subList(1, splits.size()); - switch (guessLineType(splits)) { - case v4 -> { - CidrBlock cidrBlock = CidrBlock.parseCidrBlock(splits.getFirst()); - if(!(cidrBlock instanceof CidrBlock.IpV4CidrBlock)) { - throw new IllegalArgumentException("Expected an ipv4 cidr block"); - } - ipv4Map.put((CidrBlock.IpV4CidrBlock) cidrBlock, datacenters); - } - case v6 -> { - CidrBlock cidrBlock = CidrBlock.parseCidrBlock(splits.getFirst()); - if(!(cidrBlock instanceof CidrBlock.IpV6CidrBlock)) { - throw new IllegalArgumentException("Expected an ipv6 cidr block"); - } - ipv6Map.put((CidrBlock.IpV6CidrBlock) cidrBlock, datacenters); - } - case Geo -> { - String[] geo = splits.getFirst().split("-"); - if(geo.length < 3) { - throw new IllegalStateException("Geo row key invalid, expected atleast continent, country, and protocol"); - } - String continent = geo[0]; - String country = geo[1]; - Optional subdivision = geo.length > 3 ? Optional.of(geo[2]) : Optional.empty(); - CallRoutingTable.Protocol protocol = CallRoutingTable.Protocol.valueOf(geo[geo.length - 1].toLowerCase()); - CallRoutingTable.GeoKey tableKey = new CallRoutingTable.GeoKey( - continent, - country, - subdivision, - protocol - ); - ipGeoTable.put(tableKey, datacenters); - } - } - } - - return new CallRoutingTable( - ipv4Map, - ipv6Map, - ipGeoTable - ); - } - } - - private static LineType guessLineType(List splits) { - String first = splits.getFirst(); - if (first.contains("-")) { - return LineType.Geo; - } else if(first.contains(":")) { - return LineType.v6; - } else if (first.contains(".")) { - return LineType.v4; - } - - throw new IllegalArgumentException(String.format("Invalid line, could not determine type from '%s'", first)); - } - - private enum LineType { - v4, v6, Geo - } -} diff --git a/service/src/main/java/org/whispersystems/textsecuregcm/calls/routing/CidrBlock.java b/service/src/main/java/org/whispersystems/textsecuregcm/calls/routing/CidrBlock.java deleted file mode 100644 index e3af1b2a1..000000000 --- a/service/src/main/java/org/whispersystems/textsecuregcm/calls/routing/CidrBlock.java +++ /dev/null @@ -1,137 +0,0 @@ -/* - * Copyright 2024 Signal Messenger, LLC - * SPDX-License-Identifier: AGPL-3.0-only - */ - -package org.whispersystems.textsecuregcm.calls.routing; - -import java.math.BigInteger; -import java.net.Inet4Address; -import java.net.Inet6Address; -import java.net.InetAddress; -import java.net.UnknownHostException; - -/** - * Can be used to check if an IP is in the CIDR block - */ -public interface CidrBlock { - - boolean ipInBlock(InetAddress address); - - static CidrBlock parseCidrBlock(String cidrBlock, int defaultBlockSize) { - String[] splits = cidrBlock.split("/"); - if(splits.length > 2) { - throw new IllegalArgumentException("Invalid cidr block format, expected {address}/{blocksize}"); - } - - try { - int blockSize = splits.length == 2 ? Integer.parseInt(splits[1]) : defaultBlockSize; - return parseCidrBlockInner(splits[0], blockSize); - } catch (NumberFormatException e) { - throw new IllegalArgumentException(String.format("Invalid block size specified: '%s'", splits[1])); - } - } - - static CidrBlock parseCidrBlock(String cidrBlock) { - String[] splits = cidrBlock.split("/"); - if (splits.length != 2) { - throw new IllegalArgumentException("Invalid cidr block format, expected {address}/{blocksize}"); - } - - try { - int blockSize = Integer.parseInt(splits[1]); - return parseCidrBlockInner(splits[0], blockSize); - } catch (NumberFormatException e) { - throw new IllegalArgumentException(String.format("Invalid block size specified: '%s'", splits[1])); - } - } - - private static CidrBlock parseCidrBlockInner(String rawAddress, int blockSize) { - try { - InetAddress address = InetAddress.getByName(rawAddress); - if(address instanceof Inet4Address) { - return IpV4CidrBlock.of((Inet4Address) address, blockSize); - } else if (address instanceof Inet6Address) { - return IpV6CidrBlock.of((Inet6Address) address, blockSize); - } else { - throw new IllegalArgumentException("Must be an ipv4 or ipv6 string"); - } - } catch (UnknownHostException e) { - throw new IllegalArgumentException(e); - } - } - - record IpV4CidrBlock(int subnet, int subnetMask, int cidrBlockSize) implements CidrBlock { - public static IpV4CidrBlock of(Inet4Address subnet, int cidrBlockSize) { - if(cidrBlockSize > 32 || cidrBlockSize < 0) { - throw new IllegalArgumentException("Invalid cidrBlockSize"); - } - - int subnetMask = mask(cidrBlockSize); - int maskedIp = ipToInt(subnet) & subnetMask; - return new IpV4CidrBlock(maskedIp, subnetMask, cidrBlockSize); - } - - public boolean ipInBlock(InetAddress address) { - if(!(address instanceof Inet4Address)) { - return false; - } - int ip = ipToInt((Inet4Address) address); - return (ip & subnetMask) == subnet; - } - - private static int ipToInt(Inet4Address address) { - byte[] octets = address.getAddress(); - return (octets[0] & 0xff) << 24 | - (octets[1] & 0xff) << 16 | - (octets[2] & 0xff) << 8 | - octets[3] & 0xff; - } - - private static int mask(int cidrBlockSize) { - return (int) (-1L << (32 - cidrBlockSize)); - } - - public static int maskToSize(Inet4Address address, int cidrBlockSize) { - return ipToInt(address) & mask(cidrBlockSize); - } - } - - record IpV6CidrBlock(BigInteger subnet, BigInteger subnetMask, int cidrBlockSize) implements CidrBlock { - - private static final BigInteger MINUS_ONE = BigInteger.valueOf(-1); - - public static IpV6CidrBlock of(Inet6Address subnet, int cidrBlockSize) { - if(cidrBlockSize > 128 || cidrBlockSize < 0) { - throw new IllegalArgumentException("Invalid cidrBlockSize"); - } - - BigInteger subnetMask = mask(cidrBlockSize); - BigInteger maskedIp = ipToInt(subnet).and(subnetMask); - return new IpV6CidrBlock(maskedIp, subnetMask, cidrBlockSize); - } - - public boolean ipInBlock(InetAddress address) { - if(!(address instanceof Inet6Address)) { - return false; - } - BigInteger ip = ipToInt((Inet6Address) address); - return ip.and(subnetMask).equals(subnet); - } - - private static BigInteger ipToInt(Inet6Address ipAddress) { - byte[] octets = ipAddress.getAddress(); - assert octets.length == 16; - - return new BigInteger(octets); - } - - private static BigInteger mask(int cidrBlockSize) { - return MINUS_ONE.shiftLeft(128 - cidrBlockSize); - } - - public static BigInteger maskToSize(Inet6Address address, int cidrBlockSize) { - return ipToInt(address).and(mask(cidrBlockSize)); - } - } -} diff --git a/service/src/main/java/org/whispersystems/textsecuregcm/calls/routing/TurnServerOptions.java b/service/src/main/java/org/whispersystems/textsecuregcm/calls/routing/TurnServerOptions.java deleted file mode 100644 index 0003cc99e..000000000 --- a/service/src/main/java/org/whispersystems/textsecuregcm/calls/routing/TurnServerOptions.java +++ /dev/null @@ -1,16 +0,0 @@ -/* - * Copyright 2024 Signal Messenger, LLC - * SPDX-License-Identifier: AGPL-3.0-only - */ - -package org.whispersystems.textsecuregcm.calls.routing; - -import java.util.List; -import java.util.Optional; - -public record TurnServerOptions( - String hostname, - Optional> urlsWithIps, - Optional> urlsWithHostname -) { -}