Repository navigation
v2.38.0
Crawler verification with a time limit.
Reverse-DNS verification can send its lookups to a DNS-over-HTTPS provider, so a slow resolver costs a worker about 300 ms instead of up to ten seconds. Config merging now treats lists consistently: an empty list clears one, and separate configs passed to Firewall::create() replace lists as configs: includes do.
PHP 8.1–8.5 supported. The provider is opt-in. With no global.reverse_dns, verification uses PHP's own lookups, exactly as before, and nothing is sent to a third party. One merge change needs attention on upgrade if you pass more than one config to Firewall::create(); see Upgrading.
Added
DNS-over-HTTPS providers for reverse-DNS verification
PHP's gethostbyaddr() and dns_get_record() take no timeout. On a host without a local caching resolver, one slow nameserver held a worker for up to ten seconds before the circuit breaker could trip.
-
global.reverse_dns.provider(#473, #475). Name a provider and every verifying rule uses it:global: reverse_dns: provider: cloudflare # or google timeout_ms: 300 # per lookup; the default
Each lookup is limited to
timeout_ms, connecting and the TLS handshake included. The provider's address is pinned, so its own hostname is never looked up through the operating system's resolver, and the certificate is still checked against that hostname. -
Built-in providers
cloudflareandgoogle. They're available, but never selected by default. Naming one is the opt-in: from then on, the reverse-DNS names of visitors' addresses are sent to that provider, for requests that claim to be a crawler a rule verifies. Reverse DNS documents each provider: who runs it, what it receives, what it says it logs, and its terms. -
Per rule:
verify_providerpicks a different provider, andverify_timeout_msa different limit. -
Your own provider or resolver. Define a provider under
global.reverse_dns.providers(for a DNS-over-HTTPS resolver you run), or name a class implementingReverseDnsResolverInterfacewithresolverandresolver_options. A resolver returns DNS data, never a verdict. The firewall still validates every hostname, checks the domain and makes the forward confirmation, so no resolver can make an allow rule match more than DNS confirms. -
Checked at startup. An unknown provider name,
providerandresolverboth set, or a resolver that can't be built stops the firewall starting.firewall-check --lintreports the same problems, andfirewall-doctorsays where each verifying rule's lookups go. -
Measured (#480, #482). 25 workers verifying one uncached address, with a provider that never answers: the first worker gives up at 306–308 ms, and the rest are refused in under a millisecond. Once a worker's connection is open, a verification costs 19–40 ms; the first costs 66–93 ms, TLS handshake included. The figures, and how to size
timeout_msandverify_claim_wait_msfor a provider, are in the docs. -
Checked weekly (#477, #484). A scheduled job runs each built-in provider through a reverse lookup, the forward lookup, a name with no record and a full verification, and opens an issue if one stops answering.
DnsOverHttpResolver needs PHP's curl extension. PHP's own lookups need nothing extra.
Safer verification, whatever makes the lookups
- At most two forward lookups per address. The owner of an address writes its PTR records, and could list any number of invented
googlebot.comnames to make every verification slow enough to trip the breaker for everyone. - Verdicts and the breaker belong to the resolver that reached them. Switching to a provider starts afresh: a refusal from a lookup PHP couldn't finish isn't carried over for a day. A slow resolver only trips its own breaker.
- A lookup that couldn't say either way (a timeout, a server failure) is remembered for 60 seconds, not
verify_negative_ttl, so one network blip doesn't refuse a real crawler for a day.
Fixed
- An included file couldn't clear a list (#474, #476).
lockdown_allow: []in an included file was ignored, so the earlier allowlist stayed in force. An empty list now replaces a list, as any other list does.[]over a map still changes nothing. - Lists inside a legacy
block:/bypass:entry merged position by position (#476).[b]over[a, c]gave[b, c]. They're now replaced whole. A legacy rule'sconfig:still appends. - Separate configs appended lists instead of replacing them (#481, #483). With
Firewall::create([$base, $site]), a secondtrusted_proxiesadded to the first, so both were trusted, andlockdown_allow: []couldn't clear the allowlist. Separate configs now merge lists as includes do. See Upgrading.
Upgrading
- If you pass more than one config to
Firewall::create(), a list set in two of them is now the later one's, not both joined together. That applies totrusted_proxies,lockdown_allow, a rule'sverify_suffixes, and every other list. Rootplugins:and a legacy rule'sconfig:still append, and a later config'spriorityandenable: falsestill win. A host relying on two configs each adding to one list should move the whole list into one of them. A single-config site sees no difference. - Includes: an included file's
[]now clears the list it overrides. Check any include that sets a list to[]meant as "nothing to add". - The compiled config cache reparses each configuration once after the upgrade, because its entry format changed. Nothing to do.
- To use a provider, install PHP's curl extension (most hosts have it), set
global.reverse_dns.provider, and read the provider's section of the Reverse DNS docs first. Settimeout_mswell above the provider's usual latency, since the limit includes the TLS handshake.
Full changelog: v2.37.0...v2.38.0