Skip to content

Commit

Permalink
Improve discover balena os across different networks
Browse files Browse the repository at this point in the history
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](mafintosh/multicast-dns#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
  • Loading branch information
otaviojacobi committed Jul 16, 2024
1 parent 2c0c1f8 commit 03f0f11
Show file tree
Hide file tree
Showing 2 changed files with 59 additions and 21 deletions.
67 changes: 53 additions & 14 deletions lib/utils/discover.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import Bonjour from 'bonjour-service';
import type { Service } from 'bonjour-service';
import * as os from 'os';

interface LocalBalenaOsDevice {
address: string;
Expand All @@ -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(
Expand All @@ -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 changes: 6 additions & 7 deletions npm-shrinkwrap.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

0 comments on commit 03f0f11

Please sign in to comment.