From d7320210b49d2dbd8859adf0d3eb25bbe5131b70 Mon Sep 17 00:00:00 2001 From: Ivan Jedek Date: Tue, 28 May 2024 16:10:34 +0400 Subject: [PATCH] New release --- changelog | 19 +- docs/catalogue/general-features.md | 279 +++++++++++++++++++++++++---- modules/dchain.toml | 2 +- modules/dloop.toml | 51 ++++++ modules/httpschain.toml | 29 +++ modules/httpsloop.toml | 52 ++++++ modules/self.toml | 50 ++++++ modules/svcbchain.toml | 29 +++ modules/svcbloop.toml | 52 ++++++ polardns.py | 26 +-- test/test.sh | 15 ++ 11 files changed, 550 insertions(+), 54 deletions(-) create mode 100644 modules/dloop.toml create mode 100644 modules/httpschain.toml create mode 100644 modules/httpsloop.toml create mode 100644 modules/self.toml create mode 100644 modules/svcbchain.toml create mode 100644 modules/svcbloop.toml diff --git a/changelog b/changelog index 7f67393..0796921 100644 --- a/changelog +++ b/changelog @@ -1,7 +1,20 @@ +28/May/2024 +- added the 'httpsloop' feature for creating alias loops in HTTPS records (RFC 9460) +- added the 'httpschain' feature for creating infinite alias chains in HTTPS records (RFC 9460) +- added the 'svcbloop' feature for creating alias loops in SVCB records (RFC 9460) +- added the 'svcbchain' feature for creating infinite alias chains in SVCB records (RFC 9460) +- added the 'dloop' feature for creating alias loops in DNAME records +- added tests for all the new features (total test count: 344) +- updated the catalogue documentation +- small code fixes in polardns.py + +23/May/2024 +- added the 'self' feature for returning the client's IP address (aka. what is my IP) + 08/May/2024 - release version 1.1 - update of the documentation -- sorting modules in alphabetical order when constructing polardns_real.py +- upon startup, sort the modules in alphabetical order during the construction of polardns_real.py 08/May/2024 - major update of the contribution page @@ -63,7 +76,7 @@ 18/Oct/2023 - fixed problem with dots '.' in the query domain name not being reflected in the response properly -- enhanced the tests and added 41 more tests (now there is 293 tests in total) +- enhanced the tests and added 41 more tests (total test count: 293) 17/Oct/2023 - added support for getting version information using 'dig version.polar CH TXT' command @@ -82,7 +95,7 @@ - enhanced parsing of the domain name in the request (added the backslashreplace option in decode()) 05/Jul/2023 -- added a collection of tests (252 tests) +- added a collection of tests (total test count: 252) 16/Jun/2023 - fixed exceptions when parsing various incomplete requests, too short DNS packets etc. diff --git a/docs/catalogue/general-features.md b/docs/catalogue/general-features.md index ae5a944..cb03c29 100644 --- a/docs/catalogue/general-features.md +++ b/docs/catalogue/general-features.md @@ -1,10 +1,16 @@ # PolarDNS catalogue - General features 1. [General features](general-features.md) - [Always resolve to IP (always)](#always-resolve-to-ip-always) + - [Client IP address (self / whatismyip)](#client-ip-address-self--whatismyip) - [CNAME alias chain (chain)](#cname-alias-chain-chain) - - [CNAME alias chain with 3 records (schain)](#cname-alias-chain-with-3-records-schain) - [CNAME alias loop (loop)](#cname-alias-loop-loop) - [DNAME alias chain (dchain)](#dname-alias-chain-dchain) + - [DNAME alias loop (dloop)](#dname-alias-loop-dloop) + - [HTTPS alias chain (httpschain)](#https-alias-chain-httpschain) + - [HTTPS alias loop (httpsloop)](#https-alias-loop-httpsloop) + - [SVCB alias chain (svcbchain)](#svcb-alias-chain-svcbchain) + - [SVCB alias loop (svcbloop)](#svcb-alias-loop-svcbloop) + - [CNAME alias chain with 3 records (schain)](#cname-alias-chain-with-3-records-schain) - [Chunked CNAME aliases (chunkedcnames)](#chunked-cname-aliases-chunkedcnames) - [Cut A record from the end (cutabuf)](#cut-a-record-from-the-end-cutabuf) - [Cut CNAME record from the end (cutcnamebuf)](#cut-cname-record-from-the-end-cutcnamebuf) @@ -53,67 +59,71 @@ always.yourdomain.com. 60 IN A 2.3.4.5 ;; MSG SIZE rcvd: 76 ``` -### CNAME alias chain (chain) -Respond with an incremented CNAME record. This creates an infinite alias chain. +### Client IP address (self / whatismyip) +Respond with A and TXT records containing the IP address of the connecting client. The TXT record also contains the port information. - - - + + + + +
format:chain<NUMBER>.yourdomain.com
example:dig chain.yourdomain.com @127.0.0.1
example:dig chain123456.yourdomain.com @127.0.0.1
format:self.yourdomain.com
example:dig self.yourdomain.com @127.0.0.1
example:dig A self.yourdomain.com @127.0.0.1
example:dig TXT self.yourdomain.com @127.0.0.1
example:dig TXT whatismyip.yourdomain.com @127.0.0.1
Sample: ``` -# dig chain123456.yourdomain.com @127.0.0.1 +# dig TXT whatismyip.yourdomain.com @127.0.0.1 -; <<>> DiG 9.18.10-2-Debian <<>> chain123456.yourdomain.com @127.0.0.1 +; <<>> DiG 9.18.10-2-Debian <<>> TXT whatismyip.yourdomain.com @127.0.0.1 ;; global options: +cmd ;; Got answer: -;; ->>HEADER<<- opcode: QUERY, status: NOERROR, id: 49493 -;; flags: qr aa; QUERY: 1, ANSWER: 1, AUTHORITY: 0, ADDITIONAL: 0 +;; ->>HEADER<<- opcode: QUERY, status: NOERROR, id: 15082 +;; flags: qr aa; QUERY: 1, ANSWER: 1, AUTHORITY: 0, ADDITIONAL: 1 ;; QUESTION SECTION: -;chain123456.yourdomain.com. IN A +;whatismyip.yourdomain.com. IN TXT ;; ANSWER SECTION: -chain123456.yourdomain.com. 60 IN CNAME chain123457.yourdomain.com. +whatismyip.yourdomain.com. 60 IN TXT "127.0.0.1:43732" -;; Query time: 4 msec +;; ADDITIONAL SECTION: +whatismyip.yourdomain.com. 60 IN A 127.0.0.1 + +;; Query time: 0 msec ;; SERVER: 127.0.0.1#53(127.0.0.1) (UDP) -;; WHEN: Fri Nov 03 14:30:51 +04 2023 -;; MSG SIZE rcvd: 110 +;; WHEN: Mon May 27 17:10:12 +04 2024 +;; MSG SIZE rcvd: 137 ``` -### CNAME alias chain with 3 records (schain) -Respond with 3 random CNAME records (schain####.yourdomain.com). +### CNAME alias chain (chain) +Respond with an incremented CNAME record. This creates an infinite alias chain. - - + + +
format:schain<NUMBER>.yourdomain.com
example:dig schain123456.yourdomain.com @127.0.0.1
format:chain<NUMBER>.yourdomain.com
example:dig chain.yourdomain.com @127.0.0.1
example:dig chain1234.yourdomain.com @127.0.0.1
Sample: ``` -# dig schain123456.yourdomain.com @127.0.0.1 +# dig chain1234.yourdomain.com @127.0.0.1 -; <<>> DiG 9.18.10-2-Debian <<>> schain123456.yourdomain.com @127.0.0.1 +; <<>> DiG 9.18.10-2-Debian <<>> chain1234.yourdomain.com @127.0.0.1 ;; global options: +cmd ;; Got answer: -;; ->>HEADER<<- opcode: QUERY, status: NOERROR, id: 65398 -;; flags: qr aa; QUERY: 1, ANSWER: 3, AUTHORITY: 0, ADDITIONAL: 0 +;; ->>HEADER<<- opcode: QUERY, status: NOERROR, id: 47208 +;; flags: qr aa; QUERY: 1, ANSWER: 1, AUTHORITY: 0, ADDITIONAL: 0 ;; QUESTION SECTION: -;schain123456.yourdomain.com. IN A +;chain1234.yourdomain.com. IN A ;; ANSWER SECTION: -schain123456.yourdomain.com. 60 IN CNAME schain600556.yourdomain.com. -schain123456.yourdomain.com. 60 IN CNAME schain916228.yourdomain.com. -schain123456.yourdomain.com. 60 IN CNAME schain381071.yourdomain.com. +chain1234.yourdomain.com. 60 IN CNAME chain1235.yourdomain.com. -;; Query time: 0 msec +;; Query time: 3 msec ;; SERVER: 127.0.0.1#53(127.0.0.1) (UDP) -;; WHEN: Fri Nov 03 14:30:51 +04 2023 -;; MSG SIZE rcvd: 249 +;; WHEN: Tue May 28 12:18:52 +04 2024 +;; MSG SIZE rcvd: 104 ``` ### CNAME alias loop (loop) @@ -121,6 +131,7 @@ Respond with CNAME record forming an infinite loop consisting of any number of e +
format:loop.<NUMBER>.yourdomain.com
example:dig loop.yourdomain.com @127.0.0.1
example:dig loop.5.yourdomain.com @127.0.0.1
@@ -151,29 +162,223 @@ Respond with an incremented DNAME record. This creates an infinite alias chain. - + +
format:dchain<NUMBER>.yourdomain.com
example:dig dchain123456.yourdomain.com @127.0.0.1
example:dig dchain.yourdomain.com @127.0.0.1
example:dig dchain1234.yourdomain.com @127.0.0.1
Sample: ``` -# dig dchain123456.yourdomain.com @127.0.0.1 +# dig dchain1234.yourdomain.com @127.0.0.1 -; <<>> DiG 9.18.10-2-Debian <<>> dchain123456.yourdomain.com @127.0.0.1 +; <<>> DiG 9.18.10-2-Debian <<>> dchain1234.yourdomain.com @127.0.0.1 ;; global options: +cmd ;; Got answer: -;; ->>HEADER<<- opcode: QUERY, status: NOERROR, id: 14205 +;; ->>HEADER<<- opcode: QUERY, status: NOERROR, id: 37173 ;; flags: qr aa; QUERY: 1, ANSWER: 1, AUTHORITY: 0, ADDITIONAL: 0 ;; QUESTION SECTION: -;dchain123456.yourdomain.com. IN A +;dchain1234.yourdomain.com. IN A ;; ANSWER SECTION: -dchain123456.yourdomain.com. 60 IN DNAME dchain123457.yourdomain.com. +dchain1234.yourdomain.com. 60 IN DNAME dchain1235.yourdomain.com. + +;; Query time: 0 msec +;; SERVER: 127.0.0.1#53(127.0.0.1) (UDP) +;; WHEN: Tue May 28 12:18:52 +04 2024 +;; MSG SIZE rcvd: 107 + +``` +### DNAME alias loop (dloop) +Respond with DNAME record forming an infinite loop consisting of any number of elements. + + + + + +
format:dloop.<NUMBER>.yourdomain.com
example:dig dloop.yourdomain.com @127.0.0.1
example:dig dloop.5.yourdomain.com @127.0.0.1
+ +Sample: +``` +# dig dloop.5.yourdomain.com @127.0.0.1 + +; <<>> DiG 9.18.10-2-Debian <<>> dloop.5.yourdomain.com @127.0.0.1 +;; global options: +cmd +;; Got answer: +;; ->>HEADER<<- opcode: QUERY, status: NOERROR, id: 25597 +;; flags: qr aa; QUERY: 1, ANSWER: 1, AUTHORITY: 0, ADDITIONAL: 0 + +;; QUESTION SECTION: +;dloop.5.yourdomain.com. IN A + +;; ANSWER SECTION: +dloop.5.yourdomain.com. 60 IN DNAME dloop.5.1.yourdomain.com. + +;; Query time: 0 msec +;; SERVER: 127.0.0.1#53(127.0.0.1) (UDP) +;; WHEN: Tue May 28 11:50:00 +04 2024 +;; MSG SIZE rcvd: 100 + +``` +### HTTPS alias chain (httpschain) +Respond with an incremented HTTPS alias record. This creates an infinite alias chain. + + + + + + +
format:httpschain<NUMBER>.yourdomain.com
example:dig httpschain.yourdomain.com @127.0.0.1
example:dig HTTPS httpschain.yourdomain.com @127.0.0.1
example:dig httpschain1234.yourdomain.com @127.0.0.1
+ +Sample: +``` +# dig httpschain1234.yourdomain.com @127.0.0.1 + +; <<>> DiG 9.18.10-2-Debian <<>> httpschain1234.yourdomain.com @127.0.0.1 +;; global options: +cmd +;; Got answer: +;; ->>HEADER<<- opcode: QUERY, status: NOERROR, id: 29224 +;; flags: qr aa; QUERY: 1, ANSWER: 1, AUTHORITY: 0, ADDITIONAL: 0 + +;; QUESTION SECTION: +;httpschain1234.yourdomain.com. IN A + +;; ANSWER SECTION: +httpschain1234.yourdomain.com. 60 IN HTTPS 0 httpschain1235.yourdomain.com. + +;; Query time: 0 msec +;; SERVER: 127.0.0.1#53(127.0.0.1) (UDP) +;; WHEN: Tue May 28 12:18:52 +04 2024 +;; MSG SIZE rcvd: 121 + +``` +### HTTPS alias loop (httpsloop) +Respond with an HTTPS record featuring an alias (SvcPriority 0) that creates an infinite loop with any number of elements. + + + + + + + +
format:httpsloop.<NUMBER>.yourdomain.com
example:dig httpsloop.yourdomain.com @127.0.0.1
example:dig httpsloop.5.yourdomain.com @127.0.0.1
example:dig HTTPS httpsloop.5.yourdomain.com @127.0.0.1
example:dig HTTPS httpsloop.yourdomain.com @127.0.0.1
+ +Sample: +``` +# dig HTTPS httpsloop.yourdomain.com @127.0.0.1 + +; <<>> DiG 9.18.10-2-Debian <<>> HTTPS httpsloop.yourdomain.com @127.0.0.1 +;; global options: +cmd +;; Got answer: +;; ->>HEADER<<- opcode: QUERY, status: NOERROR, id: 63583 +;; flags: qr aa; QUERY: 1, ANSWER: 1, AUTHORITY: 0, ADDITIONAL: 0 + +;; QUESTION SECTION: +;httpsloop.yourdomain.com. IN HTTPS + +;; ANSWER SECTION: +httpsloop.yourdomain.com. 60 IN HTTPS 0 httpsloop.yourdomain.com. + +;; Query time: 3 msec +;; SERVER: 127.0.0.1#53(127.0.0.1) (UDP) +;; WHEN: Mon May 27 15:26:37 +04 2024 +;; MSG SIZE rcvd: 106 + +``` +### SVCB alias chain (svcbchain) +Respond with an incremented SVCB alias record. This creates an infinite alias chain. + + + + + + +
format:svcbchain<NUMBER>.yourdomain.com
example:dig svcbchain.yourdomain.com @127.0.0.1
example:dig SVCB svcbchain.yourdomain.com @127.0.0.1
example:dig svcbchain1234.yourdomain.com @127.0.0.1
+ +Sample: +``` +# dig svcbchain1234.yourdomain.com @127.0.0.1 + +; <<>> DiG 9.18.10-2-Debian <<>> svcbchain1234.yourdomain.com @127.0.0.1 +;; global options: +cmd +;; Got answer: +;; ->>HEADER<<- opcode: QUERY, status: NOERROR, id: 45331 +;; flags: qr aa; QUERY: 1, ANSWER: 1, AUTHORITY: 0, ADDITIONAL: 0 + +;; QUESTION SECTION: +;svcbchain1234.yourdomain.com. IN A + +;; ANSWER SECTION: +svcbchain1234.yourdomain.com. 60 IN SVCB 0 svcbchain1235.yourdomain.com. + +;; Query time: 0 msec +;; SERVER: 127.0.0.1#53(127.0.0.1) (UDP) +;; WHEN: Tue May 28 12:18:52 +04 2024 +;; MSG SIZE rcvd: 118 + +``` +### SVCB alias loop (svcbloop) +Respond with an SVCB record featuring an alias (SvcPriority 0) that creates an infinite loop with any number of elements. + + + + + + + +
format:svcbloop.<NUMBER>.yourdomain.com
example:dig svcbloop.yourdomain.com @127.0.0.1
example:dig svcbloop.5.yourdomain.com @127.0.0.1
example:dig SVCB svcbloop.5.yourdomain.com @127.0.0.1
example:dig SVCB svcbloop.yourdomain.com @127.0.0.1
+ +Sample: +``` +# dig SVCB svcbloop.yourdomain.com @127.0.0.1 + +; <<>> DiG 9.18.10-2-Debian <<>> SVCB svcbloop.yourdomain.com @127.0.0.1 +;; global options: +cmd +;; Got answer: +;; ->>HEADER<<- opcode: QUERY, status: NOERROR, id: 64873 +;; flags: qr aa; QUERY: 1, ANSWER: 1, AUTHORITY: 0, ADDITIONAL: 0 + +;; QUESTION SECTION: +;svcbloop.yourdomain.com. IN SVCB + +;; ANSWER SECTION: +svcbloop.yourdomain.com. 60 IN SVCB 0 svcbloop.yourdomain.com. + +;; Query time: 0 msec +;; SERVER: 127.0.0.1#53(127.0.0.1) (UDP) +;; WHEN: Mon May 27 15:26:38 +04 2024 +;; MSG SIZE rcvd: 103 + +``` +### CNAME alias chain with 3 records (schain) +Respond with 3 random CNAME records (schain####.yourdomain.com). + + + + +
format:schain<NUMBER>.yourdomain.com
example:dig schain123456.yourdomain.com @127.0.0.1
+ +Sample: +``` +# dig schain123456.yourdomain.com @127.0.0.1 + +; <<>> DiG 9.18.10-2-Debian <<>> schain123456.yourdomain.com @127.0.0.1 +;; global options: +cmd +;; Got answer: +;; ->>HEADER<<- opcode: QUERY, status: NOERROR, id: 65398 +;; flags: qr aa; QUERY: 1, ANSWER: 3, AUTHORITY: 0, ADDITIONAL: 0 + +;; QUESTION SECTION: +;schain123456.yourdomain.com. IN A + +;; ANSWER SECTION: +schain123456.yourdomain.com. 60 IN CNAME schain600556.yourdomain.com. +schain123456.yourdomain.com. 60 IN CNAME schain916228.yourdomain.com. +schain123456.yourdomain.com. 60 IN CNAME schain381071.yourdomain.com. ;; Query time: 0 msec ;; SERVER: 127.0.0.1#53(127.0.0.1) (UDP) ;; WHEN: Fri Nov 03 14:30:51 +04 2023 -;; MSG SIZE rcvd: 113 +;; MSG SIZE rcvd: 249 ``` ### Chunked CNAME aliases (chunkedcnames) diff --git a/modules/dchain.toml b/modules/dchain.toml index 3ed6d1f..9a2b99d 100644 --- a/modules/dchain.toml +++ b/modules/dchain.toml @@ -15,7 +15,7 @@ if req.first_subdomain.startswith("dchain"): ### QUESTION SECTION ######## if resp.noq: buffer += convDom2Bin(req.full_domain) + req.type_bin + req.class_bin ### ANSWER SECTION ######## - # CNAME + # DNAME buffer += convDom2Bin(req.full_domain) + getTypeBin("DNAME") + getClassBin("IN") buffer += struct.pack(">L", resp.TTL) ## TTL buffer += struct.pack(">H", len(new_domain_name)+2) ## Data length diff --git a/modules/dloop.toml b/modules/dloop.toml new file mode 100644 index 0000000..63d2444 --- /dev/null +++ b/modules/dloop.toml @@ -0,0 +1,51 @@ +[module] +name = "dloop" +type = "feature" +info = "DNAME alias loop" +desc = "Respond with DNAME record forming an infinite loop consisting of any number of elements." +author = "ivan.jedek@oryxlabs.com" +category = "General features" + +code = ''' +if req.first_subdomain.startswith("dloop"): + # Do a DNAME loop + if req.subdomains[1].isnumeric() and req.subdomains[2].isnumeric(): + # we are already in a loop, e.g.: + # dloop.10.4.dnslabtest.com + max = int(req.subdomains[1]) + cur = int(req.subdomains[2]) + if cur >= max: + # go back to the beginning of the loop + new_domain_name = req.subdomains[0] + "." + str(max) + ".1" + else: + # increment the current index + new_domain_name = req.subdomains[0] + "." + str(max) + "." + str(cur+1) + for i in range(len(req.subdomains)-3): + new_domain_name += "." + req.subdomains[i+3] + elif req.subdomains[1].isnumeric(): + # we are in beginning of a loop with a requested max value, e.g.: + # dloop.10.dnslabtest.com + max = int(req.subdomains[1]) + if max < 1: + max = 1 + new_domain_name = req.subdomains[0] + "." + str(max) + "." + "1" + for i in range(len(req.subdomains)-2): + new_domain_name += "." + req.subdomains[i+2] + else: + # just immediate loop + new_domain_name = req.full_domain + ### DNS header ######## + buffer = prep_dns_header(b'\x84\x00', req.QURR, 1, 0, 0) + ### QUESTION SECTION ######## + if resp.noq: buffer += convDom2Bin(req.full_domain) + req.type_bin + req.class_bin + ### ANSWER SECTION ######## + # DNAME + buffer += convDom2Bin(req.full_domain) + getTypeBin("DNAME") + getClassBin("IN") + buffer += struct.pack(">L", resp.TTL) ## TTL + buffer += struct.pack(">H", len(new_domain_name)+2) ## Data length + buffer += convDom2Bin(new_domain_name) ## DNAME value + # log and send + log("DNAME LOOP %s" % (new_domain_name)) + send_buf(self, buffer) + ##################################################################### +''' diff --git a/modules/httpschain.toml b/modules/httpschain.toml new file mode 100644 index 0000000..c8d35bc --- /dev/null +++ b/modules/httpschain.toml @@ -0,0 +1,29 @@ +[module] +name = "httpschain" +type = "feature" +info = "HTTPS alias chain" +desc = "Respond with an incremented HTTPS alias record. This creates an infinite alias chain." +author = "ivan.jedek@oryxlabs.com" +category = "General features" + +code = ''' +if req.first_subdomain.startswith("httpschain"): + # Send incremented HTTPS alias record + new_domain_name = increment_chain(req.full_domain) + ### DNS header ######## + buffer = prep_dns_header(b'\x84\x00', req.QURR, 1, 0, 0) + ### QUESTION SECTION ######## + if resp.noq: buffer += convDom2Bin(req.full_domain) + req.type_bin + req.class_bin + ### ANSWER SECTION ######## + # HTTPS + data_len = 2+len(convDom2Bin(new_domain_name)) # SvcPriority (2 bytes) + the target name + buffer += convDom2Bin(req.full_domain) + getTypeBin("HTTPS") + getClassBin("IN") + buffer += struct.pack(">L", resp.TTL) ## TTL + buffer += struct.pack(">H", data_len) ## Data length + buffer += struct.pack(">H", 0) ## SvcPriority (0 means alias mode - RFC 9460) + buffer += convDom2Bin(new_domain_name) ## TargetName + # log and send + log("HTTPS %s" % (new_domain_name)) + send_buf(self, buffer) + ##################################################################### +''' diff --git a/modules/httpsloop.toml b/modules/httpsloop.toml new file mode 100644 index 0000000..c7ebed3 --- /dev/null +++ b/modules/httpsloop.toml @@ -0,0 +1,52 @@ +[module] +name = "httpsloop" +type = "feature" +info = "HTTPS alias loop" +desc = "Respond with an HTTPS record featuring an alias (SvcPriority 0) that creates an infinite loop with any number of elements." +author = "ivan.jedek@oryxlabs.com" +category = "General features" + +code = ''' +if req.first_subdomain.startswith("httpsloop"): + # Do an alias loop in a HTTPS record (SvcPriority 0) + if req.subdomains[1].isnumeric() and req.subdomains[2].isnumeric(): + # we are already in a loop, e.g.: + # httpsloop.10.4.dnslabtest.com + max = int(req.subdomains[1]) + cur = int(req.subdomains[2]) + if cur >= max: + # go back to the beginning of the loop + new_domain_name = req.subdomains[0] + "." + str(max) + ".1" + else: + # increment the current index + new_domain_name = req.subdomains[0] + "." + str(max) + "." + str(cur+1) + for i in range(len(req.subdomains)-3): + new_domain_name += "." + req.subdomains[i+3] + elif req.subdomains[1].isnumeric(): + # we are in beginning of a loop with a requested max value, e.g.: + # httpsloop.10.dnslabtest.com + max = int(req.subdomains[1]) + if max < 1: + max = 1 + new_domain_name = req.subdomains[0] + "." + str(max) + "." + "1" + for i in range(len(req.subdomains)-2): + new_domain_name += "." + req.subdomains[i+2] + else: + # just immediate loop + new_domain_name = req.full_domain + ### DNS header ######## + buffer = prep_dns_header(b'\x84\x00', req.QURR, 1, 0, 0) + ### QUESTION SECTION ######## + if resp.noq: buffer += convDom2Bin(req.full_domain) + req.type_bin + req.class_bin + ### ANSWER SECTION ######## + data_len = 2+len(convDom2Bin(new_domain_name)) # SvcPriority (2 bytes) + the target name + buffer += convDom2Bin(req.full_domain) + getTypeBin("HTTPS") + getClassBin("IN") + buffer += struct.pack(">L", resp.TTL) ## TTL + buffer += struct.pack(">H", data_len) ## Data length + buffer += struct.pack(">H", 0) ## SvcPriority (0 means alias mode - RFC 9460) + buffer += convDom2Bin(new_domain_name) ## TargetName + # log and send + log("HTTPS LOOP %s" % (new_domain_name)) + send_buf(self, buffer) + ##################################################################### +''' diff --git a/modules/self.toml b/modules/self.toml new file mode 100644 index 0000000..ca8bb65 --- /dev/null +++ b/modules/self.toml @@ -0,0 +1,50 @@ +[module] +name = "self" +type = "feature" +info = "Client IP address" +desc = "Respond with A and TXT records containing the IP address of the connecting client. The TXT record also contains the port information" +author = "ivan.jedek@oryxlabs.com" +category = "General features" + +code = ''' +if req.first_subdomain.startswith("self") or req.first_subdomain.startswith("whatismyip"): + # Respond with the client's IP address in A record and in TXT record (IP:port) + ### DNS header ######## + buffer = prep_dns_header(b'\x84\x00', resp.QURR, 1, 0, 1) + ### QUESTION SECTION ######## + if resp.noq: buffer += convDom2Bin(req.full_domain) + req.type_bin + req.class_bin + ### ANSWER SECTION ######## + if req.type_str == "TXT": + # TXT + ipport = str(self.client_address[0]) + ":" + str(self.client_address[1]) + buffer += convDom2Bin(req.full_domain) + getTypeBin("TXT") + getClassBin("IN") + buffer += struct.pack(">L", resp.TTL) ## TTL + buffer += struct.pack(">H", len(ipport)+1) ## Data length + buffer += struct.pack(">B", len(ipport)) ## TXT length + buffer += bytes(ipport, "utf-8") + # A + ip = self.client_address[0] + buffer += convDom2Bin(req.full_domain) + getTypeBin("A") + getClassBin("IN") + buffer += struct.pack(">L", resp.TTL) ## TTL + buffer += struct.pack(">H", 4) ## Data length + buffer += socket.inet_aton(ip) ## IP + log("TXT + A with the client address") + else: + # A + ip = self.client_address[0] + buffer += convDom2Bin(req.full_domain) + getTypeBin("A") + getClassBin("IN") + buffer += struct.pack(">L", resp.TTL) ## TTL + buffer += struct.pack(">H", 4) ## Data length + buffer += socket.inet_aton(ip) ## IP + # TXT + ipport = str(self.client_address[0]) + ":" + str(self.client_address[1]) + buffer += convDom2Bin(req.full_domain) + getTypeBin("TXT") + getClassBin("IN") + buffer += struct.pack(">L", resp.TTL) ## TTL + buffer += struct.pack(">H", len(ipport)+1) ## Data length + buffer += struct.pack(">B", len(ipport)) ## TXT length + buffer += bytes(ipport, "utf-8") + log("A + TXT with the client address") + # send + send_buf(self, buffer) + ##################################################################### +''' diff --git a/modules/svcbchain.toml b/modules/svcbchain.toml new file mode 100644 index 0000000..96b18c0 --- /dev/null +++ b/modules/svcbchain.toml @@ -0,0 +1,29 @@ +[module] +name = "svcbchain" +type = "feature" +info = "SVCB alias chain" +desc = "Respond with an incremented SVCB alias record. This creates an infinite alias chain." +author = "ivan.jedek@oryxlabs.com" +category = "General features" + +code = ''' +if req.first_subdomain.startswith("svcbchain"): + # Send incremented SVCB alias record + new_domain_name = increment_chain(req.full_domain) + ### DNS header ######## + buffer = prep_dns_header(b'\x84\x00', req.QURR, 1, 0, 0) + ### QUESTION SECTION ######## + if resp.noq: buffer += convDom2Bin(req.full_domain) + req.type_bin + req.class_bin + ### ANSWER SECTION ######## + # SVCB + data_len = 2+len(convDom2Bin(new_domain_name)) # SvcPriority (2 bytes) + the target name + buffer += convDom2Bin(req.full_domain) + getTypeBin("SVCB") + getClassBin("IN") + buffer += struct.pack(">L", resp.TTL) ## TTL + buffer += struct.pack(">H", data_len) ## Data length + buffer += struct.pack(">H", 0) ## SvcPriority (0 means alias mode - RFC 9460) + buffer += convDom2Bin(new_domain_name) ## TargetName + # log and send + log("SVCB %s" % (new_domain_name)) + send_buf(self, buffer) + ##################################################################### +''' diff --git a/modules/svcbloop.toml b/modules/svcbloop.toml new file mode 100644 index 0000000..512cfe4 --- /dev/null +++ b/modules/svcbloop.toml @@ -0,0 +1,52 @@ +[module] +name = "svcbloop" +type = "feature" +info = "SVCB alias loop" +desc = "Respond with an SVCB record featuring an alias (SvcPriority 0) that creates an infinite loop with any number of elements." +author = "ivan.jedek@oryxlabs.com" +category = "General features" + +code = ''' +if req.first_subdomain.startswith("svcbloop"): + # Do an alias loop in a SVCB record (SvcPriority 0) + if req.subdomains[1].isnumeric() and req.subdomains[2].isnumeric(): + # we are already in a loop, e.g.: + # svcbloop.10.4.dnslabtest.com + max = int(req.subdomains[1]) + cur = int(req.subdomains[2]) + if cur >= max: + # go back to the beginning of the loop + new_domain_name = req.subdomains[0] + "." + str(max) + ".1" + else: + # increment the current index + new_domain_name = req.subdomains[0] + "." + str(max) + "." + str(cur+1) + for i in range(len(req.subdomains)-3): + new_domain_name += "." + req.subdomains[i+3] + elif req.subdomains[1].isnumeric(): + # we are in beginning of a loop with a requested max value, e.g.: + # svcbloop.10.dnslabtest.com + max = int(req.subdomains[1]) + if max < 1: + max = 1 + new_domain_name = req.subdomains[0] + "." + str(max) + "." + "1" + for i in range(len(req.subdomains)-2): + new_domain_name += "." + req.subdomains[i+2] + else: + # just immediate loop + new_domain_name = req.full_domain + ### DNS header ######## + buffer = prep_dns_header(b'\x84\x00', req.QURR, 1, 0, 0) + ### QUESTION SECTION ######## + if resp.noq: buffer += convDom2Bin(req.full_domain) + req.type_bin + req.class_bin + ### ANSWER SECTION ######## + data_len = 2+len(convDom2Bin(new_domain_name)) # SvcPriority (2 bytes) + the target name + buffer += convDom2Bin(req.full_domain) + getTypeBin("SVCB") + getClassBin("IN") + buffer += struct.pack(">L", resp.TTL) ## TTL + buffer += struct.pack(">H", data_len) ## Data length + buffer += struct.pack(">H", 0) ## SvcPriority (0 means alias mode - RFC 9460) + buffer += convDom2Bin(new_domain_name) ## TargetName + # log and send + log("SVCB LOOP %s" % (new_domain_name)) + send_buf(self, buffer) + ##################################################################### +''' diff --git a/polardns.py b/polardns.py index cc0ea4c..d6464ef 100644 --- a/polardns.py +++ b/polardns.py @@ -1,6 +1,5 @@ -# required minimal Python version 3.11 import sys -MIN_VERSION = (3, 11) +MIN_VERSION = (3, 11) # required minimal Python version if sys.version_info < MIN_VERSION: sys.exit(f"Python version {'.'.join(map(str, MIN_VERSION))} or later is required.") @@ -16,9 +15,10 @@ import time import os +polardns_version = "1.2" + ################################ -version = "1.1" # PolarDNS stamp = str(time.time()).ljust(18, "0") # load config @@ -675,13 +675,13 @@ def process_DNS(self, req): # Check if any domain label starts with any of the global modifiers # Is there custom sleep (".slpXXXX.") or custom TTL (".ttlXXX.") or custom length (".lenXXX.") in the domain name? for label in req.subdomains_lc: - if label.startswith('slp'): # custom delay requested + if label.startswith("slp"): # custom delay requested if label[3:].isnumeric(): resp.sleep = float(int(label[3:])/1000) - elif label.startswith('ttl'): # custom TTL requested + elif label.startswith("ttl"): # custom TTL requested if label[3:].isnumeric(): resp.TTL = int(label[3:]) - elif label.startswith('len'): # TCP length override + elif label.startswith("len"): # TCP length override if label[3:].isnumeric(): n = int(label[3:]) if n > 65535: n = 65535 @@ -689,7 +689,7 @@ def process_DNS(self, req): elif label == "newid": # new random transaction ID resp.ID = struct.pack(">H", random.randint(0,65535)) addcustomlog("NEWID") - elif label.startswith('flgs'): # set custom flags in the DNS header + elif label.startswith("flgs"): # set custom flags in the DNS header if label[4:].isnumeric(): n = int(label[4:]) if n > 65535: n = 65535 @@ -704,25 +704,25 @@ def process_DNS(self, req): n = random.randint(0,65535) resp.FLGS = struct.pack(">H", n) addcustomlog("FLGS:" + hex(n)) - elif label.startswith('qurr'): # set custom number of questions in the DNS header + elif label.startswith("qurr"): # set custom number of questions in the DNS header if label[4:].isnumeric(): n = int(label[4:]) if n > 65535: n = 65535 resp.QURR = n addcustomlog("QURR:" + str(resp.QURR)) - elif label.startswith('anrr'): # set custom number of answer RR in the DNS header + elif label.startswith("anrr"): # set custom number of answer RR in the DNS header if label[4:].isnumeric(): n = int(label[4:]) if n > 65535: n = 65535 resp.ANRR = n addcustomlog("ANRR:" + str(resp.ANRR)) - elif label.startswith('aurr'): # set custom number of authority RR in the DNS header + elif label.startswith("aurr"): # set custom number of authority RR in the DNS header if label[4:].isnumeric(): n = int(label[4:]) if n > 65535: n = 65535 resp.AURR = n addcustomlog("AURR:" + str(resp.AURR)) - elif label.startswith('adrr'): # set custom number of additional RR in the DNS header + elif label.startswith("adrr"): # set custom number of additional RR in the DNS header if label[4:].isnumeric(): n = int(label[4:]) if n > 65535: n = 65535 @@ -841,7 +841,7 @@ def process_DNS(self, req): ##################################################################### elif req.full_domain == "version.polar" and req.type_str == "TXT" and req.class_str == "CH": # Version - v = "PolarDNS " + version + v = "PolarDNS " + polardns_version ### DNS header ######## buffer = prep_dns_header(b'\x84\x00', req.QURR, 1, 0, 0) ### QUESTION SECTION ######## @@ -908,7 +908,7 @@ def add_modules_and_rerun(): add_modules_and_rerun() exit(0) - print("%s | PolarDNS v%s server starting up" % (stamp, version)) + print("%s | PolarDNS v%s server starting up" % (stamp, polardns_version)) ip, sep, port = config['listen_addr'].rpartition(':') assert sep ip = str(ip) diff --git a/test/test.sh b/test/test.sh index 0fdff50..2d584b3 100755 --- a/test/test.sh +++ b/test/test.sh @@ -120,6 +120,19 @@ fi #runtest "always123.anrr0.tc.${domain}" "911aebd69bf9ecd9e7b844b99869f9e6" #exit 0 +runtest "CNAME loop.${domain}" "54211ccf692445b41b84a289cd0afbd1" +runtest "loop.${domain}" "2cc3f7970c8aa4b40497bf360182070c" +runtest "loop.5.4.${domain}" "717aaaa779c6e9875da5b3c32d085e45" +runtest "DNAME dloop.${domain}" "66049f9e66cdff51b2a03552bdbd49ff" +runtest "dloop.${domain}" "0e275c7e4f069840b6b3a09ffc91e95c" +runtest "dloop.5.4.${domain}" "00ccb91bc785bb54584c71078038b166" +runtest "HTTPS httpsloop.${domain}" "179507fe9c06817d8bf27064646091d4" +runtest "httpsloop.${domain}" "795f3bfdd8220a49a08a420f7f1fe268" +runtest "httpsloop.5.4.${domain}" "228757b20ae135d86846133269cbe82a" +runtest "SVCB svcbloop.${domain}" "cd31a2bb52280c0516a8b3d043030c07" +runtest "svcbloop.${domain}" "c725708a02582ec3437b523370f49a59" +runtest "svcbloop.5.4.${domain}" "affdfba0ca433c15f63fbcc179ef4781" + runtest "queryback2.${domain}" "8e7424518528278533bc12ecc7cbabff" runtest "cgena.1.${domain}" "8bee2a94ebe12cee620a8bfa18169b1d" runtest "cgena.2.${domain}" "dc2bd1660fcad388dfabfca711546326" @@ -338,6 +351,8 @@ runtest "${domain} MX" "508918c4fb499d55abe8d6f9e3b73900" runtest "always123.${domain}" "38da4075b11e62b0473782ea144459be" runtest "chain123.${domain}" "af14c76738cb2fea1cab8e07e01df018" runtest "dchain123.${domain}" "188b343589d514d0400462214c95b0c8" +runtest "svcbchain123.${domain}" "0f9bc2fa36756dd02eba06d42306fc62" +runtest "httpschain123.${domain}" "e36bf04d94df6567cd2dfb9bfa3bfb21" runtest "inj01.${domain}" "0c2071983b97bcc2d837a31b80116f83" runtest "inj02.${domain}" "619439d41f0ea32f2da0c9389dacc576" runtest "inj03.${domain}" "c64099d8fe54b82d8cc9a81a41eac048"