fix: use native DNS resolution by default for Docker services (#6997)
* fix: use native DNS resolution by default for Docker services * test: verify Docker service DNS through production Compose * test: isolate Docker DNS smoke resources per invocation [skip ci]
This commit is contained in:
1 parent
907b166f46
commit
67d76f034d
8 files changed
+483
-11
No files matched your search
@@ -2,7 +2,7 @@ import { createBooleanSchema, createEnv } from "../env";
|
||||
|
||||
export const dnsEnv = createEnv({
|
||||
server: {
|
||||
ENABLE_DNS_CACHING: createBooleanSchema(true),
|
||||
ENABLE_DNS_CACHING: createBooleanSchema(false),
|
||||
},
|
||||
experimental__runtimeEnv: process.env,
|
||||
});
|
||||
@@ -14,15 +14,15 @@ declare global {
|
||||
|
||||
const logger = createLogger({ module: "dns" });
|
||||
|
||||
// Initialize global.homarr if not present
|
||||
// eslint-disable-next-line @typescript-eslint/no-unnecessary-condition
|
||||
global.homarr ??= {};
|
||||
global.homarr.dnsCacheManager ??= new DnsCacheManager({
|
||||
cacheMaxEntries: 1000,
|
||||
forceMinTtl: 5 * 60 * 1000, // 5 minutes
|
||||
logger,
|
||||
});
|
||||
|
||||
if (dnsEnv.ENABLE_DNS_CACHING) {
|
||||
// Opt-in only: cached addresses can outlive Docker service endpoints, and
|
||||
// DNS-first resolution does not preserve /etc/hosts precedence.
|
||||
// eslint-disable-next-line @typescript-eslint/no-unnecessary-condition
|
||||
global.homarr ??= {};
|
||||
global.homarr.dnsCacheManager ??= new DnsCacheManager({
|
||||
cacheMaxEntries: 1000,
|
||||
forceMinTtl: 5 * 60 * 1000, // 5 minutes
|
||||
logger,
|
||||
});
|
||||
global.homarr.dnsCacheManager.initialize();
|
||||
}
|
||||
@@ -0,0 +1,56 @@
|
||||
// @vitest-environment node
|
||||
|
||||
import dns from "node:dns";
|
||||
import { afterEach, beforeEach, expect, test, vi } from "vitest";
|
||||
|
||||
vi.mock("@homarr/core/infrastructure/logs", () => ({
|
||||
createLogger: () => ({ debug: vi.fn(), info: vi.fn(), warn: vi.fn(), error: vi.fn() }),
|
||||
}));
|
||||
|
||||
const nativeLookup = dns.lookup;
|
||||
const nativePromiseLookup = dns.promises.lookup;
|
||||
|
||||
beforeEach(() => {
|
||||
vi.resetModules();
|
||||
// Exercise runtime validation, including the default, rather than CI's raw env values.
|
||||
vi.stubEnv("CI", "");
|
||||
vi.stubEnv("SKIP_ENV_VALIDATION", "");
|
||||
vi.stubEnv("npm_lifecycle_event", "test");
|
||||
vi.stubEnv("ENABLE_DNS_CACHING", "");
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
dns.lookup = nativeLookup;
|
||||
dns.promises.lookup = nativePromiseLookup;
|
||||
delete global.homarr?.dnsCacheManager;
|
||||
vi.unstubAllEnvs();
|
||||
});
|
||||
|
||||
test.each([undefined, "false"])(
|
||||
"preserves native address-family filtering with DNS caching set to '%s'",
|
||||
async (value) => {
|
||||
vi.stubEnv("ENABLE_DNS_CACHING", value);
|
||||
await import("@homarr/core/infrastructure/dns/init");
|
||||
const options = { family: 4, all: true } as const;
|
||||
const expected = await nativePromiseLookup("localhost", options);
|
||||
const addresses = await dns.promises.lookup("localhost", options);
|
||||
expect(addresses).toEqual(expected);
|
||||
expect(addresses.every(({ family }) => family === 4)).toBe(true);
|
||||
|
||||
const callbackAddresses = await new Promise<dns.LookupAddress[]>((resolve, reject) => {
|
||||
dns.lookup("localhost", options, (error, result) => {
|
||||
if (error) reject(error);
|
||||
else resolve(result);
|
||||
});
|
||||
});
|
||||
expect(callbackAddresses).toEqual(expected);
|
||||
},
|
||||
);
|
||||
|
||||
test("still enables DNS caching when explicitly requested", async () => {
|
||||
vi.stubEnv("ENABLE_DNS_CACHING", "true");
|
||||
await import("@homarr/core/infrastructure/dns/init");
|
||||
expect(dns.lookup).not.toBe(nativeLookup);
|
||||
expect(dns.promises.lookup).not.toBe(nativePromiseLookup);
|
||||
expect(await dns.promises.lookup("localhost")).toEqual({ address: "127.0.0.1", family: 4 });
|
||||
});
|
||||
Reference in new issue
Block a user