Skip to content

Commit

Permalink
feat(cluster): add an option to resolve mdns hostnames with dig
Browse files Browse the repository at this point in the history
libnss-mdns is complicated and does not work on Alpine, which means the
auto discovery could never resolve the hosts on such systems. This adds
an option to resolve the hostname using dig instead, which is especially
great for Docker images to use.
  • Loading branch information
mKeRix committed Feb 20, 2020
1 parent 567a6fb commit 67593d4
Show file tree
Hide file tree
Showing 4 changed files with 129 additions and 3 deletions.
10 changes: 10 additions & 0 deletions bin/room-assistant.js
Expand Up @@ -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.',
Expand Down Expand Up @@ -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');
9 changes: 6 additions & 3 deletions src/cluster/cluster.service.ts
Expand Up @@ -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 {
Expand Down Expand Up @@ -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'), {
Expand Down
91 changes: 91 additions & 0 deletions 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);
}
});
});
});
22 changes: 22 additions & 0 deletions 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<void> {
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);
}
}

0 comments on commit 67593d4

Please sign in to comment.