diff --git a/service/src/main/java/org/whispersystems/textsecuregcm/calls/routing/TurnCallRouter.java b/service/src/main/java/org/whispersystems/textsecuregcm/calls/routing/TurnCallRouter.java index 79f2ad3cc..7d6a4c1ad 100644 --- a/service/src/main/java/org/whispersystems/textsecuregcm/calls/routing/TurnCallRouter.java +++ b/service/src/main/java/org/whispersystems/textsecuregcm/calls/routing/TurnCallRouter.java @@ -13,6 +13,7 @@ import org.whispersystems.textsecuregcm.util.Util; import javax.annotation.Nonnull; import java.io.IOException; +import java.net.Inet6Address; import java.net.InetAddress; import java.util.*; import java.util.function.Supplier; @@ -47,7 +48,9 @@ public TurnCallRouter( } /** - * Gets Turn Instance addresses. Returns both the IPv4 and IPv6 addresses. Prioritizes V4 connections. + * 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 @@ -110,29 +113,42 @@ TurnServerOptions getRoutingForInner( subdivision ); } - List urlsWithIps = getUrlsForInstances(selectInstances(datacenters, instanceLimit)); - return new TurnServerOptions(hostname, urlsWithIps, this.configTurnRouter.randomUrls()); + + List urlsWithIps = getUrlsForInstances( + selectInstances( + datacenters, + instanceLimit, + (clientAddress.get() instanceof Inet6Address) + )); + return new TurnServerOptions(hostname, urlsWithIps, minimalRandomUrls()); + } + + // Includes only the udp options in the randomUrls + private List minimalRandomUrls(){ + return this.configTurnRouter.randomUrls().stream() + .filter(s -> s.startsWith("turn:") && !s.endsWith("transport=tcp")) + .toList(); } - private List selectInstances(List datacenters, int limit) { + private List selectInstances(List datacenters, int limit, boolean preferV6) { if(datacenters.isEmpty() || limit == 0) { return Collections.emptyList(); } + int numV6 = preferV6 ? (limit - limit / 3) : limit / 3; + int numV4 = limit - numV6; CallDnsRecords dnsRecords = this.callDnsRecords.get(); List ipv4Selection = datacenters.stream() - .flatMap(dc -> Util.randomNOfStable(dnsRecords.aByRegion().get(dc), 2).stream()) + .flatMap(dc -> Util.randomNOfStable(dnsRecords.aByRegion().get(dc), limit).stream()) .toList(); List ipv6Selection = datacenters.stream() - .flatMap(dc -> Util.randomNOfStable(dnsRecords.aaaaByRegion().get(dc), 2).stream()) + .flatMap(dc -> Util.randomNOfStable(dnsRecords.aaaaByRegion().get(dc), limit).stream()) .toList(); - if (ipv4Selection.size() < ipv6Selection.size()) { - ipv4Selection = ipv4Selection.stream().limit(limit / 2).toList(); - ipv6Selection = ipv6Selection.stream().limit(limit - ipv4Selection.size()).toList(); - } else { - ipv6Selection = ipv6Selection.stream().limit(limit / 2).toList(); - ipv4Selection = ipv4Selection.stream().limit(limit - ipv6Selection.size()).toList(); - } + + // increase numV4 if not enough v6 options. vice-versa is also true + numV4 = Math.max(numV4, limit - ipv6Selection.size()); + ipv4Selection = ipv4Selection.stream().limit(numV4).toList(); + ipv6Selection = ipv6Selection.stream().limit(limit - ipv4Selection.size()).toList(); return Stream.concat( ipv4Selection.stream().map(InetAddress::getHostAddress), @@ -143,7 +159,6 @@ private List selectInstances(List datacenters, int limit) { private static List getUrlsForInstances(List instanceIps) { return instanceIps.stream().flatMap(ip -> Stream.of( - String.format("stun:%s", ip), String.format("turn:%s", ip), String.format("turn:%s:80?transport=tcp", ip), String.format("turns:%s:443?transport=tcp", ip) diff --git a/service/src/main/java/org/whispersystems/textsecuregcm/controllers/CallRoutingController.java b/service/src/main/java/org/whispersystems/textsecuregcm/controllers/CallRoutingController.java index 6135acdab..8d69706e4 100644 --- a/service/src/main/java/org/whispersystems/textsecuregcm/controllers/CallRoutingController.java +++ b/service/src/main/java/org/whispersystems/textsecuregcm/controllers/CallRoutingController.java @@ -33,7 +33,7 @@ @io.swagger.v3.oas.annotations.tags.Tag(name = "Calling") public class CallRoutingController { - private static final int TURN_INSTANCE_LIMIT = 6; + private static final int TURN_INSTANCE_LIMIT = 3; private static final Counter INVALID_IP_COUNTER = Metrics.counter(name(CallRoutingController.class, "invalidIP")); private static final Logger log = LoggerFactory.getLogger(CallRoutingController.class); private final RateLimiters rateLimiters; diff --git a/service/src/test/java/org/whispersystems/textsecuregcm/calls/routing/TurnCallRouterTest.java b/service/src/test/java/org/whispersystems/textsecuregcm/calls/routing/TurnCallRouterTest.java index 48fae06f2..a338d24a1 100644 --- a/service/src/test/java/org/whispersystems/textsecuregcm/calls/routing/TurnCallRouterTest.java +++ b/service/src/test/java/org/whispersystems/textsecuregcm/calls/routing/TurnCallRouterTest.java @@ -33,9 +33,12 @@ public class TurnCallRouterTest { private final static String TEST_HOSTNAME = "subdomain.example.org"; private final static List TEST_URLS_WITH_HOSTS = List.of( - "one.example.com", - "two.example.com", - "three.example.com" + "stun:one.example.com", + "turn:two.example.com", + "turn:three.example.com?transport=tcp" + ); + private final static List EXPECTED_TEST_URLS_WITH_HOSTS = List.of( + "turn:two.example.com" ); private CallRoutingTable performanceTable; @@ -113,7 +116,7 @@ TurnServerOptions optionsWithUrls(List urls) { return new TurnServerOptions( TEST_HOSTNAME, urls, - TEST_URLS_WITH_HOSTS + EXPECTED_TEST_URLS_WITH_HOSTS ); } @@ -140,7 +143,11 @@ public void testRandomizes() throws UnknownHostException { .thenReturn(true); assertThat(router().getRoutingFor(aci, Optional.of(InetAddress.getByName("0.0.0.1")), 10)) - .isEqualTo(optionsWithUrls(null)); + .isEqualTo(new TurnServerOptions( + TEST_HOSTNAME, + null, + TEST_URLS_WITH_HOSTS + )); } @Test @@ -150,32 +157,26 @@ public void testOrderedByPerformance() throws UnknownHostException { assertThat(router().getRoutingFor(aci, Optional.of(InetAddress.getByName("0.0.0.1")), 10)) .isEqualTo(optionsWithUrls(List.of( - "stun:9.9.9.3", "turn:9.9.9.3", "turn:9.9.9.3:80?transport=tcp", "turns:9.9.9.3:443?transport=tcp", - "stun:9.9.9.1", "turn:9.9.9.1", "turn:9.9.9.1:80?transport=tcp", "turns:9.9.9.1:443?transport=tcp", - "stun:9.9.9.2", "turn:9.9.9.2", "turn:9.9.9.2:80?transport=tcp", "turns:9.9.9.2:443?transport=tcp", - "stun:[2222:1111:0:abc2:0:0:0:0]", "turn:[2222:1111:0:abc2:0:0:0:0]", "turn:[2222:1111:0:abc2:0:0:0:0]:80?transport=tcp", "turns:[2222:1111:0:abc2:0:0:0:0]:443?transport=tcp", - "stun:[2222:1111:0:abc0:0:0:0:0]", "turn:[2222:1111:0:abc0:0:0:0:0]", "turn:[2222:1111:0:abc0:0:0:0:0]:80?transport=tcp", "turns:[2222:1111:0:abc0:0:0:0:0]:443?transport=tcp", - "stun:[2222:1111:0:abc1:0:0:0:0]", "turn:[2222:1111:0:abc1:0:0:0:0]", "turn:[2222:1111:0:abc1:0:0:0:0]:80?transport=tcp", "turns:[2222:1111:0:abc1:0:0:0:0]:443?transport=tcp" @@ -191,12 +192,10 @@ public void testPrioritizesManualRecords() throws UnknownHostException { assertThat(router().getRoutingFor(aci, Optional.of(InetAddress.getByName("0.0.0.1")), 10)) .isEqualTo(optionsWithUrls(List.of( - "stun:1.1.1.1", "turn:1.1.1.1", "turn:1.1.1.1:80?transport=tcp", "turns:1.1.1.1:443?transport=tcp", - "stun:[2222:1111:0:dead:0:0:0:0]", "turn:[2222:1111:0:dead:0:0:0:0]", "turn:[2222:1111:0:dead:0:0:0:0]:80?transport=tcp", "turns:[2222:1111:0:dead:0:0:0:0]:443?transport=tcp" @@ -204,41 +203,38 @@ public void testPrioritizesManualRecords() throws UnknownHostException { } @Test - public void testLimitReturnsHalfIpv4AndPrioritizesPerformance() throws UnknownHostException { + public void testLimitReturnsPreferredProtocolAndPrioritizesPerformance() throws UnknownHostException { when(performanceTable.getDatacentersFor(any(), any(), any(), any())) .thenReturn(List.of("dc-performance3", "dc-performance2", "dc-performance1")); - assertThat(router().getRoutingFor(aci, Optional.of(InetAddress.getByName("0.0.0.1")), 6)) + assertThat(router().getRoutingFor(aci, Optional.of(InetAddress.getByName("0.0.0.1")), 3)) .isEqualTo(optionsWithUrls(List.of( - "stun:9.9.9.4", "turn:9.9.9.4", "turn:9.9.9.4:80?transport=tcp", "turns:9.9.9.4:443?transport=tcp", - "stun:9.9.9.3", "turn:9.9.9.3", "turn:9.9.9.3:80?transport=tcp", "turns:9.9.9.3:443?transport=tcp", - "stun:9.9.9.1", - "turn:9.9.9.1", - "turn:9.9.9.1:80?transport=tcp", - "turns:9.9.9.1:443?transport=tcp", + "turn:[2222:1111:0:abc3:0:0:0:0]", + "turn:[2222:1111:0:abc3:0:0:0:0]:80?transport=tcp", + "turns:[2222:1111:0:abc3:0:0:0:0]:443?transport=tcp" + ))); + + assertThat(router().getRoutingFor(aci, Optional.of(InetAddress.getByName("2222:1111:0:abc2:0:0:0:1")), 3)) + .isEqualTo(optionsWithUrls(List.of( + "turn:9.9.9.4", + "turn:9.9.9.4:80?transport=tcp", + "turns:9.9.9.4:443?transport=tcp", - "stun:[2222:1111:0:abc3:0:0:0:0]", "turn:[2222:1111:0:abc3:0:0:0:0]", "turn:[2222:1111:0:abc3:0:0:0:0]:80?transport=tcp", "turns:[2222:1111:0:abc3:0:0:0:0]:443?transport=tcp", - "stun:[2222:1111:0:abc2:0:0:0:0]", "turn:[2222:1111:0:abc2:0:0:0:0]", "turn:[2222:1111:0:abc2:0:0:0:0]:80?transport=tcp", - "turns:[2222:1111:0:abc2:0:0:0:0]:443?transport=tcp", - - "stun:[2222:1111:0:abc0:0:0:0:0]", - "turn:[2222:1111:0:abc0:0:0:0:0]", - "turn:[2222:1111:0:abc0:0:0:0:0]:80?transport=tcp", - "turns:[2222:1111:0:abc0:0:0:0:0]:443?transport=tcp" + "turns:[2222:1111:0:abc2:0:0:0:0]:443?transport=tcp" ))); }