Files
5chan/src/lib/__tests__/peer-geo.test.ts
T

315 lines
11 KiB
TypeScript
Raw Normal View History

import { describe, expect, it, vi } from 'vitest';
import { COUNTRY_CENTROIDS } from '../../data/country-centroids';
import {
extractIpFromAddress,
extractIpv4FromAddress,
extractIpv6FromAddress,
fetchOwnIpCountryCode,
fetchOwnPublicEndpoint,
fetchPeerMapLocation,
getApproximateCountryCode,
getApproximateLatLon,
getCountryConsistentLocation,
getFirstPublicIpFromAddresses,
isPrivateOrReservedIpv4,
} from '../peer-geo';
const addr = (ip: string) => `/ip4/${ip}/tcp/4001/ws/p2p/12D3KooWExample`;
describe('extractIpv4FromAddress', () => {
it('extracts the IPv4 from a multiaddr', () => {
expect(extractIpv4FromAddress('/ip4/147.75.84.175/tcp/4001/ws')).toBe('147.75.84.175');
});
it('returns null for IPv6 and plain DNS multiaddrs', () => {
expect(extractIpv4FromAddress('/ip6/2606:4700::1111/tcp/4001')).toBeNull();
expect(extractIpv4FromAddress('/dns4/relay.example.org/tcp/443/wss')).toBeNull();
});
it('extracts an IPv4 embedded with dashes in a DNS hostname', () => {
expect(extractIpv4FromAddress('/dns4/91-234-56-78.host.example/tcp/443/wss')).toBe('91.234.56.78');
expect(extractIpv4FromAddress('/dns4/ip-203-0-113-9.provider.net/tcp/443/wss')).toBe('203.0.113.9');
});
});
describe('extractIpv6FromAddress', () => {
it('extracts the IPv6 from a multiaddr', () => {
expect(extractIpv6FromAddress('/ip6/2001:4860:4860::8888/tcp/4001/ws')).toBe('2001:4860:4860::8888');
expect(extractIpFromAddress('/ip6/2001:4860:4860::8888/tcp/4001/ws')).toBe('2001:4860:4860::8888');
});
it('extracts an IPv6 embedded with dashes in a DNS hostname', () => {
const address = '/dns6/2a11-6100-0-5e9f--0.k51qzi5uqu5djg5pdoi9a98.example/tcp/443/wss/p2p/12D3KooWExample';
expect(extractIpv6FromAddress(address)).toBe('2a11:6100:0:5e9f::0');
expect(extractIpFromAddress(address)).toBe('2a11:6100:0:5e9f::0');
});
});
describe('isPrivateOrReservedIpv4', () => {
it('flags private and reserved ranges', () => {
for (const ip of ['10.0.0.1', '172.16.5.4', '192.168.1.10', '127.0.0.1', '169.254.1.1', '100.64.0.1', '0.0.0.0', '239.255.0.1']) {
expect(isPrivateOrReservedIpv4(ip)).toBe(true);
}
});
it('treats public addresses as routable', () => {
for (const ip of ['8.8.8.8', '147.75.84.175', '1.1.1.1', '80.80.80.80']) {
expect(isPrivateOrReservedIpv4(ip)).toBe(false);
}
});
});
describe('getApproximateLatLon', () => {
it('returns null when the peer cannot be placed offline', () => {
expect(getApproximateLatLon('/ip4/192.168.1.5/tcp/4001')).toBeNull();
expect(getApproximateLatLon('/dns4/relay.example.org/tcp/443/wss')).toBeNull();
expect(getApproximateLatLon('/ip6/2606:4700::1111/tcp/4001')).toBeNull();
});
it('is deterministic for the same address', () => {
expect(getApproximateLatLon(addr('8.8.8.8'))).toEqual(getApproximateLatLon(addr('8.8.8.8')));
});
it('snaps a peer to the centroid of its flag country', () => {
for (const ip of ['8.8.8.8', '80.80.80.80', '1.1.1.1', '41.0.0.1', '200.0.0.1', '194.110.247.146', '91.234.199.189']) {
const country = getApproximateCountryCode(addr(ip));
expect(country).toBeDefined();
const centroid = COUNTRY_CENTROIDS[country!];
expect(centroid).toBeDefined();
const loc = getApproximateLatLon(addr(ip))!;
// Marker sits at the country centroid, within the small placement jitter.
expect(Math.abs(loc.lat - centroid.lat)).toBeLessThanOrEqual(1);
expect(Math.abs(loc.lon - centroid.lon)).toBeLessThanOrEqual(1.3);
}
});
it('stays within valid coordinate bounds', () => {
const loc = getApproximateLatLon(addr('203.0.113.7'));
expect(loc).not.toBeNull();
expect(loc!.lat).toBeGreaterThanOrEqual(-85);
expect(loc!.lat).toBeLessThanOrEqual(85);
expect(loc!.lon).toBeGreaterThanOrEqual(-180);
expect(loc!.lon).toBeLessThanOrEqual(180);
});
});
describe('getCountryConsistentLocation', () => {
it('falls back to the selected country centroid when GeoIP databases disagree', () => {
expect(
getCountryConsistentLocation('VN', {
countryCode: 'ca',
label: 'Toronto, Ontario, CA',
lat: 43.6532,
lon: -79.3832,
source: 'geoip',
}),
).toEqual({
...COUNTRY_CENTROIDS.vn,
countryCode: 'vn',
label: 'VN',
source: 'coarse',
});
});
});
describe('getFirstPublicIpFromAddresses', () => {
it('returns the first public IP from multiaddrs', () => {
expect(getFirstPublicIpFromAddresses(['/ip4/127.0.0.1/tcp/4001', '/ip4/147.75.84.175/tcp/4001/ws'])).toBe('147.75.84.175');
expect(getFirstPublicIpFromAddresses(['/ip4/127.0.0.1/tcp/4001', '/ip6/2001:4860:4860::8888/tcp/443'])).toBe('2001:4860:4860::8888');
});
it('returns undefined when only private addresses are present', () => {
expect(getFirstPublicIpFromAddresses(['/ip4/127.0.0.1/tcp/4001', '/ip4/10.0.0.5/tcp/4001', '/ip6/fd00::1/tcp/4001'])).toBeUndefined();
});
});
describe('fetchOwnPublicEndpoint', () => {
it('prefers and caches the fetched IPv4 public endpoint', async () => {
const fetchMock = vi.fn(async (url: string | URL | Request) => {
const requestUrl = String(url);
if (requestUrl === 'https://api.ipify.org?format=json') {
return {
ok: true,
json: async () => ({ ip: '104.28.68.171' }),
};
}
if (requestUrl === 'https://api.country.is/104.28.68.171') {
return {
ok: true,
json: async () => ({ country: 'VN', ip: '104.28.68.171' }),
};
}
if (requestUrl === 'https://free.freeipapi.com/api/json/104.28.68.171') {
return {
ok: true,
json: async () => ({
cityName: 'Toronto',
countryCode: 'CA',
latitude: 43.6532,
longitude: -79.3832,
regionName: 'Ontario',
}),
};
}
return {
ok: true,
json: async () => ({ country: 'US', ip: '2001:4860:4860::8888' }),
};
});
vi.stubGlobal('fetch', fetchMock);
await expect(fetchOwnPublicEndpoint()).resolves.toMatchObject({
countryCode: 'vn',
ip: '104.28.68.171',
location: {
countryCode: 'vn',
label: 'VN',
source: 'coarse',
},
});
await expect(fetchOwnPublicEndpoint()).resolves.toMatchObject({ countryCode: 'vn', ip: '104.28.68.171' });
expect(fetchMock).toHaveBeenCalledTimes(3);
expect(fetchMock).not.toHaveBeenCalledWith('https://api.country.is', expect.anything());
vi.unstubAllGlobals();
});
});
describe('fetchOwnIpCountryCode', () => {
it("resolves and caches the accurate country for the node's own ip", async () => {
const fetchMock = vi.fn().mockResolvedValue({
ok: true,
json: async () => ({ country: 'VN', ip: '172.225.56.8' }),
});
vi.stubGlobal('fetch', fetchMock);
await expect(fetchOwnIpCountryCode('172.225.56.8')).resolves.toBe('vn');
await expect(fetchOwnIpCountryCode('172.225.56.8')).resolves.toBe('vn');
expect(fetchMock).toHaveBeenCalledTimes(1);
expect(fetchMock.mock.calls[0][0]).toBe('https://api.country.is/172.225.56.8');
vi.unstubAllGlobals();
});
it('does not cache a result from an aborted lookup', async () => {
const fetchMock = vi.fn().mockResolvedValue({
ok: true,
json: async () => ({ country: 'VN', ip: '203.0.113.50' }),
});
vi.stubGlobal('fetch', fetchMock);
const controller = new AbortController();
controller.abort();
// An aborted request is cancellation, not a real result, so it must not be cached.
await fetchOwnIpCountryCode('203.0.113.50', controller.signal);
// A later non-aborted call must perform a fresh lookup instead of a cached blank.
await expect(fetchOwnIpCountryCode('203.0.113.50')).resolves.toBe('vn');
expect(fetchMock).toHaveBeenCalledTimes(2);
vi.unstubAllGlobals();
});
});
describe('fetchPeerMapLocation', () => {
it('resolves and caches a city-level peer location', async () => {
const fetchMock = vi.fn().mockResolvedValue({
ok: true,
json: async () => ({
cityName: 'Haarlem',
countryCode: 'NL',
ipAddress: '91.234.199.189',
latitude: 52.3874,
longitude: 4.64622,
regionName: 'North Holland',
}),
});
vi.stubGlobal('fetch', fetchMock);
await expect(fetchPeerMapLocation('/ip4/91.234.199.189/tcp/4001')).resolves.toMatchObject({
countryCode: 'nl',
label: 'Haarlem, North Holland, NL',
lat: 52.3874,
lon: 4.64622,
source: 'geoip',
});
await expect(fetchPeerMapLocation('/ip4/91.234.199.189/tcp/4001')).resolves.toMatchObject({
lat: 52.3874,
lon: 4.64622,
source: 'geoip',
});
expect(fetchMock).toHaveBeenCalledTimes(1);
expect(fetchMock.mock.calls[0][0]).toBe('https://free.freeipapi.com/api/json/91.234.199.189');
vi.unstubAllGlobals();
});
it('falls back to the offline country estimate when lookup fails', async () => {
const fetchMock = vi.fn().mockResolvedValue({ ok: false });
vi.stubGlobal('fetch', fetchMock);
const location = await fetchPeerMapLocation(addr('80.80.80.80'));
const centroid = COUNTRY_CENTROIDS[location!.countryCode!];
expect(location).toMatchObject({ source: 'coarse' });
expect(centroid).toBeDefined();
expect(Math.abs(location!.lat - centroid.lat)).toBeLessThanOrEqual(1);
expect(Math.abs(location!.lon - centroid.lon)).toBeLessThanOrEqual(1.3);
vi.unstubAllGlobals();
});
it('does not call GeoIP for private peer addresses', async () => {
const fetchMock = vi.fn();
vi.stubGlobal('fetch', fetchMock);
await expect(fetchPeerMapLocation('/ip4/10.0.0.1/tcp/4001')).resolves.toBeUndefined();
expect(fetchMock).not.toHaveBeenCalled();
vi.unstubAllGlobals();
});
it('resolves a peer location from a DNS6 hostname with an embedded IPv6', async () => {
const fetchMock = vi.fn().mockResolvedValue({
ok: true,
json: async () => ({
cityName: 'Reykjavik',
countryCode: 'IS',
ipAddress: '2a11:6100:0:5e9f::0',
latitude: 64.1466,
longitude: -21.9426,
}),
});
vi.stubGlobal('fetch', fetchMock);
await expect(fetchPeerMapLocation('/dns6/2a11-6100-0-5e9f--0.k51qzi5uqu5djg5pdoi9a98.example/tcp/443/wss')).resolves.toMatchObject({
countryCode: 'is',
label: 'Reykjavik, IS',
lat: 64.1466,
lon: -21.9426,
source: 'geoip',
});
expect(fetchMock.mock.calls[0][0]).toBe('https://free.freeipapi.com/api/json/2a11%3A6100%3A0%3A5e9f%3A%3A0');
vi.unstubAllGlobals();
});
});
describe('getApproximateCountryCode', () => {
it('returns undefined when the peer cannot be placed offline', () => {
expect(getApproximateCountryCode('/ip4/10.0.0.1/tcp/4001')).toBeUndefined();
expect(getApproximateCountryCode('/dns4/relay.example.org/tcp/443/wss')).toBeUndefined();
expect(getApproximateCountryCode('/ip6/2606:4700::1111/tcp/4001')).toBeUndefined();
});
it('is deterministic and returns a known 2-letter code for public peers', () => {
const code = getApproximateCountryCode(addr('8.8.8.8'));
expect(code).toBe(getApproximateCountryCode(addr('8.8.8.8')));
expect(code).toMatch(/^[a-z]{2}$/);
});
it('derives a code from an IPv4 embedded in a DNS hostname', () => {
expect(getApproximateCountryCode('/dns4/91-234-56-78.host.example/tcp/443/wss')).toMatch(/^[a-z]{2}$/);
});
});