How to relay Client-IP's behind Upstream DNS Servers with EDNS

How to relay Client-IP's behind Upstream DNS Servers with EDNS

If you're using more than one Upstream DNS Server, the Client IP's are hidden behind the last one.

The solution for this is: Extended DNS , short: EDNS , Client Subnet (ECS, EDNS0)

This example here used a Client in an OpenWRT Subnet, using Unify Access Points and PiHole FTL as Backend DNS Server:

DNSMasq used the following configs for EDNS:

--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=<string>
    Add an arbitrary identifying string to DNS queries which are forwarded upstream. 
    
--add-subnet[[=[<IPv4 address>/]<IPv4 prefix length>][,[<IPv6 address>/]<IPv6 prefix length>]]
    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:<deviceid>[,orgid:<orgid>]]
    Embeds the requestor's IP address in DNS queries forwarded upstream. If device 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 deviceid and orgid attributes is irrelevant, but must be separated by a comma. 

Setting up on openWRT:

Edit /etc/dnsmasq.conf

add-mac=text			#Set up MAC Adress of Rerquestor in text format
add-subnet=32,128		#<v4 pref>[,<v6 pref>]
#add-mac[=base64|text]		#Add requestor's MAC to forwarded DNS queries.
add-cpe-id=MY_DNS

Setting up unify AP's:

#some

Setting up Pihole:

echo -e "EDNS0_ECS=true\nPRIVACYLEVEL=0" >>/etc/pihole/pihole-FTL.conf \
&& pihole restartdns

Now you can see the Requestors IP Adress information in pihole backend.

How to use EDNS on an EdgeRouter ER-x / PiHole enviroment

Setup ER-x:

## Set on edge router:
# https://help.ui.com/hc/en-us/articles/115010913367-EdgeRouter-DNS-Forwarding-Setup-and-Options
configure

set service dns forwarding cache-size 666
set service dns forwarding listen-on switch0.XX
#set dns upstream
set service dns forwarding system
set system name-server <IPOFDNS-PIHOLE>

# Setup extra dnsmasq options:
#set service dns forwarding options address=/domain.com/1.1.1.2
set service dns forwarding options add-mac=text
set service dns forwarding options add-subnet=32,128

# Save commit
commit ; save

Show some debugging stuff:

# debug show stats etc
show dns forwarding nameservers
show dns forwarding statistics

# On windows: query dns
nslookup -d -q=a www.heise.de. <IPOFEDGEGATEWAY>

Setting up Pihole:

echo -e "EDNS0_ECS=true\nPRIVACYLEVEL=0" >>/etc/pihole/pihole-FTL.conf \
&& pihole restartdns

Setting up Access Points:

To setup the AP's to bypass our EDNS packages we have to change something on the AP, like Firewall rules and IP_Forwarding:

# iptables -A PREROUTING -p udp -m udp --dport 53 -m mark --mark 0x40000000/0xc0000000 -j DNAT --to-ports 53
# Change to:
# iptables -A PREROUTING -p udp -m udp --dport 53 -m mark --mark 0x40000000/0xc0000000 -j DNAT --to-destination <IPOFEDGEGATEWAY>:53
# Same with TCP Packages
# IP Forwarding
# echo 1 >/proc/sys/net/ipv4/ip_forward

The Problem and the Shellinjection as Solution

For the first test the lines above seems to be enough first, but if you want to mage your config persisting your reboot you have to be set the /tmp/system.cfg file and the following lines:

iptables.2.cmd=-t nat -R PREROUTING 2 -p udp -m udp --dport 53 -m mark --mark 0x40000000/0xc0000000 -j DNAT --to-destination <IPOFEDGEGATEWAY>:53
iptables.3.cmd=-t nat -R PREROUTING 2 -p tcp -m udp --dport 53 -m mark --mark 0x40000000/0xc0000000 -j DNAT --to-destination <IPOFEDGEGATEWAY>:53

# safe on rom:
syswrapper.sh save-config

But how to solve the IP forwarding problem?
My solution here is to inject a command at the end of the first iptables command like:

sed -E -i 's+iptables.1.cmd=1.*$+\
iptables.1.cmd=-t nat -A PREROUTING -p tcp -m mark --mark 0x40000000/0xc0000000 -j REDIRECT --to-ports 80; sleep 2; echo 1 > /proc/sys/net/ipv4/ip_forward
Dangerous, injection with wrong commands but it works like charm

The Magic Script <TL;DR>

just replace: Username / Password / IP of Edge Gateway and maybe port with your infos and copy and run with the --set-hosts flag.

#!/bin/bash
#
# ============================================================="
# ============ UNIFI EDNS ENABLER ============================="
# ============ (c) 2023 suuhm ================================="
# ============================================================="
#
## ORIGIN CODE:
## https://the-suuhmmary.coldwareveryday.com/how-to-relay-client-ips-behind-upstream-dns-servers-with-edns
## https://www.commandlinux.com/man-page/man1/busybox.1.html
#
# Find Alias on root disk/ sho-t
# find / -type f -exec grep -H 'alias save' {} \;
# /usr/etc/profile:alias save='syswrapper.sh save-config'
#

#
# CONFIG SECTION
#
HOSTS="unifi_ap_hosts.lst"
UUN="ubnt"
# Plain Text Password
# USE WITH CAUTION!!!
PASSH_WRD="ubnt"
# echo "YOURPASSWORD" > /root/.unifi_ap.pass; chmod 600 /root/.unifi_ap.pass 
# PASSH_WRD="$(cat /root/.unifi_ap.pass)"
_IP_OF_EDGE_GATEWAY=192.168.1.1
_DNS_PORT=53

#
# END CONFIG SECTION
#

# GLOBAL CONF
_AUTOLOGIN=${2:-0}
OBFC=""
IDD=1D
_XC=0

# COMMANDLIST
_CMD_INFO='SYSF=/tmp/system.cfg;echo -e "[*] Version: $(cat /etc/version)\n[*] Checking for Forward Routermode (0x'"$IDD"'): $(cat /proc/sys/net/ipv4/ip_forward)"; info && sleep 2;grep -E "iptables.*cmd" $SYSF'
_CMD_SETHOST='SYSF=/tmp/system.cfg;clear;echo "[*] Checking for Forward Routermode: $(cat /proc/sys/net/ipv4/ip_forward)";sleep 2; echo ;sed -E -i "s+iptables.1.cmd=.*$+iptables.1.cmd=-t nat -A PREROUTING -p tcp -m mark --mark 0x40000000/0xc0000000 -j REDIRECT --to-ports 80; sleep 2; echo 1 > /proc/sys/net/ipv4/ip_forward+g" $SYSF; sed -E -i "s+iptables.2.cmd=.*$+iptables.2.cmd=-t nat -R PREROUTING 2 -p udp -m udp --dport 53 -m mark --mark 0x40000000/0xc0000000 -j DNAT --to-destination '"$_IP_OF_EDGE_GATEWAY"':'"$_DNS_PORT"'\niptables.3.cmd=-t nat -R PREROUTING 2 -p tcp -m tcp --dport 53 -m mark --mark 0x40000000/0xc0000000 -j DNAT --to-destination '"$_IP_OF_EDGE_GATEWAY"':'"$_DNS_PORT"'+g" $SYSF; echo;echo "Get IPTABLES" ;echo; iptables -t nat -S && echo && if [ `grep v4 /etc/version` ]; then echo "Old Version save.."; sleep 1; syswrapper.sh save-config; else echo "New Version save alias.."; save; fi && echo "[*] Done, now restart device"; sleep 2 && reboot'

#
# CUSTOM COMMAND LIST;
# PLEASE UNCOMMENT YOU WISHED COMMAND
#
# Some info test command
_CUSTOM_COMMAND='echo "Run Test command"; echo; sleep 1; uptime; echo; info'
#
# Run Firware Upgrade (Setup URL first)
#_CUSTOM_COMMAND='fwupdate --url https://<URL>.bin'
#
# Inform UNiFi server for new device
#_CUSTOM_COMMAND='set-inform http://'"$_IP_OF_EDGE_GATEWAY"':8080/inform'
#
# Set to system defaults and rebbot
#_CUSTOM_COMMAND='set-default && reboot'
#
# Set DNS Server
#_CUSTOM_COMMAND='echo "nameserver 9.9.9.9" > /etc/resolv.conf'
#

function _help() {
    echo -e "Usage: $0 [OPTION] [1] | = 1 For AUTOLOGINFUNCTION\n\n" \
             "\t--get-apinfo                                              Get some info of APs\n" \
             "\t--set-hosts                                               Setup APs for edns\n" \
             "\t--run-cmd 0|1 (AUTOLOGIN MUST BE SET!!) ['COMMANDS']      Run Custom cmmands on AP\n" \
             "\t--help                                                    Get help/ this view\n"
}


function obfuscate_cmd()
{
   CMD=${1:-$_CMD_INFO}
   # OBFUSCATE:
   OBFC=$(echo -n $CMD | base64)
}


function _run_cmd()
{
   if [ "$1" == "get-apinfo" ]; then
      obfuscate_cmd "$_CMD_INFO"
   elif [ "$1" == "set-hosts" ]; then
      obfuscate_cmd "$_CMD_SETHOST"
   else
      # TRY RUNNING CUSTOM COMMANDS:
      [ -z "$1" ] && echo "No Custom CMD was set, exit." && exit 1
      obfuscate_cmd "$1"
   fi
   echo "Using obfuscated base64 command.. no iterations yet.."
   echo -e "Command:\n$OBFC\r\n"; sleep 2

   for apnhostn in $(grep -v '#' $HOSTS); do
      apn=$(echo $apnhostn | cut -d ":" -f1)
      hostn=$(echo $apnhostn | cut -d ":" -f2)
      echo -e "\n\n-----------------------------------------------------------"
      echo -e " [+] Set up first Host $apn => IP: $hostn";
      echo -e "-----------------------------------------------------------"
      sleep 2; ((_XC++))

   #   ssh $UUN@$hostn 'SYSF=/tmp/system.cfg; \
   #   echo "[*] Checking for Forward Routermode: $(cat /proc/sys/net/ipv4/ip_forward)"; echo \
   #   sed -E -i "s+iptables.1.cmd=.*$+ \
   #   iptables.1.cmd=-t nat -A PREROUTING -p tcp -m mark --mark 0x40000000/0xc0000000 -j REDIRECT --to-ports 80; sleep 2; echo 1 > /proc/sys/net/ipv4/ip_forward+g" $SYSF \
   #   sed -E -i "s+iptables.2.cmd=.*$+ \
   #   iptables.2.cmd=-t nat -R PREROUTING 2 -p udp -m udp --dport 53 -m mark --mark 0x40000000/0xc0000000 -j DNAT --to-destination <IPOFEDGEGATEWAY>:53 \n \
   #   iptables.3.cmd=-t nat -R PREROUTING 2 -p tcp -m tcp --dport 53 -m mark --mark 0x40000000/0xc0000000 -j DNAT --to-destination <IPOFEDGEGATEWAY>:53+g" $SYSF  \
   #   echo; sleep 2 && echo && && [ `grep v4 /etc/version` ] && syswrapper.sh save-config || save ; sleep 2 ; reboot'

   #   ssh $UUN@$hostn 'SYSF=/tmp/system.cfg;clear;echo "[*] Checking for Forward Routermode: $(cat /proc/sys/net/ipv4/ip_forward)";sleep 2; echo ;sed -E -i "s+iptables.1.cmd=.*$+iptables.1.cmd=-t nat -A PREROUTING -p tcp -m mark --mark 0x40000000/0xc0000000 -j REDIRECT --to-ports 80; sleep 2; echo 1 > /proc/sys/net/ipv4/ip_forward+g" $SYSF; sed -E -i "s+iptables.2.cmd=.*$+iptables.2.cmd=-t nat -R PREROUTING 2 -p udp -m udp --dport 53 -m mark --mark 0x40000000/0xc0000000 -j DNAT --to-destination <IPOFEDGEGATEWAY>:53\niptables.3.cmd=-t nat -R PREROUTING 2 -p tcp -m udp --dport 53 -m mark --mark 0x40000000/0xc0000000 -j DNAT --to-destination <IPOFEDGEGATEWAY>:53+g" $SYSF; echo;echo "Get IPTABLES" ;echo; iptables -t nat -S && echo && syswrapper.sh save-config ; sleep 2 ; reboot'

      if [ $_AUTOLOGIN -eq 1 ]; then
         # FALLBACK: ash only or legacy bash methode:
         # echo $(echo -n $OBFC | base64 -di) | sshpass -p $PASSH_WRD ssh -T -o StrictHostKeyChecking=no $UUN@$hostn 'sh -s'
         sshpass -p $PASSH_WRD ssh -T -q -o StrictHostKeyChecking=no $UUN@$hostn <<<$(echo -n $OBFC | base64 -di) 2>/dev/null | egrep -v "^\ |^\*|^\t|^\r"
      else
         # echo $(echo -n $OBFC | base64 -di) | ssh -o StrictHostKeyChecking=no $UUN@$hostn 'sh -s'
         ssh -T -q -o StrictHostKeyChecking=no $UUN@$hostn <<<$(echo -n $OBFC | base64 -di) 2>/dev/null | egrep -v "^\ |^\*|^\t"
      fi
   done
}

#
## FUNCTION MAIN()
#
clear
echo
echo "  _____  __      ___________________    _____________  _____________ __"
echo "  __  / / /_________(_)__  ____/__(_)   ___  __ )_  / / /__  /___  //_/"
echo "  _  / / /__  __ \_  /__  /_   __  /    __  __  |  / / /__  / __  ,<   "
echo "  / /_/ / _  / / /  / _  __/   _  /     _  /_/ // /_/ / _  /___  /| |  "
echo "  \____/  /_/ /_//_/  /_/      /_/      /_____/ \____/  /_____/_/ |_|  "
echo "                                                                       "
echo
echo "======================================================================="
echo "========= UNIFI BULK PROCESSOR & EDNS ENABLER (c) 2023 suuhm =========="
echo "======================================================================="
echo; sleep 1

if [ "$1" == "--get-apinfo" ]; then
    _run_cmd "get-apinfo"
elif [ "$1" == "--set-hosts" ]; then
    _run_cmd "set-hosts"
# CMD LINE $0 --run-cmd <0|1> <- MUST BE SET! [COMMANDS] !!
elif [ "$1" == "--run-cmd" ]; then
    [ "$3" == "-" ] && _run_cmd "$_CUSTOM_COMMAND" \
                    || _run_cmd "$3"
else
    _help
    exit 1
fi

echo -e "\a\n-----------------------------\n[*] Scanned Hosts ($_XC) Done.\n"
exit 0;

... and you are done. Now you will see some more IP info on Pihole backend.