Reason#
Five years ago, I set up a website http://ip.flares.cloud to allow users to directly test Cloudflare's IP in their browsers without downloading software. There were two key factors for the establishment of this website: one was the free CNAME integration with Cloudflare, and the other was the need for DNS to resolve the domain name in the format a-b-c-d.ip.flares.cloud
to a.b.c.d
.
For the latter, I initially adopted a simple method of generating Zone file entries in bulk using Excel and then importing them into the DNS service. Since Cloudflare's IPv4 range is quite large, the suffix was uniformly set to .0, and even so, there were thousands of entries. With the Zone file completed, where could I find a DNS service that allows Zone uploads? Initially, the domain was registered with GoDaddy, which supports importing Zones (but not all at once, it has to be done piece by piece). Later, it became less cost-effective to renew there, so I migrated to another domain registrar. Since the website is a purely public welfare project and I was worried about "DDoS attacks" on my wallet, I dared not use any pay-as-you-go services and needed to find a free DNS service that supports importing thousands of entries. After some investigation, there were very few providers that could do this for free, and I ultimately chose GeoScaling.
Over the years, I occasionally received messages in the issue tracker about GeoScaling having resolution problems, which usually resolved themselves after a while. Until this year, the resolution service went down for an extended period, and I could not log into my account (I did not receive any explanatory emails), forcing me to finally make the decision to build my own DNS resolution service.
Python Implementation#
Typically, building a DNS service would use dnsmasq or bind, but these software also require enumerating each Zone entry. I wanted to achieve a programmable resolution directly from a-b-c-d.ip.flares.cloud
to a.b.c.d
in one step. After some research, I found that if only A and AAAA resolutions are needed, writing a script is relatively easy. There is a pip package called dnslib in Python that can send and receive DNS requests. So I drafted a script for a DNS resolution server using AI and made some manual adjustments (Github):
from dnslib import DNSRecord, QTYPE, RR, A, AAAA
from dnslib.server import DNSServer, DNSHandler, BaseResolver
class DynamicResolver(BaseResolver):
def resolve(self, request, handler):
# Get the requested domain name
qname = str(request.q.qname).lower().strip(".")
reply = request.reply()
qtype = request.q.qtype
base_domain = "flares.cloud"
# Dynamically resolve a-b-c-d to a.b.c.d
if qname.endswith(".ip." + base_domain):
parts = qname.split(".")[0].split("-")
#print(parts)
if len(parts) == 4 and all(p.isdigit() for p in parts):
# Resolve to IPv4 address
if qtype==1:
ip = ".".join(parts)
reply.add_answer(RR(qname, QTYPE.A, rdata=A(ip), ttl=300))
else:
reply.header.rcode = 0
else:
# If the format does not match, return NXDOMAIN
reply.header.rcode = 3 # NXDOMAIN
elif qname.endswith(base_domain):
if qtype==1:
reply.add_answer(RR(qname, QTYPE.A, rdata=A("104.21.55.142"), ttl=300))
reply.add_answer(RR(qname, QTYPE.A, rdata=A("172.67.149.32"), ttl=300))
elif qtype==28:
reply.add_answer(RR(qname, QTYPE.AAAA, rdata=AAAA("2606:4700:3034::ac43:9520"), ttl=300))
reply.add_answer(RR(qname, QTYPE.AAAA, rdata=AAAA("2606:4700:3030::6815:378e"), ttl=300))
else:
reply.header.rcode = 0
else:
# If it does not belong to a defined domain, return NXDOMAIN
reply.header.rcode = 3 # NXDOMAIN
return reply
if __name__ == "__main__":
# Listening address and port
resolver = DynamicResolver()
server = DNSServer(resolver, port=53, address="::")
print("Starting DNS server")
try:
server.start()
except KeyboardInterrupt:
server.stop()
Configuration and Testing#
Choose a server that has port 53 open (not recommended to do this within China). If there is a program occupying port 53, close it (such as dnsmasq, systemd-resolved), and ensure that the system DNS queries are functioning properly. Run the Python program and execute a command like:
nslookup 104-16-96-3.ip.flares.cloud 127.0.0.1
If everything is normal, then execute on a remote host:
nslookup 104-16-96-3.ip.flares.cloud DNS server's IP
If that works too, you can try pointing the domain's NameServer to your own DNS server. For example, this site created two subdomains ns1.ipv6.stream
and ns2.ipv6.stream
pointing to the DNS server's IPv4/IPv6 (some domain service providers will check if the NS domain's A resolution IP address is the DNS; if not, the setup will fail). After setting it up with the domain service provider, just wait a while. If unsure, you can execute the following command to check:
dig +trace 104-16-96-3.ip.flares.cloud
You can see that the resolution process from root → cloud top-level domain → self-built DNS → result is smooth:
;; global options: +cmd
. 87203 IN NS k.root-servers.net.
. 87203 IN NS a.root-servers.net.
. 87203 IN NS d.root-servers.net.
. 87203 IN NS h.root-servers.net.
. 87203 IN NS j.root-servers.net.
. 87203 IN NS e.root-servers.net.
. 87203 IN NS c.root-servers.net.
. 87203 IN NS i.root-servers.net.
. 87203 IN NS f.root-servers.net.
. 87203 IN NS l.root-servers.net.
. 87203 IN NS g.root-servers.net.
. 87203 IN NS b.root-servers.net.
. 87203 IN NS m.root-servers.net.
. 87203 IN RRSIG NS 8 0 518400 20250124050000 20250111040000 26470 . R1Mbylc4QrkWbTyoX5X/fM7XkGSlaovOqYSyQ1ZuujRL+pV3ekI9ytoz 9X3LS6Hm7T40XeO/Z6d2pb3yryqYoXeAY0meJHdEfZwYT3/7AIUuWu4r eKt/K/G0bL5VM26qHgj6aTYheHIh2M8dXtpx43o0hHuM87X2hnReLNbX V8+gc8cjbpmATREbzU4db70gckL9NuZtleQdpbRUcwJcakudwXwIST1S X3wnIWMo0R52A4hfcODsb3tL3LS0drJuceIvhMZSNwubAowfWt7CzMjd MkEYaErsIfJMrbJ8AXMcb9wZjsPUBzUqyLm7Smt5/LvxtPzJLSQRNr2B a6K4+Q==
;; Received 525 bytes from 8.8.8.8#53(8.8.8.8) in 4 ms
cloud. 172800 IN NS ns01.trs-dns.com.
cloud. 172800 IN NS ns01.trs-dns.net.
cloud. 172800 IN NS ns01.trs-dns.org.
cloud. 172800 IN NS ns01.trs-dns.info.
cloud. 86400 IN DS 7041 13 2 03E1B41319FDD3A14E27CE35FFBA8036DA51E328165CE102AE69A6C1 203F64E4
cloud. 86400 IN RRSIG DS 8 1 86400 20250124050000 20250111040000 26470 . PB/tyNRHUg9+Fpg4tAweaHLFSwvam2nZxTnwikFfmRPTc9121azZ8WRX r8/N92OKcPBpISyi4dONjyFa5n60xDJDg5SwW68cnE435Dpa2vCqqECC 5rkOMTj8WliC2Coyt8cdEHDKYA4IACWAxkKCG6GLY6FKuMUUIeF1eQwt 4QIWg5ac8UKR0/exlgpChNPPfgOE6f7NKl7tXnx9s9P38MgVGM531BAl j0ES74lQaHkQBF7nTP8kSyu2A3Z5Fh4Ly3lWbhzJwzZuiuh90ybStCU3 cPt/gxaEpfq/PC79RgHxo2db61i4PsJmxT2a0IlgkF9nApJ9gqgwV9GB Uj7txw==
;; Received 693 bytes from 2001:7fe::53#53(i.root-servers.net) in 8 ms
flares.cloud. 900 IN NS ns1.ipv6.stream.
flares.cloud. 900 IN NS ns2.ipv6.stream.
HT3BBE52QFEA3EKHEL6VAE4CVUE8777V.cloud. 300 IN NSEC3 1 1 0 - HT6L5OLG1P6S8KJ46GSSFH8NK8V88KN8 NS SOA RRSIG DNSKEY NSEC3PARAM
HT3BBE52QFEA3EKHEL6VAE4CVUE8777V.cloud. 300 IN RRSIG NSEC3 13 2 300 20250210064627 20250111064627 36804 cloud. f7tgopfGAr1qRF2uaDshi25DHDi+tYkRe4Il/6OQziUQ8v4ykr8vou2Z WWYiRqhPuk170ftGZhLJEiyCVRIkuQ==
U5U5ADCUK0HTP2QQ80FQH8566IQU6VLU.cloud. 300 IN NSEC3 1 1 0 - U60GR3IIOBUJHOBIHNJQISHOGCPPUO4E NS DS RRSIG
U5U5ADCUK0HTP2QQ80FQH8566IQU6VLU.cloud. 300 IN RRSIG NSEC3 13 2 300 20250210064627 20250111064627 36804 cloud. sjsXE16m+Qlew47+Ifrvq9L2C/Mq/HkPelt5l2Vd+UCi3U0Vyd4mddlH LTiR+QqsW0ZxToZfIj2VS6w9z43IBg==
;; Received 504 bytes from 2620:57:4001::1#53(ns01.trs-dns.com) in 8 ms
;; expected opt record in response
104-16-96-3.ip.flares.cloud. 300 IN A 104.16.96.3
;; Received 61 bytes from 148.135.6.152#53(ns1.ipv6.stream) in 4 ms
Running as a Service#
If everything is fine, you can create a systemctl service at /etc/systemd/system/dns-server.service to keep the script running:
[Unit]
Description=Custom DNS Server for ip.flares.cloud
After=network.target
[Service]
ExecStart=/usr/bin/python3 /root/dns-server/dns_server.py
WorkingDirectory=/root/
StandardOutput=journal
StandardError=journal
Restart=always
User=root
Group=root
Environment=PYTHONUNBUFFERED=1
[Install]
WantedBy=multi-user.target
Execute the following commands to start:
sudo systemctl daemon-reload
sudo systemctl restart dns-server.service
Check the logs:
journalctl -u dns-server.service -f
Running in Docker#
Of course, you can also package it to run in Docker. Create a Dockerfile in the same folder:
FROM python:3.9
WORKDIR /app
COPY requirements.txt .
RUN pip install --no-cache-dir -r requirements.txt
COPY . .
CMD [ "python", "./dns_server.py" ]
Build the Docker and run:
docker build -t dns_server .
docker run --network=host --restart unless-stopped --name=dns_server -e PYTHONUNBUFFERED=1 dns_server
Note#
It should be emphasized that the above implementation is very simple and only has the most basic functionality, and its behavior may not be standard. The code has not undergone any security audit, so please only conduct research in virtual machines without important data that can be easily rebuilt.
By the way, although specifying any suffix and resolving IPv6 is no longer a barrier, the current network situation of various IPv6 segments and the same /24 IPv4 segments is quite similar, so testing them separately is not very meaningful. Therefore, it will not be implemented on the website for now, and changes will be made only if there is a clear demand in the future.