Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions samples/audit/squid.conf
Original file line number Diff line number Diff line change
Expand Up @@ -74,6 +74,9 @@ cache deny all

# DNS settings - Squid resolves all domains for HTTP/HTTPS traffic
dns_nameservers 8.8.8.8 8.8.4.4
negative_dns_ttl 1 seconds
dns_retransmit_interval 1 seconds
dns_timeout 10 seconds

# Forwarded headers
forwarded_for delete
Expand Down
54 changes: 53 additions & 1 deletion src/squid/config-sections.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,34 @@ function buildWithDefaults(overrides: Partial<Parameters<typeof buildConfigSecti
});
}

function parseSecondsDirective(section: string, directive: string): number {
const directiveLine = section.split('\n').find(line => line.startsWith(`${directive} `));
const value = directiveLine?.split(/\s+/)[1];
if (!value) {
throw new Error(`Missing ${directive} directive`);
}
return Number(value);
}

function canReachFallbackResolver(options: {
dnsRetransmitIntervalSeconds: number;
dnsTimeoutSeconds: number;
resolverOutcomes: ('stall' | 'success')[];
}): boolean {
const { dnsRetransmitIntervalSeconds, dnsTimeoutSeconds, resolverOutcomes } = options;
let elapsedSeconds = 0;
for (const resolverOutcome of resolverOutcomes) {
if (elapsedSeconds >= dnsTimeoutSeconds) {
return false;
}
if (resolverOutcome === 'success') {
return true;
}
elapsedSeconds += dnsRetransmitIntervalSeconds;
}
return false;
}

describe('buildConfigSections', () => {
describe('portConfig', () => {
it('emits http_port with the configured port', () => {
Expand Down Expand Up @@ -186,7 +214,31 @@ describe('buildConfigSections', () => {

it('uses custom DNS servers when provided', () => {
const { dnsSection } = buildWithDefaults({ dnsServers: ['1.1.1.1', '1.0.0.1'] });
expect(dnsSection).toBe('dns_nameservers 1.1.1.1 1.0.0.1');
expect(dnsSection).toMatch(/^dns_nameservers 1\.1\.1\.1 1\.0\.0\.1$/m);
});

it('shrinks negative_dns_ttl to avoid caching a single transient SERVFAIL', () => {
const { dnsSection } = buildWithDefaults();
expect(dnsSection).toMatch(/^negative_dns_ttl 1 seconds$/m);
});

it('uses a short retransmit interval with enough total timeout for resolver fallback', () => {
const { dnsSection } = buildWithDefaults();
expect(dnsSection).toMatch(/^dns_retransmit_interval 1 seconds$/m);
expect(dnsSection).toMatch(/^dns_timeout 10 seconds$/m);
});

it('keeps DNS timeout above retransmit interval so fallback nameservers are queried', () => {
const { dnsSection } = buildWithDefaults();
const dnsRetransmitIntervalSeconds = parseSecondsDirective(dnsSection, 'dns_retransmit_interval');
const dnsTimeoutSeconds = parseSecondsDirective(dnsSection, 'dns_timeout');

expect(dnsRetransmitIntervalSeconds).toBeLessThan(dnsTimeoutSeconds);
expect(canReachFallbackResolver({
dnsRetransmitIntervalSeconds,
dnsTimeoutSeconds,
resolverOutcomes: ['stall', 'success'],
})).toBe(true);
});
});

Expand Down
17 changes: 16 additions & 1 deletion src/squid/config-sections.ts
Original file line number Diff line number Diff line change
Expand Up @@ -201,7 +201,22 @@ function generateAllowedIpSection(domains: string[]): string {
}

function generateDnsSection(dnsServers?: string[]): string {
return `dns_nameservers ${(dnsServers && dnsServers.length > 0) ? dnsServers.join(' ') : DEFAULT_DNS_SERVERS.join(' ')}`;
const servers = (dnsServers && dnsServers.length > 0) ? dnsServers.join(' ') : DEFAULT_DNS_SERVERS.join(' ');
return `dns_nameservers ${servers}
# A single transient upstream DNS failure (e.g. a SERVFAIL from an overloaded
# resolver during concurrent container startup) must not turn into a sustained
# false-positive block of an allowlisted domain. Squid's default
# negative_dns_ttl (1 minute) caches that one failure and rejects every
# request for the same domain for up to 60 seconds. Shrinking it means the
# very next lookup attempt re-queries the resolver instead of replaying the
# cached failure.
negative_dns_ttl 1 seconds
# Retry another configured nameserver quickly while keeping the total timeout
# above the retry interval. If dns_timeout is equal to Squid's default
# dns_retransmit_interval (5 seconds), Squid can hit the total timeout before
# sending the retry to the fallback nameserver.
dns_retransmit_interval 1 seconds
dns_timeout 10 seconds`;
}

function generateConfigSections(options: {
Expand Down
Loading