AdGuardHome icon indicating copy to clipboard operation
AdGuardHome copied to clipboard

Support PROXY Protocol

Open ZeroClover opened this issue 4 years ago • 36 comments

When using AGH through a load balancer or reverse proxy (probably because I don't want to expose the AGH directly or just want to high availability), DoH can get the user's original IP address through the HTTP header but regular DNS and DoT cannot.

This isn't good for use cases where the AGH is deployed on a server. This is especially important for sites that use GeoDNS for CDN allocation because AGH needs the user's original IP to send the ECS to the recursive DNS server.

This situation is also mentioned in this issue (#1789), where @ameshkov says, "Unfortunately, there are no such options for TLS/TCP."

But in fact, both Nginx and HAProxy can use PROXY Protocol to send the user's real IP over Layer 4. nginx.com has written an article describing how to use Nginx as a DoH / DoT gateway. We just need to add proxy_protocol on; in the stream section to send the user's original IP via PROXY Protocol, but this requires AGH support.

ZeroClover avatar Mar 10 '21 18:03 ZeroClover

I am also interested in this feature. Right now, I am using Kubernetes with Traefik and MetalLB to expose ports on my nodes. All my services are behind Traefik except AGH because of the missing distinction of the clients in AGH. I seeh @ameshkov has remarked it to be in the mileston v1.0.0 would love to see it earlier. ;-) Regards!

dvonessen avatar Mar 31 '21 17:03 dvonessen

Same Here.

Akruidenberg avatar Apr 27 '21 05:04 Akruidenberg

@Akruidenberg don't forget to thumbs up first post.

dvonessen avatar Apr 27 '21 05:04 dvonessen

@dvonessen @Akruidenberg

Although AdGuard Home does not support this feature at this time, the same effect can be achieved with go-mmproxy.

https://github.com/path-network/go-mmproxy

The go-mmproxy gives AdGuard Home the ability to obtain the original IP of the client sent by the load balancer via PROXY Protocol.

ZeroClover avatar Apr 27 '21 06:04 ZeroClover

Oh, there's a ready-to-use golang code for that? That's nice, makes it easier to implement inside AGH when we finally are ready to.

Meanwhile, please use go-mmproxy for that.

ameshkov avatar Apr 27 '21 08:04 ameshkov

Oh, there's a ready-to-use golang code for that? That's nice, makes it easier to implement inside AGH when we finally are ready to.

Meanwhile, please use go-mmproxy for that.

Sadly, there is no docker support for mmproxy.

Akruidenberg avatar Apr 30 '21 18:04 Akruidenberg

@Akruidenberg It seems to be possible to use mmproxy on Docker / k8s.

https://andrewmichaelsmith.com/2020/02/preserving-client-ip-in-kubernetes/ https://hub.docker.com/r/unixfox/go-mmproxy

ZeroClover avatar May 01 '21 02:05 ZeroClover

https://github.com/path-network/go-mmproxy

i try it (https://hub.docker.com/r/unixfox/go-mmproxy) but do not support ipv6 my nginx config:

map $ssl_preread_server_name $dot_map {

alist.example.com dot; }

upstream dot{

    server 192.168.1.2:25577;
}

server { listen 853 ; listen [::]:853 ; proxy_pass $dot_map; ssl_preread on;

proxy_protocol on;

}

my docker go-mmproxy config :

#!/bin/sh sleep 5 ip rule add from 127.0.0.1/8 iif lo table 123 ip route add local 0.0.0.0/0 dev lo table 123

ip -6 rule add from ::1/128 iif lo table 123 ip -6 route add local ::/0 dev lo table 123

echo -en "0.0.0.0/0\n::/0\n" > allowed-networks.txt /usr/bin/go-mmproxy -l 0.0.0.0:25577 -4 127.0.0.1:853 -6 [::1]:853 --allowed-subnets allowed-networks.txt -v 2 /bin/sh

my adguard home listen 853 for dot i checked adguard home it worked for ipv4 only, only have client real ip for ipv4 adguard home version v0.108.0-b.29 go-mmproxy log:

tempsnip

heygo1345678 avatar Mar 10 '23 07:03 heygo1345678

Bump. I need this feature for Nginx in order to be able to securely and correctly proxy DoT and DoUDP / DoTCP.

I'm quite unsure why this has been put on the back burner, it's quite an important feature to add.

ghost avatar Apr 06 '23 10:04 ghost

It would be really nice if AdGuard Home worked on adding support for The PROXY protocol, by doing so it would allow us to identify DoT clients while using a reverse proxy.

I'm currently running AdGuard Home together with Traefik in Docker using Docker Compose. Traefik is really convenient for routing to multiple Docker containers using labels, and for managing multiple certificates without human input.

docker-compose.yaml
version: "3"

services:
  traefik:
    command:
      - --providers.docker=true
      - --providers.docker.exposedbydefault=false

      - --certificatesresolvers.myresolver.acme.dnschallenge=true
      - --certificatesresolvers.myresolver.acme.dnschallenge.provider=duckdns
      - --certificatesresolvers.myresolver.acme.email=webmaster@👀.duckdns.org
      - --certificatesresolvers.myresolver.acme.storage=/letsencrypt/acme.json

      - --entrypoints.websecure.address=:443

      - --entrypoints.web.address=:80
      - --entrypoints.web.http.redirections.entrypoint.to=websecure

      - --entrypoints.dot.address=:853
    container_name: traefik
    depends_on:
      - adguardhome
    environment:
      - DUCKDNS_TOKEN=👀
    image: traefik:v3.0
    ports:
      - 80:80
      - 443:443
      - 853:853
    restart: unless-stopped
    volumes:
      - /var/run/docker.sock:/var/run/docker.sock:ro
      - ./letsencrypt:/letsencrypt

  adguardhome:
    container_name: adguardhome
    image: adguard/adguardhome
    labels:
      - traefik.enable=true

      - traefik.http.routers.adguardhome-https.rule=Host(`adguardhome.👀.duckdns.org`)
      - traefik.http.routers.adguardhome-https.entrypoints=websecure
      - traefik.http.routers.adguardhome-https.tls.certresolver=myresolver
      - traefik.http.services.adguardhome-https.loadbalancer.server.port=3000
      - traefik.http.routers.adguardhome-https.service=adguardhome-https

      - traefik.tcp.routers.adguardhome_dot.rule=HostSNI(`adguardhome.👀.duckdns.org`) || HostSNIRegexp(`^.+\.adguardhome\.👀\.duckdns\.org$`)
      - traefik.tcp.routers.adguardhome_dot.entrypoints=dot
      - traefik.tcp.routers.adguardhome_dot.tls.domains[0].main=adguardhome.👀.duckdns.org
      - traefik.tcp.routers.adguardhome_dot.tls.domains[0].sans=*.adguardhome.👀.duckdns.org
      - traefik.tcp.routers.adguardhome_dot.tls.certresolver=myresolver

      # - traefik.tcp.services.adguardhome_dot.loadbalancer.proxyprotocol.version=2
      # - traefik.tcp.routers.adguardhome_dot.service=adguardhome_dot
    restart: unless-stopped
    volumes:
      - ./adguardhome/work:/opt/adguardhome/work
      - ./adguardhome/conf:/opt/adguardhome/conf
AdGuardHome.yaml
dns:
  trusted_proxies:
    - 127.0.0.0/8
    - ::1/128
    - 10.0.0.0/8
    - 172.16.0.0/12
    - 192.168.0.0/16
tls:
  enabled: true
  server_name: adguardhome.👀.duckdns.org
  allow_unencrypted_doh: true

eiqnepm avatar Sep 19 '23 01:09 eiqnepm

I would also find PROXY protocol support extremly helpful! Would really like to the that feature.

mash-tec avatar Nov 18 '23 14:11 mash-tec

I just wasted 3 nights to do passing real-IP through DoT and found out that AG does not support it.

I think this is an important feature need to implement since DoT is used in Android device a lot.

cr0wdelex avatar Dec 17 '23 04:12 cr0wdelex

Bumping this thread since as of 2024, this is still unsupported and i truly believe that we should not resort to unsafe workarounds with go-mmproxy

maretodoric avatar Feb 29 '24 11:02 maretodoric

I would also appreciate support for that. I've moved from static blocklist zones to AdGuard recently, so I could change them dynamically should I need to – but since my AdGuard is behind CoreDNS, I am unable to use per-client filtering.

jaen avatar Mar 26 '24 08:03 jaen

Also interested in seeing proxy protocol supported.

paulcsiki avatar Dec 10 '24 17:12 paulcsiki

Just adding another "I'd love to see this in AGH" vote.

chrislea avatar Jan 12 '25 01:01 chrislea

I'd also love AGH to support PROXY protocol for DoT client identification behind Traefik

apoeteo avatar Jan 12 '25 13:01 apoeteo

Also look forward to PROXY protocol support...

cxw620 avatar Mar 06 '25 14:03 cxw620

I got bored enough some time ago to start hacking on CoreDNS and AdGuard to see if I can pass the client IP between them. And it seems to work... kind of. As far as I can tell the issue is that the PROXY frame is only ever sent when the connection is initially made, which is all well and good as long as you only ever send single client's queries through the connection. CoreDNS however, caches connections to reduce latency, so you will end up with all the queries sent through this connection having the source IP of the client it was initially opened for — which doesn't help us much, if what we want from this is correct client IPs.

I am not a network engineer, so I might be wrong, but it seems like the PROXY Protocol might be the wrong tool for this use case (they even explicitly call out multiplexing different clients onto a single connection as something that the protocol is not designed for in the RFC)? Some time later I've noticed that you can attach arbitrary data to a DNS packet with EDNS, so maybe that could be an option? I'll try to play around with that later, if I find some time (no hard commitments though, I suck at that).

jaen avatar Mar 27 '25 10:03 jaen

@jaen the PROXY Protocol is designed for TCP/UDP load balancers. In your use case, what you have implemented is actually a DNS proxy, and for DNS proxies, application-layer implementations (EDNS Client Subnet) should be preferred.

ZeroClover avatar Mar 28 '25 15:03 ZeroClover

One more vote from me - I run two AdGuard Home servers behind a Hetzner LB (tcp forwarding of 443 and 853) and the only thing that's missing is support by AdGuard for the proxy protocol so I can see real client IPs in the logs.

dinosmm avatar May 20 '25 11:05 dinosmm

+4 years of people requesting this and it has yet to happen lol

assellalou avatar Jun 28 '25 19:06 assellalou

+4 years of people requesting this and it has yet to happen lol

Who says it's gonna happen? 🤣 I've lost all hope 🤣

maretodoric avatar Jun 28 '25 19:06 maretodoric

Image

Image

bcookatpcsd avatar Jun 30 '25 21:06 bcookatpcsd

Image

Image

what are you using as a server/lb ?

assellalou avatar Jun 30 '25 21:06 assellalou

@bcookatpcsd yes Technitium appears to support the proxy protocol, however when I tried it I couldn't get it to work. Their implementation is very weird, requiring a different port for the proxy protocol, I didn't understand why or how to set it up.

The solution should be simple: if the DNS forwarder (AGH in our case) supports the proxy protocol, that should be transparent. I set my load balancer to forward tcp packets from 853 to 853, and enable the proxy protocol on it, AGH should support that for that stream, without having to add any more ports (which I don't see how exactly would be done anyway as I want the client's IP reported for DoT connections on 853, not on some other random port).

dinosmm avatar Jun 30 '25 21:06 dinosmm

dnsdist:

newServer({ address="[::1]:538", useProxyProtocol=true, tcpFastOpen=true, healthCheckMode='lazy', name="pxy-Technitium", checkName="accounts.google.com", sockets="4", pool="pc-proxyv2" })

newServer({ address="[::1]:539", useProxyProtocol=true, tcpFastOpen=true, healthCheckMode='lazy', name="pxy-unbound", checkName="accounts.google.com", sockets="4", pool="pc-guest" })

unbound:

server:
  #@ interface: 10.192.144.253@53
  #@ port: 53
  interface: ::1@539
  proxy-protocol-port: 539
  outgoing-interface: 10.192.144.253

Image

You need different ports because 'dns53 protocol', 'doh protocol', 'dot protocol', and 'proxy protocol' are all different..

Of course you could run all of it on 'port 53' but then you would have to bind a lot of different IPs together..

This is dnsdist on the outside (dns53 and doh), sending to one server via proxy protocol for one range of IPs, and sending to another server via proxy protocol for a different range of IPs..

YMMV

my 0.02

bcookatpcsd avatar Jul 01 '25 01:07 bcookatpcsd

Please, this would make my life so much easier.

TankObliterator avatar Sep 23 '25 05:09 TankObliterator

.. more examples here

https://github.com/TechnitiumSoftware/DnsServer/discussions/1099

HTH

bcookatpcsd avatar Sep 23 '25 11:09 bcookatpcsd

waiting for this... and why it can't just read the X-Forwarded-For header passed from the nginx reverse proxy DoH?

MengXin001 avatar Oct 06 '25 09:10 MengXin001