diff --git a/service/pom.xml b/service/pom.xml index 035b54a75..7d6d230c2 100644 --- a/service/pom.xml +++ b/service/pom.xml @@ -163,6 +163,12 @@ 3.12.0 + + org.apache.commons + commons-csv + 1.8 + + org.glassfish.jersey.test-framework.providers jersey-test-framework-provider-grizzly2 diff --git a/service/src/main/java/org/whispersystems/textsecuregcm/util/AsnTable.java b/service/src/main/java/org/whispersystems/textsecuregcm/util/AsnTable.java new file mode 100644 index 000000000..b3387565d --- /dev/null +++ b/service/src/main/java/org/whispersystems/textsecuregcm/util/AsnTable.java @@ -0,0 +1,80 @@ +/* + * Copyright 2021 Signal Messenger, LLC + * SPDX-License-Identifier: AGPL-3.0-only + */ + +package org.whispersystems.textsecuregcm.util; + +import com.google.common.annotations.VisibleForTesting; +import java.io.IOException; +import java.io.Reader; +import java.net.Inet4Address; +import java.nio.ByteBuffer; +import java.util.NavigableMap; +import java.util.Optional; +import java.util.TreeMap; +import org.apache.commons.csv.CSVFormat; +import org.apache.commons.csv.CSVParser; +import org.apache.commons.csv.CSVRecord; + +/** + * Allows IP->ASN lookup operations using data from https://iptoasn.com/. + */ +class AsnTable { + private final NavigableMap asnBlocksByFirstIp; + + private static class AsnRange { + private final long rangeStart; + private final long rangeEnd; + + private final long asn; + + private AsnRange(long rangeStart, long rangeEnd, long asn) { + this.rangeStart = rangeStart; + this.rangeEnd = rangeEnd; + this.asn = asn; + } + + boolean contains(final long address) { + return address >= rangeStart && address <= rangeEnd; + } + + long getAsn() { + return asn; + } + } + + public AsnTable(final Reader tsvReader) throws IOException { + final TreeMap treeMap = new TreeMap<>(); + + try (final CSVParser csvParser = CSVFormat.TDF.parse(tsvReader)) { + for (final CSVRecord record : csvParser) { + final long start = Long.parseLong(record.get(0), 10); + final long end = Long.parseLong(record.get(1), 10); + final long asn = Long.parseLong(record.get(2), 10); + + treeMap.put(start, new AsnRange(start, end, asn)); + } + } + + asnBlocksByFirstIp = treeMap; + } + + public Optional getAsn(final Inet4Address address) { + final long addressAsLong = ipToLong(address); + + return Optional.ofNullable(asnBlocksByFirstIp.floorEntry(addressAsLong)) + .filter(entry -> entry.getValue().contains(addressAsLong)) + .map(entry -> entry.getValue().getAsn()); + } + + @VisibleForTesting + static long ipToLong(final Inet4Address address) { + final ByteBuffer buffer = ByteBuffer.allocate(8); + buffer.position(4); + buffer.put(address.getAddress()); + + buffer.flip(); + return buffer.getLong(); + } +} diff --git a/service/src/test/java/org/whispersystems/textsecuregcm/util/AsnTableTest.java b/service/src/test/java/org/whispersystems/textsecuregcm/util/AsnTableTest.java new file mode 100644 index 000000000..edf19385b --- /dev/null +++ b/service/src/test/java/org/whispersystems/textsecuregcm/util/AsnTableTest.java @@ -0,0 +1,38 @@ +/* + * Copyright 2021 Signal Messenger, LLC + * SPDX-License-Identifier: AGPL-3.0-only + */ + +package org.whispersystems.textsecuregcm.util; + +import org.junit.jupiter.api.Test; + +import java.io.IOException; +import java.io.InputStreamReader; +import java.net.Inet4Address; +import java.net.UnknownHostException; +import java.util.Optional; +import java.util.zip.GZIPInputStream; + +import static org.junit.jupiter.api.Assertions.*; + +class AsnTableTest { + + @Test + void getAsn() throws IOException { + try (final InputStreamReader reader = new InputStreamReader(getClass().getResourceAsStream("ip2asn-test.tsv"))) { + final AsnTable asnTable = new AsnTable(reader); + + assertEquals(Optional.of(7922L), asnTable.getAsn((Inet4Address) Inet4Address.getByName("50.79.54.1"))); + assertEquals(Optional.of(7552L), asnTable.getAsn((Inet4Address) Inet4Address.getByName("27.79.32.1"))); + assertEquals(Optional.empty(), asnTable.getAsn((Inet4Address) Inet4Address.getByName("32.79.117.1"))); + assertEquals(Optional.empty(), asnTable.getAsn((Inet4Address) Inet4Address.getByName("10.0.0.1"))); + } + } + + @Test + void ipToLong() throws UnknownHostException { + assertEquals(0x00000000ffffffffL, AsnTable.ipToLong((Inet4Address) Inet4Address.getByName("255.255.255.255"))); + assertEquals(0x0000000000000001L, AsnTable.ipToLong((Inet4Address) Inet4Address.getByName("0.0.0.1"))); + } +} diff --git a/service/src/test/resources/org/whispersystems/textsecuregcm/util/ip2asn-test.tsv b/service/src/test/resources/org/whispersystems/textsecuregcm/util/ip2asn-test.tsv new file mode 100644 index 000000000..d248be4fd --- /dev/null +++ b/service/src/test/resources/org/whispersystems/textsecuregcm/util/ip2asn-test.tsv @@ -0,0 +1,2 @@ +458051584 458227711 7552 VN VIETEL-AS-AP Viettel Group +843841536 844103679 7922 US COMCAST-7922 - Comcast Cable Communications, LLC