From 03f0f11f8b4c9fa4b5b4a9612fa9550b08d4bdd7 Mon Sep 17 00:00:00 2001 From: Otavio Jacobi Date: Mon, 15 Jul 2024 19:39:45 -0300 Subject: [PATCH] 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 --- lib/utils/discover.ts | 67 ++++++++++++++++++++++++++++++++++--------- npm-shrinkwrap.json | 13 ++++----- 2 files changed, 59 insertions(+), 21 deletions(-) diff --git a/lib/utils/discover.ts b/lib/utils/discover.ts index 76945582..813a5cf4 100644 --- a/lib/utils/discover.ts +++ b/lib/utils/discover.ts @@ -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 { - const services = await new Promise((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 { + return await new Promise((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'; +} diff --git a/npm-shrinkwrap.json b/npm-shrinkwrap.json index 58646faa..fee88c9d 100644 --- a/npm-shrinkwrap.json +++ b/npm-shrinkwrap.json @@ -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" }