The intention

As the maintainer of the moeOS project, I want systemd-resolved acting as a DNS caching server, as well as a DNS over TLS resolver even when Clash.Meta isn’t running. But according to Clash.Meta Docs, automatic DNS hijacking when using systemd-resolved is not possible and not recommended by the devs.

However, this arbitrary limit can be bypassed using very simple tricks, allowing us to use systemd-resolved with Clash.Meta hijacking the DNS. As moeOS project is using transparent proxy on CGroup / by default.

Implementation

As I searched through the web, a very interesting thing showed up: Clash.Meta Pull Request #1078. This pull request allows redirecting all network traffic on the :53 UDP port to Clash.Meta’s internal DNS module, which then gets processed and hijacked by the Meta kernel.

Create the appropriate outbound.

The Pull Request introduced a special type of outbound proxy: dns, whose main purpose is to act as a proxy intercepting all DNS traffic, as the title suggests. So, we’ll create a new outbound in the proxies section of the config:

1
2
3
proxies:
- name: "dns-out"
type: dns

Route DNS traffic to the arbitrary outbound

Now that we have the dns-out outbound created, it’s time to re-route DNS traffic. Typically DNS traffics runs on UDP port 53 and is unencrypted, which is why it is easy to hijack. This time, we need to introduce some route rules to the .rules section in the config.

1
2
3
rules:
# DNS routing
- AND,((NETWORK,udp),(DST-PORT,53)),dns-out

This should be sufficient for most scenarios. But there exists 2 newer forms of DNS: DNS over TLS and DNS over HTTP. The former one is not that popular, but the latter one is commonly used in some applications. It can bypass the DNS hijacking procedure so we need to block it using GEOSite rules.

1
2
3
4
rules:
# DNS routing
- AND,((NETWORK,udp),(DST-PORT,53)),dns-out
- GEOSITE,category-httpdns,REJECT-DROP

Done!

Now that we’ve successfully implemented, it’s time for the test run~

We’ll use resolvectl to do a query on a domain blocked by my DNS, to see if DNS is working in the first place:

1
resolvectl query right.com.cn

right.com.cn: 0.0.0.0 – link: enp0s13f0u1u1u2

– Information acquired via protocol DNS in 213.4ms.

Now let’s check whether the hijacking is working:

1
dig @8.8.8.8 right.com.cn

right.com.cn. 440 IN A 0.0.0.0

There you have it. A fully functional DNS hijacking provided by Meta, even if systemd-resolved is managing system DNS preference.