nelexa / guzzle-doh-middleware
A Dns over Https (DoH) middleware for Guzzle >= 6.0
Installs: 143
Dependents: 0
Suggesters: 0
Security: 0
Stars: 1
Watchers: 1
Forks: 0
Open Issues: 0
pkg:composer/nelexa/guzzle-doh-middleware
Requires
- php: ^7.1 | ^8.0
- ext-curl: *
- daverandom/libdns: ^2.0
- guzzlehttp/guzzle: ^6.3 | ^7.0
- psr/cache: ^1.0 | ^2.0 | ^3.0
- psr/log: ^1.0 | ^2.0 | ^3.0
- psr/simple-cache: ^1.0 | ^2.0 | ^3.0
Requires (Dev)
- phpunit/phpunit: ^7 | ^8.5.23 | ^9
- symfony/cache: >=4.4
- symfony/var-dumper: *
- vimeo/psalm: ^4.22
Suggests
- symfony/cache: Install the PSR-6 or PSR-16 caching implementation
This package is auto-updated.
Last update: 2025-10-06 12:47:03 UTC
README
guzzle-doh-middleware
A DNS over HTTPS (DoH) middleware for Guzzle.Goals
- Resolving domains, via DoH before sending HTTP requests.
- Bypassing blocked sites, through DNS packet spoofing.
- Support for caching DNS responses, via PSR-6 and PSR-16 compatible packages.
- Support for multiple DoH providers.
Install
composer require nelexa/guzzle-doh-middleware
Usage
use GuzzleHttp\Client; use GuzzleHttp\HandlerStack; use Nelexa\Doh\DohMiddleware; // Create default HandlerStack $stack = HandlerStack::create(); // Add this middleware to the top with `push` $stack->push(DohMiddleware::create(), 'doh'); // Initialize the client with the handler option $client = new Client(['handler' => $stack]);
Setup cache
It is very important to configure caching of DNS requests, so that you do not have to contact a DNS server to resolve domains for every HTTP request.
Install a PSR-6 or PSR-16 compatible caching package.
For example, you can install the popular symfony/cache package.
composer require symfony/cache
Example init PSR-6 redis cache
$cache = new \Symfony\Component\Cache\Adapter\RedisAdapter( \Symfony\Component\Cache\Adapter\RedisAdapter::createConnection() );
Example init PSR-16 redis cache
$cache = new \Symfony\Component\Cache\Psr16Cache( new \Symfony\Component\Cache\Adapter\RedisAdapter( \Symfony\Component\Cache\Adapter\RedisAdapter::createConnection() ) );
You can pass the configured cache as the first argument when creating middleware.
If you don't pass the argument or pass null, the cache will only be stored for the lifetime of the PHP process.
$stack->push(DohMiddleware::create($cache), 'doh');
Setup DoH Servers
You can specify which DoH servers to use as a second argument when creating middleware. They will be chosen in random order.
The defaults are Cloudflare (for Mozilla) and Google.
Example:
$dohServers = [ 'https://mozilla.cloudflare-dns.com/dns-query', 'https://dns.google/dns-query', 'https://doh.cleanbrowsing.org/doh/security-filter', \Nelexa\Doh\DohServers::SERVER_ADGUARD_FAMILY, 'https://doh.opendns.com/dns-query', ]; $stack->push(DohMiddleware::create($cache, $dohServers), 'doh');
Setup Logger & Debug
You can add the PSR-3 compatible Logger as a 3rd argument when creating middleware.
use Monolog\Logger; use Monolog\Handler\StreamHandler; $logger = new Logger('doh'); $logger->pushHandler(new StreamHandler('path/to/doh.log', Logger::DEBUG)); $stack->push(DohMiddleware::create( cache: $cache, logger: $logger ), 'doh');
For debugging and console output of all DoH requests to servers, you can pass true as 4th parameter when creating middleware.
$stack->push(DohMiddleware::create( cache: $cache, debug: true, ), 'doh');
Example debug info
* Trying 104.16.248.249:443...
* TCP_NODELAY set
* connect to 104.16.248.249 port 443 failed: Connection refused
* Trying 104.16.249.249:443...
* TCP_NODELAY set
* Connected to mozilla.cloudflare-dns.com (104.16.249.249) port 443 (#0)
* ALPN, offering http/1.1
* successfully set certificate verify locations:
* CAfile: /etc/ssl/certs/ca-certificates.crt
CApath: /etc/ssl/certs
* SSL connection using TLSv1.3 / TLS_AES_256_GCM_SHA384
* ALPN, server accepted to use http/1.1
* Server certificate:
* subject: C=US; ST=California; L=San Francisco; O=Cloudflare, Inc.; CN=cloudflare-dns.com
* start date: Oct 25 00:00:00 2021 GMT
* expire date: Oct 25 23:59:59 2022 GMT
* subjectAltName: host "mozilla.cloudflare-dns.com" matched cert's "*.cloudflare-dns.com"
* issuer: C=US; O=DigiCert Inc; CN=DigiCert TLS Hybrid ECC SHA384 2020 CA1
* SSL certificate verify ok.
> GET /dns-query?dns=q80BAAABAAAAAAAACmdvb2dsZW1haWwBbAZnb29nbGUDY29tAAABAAE HTTP/1.1
Host: mozilla.cloudflare-dns.com
Accept: application/dns-udpwireformat, application/dns-message
User-Agent: DoH-Client
* old SSL session ID is stale, removing
* Mark bundle as not supporting multiuse
< HTTP/1.1 200 OK
< Server: cloudflare
< Date: Thu, 03 Mar 2022 09:58:15 GMT
< Content-Type: application/dns-message
< Connection: keep-alive
< Access-Control-Allow-Origin: *
< Content-Length: 105
< CF-RAY: 6e6183398ec716f0-DME
<
* Connection #0 to host mozilla.cloudflare-dns.com left intact
Request options
You can configure requests created and transmitted by the client using request options.
Option "doh"
- Summary
Set
falseto disable domain resolving, via DoH.- Types
-
- bool
- Default
true- Constant
\Nelexa\Doh\DohMiddleware::OPTION_DOH_ENABLED
// Disable DoH for concrete request $client->request('GET', 'https://...', [ 'doh' => false, ]);
To disable DoH middleware by default, pass false for the doh option when creating the HTTP client.
$stack = HandlerStack::create(); $stack->push(DohMiddleware::create($cache), 'doh'); $client = new Client([ 'handler' => $stack, 'doh' => false, ]);
Option "doh_ttl"
- Summary
Forced setting of caching time for resolving results. If the option is not passed or
nullis passed, the caching time from the DNS server is used.- Types
-
- integer
- \DateInterval
- null
- Default
null- Constant
\Nelexa\Doh\DohMiddleware::OPTION_DOH_TTL
$client->request('GET', 'https://...', [ 'doh_ttl' => \DateInterval::createFromDateString('1 hour'), ]);
Option "doh_shuffle"
- Summary
Set
trueto enable shuffling of ip addresses in random order when more than one ip address has been received as a result of domain resolving.- Types
-
- bool
- Default
false- Constant
\Nelexa\Doh\DohMiddleware::OPTION_DOH_SHUFFLE
// Enable shuffle ip addresses $client->request('GET', 'https://...', [ 'doh_shuffle' => true, ]);
To enable ip mixing for all requests by default, pass true for the ttl_shuffle option when creating the HTTP client.
$stack = HandlerStack::create(); $stack->push(DohMiddleware::create($cache), 'doh'); $client = new Client([ 'handler' => $stack, 'doh_shuffle' => true, ]);
Symfony config DI
# config/services.yaml parameters: doh.servers: - 'https://mozilla.cloudflare-dns.com/dns-query', - 'https://dns.google/dns-query', - 'https://doh.opendns.com/dns-query' services: app.client.doh_middleware: factory: Nelexa\Doh\DohMiddleware::create class: Nelexa\Doh\DohMiddleware arguments: - '@cache.app' - '%doh.servers%' - '@logger' - '%kernel.debug%' app.client.handler_stack: factory: GuzzleHttp\HandlerStack::create class: GuzzleHttp\HandlerStack calls: - [ push, [ '@app.client.doh_middleware' ] ] app.client: class: GuzzleHttp\Client arguments: app.client: class: GuzzleHttp\Client arguments: - handler: '@app.client.handler_stack' doh: true # doh_ttl: 3600 # doh_shuffle: true # Aliases GuzzleHttp\Client: '@app.client'
Credits
Changelog
Changes are documented in the releases page.
License
The MIT License (MIT). Please see LICENSE for more information.