DNS in PAC files: why isInNet and dnsResolve slow every request
The most common performance problem in PAC files is a line that looks like simple arithmetic, isInNet(host, "10.0.0.0", "255.0.0.0"). When host is a name, this is a DNS lookup, and it runs before every connection.
What happens
FindProxyForURL runs before every connection. The helper functions dnsResolve, isResolvable
and isInNet with a host name each ask the resolver and wait for the answer
(PAC-P001):
- normally that is tens of milliseconds added to every first byte;
- on a degraded resolver it is seconds;
- on a broken one, the request hangs.
isInNet(host, …) hides the lookup. In Chromium’s helper library it calls dnsResolve whenever its
first argument is not already an IP address (source: code, verified 2026-10-04;
PAC-P002).
The engines wait in different ways (source: code, verified 2026-10-04, unless marked):
| Engine | DNS in the PAC |
|---|---|
| Chromium | asynchronous; the script may run again once the answer arrives (expert, unverified) |
| Firefox | synchronous, blocks the PAC thread |
| pacparser | synchronous, blocking getaddrinfo |
Why it also fails open
When resolution fails, dnsResolve returns an empty value and isInNet returns false. The rule
simply does not match, and the request takes whatever comes next, normally the default route
(PAC-X010). On a hostile network the resolver answer is
controlled by the attacker, so the routing decision is too. If a name has several addresses, only
one of them is compared.
The fix: decide on the string first
Most PAC files can decide everything from the host name. Reorder so that cheap string tests come first (PAC-P005), and use address tests only for IP literals:
function FindProxyForURL(url, host) {
host = host.toLowerCase();
// 1. names: string tests, no DNS
if (isPlainHostName(host) || dnsDomainIs(host, ".corp.example") || host == "corp.example") {
return "DIRECT";
}
// 2. IP literals only: isInNet on a literal does not resolve anything
if (/^\d+\.\d+\.\d+\.\d+$/.test(host)) {
if (isInNet(host, "10.0.0.0", "255.0.0.0") ||
isInNet(host, "172.16.0.0", "255.240.0.0") ||
isInNet(host, "192.168.0.0", "255.255.0.0")) {
return "DIRECT";
}
}
// default route
return "PROXY proxy1.corp.example:8080; PROXY proxy2.corp.example:8080";
}
If you really must route by the resolved address, resolve once, keep the result in a variable, and do it after all string rules (PAC-P004):
var ip = dnsResolve(host);
if (ip && isInNet(ip, "10.0.0.0", "255.0.0.0")) {
return "DIRECT";
}
Related mistakes
isInNet("10.0.0.0", host, "255.0.0.0"): arguments in the wrong order, always false (PAC-C016).- An invalid network or mask such as
10.0.0or255:255.255.0: always false (PAC-E010). - IPv6 networks in
isInNetnever match.isInNetExexists only in some engines (PAC-K014). isResolvable(host)as an “is it internal” test: it answers a different question (PAC-P003).
Check your file
The online PAC file tester flags every DNS call on the request path and its position in the file. Set DNS answers in the tester to see how each rule behaves when a name resolves, and when it does not.
Frequently asked questions
Does isInNet do a DNS lookup?
Yes, when the first argument is a host name. isInNet resolves it first and then compares the address with the network and mask. Given an IP address it does no lookup.
Why is my PAC file slow?
Usually because it calls dnsResolve, isResolvable or isInNet on a host name for every request. Each call waits for the DNS resolver before the browser can open the connection. Decide on the host name string first and run DNS rules only for the few requests that need them, or not at all.
What happens when DNS fails inside a PAC file?
dnsResolve returns an empty value and isInNet returns false, so the request skips the rule and takes the next one, usually the default route. Routing then depends on the resolver, which fails open.