calling: update TurnCallRouter to reduce returned options

This commit is contained in:
adel-signal 2024-03-05 11:26:19 -08:00 committed by GitHub
parent 84c6731ddf
commit 8f100a792e
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
3 changed files with 55 additions and 44 deletions

View File

@ -13,6 +13,7 @@ import org.slf4j.LoggerFactory;
import org.whispersystems.textsecuregcm.util.Util; import org.whispersystems.textsecuregcm.util.Util;
import javax.annotation.Nonnull; import javax.annotation.Nonnull;
import java.io.IOException; import java.io.IOException;
import java.net.Inet6Address;
import java.net.InetAddress; import java.net.InetAddress;
import java.util.*; import java.util.*;
import java.util.function.Supplier; import java.util.function.Supplier;
@ -47,7 +48,9 @@ public class 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 aci aci of client
* @param clientAddress IP address to base routing on * @param clientAddress IP address to base routing on
* @param instanceLimit max instances to return options for * @param instanceLimit max instances to return options for
@ -110,29 +113,42 @@ public class TurnCallRouter {
subdivision subdivision
); );
} }
List<String> urlsWithIps = getUrlsForInstances(selectInstances(datacenters, instanceLimit));
return new TurnServerOptions(hostname, urlsWithIps, this.configTurnRouter.randomUrls()); List<String> urlsWithIps = getUrlsForInstances(
selectInstances(
datacenters,
instanceLimit,
(clientAddress.get() instanceof Inet6Address)
));
return new TurnServerOptions(hostname, urlsWithIps, minimalRandomUrls());
} }
private List<String> selectInstances(List<String> datacenters, int limit) { // 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();
}
private List<String> selectInstances(List<String> datacenters, int limit, boolean preferV6) {
if(datacenters.isEmpty() || limit == 0) { if(datacenters.isEmpty() || limit == 0) {
return Collections.emptyList(); return Collections.emptyList();
} }
int numV6 = preferV6 ? (limit - limit / 3) : limit / 3;
int numV4 = limit - numV6;
CallDnsRecords dnsRecords = this.callDnsRecords.get(); CallDnsRecords dnsRecords = this.callDnsRecords.get();
List<InetAddress> ipv4Selection = datacenters.stream() List<InetAddress> ipv4Selection = datacenters.stream()
.flatMap(dc -> Util.randomNOfStable(dnsRecords.aByRegion().get(dc), 2).stream()) .flatMap(dc -> Util.randomNOfStable(dnsRecords.aByRegion().get(dc), limit).stream())
.toList(); .toList();
List<InetAddress> ipv6Selection = datacenters.stream() List<InetAddress> ipv6Selection = datacenters.stream()
.flatMap(dc -> Util.randomNOfStable(dnsRecords.aaaaByRegion().get(dc), 2).stream()) .flatMap(dc -> Util.randomNOfStable(dnsRecords.aaaaByRegion().get(dc), limit).stream())
.toList(); .toList();
if (ipv4Selection.size() < ipv6Selection.size()) {
ipv4Selection = ipv4Selection.stream().limit(limit / 2).toList(); // increase numV4 if not enough v6 options. vice-versa is also true
ipv6Selection = ipv6Selection.stream().limit(limit - ipv4Selection.size()).toList(); numV4 = Math.max(numV4, limit - ipv6Selection.size());
} else { ipv4Selection = ipv4Selection.stream().limit(numV4).toList();
ipv6Selection = ipv6Selection.stream().limit(limit / 2).toList(); ipv6Selection = ipv6Selection.stream().limit(limit - ipv4Selection.size()).toList();
ipv4Selection = ipv4Selection.stream().limit(limit - ipv6Selection.size()).toList();
}
return Stream.concat( return Stream.concat(
ipv4Selection.stream().map(InetAddress::getHostAddress), ipv4Selection.stream().map(InetAddress::getHostAddress),
@ -143,7 +159,6 @@ public class TurnCallRouter {
private static List<String> getUrlsForInstances(List<String> instanceIps) { private static List<String> getUrlsForInstances(List<String> instanceIps) {
return instanceIps.stream().flatMap(ip -> Stream.of( return instanceIps.stream().flatMap(ip -> Stream.of(
String.format("stun:%s", ip),
String.format("turn:%s", ip), String.format("turn:%s", ip),
String.format("turn:%s:80?transport=tcp", ip), String.format("turn:%s:80?transport=tcp", ip),
String.format("turns:%s:443?transport=tcp", ip) String.format("turns:%s:443?transport=tcp", ip)

View File

@ -33,7 +33,7 @@ import static org.whispersystems.textsecuregcm.metrics.MetricsUtil.name;
@io.swagger.v3.oas.annotations.tags.Tag(name = "Calling") @io.swagger.v3.oas.annotations.tags.Tag(name = "Calling")
public class CallRoutingController { 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 Counter INVALID_IP_COUNTER = Metrics.counter(name(CallRoutingController.class, "invalidIP"));
private static final Logger log = LoggerFactory.getLogger(CallRoutingController.class); private static final Logger log = LoggerFactory.getLogger(CallRoutingController.class);
private final RateLimiters rateLimiters; private final RateLimiters rateLimiters;

View File

@ -33,9 +33,12 @@ public class TurnCallRouterTest {
private final static String TEST_HOSTNAME = "subdomain.example.org"; private final static String TEST_HOSTNAME = "subdomain.example.org";
private final static List<String> TEST_URLS_WITH_HOSTS = List.of( private final static List<String> TEST_URLS_WITH_HOSTS = List.of(
"one.example.com", "stun:one.example.com",
"two.example.com", "turn:two.example.com",
"three.example.com" "turn:three.example.com?transport=tcp"
);
private final static List<String> EXPECTED_TEST_URLS_WITH_HOSTS = List.of(
"turn:two.example.com"
); );
private CallRoutingTable performanceTable; private CallRoutingTable performanceTable;
@ -113,7 +116,7 @@ public class TurnCallRouterTest {
return new TurnServerOptions( return new TurnServerOptions(
TEST_HOSTNAME, TEST_HOSTNAME,
urls, urls,
TEST_URLS_WITH_HOSTS EXPECTED_TEST_URLS_WITH_HOSTS
); );
} }
@ -140,7 +143,11 @@ public class TurnCallRouterTest {
.thenReturn(true); .thenReturn(true);
assertThat(router().getRoutingFor(aci, Optional.of(InetAddress.getByName("0.0.0.1")), 10)) 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 @Test
@ -150,32 +157,26 @@ public class TurnCallRouterTest {
assertThat(router().getRoutingFor(aci, Optional.of(InetAddress.getByName("0.0.0.1")), 10)) assertThat(router().getRoutingFor(aci, Optional.of(InetAddress.getByName("0.0.0.1")), 10))
.isEqualTo(optionsWithUrls(List.of( .isEqualTo(optionsWithUrls(List.of(
"stun:9.9.9.3",
"turn:9.9.9.3", "turn:9.9.9.3",
"turn:9.9.9.3:80?transport=tcp", "turn:9.9.9.3:80?transport=tcp",
"turns:9.9.9.3:443?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",
"turn:9.9.9.1:80?transport=tcp", "turn:9.9.9.1:80?transport=tcp",
"turns:9.9.9.1:443?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",
"turn:9.9.9.2:80?transport=tcp", "turn:9.9.9.2:80?transport=tcp",
"turns:9.9.9.2:443?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]",
"turn:[2222:1111:0:abc2:0:0:0:0]:80?transport=tcp", "turn:[2222:1111:0:abc2:0:0:0:0]:80?transport=tcp",
"turns:[2222:1111:0:abc2:0:0:0:0]:443?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]",
"turn:[2222:1111:0:abc0:0:0:0:0]:80?transport=tcp", "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: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]",
"turn:[2222:1111:0:abc1:0:0:0:0]:80?transport=tcp", "turn:[2222:1111:0:abc1:0:0:0:0]:80?transport=tcp",
"turns:[2222:1111:0:abc1:0:0:0:0]:443?transport=tcp" "turns:[2222:1111:0:abc1:0:0:0:0]:443?transport=tcp"
@ -191,12 +192,10 @@ public class TurnCallRouterTest {
assertThat(router().getRoutingFor(aci, Optional.of(InetAddress.getByName("0.0.0.1")), 10)) assertThat(router().getRoutingFor(aci, Optional.of(InetAddress.getByName("0.0.0.1")), 10))
.isEqualTo(optionsWithUrls(List.of( .isEqualTo(optionsWithUrls(List.of(
"stun:1.1.1.1",
"turn:1.1.1.1", "turn:1.1.1.1",
"turn:1.1.1.1:80?transport=tcp", "turn:1.1.1.1:80?transport=tcp",
"turns:1.1.1.1:443?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]",
"turn:[2222:1111:0:dead:0:0:0:0]:80?transport=tcp", "turn:[2222:1111:0:dead:0:0:0:0]:80?transport=tcp",
"turns:[2222:1111:0:dead:0:0:0:0]:443?transport=tcp" "turns:[2222:1111:0:dead:0:0:0:0]:443?transport=tcp"
@ -204,41 +203,38 @@ public class TurnCallRouterTest {
} }
@Test @Test
public void testLimitReturnsHalfIpv4AndPrioritizesPerformance() throws UnknownHostException { public void testLimitReturnsPreferredProtocolAndPrioritizesPerformance() throws UnknownHostException {
when(performanceTable.getDatacentersFor(any(), any(), any(), any())) when(performanceTable.getDatacentersFor(any(), any(), any(), any()))
.thenReturn(List.of("dc-performance3", "dc-performance2", "dc-performance1")); .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( .isEqualTo(optionsWithUrls(List.of(
"stun:9.9.9.4",
"turn:9.9.9.4", "turn:9.9.9.4",
"turn:9.9.9.4:80?transport=tcp", "turn:9.9.9.4:80?transport=tcp",
"turns:9.9.9.4:443?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",
"turn:9.9.9.3:80?transport=tcp", "turn:9.9.9.3:80?transport=tcp",
"turns:9.9.9.3:443?transport=tcp", "turns:9.9.9.3:443?transport=tcp",
"stun:9.9.9.1", "turn:[2222:1111:0:abc3:0:0:0:0]",
"turn:9.9.9.1", "turn:[2222:1111:0:abc3:0:0:0:0]:80?transport=tcp",
"turn:9.9.9.1:80?transport=tcp", "turns:[2222:1111:0:abc3:0:0:0:0]:443?transport=tcp"
"turns:9.9.9.1: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]",
"turn:[2222:1111:0:abc3:0:0:0:0]:80?transport=tcp", "turn:[2222:1111:0:abc3:0:0:0:0]:80?transport=tcp",
"turns:[2222:1111:0:abc3:0:0:0:0]:443?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]",
"turn:[2222:1111:0:abc2:0:0:0:0]:80?transport=tcp", "turn:[2222:1111:0:abc2:0:0:0:0]:80?transport=tcp",
"turns:[2222:1111:0:abc2:0:0:0:0]:443?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"
))); )));
} }