Skip to content

Commit 139be2b

Browse files
feat: update alt ips logic
1 parent 6ee978a commit 139be2b

File tree

5 files changed

+127
-84
lines changed

5 files changed

+127
-84
lines changed

src/helper/alt-ips-handler.ts

Lines changed: 0 additions & 77 deletions
This file was deleted.

src/helper/ip-handler.ts

Lines changed: 120 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,120 @@
1+
import os from 'node:os';
2+
import config from 'config';
3+
import is from '@sindresorhus/is';
4+
import _ from 'lodash';
5+
import { scopedLogger } from '../lib/logger.js';
6+
import got, { RequestError } from 'got';
7+
import type { Socket } from 'socket.io-client';
8+
9+
const mainLogger = scopedLogger('general');
10+
const altIpsLogger = scopedLogger('api:connect:alt-ips-handler');
11+
12+
class AltIpsManager {
13+
private readonly INTERVAL_TIME = 10 * 60 * 1000;
14+
15+
socket: Socket;
16+
ip: string;
17+
private timer?: NodeJS.Timeout;
18+
19+
constructor (socket: Socket, ip: string) {
20+
this.socket = socket;
21+
this.ip = ip;
22+
this.start();
23+
}
24+
25+
updateConfig (socket: Socket, ip: string) {
26+
this.socket = socket;
27+
this.ip = ip;
28+
this.start();
29+
}
30+
31+
start () {
32+
clearTimeout(this.timer);
33+
this.run();
34+
}
35+
36+
private run () {
37+
void this.refreshAltIps().catch((error) => {
38+
altIpsLogger.error(error);
39+
}).finally(() => {
40+
this.timer = setTimeout(() => this.run(), this.INTERVAL_TIME);
41+
});
42+
}
43+
44+
45+
async refreshAltIps (): Promise<void> {
46+
const acceptedIps = [ this.ip ];
47+
const acceptedIpsWithTokens: { ip: string; token: string }[] = [];
48+
const rejectedIps: string[] = [];
49+
const addresses = _(os.networkInterfaces())
50+
.values()
51+
.filter(is.truthy)
52+
.flatten()
53+
.uniqBy('address')
54+
.filter(address => !address.internal)
55+
.filter(address => !address.address.startsWith('fe80:')) // filter out link-local addresses
56+
.filter(address => !address.address.startsWith('169.254.')) // filter out link-local addresses
57+
.value();
58+
59+
const results = await Promise.allSettled(addresses.map(({ address, family }) => this.getAltIp(address, family === 'IPv6' ? 6 : 4)));
60+
61+
results.forEach((result, index) => {
62+
if (result.status === 'fulfilled') {
63+
acceptedIpsWithTokens.push(result.value);
64+
acceptedIps.push(result.value.ip);
65+
} else {
66+
if (!(result.reason instanceof RequestError)) {
67+
altIpsLogger.warn(result.reason);
68+
} else if (result.reason.response?.statusCode !== 400) {
69+
altIpsLogger.warn(`${result.reason.message} (via ${result.reason.options.localAddress}).`);
70+
} else {
71+
rejectedIps.push(addresses[index]!.address);
72+
}
73+
}
74+
});
75+
76+
const uniqAcceptedIps = _(acceptedIps).uniq().value();
77+
const uniqRejectedIps = _(rejectedIps).uniq().value();
78+
79+
if (uniqRejectedIps.length === 1) {
80+
altIpsLogger.info(`IP address rejected by the API: ${uniqRejectedIps.join(', ')}.`);
81+
} else if (uniqRejectedIps.length > 1) {
82+
altIpsLogger.info(`IP addresses rejected by the API: ${uniqRejectedIps.join(', ')}.`);
83+
}
84+
85+
if (uniqAcceptedIps.length === 1) {
86+
mainLogger.info(`IP address of the probe: ${uniqAcceptedIps[0]}.`);
87+
} else {
88+
mainLogger.info(`IP addresses of the probe: ${uniqAcceptedIps.join(', ')}.`);
89+
}
90+
}
91+
92+
async getAltIp (ip: string, dnsLookupIpVersion: 4 | 6) {
93+
const httpHost = config.get<string>('api.httpHost');
94+
const response = await got.post<{ ip: string; token: string }>(`${httpHost}/alternative-ip`, {
95+
localAddress: ip,
96+
dnsLookupIpVersion,
97+
retry: {
98+
limit: 2,
99+
methods: [ 'POST' ],
100+
statusCodes: [ 504 ],
101+
},
102+
timeout: {
103+
request: 15_000,
104+
},
105+
responseType: 'json',
106+
});
107+
108+
return response.body;
109+
}
110+
}
111+
112+
let altIpsManager: AltIpsManager | null = null;
113+
114+
export const ipHandler = (socket: Socket) => ({ ip }: { ip: string }) => {
115+
if (!altIpsManager) {
116+
altIpsManager = new AltIpsManager(socket, ip);
117+
} else {
118+
altIpsManager.updateConfig(socket, ip);
119+
}
120+
};

src/helper/api-connect-handler.ts renamed to src/helper/location-handler.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ import { getStatusManager } from '../lib/status-manager.js';
66

77
const logger = scopedLogger('api:connect:location');
88

9-
export const apiConnectLocationHandler = (socket: Socket) => async (data: ProbeLocation): Promise<void> => {
9+
export const locationHandler = (socket: Socket) => async (data: ProbeLocation): Promise<void> => {
1010
logger.info(`Connected from ${data.city}, ${data.country}, ${data.continent} (${data.network}, ASN: ${data.asn}, lat: ${data.latitude} long: ${data.longitude}).`);
1111
const statusManager = getStatusManager();
1212
await statusManager.start();

src/lib/stats/client.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,7 @@ const report = async (socket: Socket, jobCount: number) => {
2222
});
2323
};
2424

25-
export const run = (socket: Socket, worker: Worker) => {
25+
export const runStatsAgent = (socket: Socket, worker: Worker) => {
2626
setInterval(() => {
2727
report(socket, worker.jobs.size)
2828
.catch((error: unknown) => {

src/probe.ts

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -14,8 +14,8 @@ import { apiTransport, scopedLogger } from './lib/logger.js';
1414
import { ApiTransportSettings } from './lib/api-transport.js';
1515
import { initErrorHandler } from './helper/api-error-handler.js';
1616
import { handleTestError } from './helper/test-error-handler.js';
17-
import { apiConnectLocationHandler } from './helper/api-connect-handler.js';
18-
import { apiConnectAltIpsHandler } from './helper/alt-ips-handler.js';
17+
import { locationHandler } from './helper/location-handler.js';
18+
import { ipHandler } from './helper/ip-handler.js';
1919
import { adoptionStatusHandler } from './helper/adoption-status-handler.js';
2020
import { dnsCmd, DnsCommand } from './command/dns-command.js';
2121
import { pingCmd, PingCommand } from './command/ping-command.js';
@@ -24,7 +24,7 @@ import { mtrCmd, MtrCommand } from './command/mtr-command.js';
2424
import { httpCmd, HttpCommand } from './command/http-command.js';
2525
import { FakePingCommand } from './command/fake/fake-ping-command.js';
2626
import { FakeMtrCommand } from './command/fake/fake-mtr-command.js';
27-
import { run as runStatsAgent } from './lib/stats/client.js';
27+
import { runStatsAgent } from './lib/stats/client.js';
2828
import { initStatusManager } from './lib/status-manager.js';
2929
import { logAdoptionCode } from './lib/log-adoption-code.js';
3030
import { getAvailableDiskSpace, getTotalDiskSize, looksLikeV1HardwareDevice } from './lib/util.js';
@@ -120,9 +120,9 @@ function connect (workerId?: number) {
120120
})
121121
.on('disconnect', errorHandler.handleDisconnect)
122122
.on('connect_error', errorHandler.connectError)
123-
.on('api:connect:location', apiConnectLocationHandler(socket))
123+
.on('api:connect:location', locationHandler(socket))
124124
.on('api:connect:adoption', adoptionStatusHandler)
125-
.on('api:connect:alt-ips-token', apiConnectAltIpsHandler)
125+
.on('api:connect:ip', ipHandler(socket))
126126
.on('probe:measurement:request', (data: MeasurementRequest) => {
127127
const status = statusManager.getStatus();
128128

0 commit comments

Comments
 (0)