mirror of
https://github.com/signalapp/Signal-Server
synced 2026-04-22 22:58:06 +01:00
Add call routing API endpoint for turn servers
This commit is contained in:
@@ -0,0 +1,101 @@
|
||||
/*
|
||||
* Copyright 2024 Signal Messenger, LLC
|
||||
* SPDX-License-Identifier: AGPL-3.0-only
|
||||
*/
|
||||
|
||||
package org.whispersystems.textsecuregcm.calls.routing;
|
||||
|
||||
import org.junit.jupiter.api.Test;
|
||||
import java.io.ByteArrayInputStream;
|
||||
import java.io.IOException;
|
||||
import java.net.InetAddress;
|
||||
import java.net.UnknownHostException;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.util.Map;
|
||||
import java.util.stream.Stream;
|
||||
|
||||
import static org.assertj.core.api.AssertionsForClassTypes.assertThat;
|
||||
|
||||
public class CallDnsRecordsManagerTest {
|
||||
|
||||
@Test
|
||||
public void testParseDnsRecords() throws IOException {
|
||||
var input = """
|
||||
{
|
||||
"aByRegion": {
|
||||
"datacenter-1": [
|
||||
"127.0.0.1"
|
||||
],
|
||||
"datacenter-2": [
|
||||
"127.0.0.2",
|
||||
"127.0.0.3"
|
||||
],
|
||||
"datacenter-3": [
|
||||
"127.0.0.4",
|
||||
"127.0.0.5"
|
||||
],
|
||||
"datacenter-4": [
|
||||
"127.0.0.6",
|
||||
"127.0.0.7"
|
||||
]
|
||||
},
|
||||
"aaaaByRegion": {
|
||||
"datacenter-1": [
|
||||
"2600:1111:2222:3333:0:20:0:0",
|
||||
"2600:1111:2222:3333:0:21:0:0",
|
||||
"2600:1111:2222:3333:0:22:0:0"
|
||||
],
|
||||
"datacenter-2": [
|
||||
"2600:1111:2222:3333:0:23:0:0",
|
||||
"2600:1111:2222:3333:0:24:0:0"
|
||||
],
|
||||
"datacenter-3": [
|
||||
"2600:1111:2222:3333:0:25:0:0",
|
||||
"2600:1111:2222:3333:0:26:0:0"
|
||||
],
|
||||
"datacenter-4": [
|
||||
"2600:1111:2222:3333:0:27:0:0"
|
||||
]
|
||||
}
|
||||
}
|
||||
""";
|
||||
|
||||
var actual = CallDnsRecordsManager.parseRecords(new ByteArrayInputStream(input.getBytes(StandardCharsets.UTF_8)));
|
||||
var expected = new CallDnsRecords(
|
||||
Map.of(
|
||||
"datacenter-1", Stream.of("127.0.0.1").map(this::getAddressByName).toList(),
|
||||
"datacenter-2", Stream.of("127.0.0.2", "127.0.0.3").map(this::getAddressByName).toList(),
|
||||
"datacenter-3", Stream.of("127.0.0.4", "127.0.0.5").map(this::getAddressByName).toList(),
|
||||
"datacenter-4", Stream.of("127.0.0.6", "127.0.0.7").map(this::getAddressByName).toList()
|
||||
),
|
||||
Map.of(
|
||||
"datacenter-1", Stream.of(
|
||||
"2600:1111:2222:3333:0:20:0:0",
|
||||
"2600:1111:2222:3333:0:21:0:0",
|
||||
"2600:1111:2222:3333:0:22:0:0"
|
||||
).map(this::getAddressByName).toList(),
|
||||
"datacenter-2", Stream.of(
|
||||
"2600:1111:2222:3333:0:23:0:0",
|
||||
"2600:1111:2222:3333:0:24:0:0")
|
||||
.map(this::getAddressByName).toList(),
|
||||
"datacenter-3", Stream.of(
|
||||
"2600:1111:2222:3333:0:25:0:0",
|
||||
"2600:1111:2222:3333:0:26:0:0")
|
||||
.map(this::getAddressByName).toList(),
|
||||
"datacenter-4", Stream.of(
|
||||
"2600:1111:2222:3333:0:27:0:0"
|
||||
).map(this::getAddressByName).toList()
|
||||
)
|
||||
);
|
||||
|
||||
assertThat(actual).isEqualTo(expected);
|
||||
}
|
||||
|
||||
InetAddress getAddressByName(String ip) {
|
||||
try {
|
||||
return InetAddress.getByName(ip) ;
|
||||
} catch (UnknownHostException e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,245 @@
|
||||
/*
|
||||
* Copyright 2024 Signal Messenger, LLC
|
||||
* SPDX-License-Identifier: AGPL-3.0-only
|
||||
*/
|
||||
|
||||
package org.whispersystems.textsecuregcm.calls.routing;
|
||||
|
||||
import org.junit.jupiter.api.Test;
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.StringReader;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Optional;
|
||||
|
||||
public class CallRoutingTableParserTest {
|
||||
|
||||
@Test
|
||||
public void testParserSuccess() throws IOException {
|
||||
var input =
|
||||
"""
|
||||
192.1.12.0/24 datacenter-1 datacenter-2 datacenter-3
|
||||
193.123.123.0/24 datacenter-1 datacenter-2
|
||||
1.123.123.0/24 datacenter-1
|
||||
|
||||
2001:db8:b0aa::/48 datacenter-1
|
||||
2001:db8:b0ab::/48 datacenter-3 datacenter-1 datacenter-2
|
||||
2001:db8:b0ac::/48 datacenter-2 datacenter-1
|
||||
|
||||
SA-SR-v4 datacenter-3
|
||||
SA-UY-v4 datacenter-3 datacenter-1 datacenter-2
|
||||
NA-US-VA-v6 datacenter-2 datacenter-1
|
||||
""";
|
||||
var actual = CallRoutingTableParser.fromTsv(new StringReader(input));
|
||||
var expected = new CallRoutingTable(
|
||||
Map.of(
|
||||
(CidrBlock.IpV4CidrBlock) CidrBlock.parseCidrBlock("192.1.12.0/24"), List.of("datacenter-1", "datacenter-2", "datacenter-3"),
|
||||
(CidrBlock.IpV4CidrBlock) CidrBlock.parseCidrBlock("193.123.123.0/24"), List.of("datacenter-1", "datacenter-2"),
|
||||
(CidrBlock.IpV4CidrBlock) CidrBlock.parseCidrBlock("1.123.123.0/24"), List.of("datacenter-1")
|
||||
),
|
||||
Map.of(
|
||||
(CidrBlock.IpV6CidrBlock) CidrBlock.parseCidrBlock("2001:db8:b0aa::/48"), List.of("datacenter-1"),
|
||||
(CidrBlock.IpV6CidrBlock) CidrBlock.parseCidrBlock("2001:db8:b0ab::/48"), List.of("datacenter-3", "datacenter-1", "datacenter-2"),
|
||||
(CidrBlock.IpV6CidrBlock) CidrBlock.parseCidrBlock("2001:db8:b0ac::/48"), List.of("datacenter-2", "datacenter-1")
|
||||
),
|
||||
Map.of(
|
||||
new CallRoutingTable.GeoKey("SA", "SR", Optional.empty(), CallRoutingTable.Protocol.v4), List.of("datacenter-3"),
|
||||
new CallRoutingTable.GeoKey("SA", "UY", Optional.empty(), CallRoutingTable.Protocol.v4), List.of("datacenter-3", "datacenter-1", "datacenter-2"),
|
||||
new CallRoutingTable.GeoKey("NA", "US", Optional.of("VA"), CallRoutingTable.Protocol.v6), List.of("datacenter-2", "datacenter-1")
|
||||
)
|
||||
);
|
||||
|
||||
assertThat(actual).isEqualTo(expected);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testParserVariousWhitespaceSuccess() throws IOException {
|
||||
var input =
|
||||
"""
|
||||
|
||||
192.1.12.0/24\t \tdatacenter-1\t\t datacenter-2 datacenter-3
|
||||
\t193.123.123.0/24\tdatacenter-1\tdatacenter-2
|
||||
|
||||
|
||||
1.123.123.0/24\t datacenter-1
|
||||
2001:db8:b0aa::/48\t \tdatacenter-1
|
||||
2001:db8:b0ab::/48 \tdatacenter-3\tdatacenter-1 datacenter-2
|
||||
2001:db8:b0ac::/48\tdatacenter-2\tdatacenter-1
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
SA-SR-v4 datacenter-3
|
||||
|
||||
|
||||
|
||||
|
||||
SA-UY-v4\tdatacenter-3\tdatacenter-1\tdatacenter-2
|
||||
NA-US-VA-v6 datacenter-2 \tdatacenter-1
|
||||
""";
|
||||
var actual = CallRoutingTableParser.fromTsv(new StringReader(input));
|
||||
var expected = new CallRoutingTable(
|
||||
Map.of(
|
||||
(CidrBlock.IpV4CidrBlock) CidrBlock.parseCidrBlock("192.1.12.0/24"), List.of("datacenter-1", "datacenter-2", "datacenter-3"),
|
||||
(CidrBlock.IpV4CidrBlock) CidrBlock.parseCidrBlock("193.123.123.0/24"), List.of("datacenter-1", "datacenter-2"),
|
||||
(CidrBlock.IpV4CidrBlock) CidrBlock.parseCidrBlock("1.123.123.0/24"), List.of("datacenter-1")
|
||||
),
|
||||
Map.of(
|
||||
(CidrBlock.IpV6CidrBlock) CidrBlock.parseCidrBlock("2001:db8:b0aa::/48"), List.of("datacenter-1"),
|
||||
(CidrBlock.IpV6CidrBlock) CidrBlock.parseCidrBlock("2001:db8:b0ab::/48"), List.of("datacenter-3", "datacenter-1", "datacenter-2"),
|
||||
(CidrBlock.IpV6CidrBlock) CidrBlock.parseCidrBlock("2001:db8:b0ac::/48"), List.of("datacenter-2", "datacenter-1")
|
||||
),
|
||||
Map.of(
|
||||
new CallRoutingTable.GeoKey("SA", "SR", Optional.empty(), CallRoutingTable.Protocol.v4), List.of("datacenter-3"),
|
||||
new CallRoutingTable.GeoKey("SA", "UY", Optional.empty(), CallRoutingTable.Protocol.v4), List.of("datacenter-3", "datacenter-1", "datacenter-2"),
|
||||
new CallRoutingTable.GeoKey("NA", "US", Optional.of("VA"), CallRoutingTable.Protocol.v6), List.of("datacenter-2", "datacenter-1")
|
||||
)
|
||||
);
|
||||
|
||||
assertThat(actual).isEqualTo(expected);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testParserMissingSection() throws IOException {
|
||||
var input =
|
||||
"""
|
||||
192.1.12.0/24\t \tdatacenter-1\t\t datacenter-2 datacenter-3
|
||||
193.123.123.0/24\tdatacenter-1\tdatacenter-2
|
||||
1.123.123.0/24\t datacenter-1
|
||||
|
||||
SA-SR-v4 datacenter-3
|
||||
SA-UY-v4\tdatacenter-3\tdatacenter-1\tdatacenter-2
|
||||
NA-US-VA-v6 datacenter-2 \tdatacenter-1
|
||||
""";
|
||||
var actual = CallRoutingTableParser.fromTsv(new StringReader(input));
|
||||
var expected = new CallRoutingTable(
|
||||
Map.of(
|
||||
(CidrBlock.IpV4CidrBlock) CidrBlock.parseCidrBlock("192.1.12.0/24"), List.of("datacenter-1", "datacenter-2", "datacenter-3"),
|
||||
(CidrBlock.IpV4CidrBlock) CidrBlock.parseCidrBlock("193.123.123.0/24"), List.of("datacenter-1", "datacenter-2"),
|
||||
(CidrBlock.IpV4CidrBlock) CidrBlock.parseCidrBlock("1.123.123.0/24"), List.of("datacenter-1")
|
||||
),
|
||||
Map.of(),
|
||||
Map.of(
|
||||
new CallRoutingTable.GeoKey("SA", "SR", Optional.empty(), CallRoutingTable.Protocol.v4), List.of("datacenter-3"),
|
||||
new CallRoutingTable.GeoKey("SA", "UY", Optional.empty(), CallRoutingTable.Protocol.v4), List.of("datacenter-3", "datacenter-1", "datacenter-2"),
|
||||
new CallRoutingTable.GeoKey("NA", "US", Optional.of("VA"), CallRoutingTable.Protocol.v6), List.of("datacenter-2", "datacenter-1")
|
||||
)
|
||||
);
|
||||
|
||||
assertThat(actual).isEqualTo(expected);
|
||||
}
|
||||
|
||||
|
||||
@Test
|
||||
public void testParserMixedSections() throws IOException {
|
||||
var input =
|
||||
"""
|
||||
|
||||
|
||||
1.123.123.0/24\t datacenter-1
|
||||
2001:db8:b0aa::/48\t \tdatacenter-1
|
||||
2001:db8:b0ab::/48 \tdatacenter-3\tdatacenter-1 datacenter-2
|
||||
2001:db8:b0ac::/48\tdatacenter-2\tdatacenter-1
|
||||
|
||||
|
||||
|
||||
192.1.12.0/24\t \tdatacenter-1\t\t datacenter-2 datacenter-3
|
||||
193.123.123.0/24\tdatacenter-1\tdatacenter-2
|
||||
|
||||
|
||||
|
||||
SA-SR-v4 datacenter-3
|
||||
|
||||
|
||||
|
||||
|
||||
SA-UY-v4\tdatacenter-3\tdatacenter-1\tdatacenter-2
|
||||
NA-US-VA-v6 datacenter-2 \tdatacenter-1
|
||||
""";
|
||||
var actual = CallRoutingTableParser.fromTsv(new StringReader(input));
|
||||
var expected = new CallRoutingTable(
|
||||
Map.of(
|
||||
(CidrBlock.IpV4CidrBlock) CidrBlock.parseCidrBlock("1.123.123.0/24"), List.of("datacenter-1"),
|
||||
(CidrBlock.IpV4CidrBlock) CidrBlock.parseCidrBlock("192.1.12.0/24"), List.of("datacenter-1", "datacenter-2", "datacenter-3"),
|
||||
(CidrBlock.IpV4CidrBlock) CidrBlock.parseCidrBlock("193.123.123.0/24"), List.of("datacenter-1", "datacenter-2")
|
||||
),
|
||||
Map.of(
|
||||
(CidrBlock.IpV6CidrBlock) CidrBlock.parseCidrBlock("2001:db8:b0aa::/48"), List.of("datacenter-1"),
|
||||
(CidrBlock.IpV6CidrBlock) CidrBlock.parseCidrBlock("2001:db8:b0ab::/48"), List.of("datacenter-3", "datacenter-1", "datacenter-2"),
|
||||
(CidrBlock.IpV6CidrBlock) CidrBlock.parseCidrBlock("2001:db8:b0ac::/48"), List.of("datacenter-2", "datacenter-1")
|
||||
),
|
||||
Map.of(
|
||||
new CallRoutingTable.GeoKey("SA", "SR", Optional.empty(), CallRoutingTable.Protocol.v4), List.of("datacenter-3"),
|
||||
new CallRoutingTable.GeoKey("SA", "UY", Optional.empty(), CallRoutingTable.Protocol.v4), List.of("datacenter-3", "datacenter-1", "datacenter-2"),
|
||||
new CallRoutingTable.GeoKey("NA", "US", Optional.of("VA"), CallRoutingTable.Protocol.v6), List.of("datacenter-2", "datacenter-1")
|
||||
)
|
||||
);
|
||||
|
||||
assertThat(actual).isEqualTo(expected);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testJsonParserSuccess() throws IOException {
|
||||
var input =
|
||||
"""
|
||||
{
|
||||
"ipv4GeoToDataCenters": {
|
||||
"SA-SR": ["datacenter-3"],
|
||||
"SA-UY": ["datacenter-3", "datacenter-1", "datacenter-2"]
|
||||
},
|
||||
"ipv6GeoToDataCenters": {
|
||||
"NA-US-VA": ["datacenter-2", "datacenter-1"]
|
||||
},
|
||||
"ipv4SubnetsToDatacenters": {
|
||||
"192.1.12.0": ["datacenter-1", "datacenter-2", "datacenter-3"],
|
||||
"193.123.123.0": ["datacenter-1", "datacenter-2"],
|
||||
"1.123.123.0": ["datacenter-1"]
|
||||
},
|
||||
"ipv6SubnetsToDatacenters": {
|
||||
"2001:db8:b0aa::": ["datacenter-1"],
|
||||
"2001:db8:b0ab::": ["datacenter-3", "datacenter-1", "datacenter-2"],
|
||||
"2001:db8:b0ac::": ["datacenter-2", "datacenter-1"]
|
||||
}
|
||||
}
|
||||
""";
|
||||
var actual = CallRoutingTableParser.fromJson(new StringReader(input));
|
||||
var expected = new CallRoutingTable(
|
||||
Map.of(
|
||||
(CidrBlock.IpV4CidrBlock) CidrBlock.parseCidrBlock("192.1.12.0/24"), List.of("datacenter-1", "datacenter-2", "datacenter-3"),
|
||||
(CidrBlock.IpV4CidrBlock) CidrBlock.parseCidrBlock("193.123.123.0/24"), List.of("datacenter-1", "datacenter-2"),
|
||||
(CidrBlock.IpV4CidrBlock) CidrBlock.parseCidrBlock("1.123.123.0/24"), List.of("datacenter-1")
|
||||
),
|
||||
Map.of(
|
||||
(CidrBlock.IpV6CidrBlock) CidrBlock.parseCidrBlock("2001:db8:b0aa::/48"), List.of("datacenter-1"),
|
||||
(CidrBlock.IpV6CidrBlock) CidrBlock.parseCidrBlock("2001:db8:b0ab::/48"), List.of("datacenter-3", "datacenter-1", "datacenter-2"),
|
||||
(CidrBlock.IpV6CidrBlock) CidrBlock.parseCidrBlock("2001:db8:b0ac::/48"), List.of("datacenter-2", "datacenter-1")
|
||||
),
|
||||
Map.of(
|
||||
new CallRoutingTable.GeoKey("SA", "SR", Optional.empty(), CallRoutingTable.Protocol.v4), List.of("datacenter-3"),
|
||||
new CallRoutingTable.GeoKey("SA", "UY", Optional.empty(), CallRoutingTable.Protocol.v4), List.of("datacenter-3", "datacenter-1", "datacenter-2"),
|
||||
new CallRoutingTable.GeoKey("NA", "US", Optional.of("VA"), CallRoutingTable.Protocol.v6), List.of("datacenter-2", "datacenter-1")
|
||||
)
|
||||
);
|
||||
|
||||
assertThat(actual).isEqualTo(expected);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testParseVariousEdgeCases() throws IOException {
|
||||
var input =
|
||||
"""
|
||||
{
|
||||
"ipv4GeoToDataCenters": {},
|
||||
"ipv6GeoToDataCenters": {},
|
||||
"ipv4SubnetsToDatacenters": {},
|
||||
"ipv6SubnetsToDatacenters": {}
|
||||
}
|
||||
""";
|
||||
assertThat(CallRoutingTableParser.fromJson(new StringReader(input))).isEqualTo(CallRoutingTable.empty());
|
||||
assertThat(CallRoutingTableParser.fromJson(new StringReader("{}"))).isEqualTo(CallRoutingTable.empty());
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,140 @@
|
||||
/*
|
||||
* Copyright 2024 Signal Messenger, LLC
|
||||
* SPDX-License-Identifier: AGPL-3.0-only
|
||||
*/
|
||||
|
||||
package org.whispersystems.textsecuregcm.calls.routing;
|
||||
|
||||
import io.vavr.Tuple2;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import java.net.Inet4Address;
|
||||
import java.net.Inet6Address;
|
||||
import java.net.UnknownHostException;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Optional;
|
||||
|
||||
import static org.assertj.core.api.AssertionsForClassTypes.assertThat;
|
||||
|
||||
public class CallRoutingTableTest {
|
||||
|
||||
static final CallRoutingTable basicTable = new CallRoutingTable(
|
||||
Map.of(
|
||||
(CidrBlock.IpV4CidrBlock) CidrBlock.parseCidrBlock("192.1.12.0/24"), List.of("datacenter-1", "datacenter-2", "datacenter-3"),
|
||||
(CidrBlock.IpV4CidrBlock) CidrBlock.parseCidrBlock("193.123.123.0/24"), List.of("datacenter-1", "datacenter-2"),
|
||||
(CidrBlock.IpV4CidrBlock) CidrBlock.parseCidrBlock("1.123.123.0/24"), List.of("datacenter-4")
|
||||
),
|
||||
Map.of(
|
||||
(CidrBlock.IpV6CidrBlock) CidrBlock.parseCidrBlock("2001:db8:b0aa::/48"), List.of("datacenter-1"),
|
||||
(CidrBlock.IpV6CidrBlock) CidrBlock.parseCidrBlock("2001:db8:b0ab::/48"), List.of("datacenter-3", "datacenter-1", "datacenter-2"),
|
||||
(CidrBlock.IpV6CidrBlock) CidrBlock.parseCidrBlock("2001:db8:b0ac::/48"), List.of("datacenter-2", "datacenter-1")
|
||||
),
|
||||
Map.of(
|
||||
new CallRoutingTable.GeoKey("SA", "SR", Optional.empty(), CallRoutingTable.Protocol.v4), List.of("datacenter-3"),
|
||||
new CallRoutingTable.GeoKey("SA", "UY", Optional.empty(), CallRoutingTable.Protocol.v4), List.of("datacenter-3", "datacenter-1", "datacenter-2"),
|
||||
new CallRoutingTable.GeoKey("NA", "US", Optional.of("VA"), CallRoutingTable.Protocol.v6), List.of("datacenter-2", "datacenter-1"),
|
||||
new CallRoutingTable.GeoKey("NA", "US", Optional.empty(), CallRoutingTable.Protocol.v6), List.of("datacenter-3", "datacenter-4")
|
||||
)
|
||||
);
|
||||
|
||||
// has overlapping subnets
|
||||
static final CallRoutingTable overlappingTable = new CallRoutingTable(
|
||||
Map.of(
|
||||
(CidrBlock.IpV4CidrBlock) CidrBlock.parseCidrBlock("192.1.12.0/24"), List.of("datacenter-1", "datacenter-2", "datacenter-3"),
|
||||
(CidrBlock.IpV4CidrBlock) CidrBlock.parseCidrBlock("1.123.123.0/24"), List.of("datacenter-4"),
|
||||
(CidrBlock.IpV4CidrBlock) CidrBlock.parseCidrBlock("1.123.0.0/16"), List.of("datacenter-1")
|
||||
),
|
||||
Map.of(
|
||||
(CidrBlock.IpV6CidrBlock) CidrBlock.parseCidrBlock("2001:db8:b0aa::/48"), List.of("datacenter-1"),
|
||||
(CidrBlock.IpV6CidrBlock) CidrBlock.parseCidrBlock("2001:db8:b0ac::/48"), List.of("datacenter-3", "datacenter-1", "datacenter-2"),
|
||||
(CidrBlock.IpV6CidrBlock) CidrBlock.parseCidrBlock("2001:db8:b0a0::/44"), List.of("datacenter-2", "datacenter-1")
|
||||
),
|
||||
Map.of(
|
||||
new CallRoutingTable.GeoKey("SA", "SR", Optional.empty(), CallRoutingTable.Protocol.v4), List.of("datacenter-3"),
|
||||
new CallRoutingTable.GeoKey("SA", "UY", Optional.empty(), CallRoutingTable.Protocol.v4), List.of("datacenter-3", "datacenter-1", "datacenter-2"),
|
||||
new CallRoutingTable.GeoKey("NA", "US", Optional.of("VA"), CallRoutingTable.Protocol.v6), List.of("datacenter-2", "datacenter-1")
|
||||
)
|
||||
);
|
||||
|
||||
@Test
|
||||
void testGetFastestDataCentersBySubnet() throws UnknownHostException {
|
||||
var v4address = Inet4Address.getByName("1.123.123.1");
|
||||
var actualV4 = basicTable.getDatacentersBySubnet(v4address);
|
||||
assertThat(actualV4).isEqualTo(List.of("datacenter-4"));
|
||||
|
||||
var v6address = Inet6Address.getByName("2001:db8:b0ac:aaaa:aaaa:aaaa:aaaa:0001");
|
||||
var actualV6 = basicTable.getDatacentersBySubnet(v6address);
|
||||
assertThat(actualV6).isEqualTo(List.of("datacenter-2", "datacenter-1"));
|
||||
}
|
||||
|
||||
@Test
|
||||
void testGetFastestDataCentersBySubnetOverlappingTable() throws UnknownHostException {
|
||||
var v4address = Inet4Address.getByName("1.123.123.1");
|
||||
var actualV4 = overlappingTable.getDatacentersBySubnet(v4address);
|
||||
assertThat(actualV4).isEqualTo(List.of("datacenter-4"));
|
||||
|
||||
var v6address = Inet6Address.getByName("2001:db8:b0ac:aaaa:aaaa:aaaa:aaaa:0001");
|
||||
var actualV6 = overlappingTable.getDatacentersBySubnet(v6address);
|
||||
assertThat(actualV6).isEqualTo(List.of("datacenter-3", "datacenter-1", "datacenter-2"));
|
||||
}
|
||||
|
||||
@Test
|
||||
void testGetFastestDataCentersByGeo() {
|
||||
var actual = basicTable.getDatacentersByGeo("SA", "SR", Optional.empty());
|
||||
assertThat(actual).isEqualTo(List.of("datacenter-3"));
|
||||
|
||||
var actualWithSubdvision = basicTable.getDatacentersByGeo("NA", "US", Optional.of("VA"));
|
||||
assertThat(actualWithSubdvision).isEqualTo(List.of("datacenter-2", "datacenter-1"));
|
||||
}
|
||||
|
||||
@Test
|
||||
void testGetFastestDataCentersByGeoFallback() {
|
||||
var actualExactMatch = basicTable.getDatacentersByGeo("NA", "US", Optional.of("VA"));
|
||||
assertThat(actualExactMatch).isEqualTo(List.of("datacenter-2", "datacenter-1"));
|
||||
|
||||
var actualApproximateMatch = basicTable.getDatacentersByGeo("NA", "US", Optional.of("MD"));
|
||||
assertThat(actualApproximateMatch).isEqualTo(List.of("datacenter-3", "datacenter-4"));
|
||||
}
|
||||
|
||||
@Test
|
||||
void testGetFastestDatacentersPrioritizesSubnet() throws UnknownHostException {
|
||||
var v4address = Inet4Address.getByName("1.123.123.1");
|
||||
var actual = basicTable.getDatacentersFor(v4address, "NA", "US", Optional.of("VA"));
|
||||
assertThat(actual).isEqualTo(List.of("datacenter-4", "datacenter-2", "datacenter-1"));
|
||||
}
|
||||
|
||||
@Test
|
||||
void testGetFastestDatacentersEmptySubnet() throws UnknownHostException {
|
||||
var v4address = Inet4Address.getByName("200.200.123.1");
|
||||
var actual = basicTable.getDatacentersFor(v4address, "NA", "US", Optional.of("VA"));
|
||||
assertThat(actual).isEqualTo(List.of("datacenter-2", "datacenter-1"));
|
||||
}
|
||||
|
||||
@Test
|
||||
void testGetFastestDatacentersEmptySubnetTakesExtraFromGeo() throws UnknownHostException {
|
||||
var v4address = Inet4Address.getByName("200.200.123.1");
|
||||
var actual = basicTable.getDatacentersFor(v4address, "SA", "UY", Optional.empty());
|
||||
assertThat(actual).isEqualTo(List.of("datacenter-3", "datacenter-1", "datacenter-2"));
|
||||
}
|
||||
|
||||
@Test
|
||||
void testGetFastestDatacentersEmptyGeoResults() throws UnknownHostException {
|
||||
var v4address = Inet4Address.getByName("1.123.123.1");
|
||||
var actual = basicTable.getDatacentersFor(v4address, "ZZ", "AA", Optional.empty());
|
||||
assertThat(actual).isEqualTo(List.of("datacenter-4"));
|
||||
}
|
||||
|
||||
@Test
|
||||
void testGetFastestDatacentersEmptyGeoTakesFromSubnet() throws UnknownHostException {
|
||||
var v4address = Inet4Address.getByName("192.1.12.1");
|
||||
var actual = basicTable.getDatacentersFor(v4address, "ZZ", "AA", Optional.empty());
|
||||
assertThat(actual).isEqualTo(List.of("datacenter-1", "datacenter-2", "datacenter-3"));
|
||||
}
|
||||
|
||||
@Test
|
||||
void testGetFastestDatacentersDistinct() throws UnknownHostException {
|
||||
var v6address = Inet6Address.getByName("2001:db8:b0ac:aaaa:aaaa:aaaa:aaaa:0001");
|
||||
var actual = basicTable.getDatacentersFor(v6address, "NA", "US", Optional.of("VA"));
|
||||
assertThat(actual).isEqualTo(List.of("datacenter-2", "datacenter-1"));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,63 @@
|
||||
/*
|
||||
* Copyright 2024 Signal Messenger, LLC
|
||||
* SPDX-License-Identifier: AGPL-3.0-only
|
||||
*/
|
||||
|
||||
package org.whispersystems.textsecuregcm.calls.routing;
|
||||
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import java.math.BigInteger;
|
||||
import java.net.InetAddress;
|
||||
import java.net.UnknownHostException;
|
||||
import java.util.HexFormat;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
|
||||
public class CidrBlockTest {
|
||||
|
||||
private HexFormat hex = HexFormat.ofDelimiter(":").withLowerCase();
|
||||
|
||||
@Test
|
||||
public void testIPv4CidrBlockParseSuccess() {
|
||||
var actual = CidrBlock.parseCidrBlock("255.32.15.0/24");
|
||||
var expected = new CidrBlock.IpV4CidrBlock(0xFF_20_0F_00, 0xFFFFFF00, 24);
|
||||
|
||||
assertThat(actual).isInstanceOf(CidrBlock.IpV4CidrBlock.class);
|
||||
assertThat(actual).isEqualTo(expected);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testIPv6CidrBlockParseSuccess() {
|
||||
var actual = CidrBlock.parseCidrBlock("2001:db8:b0aa::/48");
|
||||
var expected = new CidrBlock.IpV6CidrBlock(
|
||||
new BigInteger(hex.parseHex("20:01:0d:b8:b0:aa:00:00:00:00:00:00:00:00:00:00")),
|
||||
new BigInteger(hex.parseHex("FF:FF:FF:FF:FF:FF:00:00:00:00:00:00:00:00:00:00")),
|
||||
48
|
||||
);
|
||||
|
||||
assertThat(actual).isInstanceOf(CidrBlock.IpV6CidrBlock.class);
|
||||
assertThat(actual).isEqualTo(expected);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testIPv4InBlock() throws UnknownHostException {
|
||||
var block = CidrBlock.parseCidrBlock("255.32.15.0/24");
|
||||
|
||||
assertThat(block.ipInBlock(InetAddress.getByName("255.32.15.123"))).isTrue();
|
||||
assertThat(block.ipInBlock(InetAddress.getByName("255.32.15.0"))).isTrue();
|
||||
assertThat(block.ipInBlock(InetAddress.getByName("255.32.16.0"))).isFalse();
|
||||
assertThat(block.ipInBlock(InetAddress.getByName("255.33.15.0"))).isFalse();
|
||||
assertThat(block.ipInBlock(InetAddress.getByName("254.33.15.0"))).isFalse();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testIPv6InBlock() throws UnknownHostException {
|
||||
var block = CidrBlock.parseCidrBlock("2001:db8:b0aa::/48");
|
||||
|
||||
assertThat(block.ipInBlock(InetAddress.getByName("2001:db8:b0aa:1:1::"))).isTrue();
|
||||
assertThat(block.ipInBlock(InetAddress.getByName("2001:db8:b0aa:0:0::"))).isTrue();
|
||||
assertThat(block.ipInBlock(InetAddress.getByName("2001:db8:b0ab:1:1::"))).isFalse();
|
||||
assertThat(block.ipInBlock(InetAddress.getByName("2001:da8:b0aa:1:1::"))).isFalse();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,137 @@
|
||||
/*
|
||||
* Copyright 2024 Signal Messenger, LLC
|
||||
* SPDX-License-Identifier: AGPL-3.0-only
|
||||
*/
|
||||
|
||||
package org.whispersystems.textsecuregcm.calls.routing;
|
||||
|
||||
import com.fasterxml.jackson.core.JsonProcessingException;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.whispersystems.textsecuregcm.configuration.dynamic.DynamicConfiguration;
|
||||
import org.whispersystems.textsecuregcm.storage.DynamicConfigurationManager;
|
||||
import java.util.Collection;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.UUID;
|
||||
import java.util.stream.Collectors;
|
||||
import java.util.stream.Stream;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
import static org.junit.jupiter.api.Assertions.assertTrue;
|
||||
import static org.mockito.Mockito.mock;
|
||||
import static org.mockito.Mockito.when;
|
||||
|
||||
public class DynamicConfigTurnRouterTest {
|
||||
@Test
|
||||
public void testAlwaysSelectFirst() throws JsonProcessingException {
|
||||
final String configString = """
|
||||
captcha:
|
||||
scoreFloor: 1.0
|
||||
turn:
|
||||
uriConfigs:
|
||||
- uris:
|
||||
- always1.org
|
||||
- always2.org
|
||||
- uris:
|
||||
- never.org
|
||||
weight: 0
|
||||
""";
|
||||
DynamicConfiguration config = DynamicConfigurationManager
|
||||
.parseConfiguration(configString, DynamicConfiguration.class)
|
||||
.orElseThrow();
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
DynamicConfigurationManager<DynamicConfiguration> mockDynamicConfigManager = mock(
|
||||
DynamicConfigurationManager.class);
|
||||
|
||||
when(mockDynamicConfigManager.getConfiguration()).thenReturn(config);
|
||||
|
||||
final DynamicConfigTurnRouter configTurnRouter = new DynamicConfigTurnRouter(mockDynamicConfigManager);
|
||||
|
||||
final long COUNT = 1000;
|
||||
|
||||
final Map<String, Long> urlCounts = Stream
|
||||
.generate(configTurnRouter::randomUrls)
|
||||
.limit(COUNT)
|
||||
.flatMap(Collection::stream)
|
||||
.collect(Collectors.groupingBy(i -> i, Collectors.counting()));
|
||||
|
||||
assertThat(urlCounts.get("always1.org")).isEqualTo(COUNT);
|
||||
assertThat(urlCounts.get("always2.org")).isEqualTo(COUNT);
|
||||
assertThat(urlCounts).doesNotContainKey("never.org");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testProbabilisticUrls() throws JsonProcessingException {
|
||||
final String configString = """
|
||||
captcha:
|
||||
scoreFloor: 1.0
|
||||
turn:
|
||||
uriConfigs:
|
||||
- uris:
|
||||
- always.org
|
||||
- sometimes1.org
|
||||
weight: 5
|
||||
- uris:
|
||||
- always.org
|
||||
- sometimes2.org
|
||||
weight: 5
|
||||
""";
|
||||
DynamicConfiguration config = DynamicConfigurationManager
|
||||
.parseConfiguration(configString, DynamicConfiguration.class)
|
||||
.orElseThrow();
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
DynamicConfigurationManager<DynamicConfiguration> mockDynamicConfigManager = mock(
|
||||
DynamicConfigurationManager.class);
|
||||
|
||||
when(mockDynamicConfigManager.getConfiguration()).thenReturn(config);
|
||||
final DynamicConfigTurnRouter configTurnRouter = new DynamicConfigTurnRouter(mockDynamicConfigManager);
|
||||
|
||||
final long COUNT = 1000;
|
||||
|
||||
final Map<String, Long> urlCounts = Stream
|
||||
.generate(configTurnRouter::randomUrls)
|
||||
.limit(COUNT)
|
||||
.flatMap(Collection::stream)
|
||||
.collect(Collectors.groupingBy(i -> i, Collectors.counting()));
|
||||
|
||||
assertThat(urlCounts.get("always.org")).isEqualTo(COUNT);
|
||||
assertThat(urlCounts.get("sometimes1.org")).isGreaterThan(0);
|
||||
assertThat(urlCounts.get("sometimes2.org")).isGreaterThan(0);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testExplicitEnrollment() throws JsonProcessingException {
|
||||
final String configString = """
|
||||
captcha:
|
||||
scoreFloor: 1.0
|
||||
turn:
|
||||
secret: bloop
|
||||
uriConfigs:
|
||||
- uris:
|
||||
- enrolled.org
|
||||
weight: 0
|
||||
enrolledAcis:
|
||||
- 732506d7-d04f-43a4-b1d7-8a3a91ebe8a6
|
||||
- uris:
|
||||
- unenrolled.org
|
||||
weight: 1
|
||||
""";
|
||||
DynamicConfiguration config = DynamicConfigurationManager
|
||||
.parseConfiguration(configString, DynamicConfiguration.class)
|
||||
.orElseThrow();
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
DynamicConfigurationManager<DynamicConfiguration> mockDynamicConfigManager = mock(
|
||||
DynamicConfigurationManager.class);
|
||||
|
||||
when(mockDynamicConfigManager.getConfiguration()).thenReturn(config);
|
||||
final DynamicConfigTurnRouter configTurnRouter = new DynamicConfigTurnRouter(mockDynamicConfigManager);
|
||||
|
||||
List<String> urls = configTurnRouter.targetedUrls(UUID.fromString("732506d7-d04f-43a4-b1d7-8a3a91ebe8a6"));
|
||||
assertThat(urls.getFirst()).isEqualTo("enrolled.org");
|
||||
urls = configTurnRouter.targetedUrls(UUID.randomUUID());
|
||||
assertTrue(urls.isEmpty());
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,262 @@
|
||||
/*
|
||||
* Copyright 2024 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 com.maxmind.geoip2.record.Continent;
|
||||
import com.maxmind.geoip2.record.Country;
|
||||
import com.maxmind.geoip2.record.Subdivision;
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.net.InetAddress;
|
||||
import java.net.UnknownHostException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Optional;
|
||||
import java.util.UUID;
|
||||
|
||||
import static org.assertj.core.api.AssertionsForClassTypes.assertThat;
|
||||
import static org.mockito.ArgumentMatchers.any;
|
||||
import static org.mockito.Mockito.mock;
|
||||
import static org.mockito.Mockito.when;
|
||||
|
||||
public class TurnCallRouterTest {
|
||||
|
||||
private final static String TEST_HOSTNAME = "subdomain.example.org";
|
||||
private final static List<String> TEST_URLS_WITH_HOSTS = List.of(
|
||||
"one.example.com",
|
||||
"two.example.com",
|
||||
"three.example.com"
|
||||
);
|
||||
|
||||
private CallRoutingTable performanceTable;
|
||||
private CallRoutingTable manualTable;
|
||||
private DynamicConfigTurnRouter configTurnRouter;
|
||||
private DatabaseReader geoIp;
|
||||
private Country country;
|
||||
private Continent continent;
|
||||
private CallDnsRecords callDnsRecords;
|
||||
private Subdivision subdivision;
|
||||
private UUID aci = UUID.randomUUID();
|
||||
|
||||
@BeforeEach
|
||||
void setup() throws IOException, GeoIp2Exception {
|
||||
performanceTable = mock(CallRoutingTable.class);
|
||||
manualTable = mock(CallRoutingTable.class);
|
||||
configTurnRouter = mock(DynamicConfigTurnRouter.class);
|
||||
geoIp = mock(DatabaseReader.class);
|
||||
continent = mock(Continent.class);
|
||||
country = mock(Country.class);
|
||||
subdivision = mock(Subdivision.class);
|
||||
ArrayList<Subdivision> subdivisions = new ArrayList<>();
|
||||
subdivisions.add(subdivision);
|
||||
|
||||
when(geoIp.city(any())).thenReturn(new CityResponse(null, continent, country, null, null, null, null, null, subdivisions, null));
|
||||
setupDefault();
|
||||
}
|
||||
|
||||
void setupDefault() {
|
||||
when(configTurnRouter.targetedUrls(any())).thenReturn(Collections.emptyList());
|
||||
when(configTurnRouter.randomUrls()).thenReturn(TEST_URLS_WITH_HOSTS);
|
||||
when(configTurnRouter.getHostname()).thenReturn(TEST_HOSTNAME);
|
||||
when(configTurnRouter.shouldRandomize()).thenReturn(false);
|
||||
when(manualTable.getDatacentersFor(any(), any(), any(), any())).thenReturn(Collections.emptyList());
|
||||
when(continent.getCode()).thenReturn("NA");
|
||||
when(country.getIsoCode()).thenReturn("US");
|
||||
when(subdivision.getIsoCode()).thenReturn("VA");
|
||||
try {
|
||||
callDnsRecords = new CallDnsRecords(
|
||||
Map.of(
|
||||
"dc-manual", List.of(InetAddress.getByName("1.1.1.1")),
|
||||
"dc-performance1", List.of(
|
||||
InetAddress.getByName("9.9.9.1"),
|
||||
InetAddress.getByName("9.9.9.2")
|
||||
),
|
||||
"dc-performance2", List.of(InetAddress.getByName("9.9.9.3")),
|
||||
"dc-performance3", List.of(InetAddress.getByName("9.9.9.4"))
|
||||
),
|
||||
Map.of(
|
||||
"dc-manual", List.of(InetAddress.getByName("2222:1111:0:dead::")),
|
||||
"dc-performance1", List.of(
|
||||
InetAddress.getByName("2222:1111:0:abc0::"),
|
||||
InetAddress.getByName("2222:1111:0:abc1::")
|
||||
),
|
||||
"dc-performance2", List.of(InetAddress.getByName("2222:1111:0:abc2::")),
|
||||
"dc-performance3", List.of(InetAddress.getByName("2222:1111:0:abc3::"))
|
||||
)
|
||||
);
|
||||
} catch (UnknownHostException e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
}
|
||||
|
||||
private TurnCallRouter router() {
|
||||
return new TurnCallRouter(
|
||||
() -> callDnsRecords,
|
||||
() -> performanceTable,
|
||||
() -> manualTable,
|
||||
configTurnRouter,
|
||||
() -> geoIp
|
||||
);
|
||||
}
|
||||
|
||||
TurnServerOptions optionsWithUrls(List<String> urls) {
|
||||
return new TurnServerOptions(
|
||||
TEST_HOSTNAME,
|
||||
urls,
|
||||
TEST_URLS_WITH_HOSTS
|
||||
);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testPrioritizesTargetedUrls() throws UnknownHostException {
|
||||
List<String> targetedUrls = List.of(
|
||||
"targeted1.example.com",
|
||||
"targeted.example.com"
|
||||
);
|
||||
when(configTurnRouter.targetedUrls(any()))
|
||||
.thenReturn(targetedUrls);
|
||||
|
||||
assertThat(router().getRoutingFor(aci, Optional.of(InetAddress.getByName("0.0.0.1")), 10))
|
||||
.isEqualTo(new TurnServerOptions(
|
||||
TEST_HOSTNAME,
|
||||
null,
|
||||
targetedUrls
|
||||
));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testRandomizes() throws UnknownHostException {
|
||||
when(configTurnRouter.shouldRandomize())
|
||||
.thenReturn(true);
|
||||
|
||||
assertThat(router().getRoutingFor(aci, Optional.of(InetAddress.getByName("0.0.0.1")), 10))
|
||||
.isEqualTo(optionsWithUrls(null));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testOrderedByPerformance() throws UnknownHostException {
|
||||
when(performanceTable.getDatacentersFor(any(), any(), any(), any()))
|
||||
.thenReturn(List.of("dc-performance2", "dc-performance1"));
|
||||
|
||||
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"
|
||||
)));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testPrioritizesManualRecords() throws UnknownHostException {
|
||||
when(performanceTable.getDatacentersFor(any(), any(), any(), any()))
|
||||
.thenReturn(List.of("dc-performance1"));
|
||||
when(manualTable.getDatacentersFor(any(), any(), any(), any()))
|
||||
.thenReturn(List.of("dc-manual"));
|
||||
|
||||
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"
|
||||
)));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testLimitReturnsHalfIpv4AndPrioritizesPerformance() 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))
|
||||
.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",
|
||||
|
||||
"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"
|
||||
)));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testNoDatacentersMatched() throws UnknownHostException {
|
||||
when(performanceTable.getDatacentersFor(any(), any(), any(), any()))
|
||||
.thenReturn(List.of());
|
||||
|
||||
assertThat(router().getRoutingFor(aci, Optional.of(InetAddress.getByName("0.0.0.1")), 10))
|
||||
.isEqualTo(optionsWithUrls(List.of()));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testHandlesDatacenterNotInDnsRecords() throws UnknownHostException {
|
||||
when(performanceTable.getDatacentersFor(any(), any(), any(), any()))
|
||||
.thenReturn(List.of("unsynced-datacenter"));
|
||||
|
||||
assertThat(router().getRoutingFor(aci, Optional.of(InetAddress.getByName("0.0.0.1")), 10))
|
||||
.isEqualTo(optionsWithUrls(List.of()));
|
||||
}
|
||||
}
|
||||
@@ -333,6 +333,8 @@ class DynamicConfigurationTest {
|
||||
weight: 2
|
||||
enrolledAcis:
|
||||
- 732506d7-d04f-43a4-b1d7-8a3a91ebe8a6
|
||||
randomizeRate: 100_000
|
||||
hostname: test.domain.org
|
||||
""");
|
||||
DynamicTurnConfiguration turnConfiguration = DynamicConfigurationManager
|
||||
.parseConfiguration(config, DynamicConfiguration.class)
|
||||
@@ -345,6 +347,8 @@ class DynamicConfigurationTest {
|
||||
assertThat(turnConfiguration.getUriConfigs().get(1).getEnrolledAcis())
|
||||
.containsExactly(UUID.fromString("732506d7-d04f-43a4-b1d7-8a3a91ebe8a6"));
|
||||
|
||||
assertThat(turnConfiguration.getHostname()).isEqualTo("test.domain.org");
|
||||
assertThat(turnConfiguration.getRandomizeRate()).isEqualTo(100_000L);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,142 @@
|
||||
/*
|
||||
* Copyright 2013 Signal Messenger, LLC
|
||||
* SPDX-License-Identifier: AGPL-3.0-only
|
||||
*/
|
||||
|
||||
package org.whispersystems.textsecuregcm.controllers;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
import static org.mockito.ArgumentMatchers.any;
|
||||
import static org.mockito.ArgumentMatchers.anyInt;
|
||||
import static org.mockito.ArgumentMatchers.eq;
|
||||
import static org.mockito.Mockito.doThrow;
|
||||
import static org.mockito.Mockito.mock;
|
||||
import static org.mockito.Mockito.when;
|
||||
|
||||
import com.google.common.net.HttpHeaders;
|
||||
import io.dropwizard.auth.AuthValueFactoryProvider;
|
||||
import io.dropwizard.testing.junit5.DropwizardExtensionsSupport;
|
||||
import io.dropwizard.testing.junit5.ResourceExtension;
|
||||
import java.net.InetAddress;
|
||||
import java.net.UnknownHostException;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.util.List;
|
||||
import java.util.Optional;
|
||||
import javax.ws.rs.core.Response;
|
||||
import org.glassfish.jersey.test.grizzly.GrizzlyWebTestContainerFactory;
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.junit.jupiter.api.extension.ExtendWith;
|
||||
import org.whispersystems.textsecuregcm.auth.AuthenticatedAccount;
|
||||
import org.whispersystems.textsecuregcm.auth.TurnToken;
|
||||
import org.whispersystems.textsecuregcm.auth.TurnTokenGenerator;
|
||||
import org.whispersystems.textsecuregcm.calls.routing.TurnCallRouter;
|
||||
import org.whispersystems.textsecuregcm.calls.routing.TurnServerOptions;
|
||||
import org.whispersystems.textsecuregcm.configuration.dynamic.DynamicConfiguration;
|
||||
import org.whispersystems.textsecuregcm.limits.RateLimiter;
|
||||
import org.whispersystems.textsecuregcm.limits.RateLimiters;
|
||||
import org.whispersystems.textsecuregcm.mappers.RateLimitExceededExceptionMapper;
|
||||
import org.whispersystems.textsecuregcm.storage.DynamicConfigurationManager;
|
||||
import org.whispersystems.textsecuregcm.tests.util.AuthHelper;
|
||||
import org.whispersystems.textsecuregcm.util.SystemMapper;
|
||||
import org.whispersystems.textsecuregcm.util.TestRemoteAddressFilterProvider;
|
||||
|
||||
@ExtendWith(DropwizardExtensionsSupport.class)
|
||||
class CallRoutingControllerTest {
|
||||
private static final RateLimiters rateLimiters = mock(RateLimiters.class);
|
||||
private static final RateLimiter getCallEndpointLimiter = mock(RateLimiter.class);
|
||||
private static final DynamicConfigurationManager<DynamicConfiguration> configManager = mock(DynamicConfigurationManager.class);
|
||||
private static final TurnTokenGenerator turnTokenGenerator = new TurnTokenGenerator(configManager, "bloop".getBytes(
|
||||
StandardCharsets.UTF_8));
|
||||
private static final TurnCallRouter turnCallRouter = mock(TurnCallRouter.class);
|
||||
private static final String GET_CALL_ENDPOINTS_PATH = "v1/calling/relays";
|
||||
private static final String REMOTE_ADDRESS = "123.123.123.1";
|
||||
|
||||
private static final ResourceExtension resources = ResourceExtension.builder()
|
||||
.addProvider(AuthHelper.getAuthFilter())
|
||||
.addProvider(new AuthValueFactoryProvider.Binder<>(AuthenticatedAccount.class))
|
||||
.addProvider(new RateLimitExceededExceptionMapper())
|
||||
.addProvider(new TestRemoteAddressFilterProvider(REMOTE_ADDRESS))
|
||||
.setMapper(SystemMapper.jsonMapper())
|
||||
.setTestContainerFactory(new GrizzlyWebTestContainerFactory())
|
||||
.addResource(new CallRoutingController(rateLimiters, turnCallRouter, turnTokenGenerator))
|
||||
.build();
|
||||
|
||||
@BeforeEach
|
||||
void setup() {
|
||||
when(rateLimiters.getCallEndpointLimiter()).thenReturn(getCallEndpointLimiter);
|
||||
}
|
||||
|
||||
@Test
|
||||
void testGetTurnEndpointsSuccess() throws UnknownHostException {
|
||||
TurnServerOptions options = new TurnServerOptions(
|
||||
"example.domain.org",
|
||||
List.of("stun:12.34.56.78"),
|
||||
List.of("stun:example.domain.org")
|
||||
);
|
||||
|
||||
when(turnCallRouter.getRoutingFor(
|
||||
eq(AuthHelper.VALID_UUID),
|
||||
eq(Optional.of(InetAddress.getByName(REMOTE_ADDRESS))),
|
||||
anyInt())
|
||||
).thenReturn(options);
|
||||
try(Response response = resources.getJerseyTest()
|
||||
.target(GET_CALL_ENDPOINTS_PATH)
|
||||
.request()
|
||||
.header("Authorization", AuthHelper.getAuthHeader(AuthHelper.VALID_UUID, AuthHelper.VALID_PASSWORD))
|
||||
.get()) {
|
||||
|
||||
assertThat(response.getStatus()).isEqualTo(200);
|
||||
TurnToken token = response.readEntity(TurnToken.class);
|
||||
assertThat(token.username()).isNotEmpty();
|
||||
assertThat(token.password()).isNotEmpty();
|
||||
assertThat(token.hostname()).isEqualTo(options.hostname());
|
||||
assertThat(token.urlsWithIps()).isEqualTo(options.urlsWithIps());
|
||||
assertThat(token.urls()).isEqualTo(options.urlsWithHostname());
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
void testGetTurnEndpointsInvalidIpSuccess() throws UnknownHostException {
|
||||
TurnServerOptions options = new TurnServerOptions(
|
||||
"example.domain.org",
|
||||
List.of(),
|
||||
List.of("stun:example.domain.org")
|
||||
);
|
||||
|
||||
when(turnCallRouter.getRoutingFor(
|
||||
eq(AuthHelper.VALID_UUID),
|
||||
eq(Optional.of(InetAddress.getByName(REMOTE_ADDRESS))),
|
||||
anyInt())
|
||||
).thenReturn(options);
|
||||
try(Response response = resources.getJerseyTest()
|
||||
.target(GET_CALL_ENDPOINTS_PATH)
|
||||
.request()
|
||||
.header("Authorization", AuthHelper.getAuthHeader(AuthHelper.VALID_UUID, AuthHelper.VALID_PASSWORD))
|
||||
.get()) {
|
||||
|
||||
assertThat(response.getStatus()).isEqualTo(200);
|
||||
TurnToken token = response.readEntity(TurnToken.class);
|
||||
assertThat(token.username()).isNotEmpty();
|
||||
assertThat(token.password()).isNotEmpty();
|
||||
assertThat(token.hostname()).isEqualTo(options.hostname());
|
||||
assertThat(token.urlsWithIps()).isEqualTo(options.urlsWithIps());
|
||||
assertThat(token.urls()).isEqualTo(options.urlsWithHostname());
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
void testGetTurnEndpointRateLimited() throws RateLimitExceededException {
|
||||
doThrow(new RateLimitExceededException(null, false))
|
||||
.when(getCallEndpointLimiter).validate(AuthHelper.VALID_UUID);
|
||||
|
||||
try(final Response response = resources.getJerseyTest()
|
||||
.target(GET_CALL_ENDPOINTS_PATH)
|
||||
.request()
|
||||
.header("Authorization", AuthHelper.getAuthHeader(AuthHelper.VALID_UUID, AuthHelper.VALID_PASSWORD))
|
||||
.get()) {
|
||||
|
||||
assertThat(response.getStatus()).isEqualTo(429);
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user