AdguardTeam / AdGuardHome

Network-wide ads & trackers blocking DNS server
https://adguard.com/adguard-home.html
GNU General Public License v3.0
23.62k stars 1.74k forks source link

Use EDNS as client IP in statistics and forward it to the upstream #1727

Open gorip96 opened 4 years ago

gorip96 commented 4 years ago

When AdGuard Home runs behind proxy ( ex: dnsdist ) , the statistics will show the proxy’s IP as the source IP instead of client’s IP

It would be great if the statistics source IP is based on EDNS client subnet when possible

ameshkov commented 2 years ago

Merging https://github.com/AdguardTeam/AdGuardHome/issues/3360#issuecomment-888425999 here

timkgh commented 2 years ago

Could we please bump up the priority? I think ECS forwarding to upstream is pretty important for CDN performance. Thank you.

ameshkov commented 2 years ago

@timkgh v0.108 is the closest we can get, v0.107 is sealed, we can't add anything there (only remove something from it:)).

timkgh commented 2 years ago

sgtm

ClosedPort22 commented 2 years ago

It seems that ECS passthrough is working as expected as of 0.107.2. I tested it using the following command:

dig o-o.myaddr.google.com txt +subnet=1.2.3.0/24 @192.168.1.1

In Adguard Home (192.168.1.1) I specifically configured o-o.myaddr.google.com to use 8.8.8.8, which supports ECS.

Result:

o-o.myaddr.google.com.  900     IN      TXT     "edns0-client-subnet 1.2.3.0/24"

Toggling edns-client-subnet doesn't seem to make any difference to the behavior, but I'm pretty sure it's because I run AGH in my local network and none of the clients have a public IP address (as described here).

robinsmidsrod commented 1 year ago

I would love if ECS is also used for the query log, so that you can filter on the ECS value, similar to client IP.

Another place it should probably be used is in "Client settings", so that you can use it to block only for clients with the specific ECS value.

Reasoning: I use CoreDNS as my first line DNS proxy, and I use the config rewrite edns0 subnet set 32 128 to send the full source IP of the request onward to AdGuardHome. The reason I want to use CoreDNS in front of AdGuard is to not log all the lookups to internal names, I'm only interested in the logs and statistics for recursive/external lookups, not internal ones (trying to reduce the amount of logspam).

ax42 commented 1 year ago

I have the same issue / request as Robin above -- I have dnsmasq running as my network-internal DHCP + DNS server, and AdGuard is upstream of the dnsmasq (so that internal lookups 'just work' due to the link to dhcp). Tcpdump confirms that the ECS is correctly passed to adguard (using add-subnet=32,128), but the query log shows all requests as coming from the internal dnsmasq server's IP.

carpenike commented 1 year ago

Add me to the list of folks looking for this as well. Would like to use dnsdist as my all up dns proxy and only route external / internet requests through AdguardHome.

vampywiz17 commented 11 months ago

i also say that would be good to add it... I use DNSmasq (add-subnet=32,128) now before send request to ADGuard, but it only show my firewall IP. With Pihole, same config work well.

jumpsmm7 commented 11 months ago

I am definitely wanting support for this. DNSMASQ has several ways of sharing client identification information when forwarding client request to an upstream such as pihole or AdGuardHome.

--add-mac[=base64|text] Add the MAC address of the requestor to DNS queries which are forwarded upstream. This may be used to DNS filtering by the upstream server. The MAC address can only be added if the requestor is on the same subnet as the dnsmasq server. Note that the mechanism used to achieve this (an EDNS0 option) is not yet standardised, so this should be considered experimental. Also note that exposing MAC addresses in this way may have security and privacy implications. The warning about caching given for --add-subnet applies to --add-mac too. An alternative encoding of the MAC, as base64, is enabled by adding the "base64" parameter and a human-readable encoding of hex-and-colons is enabled by added the "text" parameter. --add-cpe-id= Add an arbitrary identifying string to DNS queries which are forwarded upstream. --add-subnet[[=[/]][,[/]]] Add a subnet address to the DNS queries which are forwarded upstream. If an address is specified in the flag, it will be used, otherwise, the address of the requestor will be used. The amount of the address forwarded depends on the prefix length parameter: 32 (128 for IPv6) forwards the whole address, zero forwards none of it but still marks the request so that no upstream nameserver will add client address information either. The default is zero for both IPv4 and IPv6. Note that upstream nameservers may be configured to return different results based on this information, but the dnsmasq cache does not take account. Caching is therefore disabled for such replies, unless the subnet address being added is constant. For example, --add-subnet=24,96 will add the /24 and /96 subnets of the requestor for IPv4 and IPv6 requestors, respectively. --add-subnet=1.2.3.4/24 will add 1.2.3.0/24 for IPv4 requestors and ::/0 for IPv6 requestors. --add-subnet=1.2.3.4/24,1.2.3.4/24 will add 1.2.3.0/24 for both IPv4 and IPv6 requestors. --umbrella[=[deviceid:][,orgid:][,assetid:]] Embeds the requestor's IP address in DNS queries forwarded upstream. If device id or, asset id or organization id are specified, the information is included in the forwarded queries and may be able to be used in filtering policies and reporting. The order of the id attributes is irrelevant, but they must be separated by a comma. Deviceid is a sixteen digit hexadecimal number, org and asset ids are decimal numbers.

vampywiz17 commented 8 months ago

Any progress in this theme? it planned on 0.108 version?

reedickulus commented 5 months ago

Will be following along to see if this is implemented. Otherwise I think might need to switch to pi-hole which does this.

felipejfc commented 2 months ago

Not implemented yet?

EDIT

Did my own dirty hack to have this locally while waiting official implementation: https://github.com/felipejfc/AdGuardHome/commit/071148d2

Published in dockerhub as felipejfc/adguard-home:latest for the impatient ones

robinsmidsrod commented 2 months ago

@felipejfc Great! Are you able to add a test for that code as well, and you might have a chance of it actually being accepted. Even better if you try to provide it as a pull request.

felipejfc commented 2 months ago

Doing it "the right way" will take a little more effort. If the maintainers signal they would be willing to accept a PR I can make it. The complexity is the following: In the handleDNSRequest method, there are two places we need to use the IP coming in ECS, "processInitial" and "processQueryLogsAndStats", on the current implementation however, ECS information is only set in context in "processUpstream", so we don't have the info set yet in processInitial which is where reverse DNS lookup is done. To overcome this we need to make the very first step in the pipeline to be filling ecs info in the context, but the method which extracts this info is in another package (man, I hate multi repo golang projects), here https://github.com/AdguardTeam/dnsproxy/blob/master/proxy/proxy.go#L776 To access this method from this repo where we need it, we would need to make it public but this will take making a release on the dnsproxy project, to save me the hassle I duplicated the code. I can do it the right way and send a proper PR to both projects but before having all the trouble lets see if a maintainer confirms this would be a good approach.

carpenike commented 2 months ago

@EugeneOne1 any thoughts on @felipejfc's approach?

christianbur commented 1 month ago

I am also waiting for the implementation of the Proxy Protocol or EDNS Client Subnet (ECS). As long as there is no implementation, maybe my workaround is helpful.

Some information about ProxyProtocol or ECS: Passing the source address to the backend

My environment: The DNS proxy dnsdist is running on my OpenWRT router and a AdGuardHome docker container is running on my Intel Nuc. All public DNS requests should be forwarded to AdGuardHome via dnsdist, but I would like to see the client IP address in the AdGuardHome log. Unfortunately, ProxyProtocol or EDNS0 do not work for forwarding. Therefore, we simply use DNS over HTTPs (DoH), because AdGuardHome allows the following:

trusted_proxies (since v0.107.0) – The list of IP addresses and CIDR prefixes of trusted HTTP proxy servers. If a DNS-over-HTTPS request comes from one of these addresses or networks, AdGuard Home uses the provided proxy headers, such as X-Real-IP, to get the real IP address of the client. Requests from HTTP proxies outside of these networks are considered to be requests from the proxy itself. That is, the proxy headers are ignored.

The full list of proxy headers, in the order AdGuard Home inspects them:

CF-Connecting-IP
True-Client-IP
X-Real-IP
X-Forwarded-For

Source: https://github.com/AdguardTeam/AdGuardHome/wiki/Configuration

In the config file AdGuardHome.yaml you have to add the IP address of the OpenWRT router and doh must be activated under the “Encryption settings”. In the dnsdist configuration, the decisive parameter is “addXForwardedHeaders=true,”

example: newServer({address="10.10.20.10:8443", tls="openssl", subjectName="adguard.local.example.de", dohPath="/dns-query", validateCertificates=true, checkInterval=20, checkTimeout=2000, useClientSubnet=false, addXForwardedHeaders=true, pool='pool-adguard',

The correct client IP address (ipv4 or ipv6) is now displayed in the log.

full dnsdist config: ``` -- Debug cmd: root@router:/# dnsdist --supervised -C /etc/dnsdist.conf -- dnsdist Webserver webserver("127.0.0.1:8083") setWebserverConfig({password="xxxxxxxxxxx", apiKey="xxxxxxxxxxx"}) -- EDNS for PiHole - https://discourse.pi-hole.net/t/support-for-add-subnet-option-from-dnsmasq-ecs-edns0-client-subnet/35940/38?u=christian82 -- setECSOverride(true) -- setECSSourcePrefixV4(32) -- setECSSourcePrefixV6(128) addACL('0.0.0.0/0') addACL('::/0') setVerboseHealthChecks(true) -- DNS over UDP/TCP (local) addLocal('0.0.0.0:53',{reusePort=true}) addLocal('[::]:53', {reusePort=true}) -- DNS over UDP/TCP (local) addTLSLocal('0.0.0.0:853', '/etc/ssl/acme/*.local.example.de.fullchain.crt', '/etc/ssl/acme/*.local.example.de.key') addTLSLocal('[::]:853', '/etc/ssl/acme/*.local.example.de.fullchain.crt', '/etc/ssl/acme/*.local.example.de.key') -- Server Backend --- Quad9 DNS over TLS newServer({address="9.9.9.9:853", tls="openssl", subjectName="dns.quad9.net", validateCertificates=true, checkInterval=20, checkTimeout=2000, useClientSubnet=false, pool='pool-external', qps=100, order=10}) newServer({address="149.112.112.112:853", tls="openssl", subjectName="dns.quad9.net", validateCertificates=true, checkInterval=20, checkTimeout=2000, useClientSubnet=false, pool='pool-external', qps=100, order=10}) newServer({address="[2620:fe::fe]:853", tls="openssl", subjectName="dns.quad9.net", validateCertificates=true, checkInterval=20, checkTimeout=2000, useClientSubnet=false, pool='pool-external', qps=100, order=10}) newServer({address="[2620:fe::9]:853", tls="openssl", subjectName="dns.quad9.net", validateCertificates=true, checkInterval=20, checkTimeout=2000, useClientSubnet=false, pool='pool-external', qps=100, order=10}) --- Quad9 DNS over HTTPS newServer({address="9.9.9.9:443", tls="openssl", subjectName="dns.quad9.net", dohPath="/dns-query", validateCertificates=true, checkInterval=20, checkTimeout=2000, useClientSubnet=false, pool='pool-external', qps=100, order=11}) newServer({address="149.112.112.112:443", tls="openssl", subjectName="dns.quad9.net", dohPath="/dns-query", validateCertificates=true, checkInterval=20, checkTimeout=2000, useClientSubnet=false, pool='pool-external', qps=100, order=11}) newServer({address="[2620:fe::fe]:443", tls="openssl", subjectName="dns.quad9.net", dohPath="/dns-query", validateCertificates=true, checkInterval=20, checkTimeout=2000, useClientSubnet=false, pool='pool-external', qps=100, order=11}) newServer({address="[2620:fe::9]:443", tls="openssl", subjectName="dns.quad9.net", dohPath="/dns-query", validateCertificates=true, checkInterval=20, checkTimeout=2000, useClientSubnet=false, pool='pool-external', qps=100, order=11}) --- lokaler Adguard-Server newServer({address="10.10.20.10:8443", tls="openssl", subjectName="adguard.local.example.de", dohPath="/dns-query", validateCertificates=true, checkInterval=20, checkTimeout=2000, useClientSubnet=false, addXForwardedHeaders=true, pool='pool-adguard', , qps=100, order=1}) newServer({address="10.10.20.10:53", checkInterval=10, useClientSubnet=true, pool='pool-adguard', order=10}) --- lokaler DNS-Server (Openwrt) newServer({address="127.0.0.1:54", checkInterval=10, useClientSubnet=true, pool='pool-dnsmasq', order=1}) -- Caching pc = newPacketCache(10000, {maxTTL=86400, minTTL=0, temporaryFailureTTL=60, staleTTL=60, dontAge=false}) getPool(""):setCache(pc) -- https://blog.cloudflare.com/rfc8482-saying-goodbye-to-any/ addAction(QTypeRule(DNSQType.ANY), RCodeAction(DNSRCode.NOTIMP)) -- Backend rule: DNS over UDP/TCP, Port 53 setServerPolicy(firstAvailable) -- Override -- addAction(AndRule({ DSTPortRule(53), makeRule({"git.local.example.de"}) }), SpoofAction({"10.10.100.16"})) -- Openwrt - dnsmasq addAction( makeRule({"local.example.de"}) , PoolAction("pool-dnsmasq")) addAction( makeRule({"168.192.in-addr.arpa"}) , PoolAction("pool-dnsmasq")) addAction( makeRule({"10.in-addr.arpa"}) , PoolAction("pool-dnsmasq")) -- DNS Forwarding - https://dnsdist.org/reference/selectors.html?highlight=poolaction addAction( PoolAvailableRule("pool-adguard"), PoolAction("pool-adguard")) addAction( AllRule(), PoolAction("pool-external")) ```
soulwhisper commented 1 month ago

@christianbur,you don't need to change any config about adguard, to accomplish your goal, just add "setECSSourcePrefixV4(32)" below adguard pool inside dnsdist.conf. No need for DoH.

EDITED, example below:

-- udp/tcp dns listening
setLocal("0.0.0.0:53", {})

-- disable security status polling via DNS
setSecurityPollSuffix("")

-- Local Adguard
newServer({
  address = "192.168.10.11",
  pool = "adguard",
  healthCheckMode = "lazy",
  checkInterval = 1800,
  lazyHealthCheckFailedInterval = 30,
  rise = 2,
  maxCheckFailures = 3,
  lazyHealthCheckThreshold = 30,
  lazyHealthCheckSampleSize = 100,
  lazyHealthCheckMinSampleCount = 10,
  lazyHealthCheckMode = 'TimeoutOnly',
  useClientSubnet = true
})
-- Adblocker will be given requester IP
setECSSourcePrefixV4(32)
vampywiz17 commented 1 month ago

@christianbur It work with dnscrypt-proxy? I use OPNsense and it only support this.