/** * Moved from `modules/celilo-mgmt/scripts/discovery.test.ts` with celilo#1225. * * The parser cases came across unchanged. The `discoverDns` cases are new and * could not have been written there: the module's version read the filesystem * directly, so the file-preference rule — the part with the actual failure mode * — had no seam to test through. */ import { describe, expect, test } from 'bun:test'; import { type ResolverFileReader, chooseDns, discoverDns, parseNameservers } from './dns-discovery'; const SYSTEMD = '/run/systemd/resolve/resolv.conf'; const ETC = '/etc/resolv.conf'; /** A host offering exactly the files given, and nothing else. */ function host(files: Record): ResolverFileReader { return (path) => files[path] ?? null; } describe('parseNameservers', () => { test('extracts nameserver IPs in order', () => { const resolv = ['# comment', 'nameserver 9.9.9.9', 'nameserver 8.8.4.4', 'search lan'].join( '\n', ); expect(parseNameservers(resolv)).toEqual(['9.9.9.9', '8.8.4.4']); }); test('returns empty for no nameservers', () => { expect(parseNameservers('search lan\noptions edns0')).toEqual([]); }); }); describe('chooseDns', () => { test('drops loopback stubs (systemd-resolved 127.0.0.53)', () => { expect(chooseDns(['127.0.0.53'])).toEqual({ primary: '1.1.1.1', fallback: '1.1.1.1' }); }); test('uses the first two real upstreams', () => { expect(chooseDns(['9.9.9.9', '8.8.4.4', '1.0.0.1'])).toEqual({ primary: '9.9.9.9', fallback: '8.8.4.4', }); }); test('falls back to 1.1.1.1 for the second when only one upstream', () => { expect(chooseDns(['9.9.9.9'])).toEqual({ primary: '9.9.9.9', fallback: '1.1.1.1' }); }); }); describe('discoverDns', () => { test('prefers the systemd upstream file over the stub in /etc/resolv.conf', () => { // The case the file order exists for. Reading only /etc/resolv.conf here // discovers 127.0.0.53, which no other box on the network can reach. const servers = discoverDns( host({ [SYSTEMD]: 'nameserver 9.9.9.9\nnameserver 8.8.4.4\n', [ETC]: 'nameserver 127.0.0.53\n', }), ); expect(servers).toEqual({ primary: '9.9.9.9', fallback: '8.8.4.4' }); }); test('falls through to /etc/resolv.conf when the systemd file yields nothing real', () => { const servers = discoverDns( host({ [SYSTEMD]: 'nameserver 127.0.0.53\n', [ETC]: 'nameserver 192.0.2.10\nnameserver 192.0.2.11\n', }), ); expect(servers).toEqual({ primary: '192.0.2.10', fallback: '192.0.2.11' }); }); test('reads /etc/resolv.conf on a host with no systemd resolver file', () => { expect(discoverDns(host({ [ETC]: 'nameserver 192.0.2.10\n' }))).toEqual({ primary: '192.0.2.10', fallback: '1.1.1.1', }); }); test('accepts a loopback-only /etc/resolv.conf as the public fallback, not as an error', () => { // Last resort: whatever it gives is taken, and a stub-only host yields the // public fallback rather than an unreachable address. expect(discoverDns(host({ [ETC]: 'nameserver 127.0.0.53\n' }))).toEqual({ primary: '1.1.1.1', fallback: '1.1.1.1', }); }); test('returns the public fallback on a host with no resolver files at all', () => { expect(discoverDns(host({}))).toEqual({ primary: '1.1.1.1', fallback: '1.1.1.1' }); }); // The recurrence gate for celilo#1239 (bead ce-dxr8). The dns-client-config // aspect of a deployed dns_internal provider rewrites /etc/resolv.conf to // name that provider. Discovery reading the file back adopts the fleet's own // resolver as the fleet's upstream and writes it into dns.primary / // dns.fallback, where it reaches every LXC's permanent birth nameserver // line. Proven to fail against the accepting implementation first. describe('refusing resolvers celilo itself installed', () => { // technitium's advertised dns_internal addresses: its dmz zone address and // (once advertised) the firewall ingress `internal`-zone systems use. const FLEET = ['10.0.10.13', '192.168.0.151']; test('refuses a resolv.conf naming a deployed dns_internal provider address', () => { const servers = discoverDns(host({ [ETC]: `nameserver ${FLEET[0]}\n` }), { fleetResolverIps: FLEET, }); expect(servers).toEqual({ primary: '1.1.1.1', fallback: '1.1.1.1' }); }); test('keeps the real upstreams beside a refused fleet resolver', () => { const servers = discoverDns(host({ [ETC]: `nameserver ${FLEET[0]}\nnameserver 9.9.9.9\n` }), { fleetResolverIps: FLEET, }); expect(servers).toEqual({ primary: '9.9.9.9', fallback: '1.1.1.1' }); }); test('falls through to /etc/resolv.conf when the systemd file holds only fleet resolvers', () => { const servers = discoverDns( host({ [SYSTEMD]: `nameserver ${FLEET[0]}\n`, [ETC]: 'nameserver 9.9.9.9\n', }), { fleetResolverIps: FLEET }, ); expect(servers).toEqual({ primary: '9.9.9.9', fallback: '1.1.1.1' }); }); test('matches a capability address that carries a CIDR suffix', () => { // Capability data advertises `192.168.0.151/24` (server.ip resolves from // target_ip); the resolv.conf entry is the bare address. const servers = discoverDns(host({ [ETC]: `nameserver ${FLEET[1]}\n` }), { fleetResolverIps: [`${FLEET[1]}/24`], }); expect(servers).toEqual({ primary: '1.1.1.1', fallback: '1.1.1.1' }); }); test('accepts a private upstream that is not a fleet resolver', () => { // A home-lab upstream on the operator's own network is legitimate. The // refusal is scoped to resolvers celilo deployed, not to privateness. expect( discoverDns(host({ [ETC]: 'nameserver 192.168.0.1\n' }), { fleetResolverIps: FLEET }), ).toEqual({ primary: '192.168.0.1', fallback: '1.1.1.1' }); }); }); });