UPDATE 2021
The solution with the string
module from iptables
(see below) is not translatable with nftables
, so I had to find another solution.
I'm now using dnsdist which is a DNS load balancer. It uses more CPU and RAM, but it's clearly more powerful. And I like it for being dedicated to this task, without rewriting DNS answers, except when requested. Here is an excerpt of my configuration file:
-- dnsdist configuration file, an example can be found in /usr/share/doc/dnsdist/examples/
-- disable security status polling via DNS
setSecurityPollSuffix("")
setLocal('<PUBLIC_IP4>:53', {reusePort=true, interface="eth_adsl"})
addLocal('[<PUBLIC_IP6>]:53', {reusePort=true, interface="eth_adsl"})
setACL({'0.0.0.0/0', '::/0'})
controlSocket('127.0.0.1')
setKey("SECRET_KEY")
-- Declare NSD authoritative server. It could also listen to localhost, but I let it answer on public addresses, using port 5300
newServer({address="[<PUBLIC_IP6>]:5300", checkInterval=60, pool="root"})
newServer({address="<PUBLIC_IP4>:5300", checkInterval=60, pool="root"})
-- Declare DNS Tunnel (iodine), on port 5354
newServer({address="127.0.0.1:5354", pool="tunnel"})
-- This is using socket activation, so disable dnsdist checks to avoid waking up the tunnel
getServer(2):setUp()
-- Forward to tunnel, skipping cache
tunnelRule = makeRule("t.antipoul.fr")
addAction(tunnelRule, SkipCacheAction())
addAction(tunnelRule, PoolAction("tunnel"))
-- Allow Zone transfer from secondary only
-- See https://dnsdist.org/advanced/axfr.html
addAction(AndRule({OrRule({QTypeRule(DNSQType.AXFR), QTypeRule(DNSQType.IXFR)}), NotRule(makeRule("<SECONDARY_IP4>/32"))}), RCodeAction(DNSRCode.REFUSED))
-- All remaining traffic goes to NSD
addAction(AllRule(), PoolAction("root"))
Original 2012 solution
I've found a solution to my problem. However, it is not a generic solution for the question.
In my case, I rely on the protocol used by dns2tcp, which communicates using subdomain requests on tunnel.example.com. So the idea was to match packet containing such requests, and build a netfilter rule:
iptables -t nat -I PREROUTING -p udp --dport 53 -m string --algo bm --from 42 --hex-string "|0674756e6e656c076578616d706c6503636f6d00|" -j REDIRECT -to-ports 5354
- The
hex-string
parameter is the string corresponding to
tunnel.example.com
as seen in DNS packets.
- The
from 42
ensures
that only subrequests are redirected to dns2tcp, and not regular
requests for the domain itself such as SOA or NS requests.
- NSD now
listens on port 53, which is much better than before.
- There are no
more public recursive server, which is even better.
This is not perfect, but it does use much less software than before.