Improve discover balena os across different networks

This is an improvement over the scan, join and leave commands removing flakiness when searching over different networks. In short, instead of leaving bonjour to search across all interfaces, we forcebly conduct a search on each interface, this requires mDNS binding any ipv4 interface (0.0.0.0), otherwise it would bind over the interface itself, which is not desired as it causes services to only be able to receive information over that interface, see [mafintosh/multicast-dns#53](https://github.com/mafintosh/multicast-dns/issues/53). This targeted approach enhances the reliability and accuracy of network searches, reducing instances of missed connections or network errors typically caused by flakiness when relying on bonjour's default behavior.

Change-type: patch
This commit is contained in:
Otavio Jacobi 2024-07-15 19:39:45 -03:00
parent 2c0c1f8fd1
commit 03f0f11f8b
2 changed files with 59 additions and 21 deletions

View File

@ -1,5 +1,6 @@
import Bonjour from 'bonjour-service';
import type { Service } from 'bonjour-service';
import * as os from 'os';
interface LocalBalenaOsDevice {
address: string;
@ -19,20 +20,25 @@ const avahiBalenaSshSubtype = 'resin-device';
export async function discoverLocalBalenaOsDevices(
timeout = 4000,
): Promise<LocalBalenaOsDevice[]> {
const services = await new Promise<Service[]>((resolve) => {
const bonjour = new Bonjour({}, async (err: string | Error) => {
await (await import('../errors')).handleError(err);
});
const resinSshServices: Service[] = [];
const browser = bonjour.find(avahiBalenaSshConfig, (service) =>
resinSshServices.push(service),
);
setTimeout(() => {
browser.stop();
bonjour.destroy();
resolve(resinSshServices);
}, timeout);
});
// search over all network interfaces
const networks = os.networkInterfaces();
const validNics: os.NetworkInterfaceInfo[] = [];
for (const networkName of Object.keys(networks)) {
for (const iface of networks[networkName]!) {
if (isIPv4(iface.family) && !iface.internal) {
validNics.push(iface);
}
}
}
const allServices = await Promise.all(
validNics.map((iface) => searchBalenaDevicesOnInterface(iface, timeout)),
);
// dedupe services in case the same device is found on multiple interfaces
const services = Array.from(
new Map(allServices.flat().map((item) => [item.fqdn, item])).values(),
);
return services
.filter(
@ -46,3 +52,36 @@ export async function discoverLocalBalenaOsDevices(
port,
}));
}
async function searchBalenaDevicesOnInterface(
iface: os.NetworkInterfaceInfo,
timeout: number,
): Promise<Service[]> {
return await new Promise<Service[]>((resolve) => {
const bonjour = new Bonjour(
{
// @ts-expect-error bonjour-service types are incorrect https://github.com/onlxltd/bonjour-service/issues/10
interface: iface.address,
// binds to receive from any incoming interface
// see: https://github.com/mafintosh/multicast-dns/issues/53#issuecomment-638365104
bind: '0.0.0.0',
},
async (err: string | Error) => {
await (await import('../errors')).handleError(err);
},
);
const resinSshServices: Service[] = [];
const browser = bonjour.find(avahiBalenaSshConfig, (service) =>
resinSshServices.push(service),
);
setTimeout(() => {
browser.stop();
bonjour.destroy();
resolve(resinSshServices);
}, timeout);
});
}
function isIPv4(family: string | number) {
return family === 4 || family === 'IPv4';
}

13
npm-shrinkwrap.json generated
View File

@ -4257,9 +4257,9 @@
}
},
"node_modules/@types/node": {
"version": "20.14.10",
"resolved": "https://registry.npmjs.org/@types/node/-/node-20.14.10.tgz",
"integrity": "sha512-MdiXf+nDuMvY0gJKxyfZ7/6UFsETO7mGKF54MVD/ekJS6HdFtpZFBgrh6Pseu64XTb2MLyFPlbW6hj8HYRQNOQ==",
"version": "20.14.11",
"resolved": "https://registry.npmjs.org/@types/node/-/node-20.14.11.tgz",
"integrity": "sha512-kprQpL8MMeszbz6ojB5/tU8PLN4kesnN8Gjzw349rDlNgsSzg90lAVj3llK99Dh7JON+t9AuscPPFW6mPbTnSA==",
"dependencies": {
"undici-types": "~5.26.4"
}
@ -5780,10 +5780,9 @@
}
},
"node_modules/balena-sdk/node_modules/@types/node": {
"version": "18.19.39",
"resolved": "https://registry.npmjs.org/@types/node/-/node-18.19.39.tgz",
"integrity": "sha512-nPwTRDKUctxw3di5b4TfT3I0sWDiWoPQCZjXhvdkINntwr8lcoVCKsTgnXeRubKIlfnV+eN/HYk6Jb40tbcEAQ==",
"license": "MIT",
"version": "18.19.40",
"resolved": "https://registry.npmjs.org/@types/node/-/node-18.19.40.tgz",
"integrity": "sha512-MIxieZHrm4Ee8XArBIc+Or9HINt2StOmCbgRcXGSJl8q14svRvkZPe7LJq9HKtTI1SK3wU8b91TjntUm7T69Pg==",
"dependencies": {
"undici-types": "~5.26.4"
}