diff --git a/bin/room-assistant.js b/bin/room-assistant.js index 2183d443..588dc430 100644 --- a/bin/room-assistant.js +++ b/bin/room-assistant.js @@ -17,6 +17,12 @@ const optionDefinitions = [ type: String, defaultValue: './config' }, + { + name: 'digResolver', + description: + 'Use dig to resolve mdns hostnames instead of the native getaddrinfo.', + type: Boolean + }, { name: 'verbose', description: 'Turn on debugging output.', @@ -47,4 +53,8 @@ process.env.NODE_LOG_LEVEL = options.verbose : process.env.NODE_LOG_LEVEL || 'production'; process.env.NODE_CONFIG_DIR = options.config; +if (options.digResolver) { + process.env.NODE_DIG_RESOLVER = true; +} + require('../dist/main'); diff --git a/src/cluster/cluster.service.ts b/src/cluster/cluster.service.ts index 43a7b070..429676c8 100644 --- a/src/cluster/cluster.service.ts +++ b/src/cluster/cluster.service.ts @@ -13,6 +13,7 @@ import { NetworkInterfaceInfo } from 'os'; import { ConfigService } from '../config/config.service'; import { ClusterConfig } from './cluster.config'; import { makeId } from '../util/id'; +import { getAddrInfoDig } from './resolvers'; let mdns; try { @@ -137,11 +138,13 @@ export class ClusterService extends Democracy networkInterface: this.config.networkInterface } ); - const sequence = [ - mdns.rst.DNSServiceResolve(), + const defaultGetAddr = 'DNSServiceGetAddrInfo' in mdns.dns_sd ? mdns.rst.DNSServiceGetAddrInfo() - : mdns.rst.getaddrinfo({ families: [0] }), + : mdns.rst.getaddrinfo({ families: [0] }); + const sequence = [ + mdns.rst.DNSServiceResolve(), + process.env.NODE_DIG_RESOLVER ? getAddrInfoDig : defaultGetAddr, mdns.rst.makeAddressesUnique() ]; this.browser = mdns.createBrowser(mdns.udp('room-assistant'), { diff --git a/src/cluster/resolvers.spec.ts b/src/cluster/resolvers.spec.ts new file mode 100644 index 00000000..ae4363fa --- /dev/null +++ b/src/cluster/resolvers.spec.ts @@ -0,0 +1,91 @@ +const mockExec = jest.fn(); + +import { getAddrInfoDig } from './resolvers'; +import { Service } from 'mdns'; + +jest.mock('util', () => ({ + ...jest.requireActual('util'), + // eslint-disable-next-line @typescript-eslint/explicit-function-return-type + promisify: () => mockExec +})); + +describe('resolvers', () => { + beforeEach(async () => { + jest.clearAllMocks(); + }); + + it('should return a single address correctly', done => { + const service: Service = { + addresses: undefined, + flags: undefined, + fullname: undefined, + host: undefined, + interfaceIndex: undefined, + networkInterface: undefined, + port: undefined, + replyDomain: undefined, + type: undefined + }; + mockExec.mockResolvedValue({ stdout: '192.168.1.20\n' }); + + getAddrInfoDig(service, () => { + try { + expect(service.addresses).toStrictEqual(['192.168.1.20']); + done(); + } catch (e) { + done(e); + } + }); + }); + + it('should resolve multiple addresses correctly', done => { + const service: Service = { + addresses: undefined, + flags: undefined, + fullname: undefined, + host: undefined, + interfaceIndex: undefined, + networkInterface: undefined, + port: undefined, + replyDomain: undefined, + type: undefined + }; + mockExec.mockResolvedValue({ stdout: '192.168.1.20\n192.168.1.21\n' }); + + getAddrInfoDig(service, () => { + try { + expect(service.addresses).toStrictEqual([ + '192.168.1.20', + '192.168.1.21' + ]); + done(); + } catch (e) { + done(e); + } + }); + }); + + it('should pass errors to the callback', done => { + const service: Service = { + addresses: undefined, + flags: undefined, + fullname: undefined, + host: undefined, + interfaceIndex: undefined, + networkInterface: undefined, + port: undefined, + replyDomain: undefined, + type: undefined + }; + mockExec.mockRejectedValue({ stdout: 'dig not found' }); + + getAddrInfoDig(service, e => { + try { + expect(e).not.toBeUndefined(); + done(); + } catch (e) { + done(e); + } + }); + }); +}); diff --git a/src/cluster/resolvers.ts b/src/cluster/resolvers.ts new file mode 100644 index 00000000..8b439f72 --- /dev/null +++ b/src/cluster/resolvers.ts @@ -0,0 +1,22 @@ +import { Service } from 'mdns'; +import * as util from 'util'; +import { exec } from 'child_process'; +import * as os from 'os'; + +const execPromise = util.promisify(exec); + +export async function getAddrInfoDig( + service: Service, + next: (e?: Error) => void +): Promise { + try { + const digOutput = await execPromise( + `dig +short @224.0.0.251 -p 5353 -4 ${service.host}` + ); + const addresses = digOutput.stdout.trim().split(os.EOL); + service.addresses = (service.addresses || []).concat(addresses); + next(); + } catch (e) { + next(e); + } +}