diff --git a/.gitignore b/.gitignore index 7317422..87c0221 100644 --- a/.gitignore +++ b/.gitignore @@ -18,3 +18,4 @@ src/filter/trackers test/src/fdnstress/fdnstress test/src/ptest/ptest gcov-dir +src/cashpack-0.3 diff --git a/Makefile.in b/Makefile.in index 2754c7b..b444566 100644 --- a/Makefile.in +++ b/Makefile.in @@ -1,5 +1,6 @@ all: apps man APPS = src/fdns test/src/fdnstress +LIBS = src/cashpack-0.3 MANPAGES = fdns.1 prefix=@prefix@ @@ -15,9 +16,14 @@ NAME=@PACKAGE_NAME@ PACKAGE_TARNAME=@PACKAGE_TARNAME@ DOCDIR=@docdir@ +.PHONY: libs $(LIBS) +libs: $(LIBS) +$(LIBS): + $(MAKE) -C $@ + .PHONY: apps $(APPS) apps: $(APPS) -$(APPS): +$(APPS): $(LIBS) $(MAKE) -C $@ $(MANPAGES): $(wildcard src/man/*.txt) @@ -30,6 +36,9 @@ clean: for dir in $(APPS); do \ $(MAKE) -C $$dir clean; \ done + for dir in $(LIBS); do \ + $(MAKE) -C $$dir clean; \ + done rm -f $(MANPAGES) $(MANPAGES:%=%.gz) cd test/compile; ./compile.sh --clean; cd ../.. @@ -38,6 +47,7 @@ distclean: clean $(MAKE) -C $$dir distclean; \ done rm -fr Makefile autom4te.cache config.log config.status config.h dummy.o src/common.mk + rm -fr src/cashpack-0.3 realinstall: # fdns executable diff --git a/README b/README index 18a2451..cc8b5c1 100644 --- a/README +++ b/README @@ -54,6 +54,9 @@ Daniel Schildt (https://github.com/d2s) rcplab (https://github.com/rcplab) - containerpi and twnic +Incoporating cashpack library, https://github.com/Dridi/cashpack +Copyright ©2016-2017 Dridi Boukelmoune, license MIT + Incorporating code from Firejail Security Sandbox, https://github.com/netblue30/firejail Copyright © 2014-2020 Firejail Authors, license GPLv2 diff --git a/README.md b/README.md index ab9653f..757784d 100644 --- a/README.md +++ b/README.md @@ -43,7 +43,12 @@ FDNS is a community project. We are not affiliated with any company, and we don ## Project Status -Release `0.9.62.6` is out. +Development version 0.9.63: +
 
diff --git a/configure b/configure index 34b7f68..0c1df78 100755 --- a/configure +++ b/configure @@ -3952,6 +3952,8 @@ if test "$prefix" = /usr; then test "$sysconfdir" = '${prefix}/etc' && sysconfdir="/etc" fi +cd src && tar -xzvf cashpack-0.3.tar.gz && cd cashpack-0.3 && ./configure && cd ../.. + ac_config_files="$ac_config_files Makefile src/common.mk src/fdns/Makefile test/src/fdnstress/Makefile" cat >confcache <<\_ACEOF diff --git a/configure.ac b/configure.ac index 63e1628..c302886 100644 --- a/configure.ac +++ b/configure.ac @@ -77,6 +77,8 @@ if test "$prefix" = /usr; then test "$sysconfdir" = '${prefix}/etc' && sysconfdir="/etc" fi +cd src && tar -xzvf cashpack-0.3.tar.gz && cd cashpack-0.3 && ./configure && cd ../.. + AC_OUTPUT(Makefile src/common.mk src/fdns/Makefile test/src/fdnstress/Makefile) echo diff --git a/etc/servers b/etc/servers index c50124d..352032b 100644 --- a/etc/servers +++ b/etc/servers @@ -38,7 +38,7 @@ zone: Europe tags: UK, Europe address: 217.169.20.23:443 host: dns.aa.net.uk/dns-query -keepalive: 7 +keepalive: 30 name: a-and-a2 website: https://www.aa.net.uk/legal/dohdot-disclaimer/ @@ -46,7 +46,7 @@ zone: Europe tags: UK, Europe address: 217.169.20.22:443 host: dns.aa.net.uk/dns-query -keepalive: 7 +keepalive: 30 # server down #name: aaflalo @@ -98,13 +98,15 @@ address: 176.103.130.134:443 host: dns-family.adguard.com/dns-query keepalive: 30 -name: alekberg -website: https://github.com/DNSCrypt/dnscrypt-resolvers/blob/master/v2/public-resolvers.md -zone: Europe -tags: Netherlands, Europe -address: 51.15.124.208:443 -host: dns.alekberg.net/ -keepalive: 7 +# server down +#name: alekberg +#website: https://github.com/DNSCrypt/dnscrypt-resolvers/blob/master/v2/public-resolvers.md +#zone: Europe +#tags: Netherlands, Europe +#address: 51.15.124.208:443 +#host: dns.alekberg.net/ +#sni: yes +#keepalive: 7 # server down #name: alekberg-adblocker @@ -149,44 +151,46 @@ tags: non-profit, Austria, Europe address: 93.177.65.183:443 host: doh.appliedprivacy.net/query sni: yes -keepalive: 7 +keepalive: 20 -# very short keepalive, will work fine on Firefox! -#name: blahdns-fi -#website: https://blahdns.com -#zone: Europe -#tags: Finland, Europe, adblocker -#address: 95.216.212.177:443 -#host: doh-fi.blahdns.com/dns-query -#sni: yes -#keepalive: 7 -# -#name: blahdns-de -#website: https://blahdns.com -#zone: Europe -#tags: Germany, Europe, adblocker -#address: 159.69.198.101:443 -#host: doh-de.blahdns.com/dns-query -#sni: yes -#keepalive: 4 -# -#name: blahdns-jp -#website: https://blahdns.com -#zone: Asia-Pacific -#tags: Japan, Asia-Pacific, adblocker -#address: 45.32.55.94:443 -#host: doh-jp.blahdns.com/dns-query -#sni: yes -#keepalive: 4 -# -#name: blahdns-sg -#website: https://blahdns.com -#zone: Asia-Pacific -#tags: Singapore, Asia-Pacific, adblocker -#address: 139.180.141.57:443 -#host: doh-sg.blahdns.com/dns-query -#sni: yes -#keepalive: 4 +name: blahdns-fi +website: https://blahdns.com +zone: Europe +tags: Finland, Europe, adblocker +address: 95.216.212.177:443 +host: doh-fi.blahdns.com/dns-query +sni: yes +keepalive: 30 + +# very short timeout; works fine on Firefox +##name: blahdns-de +##website: https://blahdns.com +##zone: Europe +##tags: Germany, Europe, adblocker +##address: 159.69.198.101:443 +##host: doh-de.blahdns.com/dns-query +##sni: yes +##keepalive: 4 + +# very short timeout; works fine on Firefox +##name: blahdns-jp +##website: https://blahdns.com +##zone: Asia-Pacific +##tags: Japan, Asia-Pacific, adblocker +##address: 45.32.55.94:443 +##host: doh-jp.blahdns.com/dns-query +##sni: yes +##keepalive: 4 + +# very short timeout; works fine on Firefox +##name: blahdns-sg +##website: https://blahdns.com +##zone: Asia-Pacific +##tags: Singapore, Asia-Pacific, adblocker +##address: 139.180.141.57:443 +##host: doh-sg.blahdns.com/dns-query +##sni: yes +##keepalive: 4 name: bortzmeyer website: https://www.bortzmeyer.org/doh-bortzmeyer-fr-policy.html @@ -194,7 +198,7 @@ zone: Europe tags: France, Europe address: 193.70.85.11:443 host: doh.bortzmeyer.fr/ -keepalive: 7 +keepalive: 20 name: brahmaworld website: https://dns.brahma.world/ @@ -274,7 +278,7 @@ zone: Americas-East, Americas-West, Asia-Pacific, Europe tags: anycast, Americas, Americas-East, Americas-West, Asia-Pacific, Europe address: 1.1.1.1:443 host: cloudflare-dns.com/dns-query -keepalive: 180 +keepalive: 60 name: cloudflare2 website: https://www.cloudflare.com @@ -282,7 +286,7 @@ zone: Americas-East, Americas-West, Asia-Pacific, Europe tags: anycast, Americas, Americas-East, Americas-West, Asia-Pacific, Europe address: 1.0.0.1:443 host: cloudflare-dns.com/dns-query -keepalive: 180 +keepalive: 60 name: cloudflare-security website: https://www.cloudflare.com @@ -291,7 +295,7 @@ tags: anycast, security, Americas, Americas-East, Americas-West, Asia-Pacific, E address: 1.1.1.2:443 host: security.cloudflare-dns.com/dns-query sni: yes -keepalive: 180 +keepalive: 60 name: cloudflare-security2 website: https://www.cloudflare.com @@ -300,26 +304,34 @@ tags: anycast, security, Americas, Americas-East, Americas-West, Asia-Pacific, E address: 1.0.0.2:443 host: security.cloudflare-dns.com/dns-query sni: yes -keepalive: 180 +keepalive: 60 name: cloudflare-family website: https://www.cloudflare.com zone: Americas-East, Americas-West, Asia-Pacific, Europe -tags: family, Americas, Americas-East, Americas-West, Asia-Pacific, Europe +tags: family address: 1.0.0.3:443 host: family.cloudflare-dns.com/dns-query sni: yes -keepalive: 180 +keepalive: 60 name: cloudflare-family2 website: https://www.cloudflare.com zone: Americas-East, Americas-West, Asia-Pacific, Europe -tags: family, Americas, Americas-East, Americas-West, Asia-Pacific, Europe +tags: family address: 1.1.1.3:443 host: family.cloudflare-dns.com/dns-query sni: yes -keepalive: 180 +keepalive: 60 +name: commons-host +website: https://dev.to/commonshost/how-we-built-a-doh-cdn-with-20-global-edge-servers-in-10-days-1man +zone: Americas-East, Americas-West, Asia-Pacific, Europe +tags: geocast, Americas, Americas-East, Americas-West, Asia-Pacific, Europe +address: commons.host:443 +host: commons.host/ +sni: yes +keepalive: 30 # uncensored server redirected to cloudflare name: containerpi @@ -330,21 +342,23 @@ address: 45.77.180.10:443 host: dns.containerpi.com/dns-query keepalive: 30 -name: cznic -website: https://www.nic.cz/odvr/ -zone: Europe -tags: Czechia, Europe -address: 185.43.135.1:443 -host: odvr.nic.cz/doh -keepalive: 7 - -name: cznic2 -website: https://www.nic.cz/odvr/ -zone: Europe -tags: Czechia, Europe -address: 193.17.47.1:443 -host: odvr.nic.cz/doh -keepalive: 7 +# works fine on Firefox +##name: cznic +##website: https://www.nic.cz/odvr/ +##zone: Europe +##tags: Czechia, Europe +##address: 185.43.135.1:443 +##host: odvr.nic.cz/doh +##keepalive: 7 + +# works fine on Firefox +##name: cznic2 +##website: https://www.nic.cz/odvr/ +##zone: Europe +##tags: Czechia, Europe +##address: 193.17.47.1:443 +##host: odvr.nic.cz/doh +##keepalive: 7 name: defaultroutes website: https://doh.defaultroutes.de/ @@ -371,6 +385,24 @@ address: 185.95.218.43:443 host: dns.digitale-gesellschaft.ch/dns-query keepalive: 30 +name: dns-sb +website: https://dns.sb/ +zone: Europe +tags: Estonia, Germany, Europe +address: 185.222.222.222:443 +host: doh.dns.sb/dns-query +sni: yes +keepalive: 30 + +name: dns-sb2 +website: https://dns.sb/ +zone: Europe +tags: Estonia, Germany, Europe +address: 185.184.222.222:443 +host: doh.dns.sb/dns-query +sni: yes +keepalive: 30 + name: dnscrypt-ca website: https://dnscrypt.ca/ zone: Americas-East @@ -387,14 +419,29 @@ address: 149.56.228.45:453 host: dns2.dnscrypt.ca:453/dns-query keepalive: 30 -# very short keepalive, works fine on Firefox -#name: dnsforge -#website: https://dnsforge.de -#zone: Europe -#tags: Germany, Europe, adblocker -#address: 176.9.93.198:443 -#host: dnsforge.de/dns-query -#keepalive: 2 +name: dnsforge +website: https://dnsforge.de +zone: Europe +tags: Germany, Europe, adblocker +address: 176.9.93.198:443 +host: dnsforge.de/dns-query +keepalive: 30 + +name: dnshome +website: https://www.dnshome.de/doh-dot-public-resolver.php +zone: Europe +tags: Germany, Europe +address: 185.233.106.232:443 +host: dns.dnshome.de/dns-query +keepalive: 20 + +name: dnshome2 +website: https://www.dnshome.de/doh-dot-public-resolver.php +zone: Europe +tags: Germany, Europe +address: 185.233.107.4:443 +host: dns.dnshome.de/dns-query +keepalive: 20 name: dnslify website: https://www.dnslify.com/ @@ -402,7 +449,7 @@ zone: Asia-Pacific, Americas-East, Americas-West, Asia-Pacific, Europe tags: Americas, Americas-East, Americas-West, Asia-Pacific, Europe, Netherlands, Germany, UK, NY, Califronia, Singapore address: 185.235.81.1:443 host: doh.dnslify.com/dns-query -keepalive: 7 +keepalive: 20 name: faelix website: https://faelix.net/ref/dns/#privacy-dns @@ -410,7 +457,7 @@ zone: Europe tags: UK, Europe, adblocker address: 46.227.200.52:443 host: pdns.faelix.net/ -keepalive: 7 +keepalive: 20 name: faelix2 website: https://faelix.net/ref/dns/#privacy-dns @@ -418,7 +465,7 @@ zone: Europe tags: Switzerland, Europe, adblocker address: 185.134.196.52:443 host: pdns.faelix.net/ -keepalive: 7 +keepalive: 20 name: faelix3 website: https://faelix.net/ref/dns @@ -426,7 +473,7 @@ zone: Europe tags: UK, Europe address: 46.227.200.54:443 host: rdns.faelix.net/ -keepalive: 7 +keepalive: 20 name: ffmuc website: https://ffmuc.net/ @@ -466,7 +513,7 @@ zone: Europe tags: Switzerland, Europe, OpenNIC address: 85.5.93.230:443 host: ibksturm.synology.me/dns-query -keepalive: 7 +keepalive: 30 name: ibuki website: https://ibuki.cgnat.net/ @@ -474,7 +521,7 @@ zone: Americas-West tags: California, Americas, Americas-West address: 35.198.2.76:443 host: ibuki.cgnat.net/dns-query -keepalive: 7 +keepalive: 20 name: jcdns website: https://jcdns.fun/ @@ -516,14 +563,14 @@ address: 116.203.115.192:443 host: doh.libredns.gr/ads keepalive: 30 -# very short keepalive; works fine in Firefox -#name: meganerd -#website: https://meganerd.nl/encrypted-dns-server -#zone: Europe -#tags: Netherlands, Europe -#address: 209.250.241.25:443 -#host: jarjar.meganerd.nl/doh -#keepalive: 3 +# very short timeout; works fine on Firefox +##name: meganerd +##website: https://meganerd.nl/encrypted-dns-server +##zone: Europe +##tags: Netherlands, Europe +##address: 209.250.241.25:443 +##host: jarjar.meganerd.nl/doh +##keepalive: 3 name: nixnet website: https://nixnet.services/dns/ @@ -588,7 +635,7 @@ zone: Europe tags: Netherlands, Europe address: 136.144.215.158:443 host: doh.powerdns.org/dns-query -keepalive: 7 +keepalive: 20 name: quad9 website: https://quad9.net @@ -637,7 +684,7 @@ zone: Asia-Pacific tags: Australia, Asia-Pacific, OpenNIC address: 45.76.113.31:8443 host: dns.seby.io/dns-query -keepalive: 7 +keepalive: 20 name: seby2 website: https://dns.seby.io @@ -645,7 +692,7 @@ zone: Asia-Pacific tags: Australia, Asia-Pacific, OpenNIC address: 139.99.222.72:443 host: doh-2.seby.io/dns-query -keepalive: 7 +keepalive: 20 name: snopyta website: https://snopyta.org @@ -662,7 +709,7 @@ zone: Europe tags: non-profit, Switzerland, Europe address: 130.59.31.248:443 host: dns.switch.ch/dns-query -keepalive: 7 +keepalive: 20 name: switch2 website: https://www.switch.ch/security/info/public-dns/ @@ -670,7 +717,7 @@ zone: Europe tags: non-profit, Switzerland, Europe address: 130.59.31.251:443 host: dns.switch.ch/dns-query -keepalive: 7 +keepalive: 20 # tiarapp servers: https://github.com/pengelana name: tiarapp-sg diff --git a/src/cashpack-0.3.tar.gz b/src/cashpack-0.3.tar.gz new file mode 100644 index 0000000..f6776aa Binary files /dev/null and b/src/cashpack-0.3.tar.gz differ diff --git a/src/fdns/Makefile.in b/src/fdns/Makefile.in index f75f749..917e68f 100644 --- a/src/fdns/Makefile.in +++ b/src/fdns/Makefile.in @@ -5,8 +5,8 @@ include ../common.mk %.o : %.c $(H_FILE_LIST) $(CC) $(CFLAGS) $(EXTRA_CFLAGS) $(INCLUDE) -c $< -o $@ -fdns: $(OBJS) - $(CC) $(LDFLAGS) -o $@ $(OBJS) -lssl -lcrypto -lrt -lseccomp $(LIBS) $(EXTRA_LDFLAGS) +fdns: $(OBJS) ../cashpack-0.3/lib/.libs/libhpack.a + $(CC) $(LDFLAGS) -o $@ $(OBJS) ../cashpack-0.3/lib/.libs/libhpack.a -lssl -lcrypto -lrt -lseccomp $(LIBS) $(EXTRA_LDFLAGS) clean:; rm -f *.o fdns *.gcov *.gcda *.gcno diff --git a/src/fdns/dns.c b/src/fdns/dns.c index 7fdeb60..f3c36bb 100644 --- a/src/fdns/dns.c +++ b/src/fdns/dns.c @@ -205,14 +205,60 @@ uint8_t *dns_parser(uint8_t *buf, ssize_t *lenptr, DnsDestination *dest) { } void dns_keepalive(void) { + if (ssl_state == SSL_CLOSED) + return; + if (arg_debug) printf("(%d) send keepalive\n", arg_id); - uint8_t msg[33] = { // www.example.com - 0x00, 0x00, 0x01, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x03, 0x77, 0x77, 0x77, - 0x07, 0x65, 0x78, 0x61, 0x6d, 0x70, 0x6c, 0x65, 0x03, 0x63, 0x6f, 0x6d, 0x00, 0x00, 0x01, 0x00, - 0x01 - }; - uint8_t buf[MAXBUF]; - memcpy(buf, msg, sizeof(msg)); - ssl_rxtx_dns(buf, sizeof(msg)); + h2_send_ping(); } + +// returns the length of the response,,0 if failed +// the response is copied back in msg +int dns_query(uint8_t *msg, int cnt) { + assert(msg); + assert(cnt); + + + int datalen = h2_send_query(msg, cnt); + if (datalen == 0) + goto errout; + + if (arg_debug) { + printf("(%d) DNS data:\n", arg_id); + print_mem(msg, datalen); + printf("(%d) *** SSL transaction end ***\n", arg_id); + } + + // + // partial response parsing + // + if (lint_rx(msg, datalen)) { + if (lint_error() == DNSERR_NXDOMAIN) { + // NXDOMAIN or similar received, cache for 10 minutes + cache_set_reply(msg, datalen, CACHE_TTL_ERROR); + return datalen; + } + + // serveral adblocker/family services return addresses of 0.0.0.0 or 127.0.0.1 for blocked domains + const char *str = lint_err2str(); + if (strstr(str, "0.0.0.0") || strstr(str, "127.0.0.1")) { + // set NXDOMAIN bytes in the packet + msg[3] = 3; + rlogprintf("%s refused by service provider\n", cache_get_name()); + return datalen; + } + else + rlogprintf("Error: %s %s\n", lint_err2str(), cache_get_name()); + return 0; + } + + // cache the response and exit + cache_set_reply(msg, datalen, arg_cache_ttl); + return datalen; + +errout: + ssl_close(); + return 0; +} + diff --git a/src/fdns/fdns.h b/src/fdns/fdns.h index c8db469..9068648 100644 --- a/src/fdns/fdns.h +++ b/src/fdns/fdns.h @@ -72,10 +72,9 @@ static inline int check_addr_port(const char *str) { #define RESOLVER_KEEPALIVE_AFTER_SLEEP (RESOLVER_KEEPALIVE_TIMER * 1.2) // after sleep detection #define MONITOR_WAIT_TIMER 2 // wait for this number of seconds before restarting a failed resolver process #define CONSOLE_PRINTOUT_TIMER 5 // transfer stats from resolver to frontend -#define SSL_REOPEN_TIMER 5 // try to reopen a failed SSL connection after this time -#define OUT_OF_SLEEP (RESOLVER_KEEPALIVE_SHUTDOWN + 5) - // detect computer going out of sleep/hibernation, reinitialize SSL connections - // it should be greater than RESOLVER_KEEPALIVE_SHUTDOWN +#define SSL_REOPEN_TIMER 2 // try to reopen a failed SSL connection after this time +#define OUT_OF_SLEEP 10 // attempting to detect the computer coming out of sleep mode + #define CACHE_TTL_DEFAULT (40 * 60) // default DNS cache ttl in seconds #define CACHE_TTL_MIN (1 * 60) #define CACHE_TTL_MAX (60 * 60) @@ -131,7 +130,8 @@ typedef struct dnsserver_t { char *zone; // geographical zone char *tags; // description char *address; // IP address - char *host; // POST request first line + char *host; // authority in http2 + char *path; char *request; // full POST request int sni; // 1 or 0 int keepalive; // keepalive in seconds @@ -180,6 +180,7 @@ static inline void print_mem(unsigned char *msg, int len) { // main.c extern int arg_argc; extern int arg_debug; +extern int arg_debug_h2; extern int arg_resolvers; extern int arg_id; extern int arg_fd; @@ -195,7 +196,7 @@ extern char *arg_forwarder; extern int arg_test_hosts; extern char *arg_zone; extern int arg_cache_ttl; -extern int arg_allow_local_doh; +extern int arg_disable_local_doh; extern char *arg_whitelist_file; extern int arg_fallback_only; extern Stats stats; @@ -216,8 +217,10 @@ extern SSLState ssl_state; void ssl_init(void); void ssl_open(void); void ssl_close(void); -int ssl_rxtx_dns(uint8_t *msg, int cnt); int ssl_status_check(void); +int ssl_rx(uint8_t *buf); +int ssl_tx(uint8_t *buf, int len); +int ssl_get_socket(void); // frontend.c extern int encrypted[RESOLVERS_CNT_MAX]; @@ -239,6 +242,7 @@ typedef enum { } DnsDestination; uint8_t *dns_parser(uint8_t *buf, ssize_t *len, DnsDestination *dest); void dns_keepalive(void); +int dns_query(uint8_t *msg, int cnt); // filter.c void filter_init(void); @@ -336,4 +340,15 @@ int whitelist_blocked(const char *domain); void procs_add(void); void procs_list(void); +// h2.c +void h2_init(void); +void h2_close(void); +void h2_connect(void); +void h2_send_exampledotcom(void); +int h2_send_query(uint8_t *req, int cnt); +void h2_send_ping(void); +int h2_exchange(uint8_t *response); + + + #endif diff --git a/src/fdns/filter.c b/src/fdns/filter.c index 28292d8..b1f8ffb 100644 --- a/src/fdns/filter.c +++ b/src/fdns/filter.c @@ -127,7 +127,7 @@ static DFilter default_filter[] = { // hardcoded DoH servers // this is the last section before the NULL entry - // the NULL entry is moved up for --allow-local-doh + // the NULL entry is moved up if --disable-local-doh is not present {'D', "$dnscrypt-cert.oszx.co", 0}, {'D', "$cloudflare-dns.com", 0}, {'D', "$anycast.censurfridns.dk", 0}, @@ -161,8 +161,8 @@ void filter_init(void) { void filter_postinit(void) { int i = 0; - // --allow-local-doh: move the NULL entry up - if (arg_allow_local_doh) { + // move the NULL entry up + if (!arg_disable_local_doh) { while (default_filter[i].label != 'D' && default_filter[i].label != 0) i++; assert(default_filter[i].label == 'D'); @@ -295,7 +295,7 @@ void filter_load_all_lists(void) { filter_load_list('F', PATH_ETC_FP_TRACKERS_LIST); filter_load_list('A', PATH_ETC_ADBLOCKER_LIST); filter_load_list('M', PATH_ETC_COINBLOCKER_LIST); - if (!arg_allow_local_doh) + if (arg_disable_local_doh) filter_load_list('D', PATH_ETC_DOH_LIST); filter_load_list('H', PATH_ETC_HOSTS_LIST); } diff --git a/src/fdns/frontend.c b/src/fdns/frontend.c index aa4aa9b..ac2f62f 100644 --- a/src/fdns/frontend.c +++ b/src/fdns/frontend.c @@ -95,6 +95,8 @@ static int sandbox(void *sandbox_arg) { int last = 3; if (arg_debug) a[last++] = "--debug"; + if (arg_debug_h2) + a[last++] = "--debug-h2"; if (arg_nofilter) a[last++] = "--nofilter"; if (arg_ipv6) @@ -123,8 +125,8 @@ static int sandbox(void *sandbox_arg) { } if (arg_allow_all_queries) a[last++] = "--allow-all-queries"; - if (arg_allow_local_doh) - a[last++] = "--allow-local-doh"; + if (arg_disable_local_doh) + a[last++] = "--disable-local-doh"; if (arg_cache_ttl != CACHE_TTL_DEFAULT) { char *cmd; diff --git a/src/fdns/h2.c b/src/fdns/h2.c new file mode 100644 index 0000000..81e2540 --- /dev/null +++ b/src/fdns/h2.c @@ -0,0 +1,427 @@ +// gcc -lhpack -o cashbld cashbld.c +#include +#include +#include +#include +#include +#include + +#include "../cashpack-0.3/inc/hpack.h" +#include "h2frame.h" +#include "fdns.h" + +static uint32_t stream_id; + +#define MAX_HEADER_FIELDS 64 +#define HEADER(name, value) fields[pos++] = (struct hpack_field){ \ + .nam = (name), \ + .val = (value), \ + .flg = HPACK_FLG_TYP_LIT | HPACK_FLG_NAM_HUF | HPACK_FLG_VAL_HUF \ + } + + +struct tmp_buf { + char *data; + size_t offset; +}; + +static void header_encode_cb(enum hpack_event_e evt, const char *buf, size_t len, void *priv) { + struct tmp_buf *out = priv; + switch (evt) { + case HPACK_EVT_DATA: + memcpy(out->data + out->offset, buf, len); + out->offset += len; + break; + default: + break; + } +} + +static void print_headers(enum hpack_event_e evt, const char *buf, size_t len, void *priv) { + (void)priv; + if (!arg_debug) + return; + + /* print "\n${name}: ${value}" for each header field */ + + switch (evt) { + case HPACK_EVT_FIELD: + printf("\n"); + break; + case HPACK_EVT_VALUE: + printf(": "); + /* fall through */ + case HPACK_EVT_NAME: + printf("%s", buf); + (void)len; + /* fall through */ + default: + /* ignore other events */ + break; + } +} + + +struct hpack *hpe = NULL; +struct hpack *hpd = NULL; +void h2_init(void) { + hpe = hpack_encoder(4096, -1, hpack_default_alloc); + hpd = hpack_decoder(4096, -1, hpack_default_alloc); +} + +void h2_close(void) { + if (hpe != NULL) { + hpack_free(&hpe); + hpe = NULL; + } + if (hpd != NULL) { + hpack_free(&hpd); + hpd = NULL; + } + +} + +// encode a header frame +// frame - http2 frame +// return offset for the end of frame +static uint32_t h2_encode_header(uint8_t *frame, int len) { + assert(frame); + + char slen[20]; + sprintf(slen, "%d", len); + + // extract server data + DnsServer *srv = server_get(); + assert(srv); + assert(srv->path); + assert(srv->host); + + + // hpack encode + struct hpack_field fields[MAX_HEADER_FIELDS]; + size_t pos = 0; + + HEADER(":method", "POST"); + HEADER(":path", srv->path); + HEADER(":authority", srv->host); + HEADER(":scheme", "https"); + HEADER("accept", "application/dns-message"); + HEADER("content-type", "application/dns-message"); + HEADER("content-length", slen); //"48"); + HEADER("pragma", "no-cache"); +// HEADER("te", "trailers"); + + // encoding structure + char hpack_buf[MAXBUF]; + struct tmp_buf buf = { + frame + 9, // frame header size 9 + 0, + }; + struct hpack_encoding enc; + enc.fld = fields; + enc.fld_cnt = pos; + enc.buf = hpack_buf; + enc.buf_len = MAXBUF; + enc.cb = header_encode_cb, + enc.priv = &buf, + enc.cut = 0; + + // encoding + int rv = hpack_encode(hpe, &enc); + if (rv != HPACK_RES_OK) { + fprintf(stderr, "hpack encoding error: %s\n", hpack_strerror(rv)); + exit(1); + } + + // build header + H2Frame *frm = (H2Frame *) frame; + uint32_t length = buf.offset; + h2frame_set_length(frm, length); + frm->type = H2_TYPE_HEADERS; + frm->flag = H2_FLAG_END_HEADERS;// | H2_FLAG_END_STREAM; + h2frame_set_stream(frm, stream_id); + + return sizeof(H2Frame) + length; +} + +// decode a header frame +// frame - http2 frame +// return offset for the end of frame +static uint32_t h2_decode_header(uint8_t *frame) { + // http2 frame + H2Frame frm; + memcpy(&frm, frame, sizeof(H2Frame)); + int offset = sizeof(H2Frame); + if (frm.type != H2_TYPE_HEADERS) { + fprintf(stderr, "Not a header header\n"); + return 0; + } + + uint8_t flg = frm.flag; + uint8_t pad = 0; + uint32_t str = h2frame_extract_stream(&frm); + size_t len = h2frame_extract_length(&frm); +//todo if (len > sizeof blk) +// return (EXIT_FAILURE); /* DIY */ + + uint8_t blk[4096]; + uint8_t buf[1024]; + struct hpack_decoding dec; + dec.blk = blk; + dec.blk_len = 0; + dec.buf = buf; + dec.buf_len = sizeof buf; + dec.cb = print_headers; + dec.priv = NULL; + + if (flg & H2_FLAG_PADDED) + offset += 1; + if (flg & H2_FLAG_PRIORITY) + offset += 5; + + memcpy(blk, frame + offset, len); + offset += len; + + /* decode the HPACK block */ + if (flg & H2_FLAG_END_HEADERS && arg_debug) + printf("=== stream %u", str); + + dec.cut = ~flg & H2_FLAG_END_HEADERS; + dec.blk_len = len; + + int rv = hpack_decode(hpd, &dec); + if (rv < 0) + return 0; + + if (flg & H2_FLAG_PADDED) + offset += pad; + if (arg_debug) + printf("\n"); + return offset; +} + +// encode a data frame +// frame - http2 frame +// data and length +// using the same session id as the last encoded header frame; ending the stream +// return offset for the end of the frame +static uint32_t h2_encode_data(uint8_t *frame, uint8_t *data, unsigned length) { + assert(frame); + assert(data); + assert(length); + + // build header + H2Frame *frm = (H2Frame *) frame; + h2frame_set_length(frm, length); + frm->type = H2_TYPE_DATA; + frm->flag = H2_FLAG_END_STREAM; + h2frame_set_stream(frm, stream_id); + memcpy(frame + sizeof(H2Frame), data, length); + + return length + sizeof(H2Frame); +} + +// decode a data frame +// frame - http2 frame +// offset - offset to data section in frame +// length - length of data section +// return offset for the end of the frame +uint32_t h2_decode_data(uint8_t *frame, uint32_t *offset, uint32_t *length) { + assert(frame); + assert(length); + + // decode header + // http2 frame + H2Frame frm; + memcpy(&frm, frame, sizeof(H2Frame)); + int rv = sizeof(H2Frame); + if (frm.type != H2_TYPE_DATA) { + fprintf(stderr, "Not a data frame\n"); + return 0; + } + + uint8_t flg = frm.flag; + uint8_t pad = 0; + uint32_t stream = h2frame_extract_stream(&frm); +//todo: check the current streamid + *length = h2frame_extract_length(&frm); + *offset = rv; + + if (flg & H2_FLAG_PADDED) + rv += 1; + + *offset = rv; + return rv + *length + pad; +} + + +static uint8_t buf_query[MAXBUF]; +void h2_connect(void) { + stream_id = 0; + uint8_t connect[] = { + 0x50, 0x52, 0x49, 0x20, 0x2a, 0x20, 0x48, 0x54, 0x54, 0x50, 0x2f, 0x32, 0x2e, 0x30, 0x0d, 0x0a, + 0x0d, 0x0a, 0x53, 0x4d, 0x0d, 0x0a, 0x0d, 0x0a, 0x00, 0x00, 0x12, 0x04, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x01, 0x00, 0x01, 0x00, 0x00, 0x00, 0x04, 0x00, 0x02, 0x00, 0x00, 0x00, 0x05, 0x00, + 0x00, 0x40, 0x00, 0x00, 0x00, 0x04, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xbf, 0x00, 0x01, + 0x00, 0x00, 0x05, 0x02, 0x00, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x00, 0xc8, 0x00, 0x00, + 0x05, 0x02, 0x00, 0x00, 0x00, 0x00, 0x05, 0x00, 0x00, 0x00, 0x00, 0x64, 0x00, 0x00, 0x05, 0x02, + 0x00, 0x00, 0x00, 0x00, 0x07, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x05, 0x02, 0x00, 0x00, + 0x00, 0x00, 0x09, 0x00, 0x00, 0x00, 0x07, 0x00, 0x00, 0x00, 0x05, 0x02, 0x00, 0x00, 0x00, 0x00, + 0x0b, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x05, 0x02, 0x00, 0x00, 0x00, 0x00, 0x0d, 0x00, + 0x00, 0x00, 0x00, 0xf0 + }; + + if (arg_debug) + printf("(%d) h2 send connect\n", arg_id); + ssl_tx(connect, sizeof(connect)); + h2_exchange(buf_query); + stream_id = 13; +} + +void h2_send_exampledotcom(void) { + stream_id += 2; + + uint8_t req[] = { + 0x00, 0x00, 0x01, 0x00, 0x00, 0x01, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x01, 0x07, 0x65, 0x78, 0x61, 0x6d, 0x70, 0x6c, 0x65, 0x03, 0x63, 0x6f, + 0x6d, 0x00, 0x00, 0x02, 0x00, 0x01, 0x00, 0x00, 0x29, 0x10, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x08, 0x00, 0x08, 0x00, 0x04, 0x00, 0x01, 0x00, 0x00 + }; + uint32_t len = h2_encode_header(buf_query, sizeof(req)); + if (arg_debug || arg_debug_h2) { + printf("(%d) h2 tx ", arg_id); + h2frame_print((H2Frame *) buf_query); + } + + int len2 = h2_encode_data(buf_query + len, req, sizeof(req)); + if (arg_debug || arg_debug_h2) { + printf("(%d) h2 tx query example.com ", arg_id); + h2frame_print((H2Frame *) (buf_query + len)); + } + + ssl_tx(buf_query, len + len2); + int rv = h2_exchange(buf_query); + if (arg_debug) { + printf("DNS response (%d bytes):\n", rv); + print_mem(buf_query, rv); + } +} + + + +int h2_send_query(uint8_t *req, int cnt) { + stream_id += 2; + uint32_t len = h2_encode_header(buf_query, cnt); + if (arg_debug || arg_debug_h2) { + printf("(%d) h2 tx ", arg_id); + h2frame_print((H2Frame *) buf_query); + } + + int len2 = h2_encode_data(buf_query + len, req, cnt); + if (arg_debug || arg_debug_h2) { + printf("(%d) h2 tx query ", arg_id); + h2frame_print((H2Frame *) (buf_query + len)); + } + ssl_tx(buf_query, len + len2); + + return h2_exchange(req); +} + +void h2_send_ping(void) { + uint8_t frame[] = {0, 0, 8, 6, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }; + if (arg_debug || arg_debug_h2) { + printf("(%d) h2 tx ", arg_id); + h2frame_print((H2Frame *) frame); + } + + ssl_tx(frame, sizeof(frame)); + h2_exchange(buf_query); +} + +// copy rx data in response and return the length +int h2_exchange(uint8_t *response) { + assert(response); + int retval = 0; + + uint8_t buf[MAXBUF]; + while (1) { + fd_set readfds; + FD_ZERO(&readfds); + int fd = ssl_get_socket(); + FD_SET(fd, &readfds); + struct timeval timeout; + timeout.tv_sec = 5; + timeout.tv_usec = 0; + + int rv = select(fd + 1, &readfds, NULL, NULL, &timeout); + if (rv <= 0) + return 0; + if (rv == 0) + return 0; + + if (FD_ISSET(fd, &readfds)) { + int rv = ssl_rx(buf); + if (rv == 0) { + if (ssl_state == SSL_OPEN) + ssl_close(); + return 0; + } + + if (arg_debug) { + printf("(%d) h2 rx %d bytes\n", arg_id, rv); + print_mem(buf, rv); + } + + int offset = 0; + while (offset < rv) { + H2Frame *frm = (H2Frame *) (buf + offset); + + if (arg_debug || arg_debug_h2) { + printf("(%d) h2 rx ", arg_id); + h2frame_print(frm); + } + + if (frm->type == H2_TYPE_HEADERS) { + h2_decode_header((uint8_t *) frm); + } + else if (frm->type == H2_TYPE_DATA) { + uint32_t offset; + uint32_t length; + h2_decode_data((uint8_t *) frm, &offset, &length); + if (arg_debug) + print_mem((uint8_t *) frm + offset, length); + + // copy response in buf_query_data + if (length != 0) { + memcpy(response, (uint8_t *) frm + offset, length); + retval = length; + } + } + // ping response - do nothing + else if (frm->type == H2_TYPE_PING && frm->flag & H2_FLAG_END_STREAM) + return 0; + // ping request - set end stream flag and return the packet + else if (frm->type == H2_TYPE_PING && frm->flag & H2_FLAG_END_STREAM == 0) { + frm->flag |= H2_FLAG_END_STREAM; + ssl_tx((uint8_t *) frm, rv - offset); + return 0; + } + else if (frm->type == H2_TYPE_GOAWAY) { + ssl_close(); + return 0; + } + + if (frm->flag & H2_FLAG_END_STREAM) + return retval; // disregard the rest! + offset += sizeof(H2Frame) + h2frame_extract_length(frm); + if (arg_debug) + printf("(%d) h2 rx data offset %d\n", arg_id, offset); + } + } + } + + return retval; +} + + + diff --git a/src/fdns/h2frame.h b/src/fdns/h2frame.h new file mode 100644 index 0000000..c82140d --- /dev/null +++ b/src/fdns/h2frame.h @@ -0,0 +1,94 @@ +#ifndef H2FRAME_H +#define H2FRAME_H +#include +#include + +// +// http2 header definitions +// +typedef struct h2frame_t { + uint8_t len[3]; + +#define H2_TYPE_DATA 0x00 +#define H2_TYPE_HEADERS 0x01 +#define H2_TYPE_PRIORITY 0x02 +#define H2_TYPE_RESET 0x03 +#define H2_TYPE_SETTINGS 0x04 +#define H2_TYPE_PING 0x06 +#define H2_TYPE_GOAWAY 0x07 + uint8_t type; + +#define H2_FLAG_END_STREAM 0x01 +#define H2_FLAG_END_HEADERS 0x04 +#define H2_FLAG_PADDED 0x08 +#define H2_FLAG_PRIORITY 0x20 + uint8_t flag; + + uint8_t stream[4]; +} H2Frame; + +static inline char *h2frame_type2str(uint8_t type) { + switch (type) { + case H2_TYPE_DATA: + return "DATA"; + case H2_TYPE_HEADERS: + return "HEADERS"; + case H2_TYPE_PRIORITY: + return "PRIORITY"; + case H2_TYPE_RESET: + return "RESET"; + case H2_TYPE_SETTINGS: + return "SETTINGS"; + case H2_TYPE_PING: + return "PING"; + case H2_TYPE_GOAWAY: + return "GOAWAY"; + }; + return "UNKNOWN"; +} + +static inline uint32_t h2frame_extract_stream(H2Frame *frm) { + uint32_t rv = frm->stream[0] << 24 | frm->stream[1] << 16 | frm->stream[2] << 8 | frm->stream[3]; + return rv; +} + +static inline uint32_t h2frame_extract_length(H2Frame *frm) { + uint32_t rv = frm->len[0] << 16 | frm->len[1] << 8 | frm->len[2]; + return rv; +} + +static inline void h2frame_set_stream(H2Frame *frm, uint32_t stream) { + frm->stream[3] = stream & 0xFF; + frm->stream[2] = (stream >> 8) & 0xFF; + frm->stream[1] = (stream >> 16) & 0xFF; + frm->stream[0] = (stream >> 24) & 0x7F; +} + +static inline void h2frame_set_length(H2Frame *frm, uint32_t length) { + frm->len[2] = length & 0xFF; + frm->len[1] = (length >> 8) & 0xFF; + frm->len[0] = (length >> 16) & 0xFF; +} + +static inline void h2frame_print(H2Frame *frm) { + uint32_t len = h2frame_extract_length(frm); + uint32_t stream = h2frame_extract_stream(frm); + printf("stream %u, len %u, type 0x%02u %s, flags 0x%02u (", + stream, + len, + frm->type, h2frame_type2str(frm->type), + frm->flag); + + if (frm->flag & H2_FLAG_END_STREAM) + printf("end stream,"); + if (frm->flag & H2_FLAG_END_HEADERS) + printf("end headers,"); + if (frm->flag & H2_FLAG_PADDED) + printf("padded,"); + if (frm->flag & H2_FLAG_PRIORITY) + printf("priority,"); + printf(")\n"); + fflush(0); +} + +#endif diff --git a/src/fdns/main.c b/src/fdns/main.c index 0be1939..8bc400b 100644 --- a/src/fdns/main.c +++ b/src/fdns/main.c @@ -20,6 +20,7 @@ #include int arg_argc = 0; int arg_debug = 0; +int arg_debug_h2 = 0; int arg_resolvers = RESOLVERS_CNT_DEFAULT; int arg_id = -1; int arg_fd = -1; @@ -34,7 +35,7 @@ char *arg_certfile = NULL; int arg_test_hosts = 0; char *arg_zone = NULL; int arg_cache_ttl = CACHE_TTL_DEFAULT; -int arg_allow_local_doh = 0; +int arg_disable_local_doh = 0; char *arg_whitelist_file = NULL; int arg_fallback_only = 0; @@ -49,13 +50,15 @@ static void usage(void) { printf("Options:\n"); printf(" --allow-all-queries - allow all DNS query types; by default only\n" "\tA queries are allowed.\n"); - printf(" --allow-local-doh - allow applications on local network to connect to DoH\n" - "\tservices; disabled by default.\n"); printf(" --cache-ttl=seconds - change DNS cache TTL (default %ds).\n", CACHE_TTL_DEFAULT); printf(" --certfile=filename - SSL certificate file in PEM format.\n"); printf(" --daemonize - detach from the controlling terminal and run as a Unix\n" "\tdaemon.\n"); printf(" --debug - print debug messages.\n"); + printf(" --debug-h2 - print HTTP2 debug messages.\n"); + printf(" --disable-local-doh - blacklist DoH services for applications running on\n" + "\tlocal network.\n"); + #ifdef HAVE_GCOV printf(" --fallback-only - operate strictly in fallback mode.\n"); #endif @@ -113,6 +116,8 @@ int main(int argc, char **argv) { } else if (strcmp(argv[i], "--debug") == 0) arg_debug = 1; + else if (strcmp(argv[i], "--debug-h2") == 0) + arg_debug_h2 = 1; } } @@ -136,6 +141,8 @@ int main(int argc, char **argv) { // already processed else if (strcmp(argv[i], "--debug") == 0) // already processed ; + else if (strcmp(argv[i], "--debug-h2") == 0) // already processed + ; else if (strcmp(argv[i], "--daemonize") == 0) ; else if (strncmp(argv[i], "--zone=", 7) == 0) @@ -158,8 +165,8 @@ int main(int argc, char **argv) { arg_certfile = argv[i] + 11; else if (strcmp(argv[i], "--allow-all-queries") == 0) arg_allow_all_queries = 1; - else if (strcmp(argv[i], "--allow-local-doh") == 0) { - arg_allow_local_doh = 1; + else if (strcmp(argv[i], "--disable-local-doh") == 0) { + arg_disable_local_doh = 1; filter_postinit(); } else if (strcmp(argv[i], "--nofilter") == 0) diff --git a/src/fdns/resolver.c b/src/fdns/resolver.c index ff1e51d..b5c5f77 100644 --- a/src/fdns/resolver.c +++ b/src/fdns/resolver.c @@ -38,7 +38,6 @@ void resolver(void) { // connect SSL/DNS server ssl_init(); ssl_open(); - dns_keepalive(); // start the local DNS server on 127.0.0.1 only // in order to mitigate DDoS amplification attacks @@ -122,14 +121,13 @@ void resolver(void) { // one second timeout //*********************************************** else if (rv == 0) { + // attempting to detect the computer coming out of sleep mode time_t ts = time(NULL); if (ts - timestamp > OUT_OF_SLEEP) { rlogprintf("Suspend detected, restarting SSL connection\n"); cache_init(); - if (!arg_fallback_only) { - ssl_close(); - ssl_open(); - } + // force a PING - if the connection is already down, close SSL + dns_keepalive(); } timestamp = ts; query_second = 0; @@ -157,9 +155,15 @@ void resolver(void) { } // ssl keepalive: - // if any incoming data, probably is the session going down - force a keepalive - if (ssl_status_check()) - dns_keepalive_cnt = 0; + // if any incoming data, probably is the session going down + if (ssl_status_check()) { + h2_exchange(buf); + if (ssl_state == SSL_CLOSED) { + ssl_open(); + ssl_reopen_cnt = SSL_REOPEN_TIMER; + } + } + if (--dns_keepalive_cnt <= 0) { dns_keepalive(); dns_keepalive_cnt = srv->keepalive; @@ -302,7 +306,7 @@ void resolver(void) { int ssl_len; timetrace_start(); if (ssl_state == SSL_OPEN) - ssl_len = ssl_rxtx_dns(buf, len); + ssl_len = dns_query(buf, len); // a HTTP error from SSL, with no DNS data comming back if (ssl_state == SSL_OPEN && ssl_len == 0) diff --git a/src/fdns/server.c b/src/fdns/server.c index 9c2bdd8..85c61d2 100644 --- a/src/fdns/server.c +++ b/src/fdns/server.c @@ -142,11 +142,14 @@ static DnsServer *read_one_server(FILE *fp, int *linecnt, const char *fname) { if (!s->address) errExit("strdup"); -// todo: accept a server name in parallel with IP addresses // check address:port - if (check_addr_port(s->address)) { - fprintf(stderr, "Error: file %s, line %d, invalid address:port\n", fname, *linecnt); - exit(1); + // commons.host is a geocast host + // OpenSSL will find out the IP address using regular DNS over UDP + if (strcmp(s->name, "commons-host") != 0) { + if (check_addr_port(s->address)) { + fprintf(stderr, "Error: file %s, line %d, invalid address:port\n", fname, *linecnt); + exit(1); + } } found = 1; } @@ -158,6 +161,7 @@ static DnsServer *read_one_server(FILE *fp, int *linecnt, const char *fname) { errExit("strdup"); found = 1; + // build the DNS/HTTP request char *str = strchr(s->host, '/'); if (!str) { @@ -165,9 +169,10 @@ static DnsServer *read_one_server(FILE *fp, int *linecnt, const char *fname) { fprintf(stderr, "Error: file %s, line %d, invalid host\n", fname, *linecnt); exit(1); } + s->path = strdup(str); + if (!s->path) + errExit("strdup"); *str++ = '\0'; - if (asprintf(&s->request, "POST /%s HTTP/1.1\r\nHost: %s\r\n%s", str, s->host, push_request_tail) == -1) - errExit("asprintf"); } else if (strncmp(buf, "sni: ", 5) == 0) { if (s->sni) @@ -188,15 +193,16 @@ static DnsServer *read_one_server(FILE *fp, int *linecnt, const char *fname) { fprintf(stderr, "Error: file %s, line %d, invalid keepalive\n", fname, *linecnt); exit(1); } +// s->keepalive = 25; // check server data - if (!s->name || !s->website || !s->zone || !s->tags || !s->address || !s->host || !s->request) { + if (!s->name || !s->website || !s->zone || !s->tags || !s->address || !s->host) { fprintf(stderr, "Error: file %s, line %d, one of the server fields is missing\n", fname, *linecnt); exit(1); } // add host to filter - if (!arg_allow_local_doh) + if (arg_disable_local_doh) filter_add('D', s->host); return s; @@ -276,11 +282,11 @@ int test_server(const char *server_name) { fflush(0); timetrace_start(); - dns_keepalive(); - dns_keepalive(); - dns_keepalive(); - dns_keepalive(); - dns_keepalive(); + h2_send_exampledotcom(); + h2_send_exampledotcom(); + h2_send_exampledotcom(); + h2_send_exampledotcom(); + h2_send_exampledotcom(); ms = timetrace_end(); if (ssl_state == SSL_CLOSED) { diff --git a/src/fdns/ssl.c b/src/fdns/ssl.c index c635718..87dd730 100644 --- a/src/fdns/ssl.c +++ b/src/fdns/ssl.c @@ -144,6 +144,8 @@ void ssl_open(void) { } } } + // inform the server we are using http2 + SSL_CTX_set_alpn_protos(ctx, (const unsigned char *)"\x02h2", 3); bio = BIO_new_ssl_connect(ctx); BIO_get_ssl(bio, &ssl); @@ -171,9 +173,14 @@ void ssl_open(void) { ssl_state = SSL_OPEN; rlogprintf("SSL connection opened\n"); + + h2_init(); + h2_connect(); + h2_send_exampledotcom(); } void ssl_close(void) { + h2_close(); if (ssl) { int rv = SSL_shutdown(ssl); if (rv == 0) @@ -186,30 +193,28 @@ void ssl_close(void) { rlogprintf("SSL connection closed\n"); } -// returns the length of the response,0 if failed -int ssl_rxtx_dns(uint8_t *msg, int cnt) { - assert(msg); +int ssl_get_socket(void) { + return SSL_get_fd(ssl); +} - DnsServer *srv = server_get(); - assert(srv); +int ssl_tx(uint8_t *buf, int len) { + assert(buf); + assert(len); - if (ssl == NULL || ssl_state != SSL_OPEN) - return 0; + if (ctx == NULL || ssl == NULL || ssl_state != SSL_OPEN) + goto errout; assert(bio); assert(ctx); assert(ssl); - char buf[MAXBUF]; - sprintf(buf, srv->request, cnt); - int len = strlen(buf); - assert(cnt < MAXBUF - len); + if (arg_debug) + printf("(%d) *** SSL transaction len %d***\n", arg_id, len); + +//print_mem(buf, len); + - memcpy(buf + len, msg, cnt); - len += cnt; - if (arg_debug) - printf("(%d) *** SSL transaction ***\n", arg_id); int lentx; if((lentx = BIO_write(bio, buf, len)) <= 0) { @@ -218,15 +223,25 @@ int ssl_rxtx_dns(uint8_t *msg, int cnt) { goto errout; } if((lentx = BIO_write(bio, buf, len)) <= 0) { - rlogprintf("Error: failed SSL write, retval %d\n", lentx); + rlogprintf("Error: failed SSL write, retval %d\n", lentx);\ goto errout; } } if (arg_debug) printf("(%d) SSL write %d/%d bytes\n", arg_id, len, lentx); + return lentx; +errout: + ssl_close(); + return 0; +} + +int ssl_rx(uint8_t *buf) { + assert(buf); + if (ctx == NULL || ssl == NULL || ssl_state != SSL_OPEN) + goto errout; - len = BIO_read(bio, buf, MAXBUF); + int len = BIO_read(bio, buf, MAXBUF); if(len <= 0) { if(! BIO_should_retry(bio)) { rlogprintf("Error: failed SSL read, retval %d\n", len); @@ -238,116 +253,16 @@ int ssl_rxtx_dns(uint8_t *msg, int cnt) { goto errout; } } - buf[len] = '\0'; - - // check 200 OK - char *ptr = strstr(buf, "200 OK"); - if (!ptr) { - rlogprintf("Warning: HTTP error, 200 OK not received\n"); - printf("**************\n%s\n**************\n", buf); - fflush(0); - goto errout; - } - - // look for the end of http header - ptr = strstr(buf, "\r\n\r\n"); - if (!ptr) { - rlogprintf("Warning: cannot parse HTTPS response, didn't recieve a full http header\n"); - printf("**************\n%s\n**************\n", buf); - fflush(0); - goto errout; - } - ptr += 4; // length of "\r\n\r\n" - ptrdiff_t hlen = ptr - buf; // +4 is the length of \r\n\r\n - *(ptr - 1) = 0; if (arg_debug) - printf("(%d) http header:\n%s", arg_id, buf); - - // look for Content-Length: - char *contlen = "Content-Length: "; - ptr = strcasestr(buf, contlen); - int datalen = 0; - if (!ptr) { - rlogprintf("Warning: cannot parse HTTPS response, content-length missing\n"); - print_mem((uint8_t *) buf, len); - goto errout; - } - else { - ptr += strlen(contlen); - sscanf(ptr, "%d", &datalen); - if (datalen == 0) // we got a "Content-lenght: 0"; this is probably a HTTP error - return 0; - } - - // do we need to read more data? - int totallen = (int) hlen + datalen; - if (arg_debug) - printf("(%d) SSL read len %d, totallen %d, datalen %d\n", - arg_id, len, totallen, datalen); - if (totallen >= MAXBUF) { - rlogprintf("Warning: cannot parse HTTPS response, invalid length\n"); - print_mem((uint8_t *) buf, len); - goto errout; - } - - while (len < totallen) { - int rv = BIO_read(bio, buf + len, totallen - len); - if (arg_debug) - printf("(%d) SSL read + %d\n", arg_id, rv); - if(rv <= 0) { - if(! BIO_should_retry(bio)) { - rlogprintf("Error: failed SSL read\n"); - goto errout; - } - rv = BIO_read(bio, buf, MAXBUF); - if (arg_debug) - printf("(%d) SSL read + %d\n", arg_id, rv); - if(rv <= 0) { - rlogprintf("Error: SSL connection is probably closed\n"); - goto errout; - } - } - - len += rv; - } + printf("(%d) SSL read %d bytes\n", arg_id, len); + return len; +errout: + ssl_close(); + return 0; +} - // copy the response in buf - memcpy(msg, buf + len - datalen, datalen); - if (arg_debug) { - printf("(%d) DNS data:\n", arg_id); - print_mem((uint8_t *) buf, datalen); - printf("(%d) *** SSL transaction end ***\n", arg_id); - } - // - // partial response parsing - // - if (lint_rx(msg, datalen)) { - if (lint_error() == DNSERR_NXDOMAIN) { - // NXDOMAIN or similar received, cache for 10 minutes - cache_set_reply(msg, datalen, CACHE_TTL_ERROR); - return datalen; - } - // serveral adblocker/family services return addresses of 0.0.0.0 or 127.0.0.1 for blocked domains - const char *str = lint_err2str(); - if (strstr(str, "0.0.0.0") || strstr(str, "127.0.0.1")) { - // set NXDOMAIN bytes in the packet - msg[3] = 3; - rlogprintf("%s refused by service provider\n", cache_get_name()); - return datalen; - } - else - rlogprintf("Error: %s %s\n", lint_err2str(), cache_get_name()); - return 0; - } - // cache the response and exit - cache_set_reply(msg, datalen, arg_cache_ttl); - return datalen; -errout: - ssl_close(); - return 0; -} diff --git a/src/man/fdns.txt b/src/man/fdns.txt index 5082818..6feadf1 100644 --- a/src/man/fdns.txt +++ b/src/man/fdns.txt @@ -41,11 +41,6 @@ Asia-Pacific and Europe. Use --list=all option to print all the servers and the Allow all DNS query types; by default only A queries are allowed. In case --ipv6 is set, AAAA queries are also allowed. .TP -\fB\-\-allow-local-doh -Allow applications on local network to connect to DoH services; disabled by default. -NOTE: Applications can still use DoH-Server if they have a hardcoded IP-Address. -If you realy want to block other DoH connection you must use your firewall. -.TP \fB\-\-cache-ttl=seconds Change DNS cache TTL, in seconds. By default we use a fixed cache TTL of 40 minutes. .TP @@ -73,6 +68,14 @@ $ sudo fdns --proxy-addr-any --daemonize \fB\-\-debug Print debug messages. .TP +\fB\-\-debug-h2 +Print debug messages for HTTP2 protocol. +.TP +\fB\-\-disable-local-doh +Blacklist DoH services for applications running on the local network. +NOTE: Applications can still use DoH-Server if they have a hardcoded IP-Address. +If you realy want to block other DoH connection you must use your firewall. +.TP \fB\-\-forwarder=domain@address Conditional domain forwarding to a different DNS server. .br diff --git a/src/tools/wordpress.c b/src/tools/wordpress.c index 0b67596..dbb46f9 100644 --- a/src/tools/wordpress.c +++ b/src/tools/wordpress.c @@ -19,67 +19,21 @@ /* Simple tool to extract the server list for wordpress site - -Add to anycast list: -
  • cira-family (family, Canada)
    -family.canadianshield.cira.ca/dns-query (149.112.121.30)
  • -
  • cira-family2 (family, Canada)
    -family.canadianshield.cira.ca/dns-query (149.112.122.30)
  • - -Add to Asia-Pacific -
  • blahdns-jp (Japan, adblocker)
    -doh-jp.blahdns.com/dns-query (45.32.55.94)
  • -
  • blahdns-sg (Singapore, adblocker)
    -doh-sg.blahdns.com/dns-query (139.180.141.57)
  • - -Add to Europe -
  • blahdns-de (Germany, adblocker)
    -doh-de.blahdns.com/dns-query (159.69.198.101)
  • -
  • blahdns-fi (Finland, adblocker)
    -doh-fi.blahdns.com/dns-query (95.216.212.177)
  • - -Remove Luxembourg from nixnet/nixnet-adblocker Americas -clean up dnslify: Netherlands, Germany, UK, NY, Califronia, Singapore - */ -#define _GNU_SOURCE -#include -#include -#include -#include -#define errExit(msg) do { char msgout[500]; snprintf(msgout, 500, "Error %s: %s:%d %s", msg, __FILE__, __LINE__, __FUNCTION__); perror(msgout); exit(1);} while (0) -// check ip:port -// return -1 if error -static inline int check_addr_port(const char *str) { - unsigned a, b, c, d, e; - - // extract ip - int rv = sscanf(str, "%u.%u.%u.%u:%u", &a, &b, &c, &d, &e); - if (rv != 5 || a > 255 || b > 255 || c > 255 || d > 255 || e > 0xffffffff) - return -1; - return 0; -} +#include "../fdns/fdns.h" +// stub +int arg_disable_local_doh = 0; +void filter_add(char label, const char *domain) {(void) label; (void) domain;} -typedef struct dnsserver_t { - struct dnsserver_t *next;// linked list - int active; // flag for random purposes - - // server data - char *name; // name - char *website; // website - char *zone; // geographical zone - char *tags; // description - char *address; // IP address - char *host; // POST request first line - char *request; // full POST request - int sni; // 1 or 0 - int keepalive; // keepalive in seconds -} DnsServer; static DnsServer *slist_americas = NULL; static DnsServer *slist_asiapac = NULL; static DnsServer *slist_europe = NULL; static DnsServer *slist_anycast = NULL; +static int arg_anycast = 0; +static int arg_americas = 0; +static int arg_asia_pacific = 0; +static int arg_europe = 0; // returns NULL for end of list static DnsServer *read_one_server(FILE *fp, int *linecnt, const char *fname) { @@ -96,91 +50,112 @@ static DnsServer *read_one_server(FILE *fp, int *linecnt, const char *fname) { buf[0] = '\0'; int found = 0; while (fgets(buf, 4096, fp)) { + char *start = buf; (*linecnt)++; + if (strncmp(start, "##", 2) == 0) + start += 2; + // comments - if (*buf == '#') + if (*start == '#') continue; // remove \n - char *ptr = strchr(buf, '\n'); + char *ptr = strchr(start, '\n'); if (ptr) *ptr = '\0'; - if (strncmp(buf, "name: ", 6) == 0) { + if (strncmp(start, "name: ", 6) == 0) { if (s->name) goto errout; - s->name = strdup(buf + 6); + s->name = strdup(start + 6); if (!s->name) errExit("strdup"); found = 1; } - else if (strncmp(buf, "website: ", 9) == 0) { + else if (strncmp(start, "website: ", 9) == 0) { if (s->website) goto errout; - s->website = strdup(buf + 9); + s->website = strdup(start + 9); if (!s->website) errExit("strdup"); found = 1; } - else if (strncmp(buf, "zone: ", 6) == 0) { + else if (strncmp(start, "zone: ", 6) == 0) { if (s->zone) goto errout; - s->zone = strdup(buf + 6); + s->zone = strdup(start + 6); if (!s->zone) errExit("strdup"); found = 1; } - else if (strncmp(buf, "tags: ", 6) == 0) { + else if (strncmp(start, "tags: ", 6) == 0) { if (s->tags) goto errout; - s->tags = strdup(buf + 6); + s->tags = strdup(start + 6); if (!s->tags) errExit("strdup"); found = 1; } - else if (strncmp(buf, "address: ", 9) == 0) { + else if (strncmp(start, "address: ", 9) == 0) { if (s->address) goto errout; - s->address = strdup(buf + 9); + s->address = strdup(start + 9); if (!s->address) errExit("strdup"); -// todo: accept a server name in parallel with IP addresses // check address:port - if (check_addr_port(s->address)) { - fprintf(stderr, "Error: file %s, line %d, invalid address:port\n", fname, *linecnt); - exit(1); + // commons.host is a geocast host + // OpenSSL will find out the IP address using regular DNS over UDP + if (strcmp(s->name, "commons-host") != 0) { + if (check_addr_port(s->address)) { + fprintf(stderr, "Error: file %s, line %d, invalid address:port\n", fname, *linecnt); + exit(1); + } } found = 1; } - else if (strncmp(buf, "host: ", 6) == 0) { + else if (strncmp(start, "host: ", 6) == 0) { if (s->host) goto errout; - s->host = strdup(buf + 6); + s->host = strdup(start + 6); if (!s->host) errExit("strdup"); found = 1; + + + // build the DNS/HTTP request + char *str = strchr(s->host, '/'); + if (!str) { + free(s); + fprintf(stderr, "Error: file %s, line %d, invalid host\n", fname, *linecnt); + exit(1); + } + s->path = strdup(str); + if (!s->path) + errExit("strdup"); + *str++ = '\0'; } - else if (strncmp(buf, "sni: ", 5) == 0) { + else if (strncmp(start, "sni: ", 5) == 0) { if (s->sni) goto errout; - if (strcmp(buf + 5, "yes") == 0) + if (strcmp(start + 5, "yes") == 0) s->sni = 1; - else if (strcmp(buf + 5, "no") == 0) + else if (strcmp(start + 5, "no") == 0) s->sni = 0; else { fprintf(stderr, "Error: file %s, line %d, wrong SNI setting\n", fname, *linecnt); exit(1); } } - else if (strncmp(buf, "keepalive: ", 11) == 0) { + else if (strncmp(start, "keepalive: ", 11) == 0) { if (s->keepalive) goto errout; - if (sscanf(buf + 11, "%d", &s->keepalive) != 1 || s->keepalive <= 0) { + if (sscanf(start + 11, "%d", &s->keepalive) != 1 || s->keepalive <= 0) { fprintf(stderr, "Error: file %s, line %d, invalid keepalive\n", fname, *linecnt); exit(1); } +// s->keepalive = 25; // check server data if (!s->name || !s->website || !s->zone || !s->tags || !s->address || !s->host) { @@ -188,6 +163,10 @@ static DnsServer *read_one_server(FILE *fp, int *linecnt, const char *fname) { exit(1); } + // add host to filter + if (arg_disable_local_doh) + filter_add('D', s->host); + return s; } } @@ -270,7 +249,6 @@ static void load_list(void) { } } - printf("total %d\n", cnt); fclose(fp); } @@ -329,10 +307,26 @@ void print_server(DnsServer *ptr) { if (c) *c = '\0'; printf("
  • %s ", ptr->website, ptr->name); - if (*ptr->tags) - printf("(%s)", ptr->tags); + int geocast = 0; + if (*ptr->tags) { + if (strstr(ptr->tags, "geocast")) { + ptr->tags += 7; + if (*ptr->tags == ',') + ptr->tags++; + if (*ptr->tags == ' ') + ptr->tags++; + geocast = 1; + } + + if (ptr) + printf("(%s)", ptr->tags); + } + printf("
    \n"); - printf("%s (%s)
  • \n", ptr->host, ptr->address); + if (geocast) + printf("%s%s (geocast)\n", ptr->host, ptr->path); + else + printf("%s%s (%s)\n", ptr->host, ptr->path, ptr->address); } void print_start(void) { @@ -344,51 +338,88 @@ void print_end(void) { void print_list(void) { + DnsServer *ptr; + // anycast table - printf("\n
      \n"); - DnsServer *ptr = slist_anycast; - while (ptr) { - print_server(ptr); - ptr = ptr->next; + if (arg_anycast) { + printf("\n
        \n"); + ptr = slist_anycast; + while (ptr) { + print_server(ptr); + ptr = ptr->next; + } + printf("
      \n\n\n"); } - printf("
    \n\n\n"); - - printf("\n"); // Americas - printf("

    Americas

      \n"); - ptr = slist_americas; - while (ptr) { - print_server(ptr); - ptr = ptr->next; + if (arg_americas) { + printf("\n"); + // Americas + printf("

      Americas

        \n"); + ptr = slist_americas; + while (ptr) { + print_server(ptr); + ptr = ptr->next; + } + printf("
      \n"); } - printf("\n"); // Asia-Pacific - printf("

      Asia-Pacific

        \n"); - ptr = slist_asiapac; - while (ptr) { - print_server(ptr); - ptr = ptr->next; + if (arg_asia_pacific) { + printf("

        Asia-Pacific

          \n"); + ptr = slist_asiapac; + while (ptr) { + print_server(ptr); + ptr = ptr->next; + } + printf("
        \n"); } - printf("
      \n"); - // Europe - printf("

      Europe, Middle-East, Africa

        \n"); - ptr = slist_europe; - int i = 0; - while (ptr) { - if (i == 5) - printf("

      Europe, Middle-East, Africa (cont.)

        \n"); - print_server(ptr); - ptr = ptr->next; - i++; + if (arg_europe) { + printf("

        Europe, Middle-East, Africa

          \n"); + ptr = slist_europe; + int i = 0; + while (ptr) { + if (i == 5) + printf("

      Europe, Middle-East, Africa (cont.)

        \n"); + print_server(ptr); + ptr = ptr->next; + i++; + } + printf("
      \n"); } - printf("
      \n\n\n"); + + printf("
    \n\n\n"); +} + +void usage(void) { + printf("Usage: wordpress options\n"); + printf("Options:\n"); + printf(" --anycast\n"); + printf(" --americas\n"); + printf(" --asia-pacific\n"); + printf(" --europe\n"); } +int main(int argc, char **argv) { + if (argc < 2) { + printf("Error: invalid params\n"); + usage(); + return -1; + } + + int i; + for (i = 1; i < argc; i++) { + if (strcmp(argv[i], "--anycast") == 0) + arg_anycast = 1; + else if (strcmp(argv[i], "--americas") == 0) + arg_americas = 1; + else if (strcmp(argv[i], "--asia-pacific") == 0) + arg_asia_pacific = 1; + else if (strcmp(argv[i], "--europe") == 0) + arg_europe = 1; + } -int main(void) { load_list(); print_list(); return 0; diff --git a/test/stress/test-server.exp b/test/stress/test-server.exp index 5389271..b585f34 100755 --- a/test/stress/test-server.exp +++ b/test/stress/test-server.exp @@ -3,13 +3,13 @@ # Copyright (C) 2019-2020 FDNS Authors # License GPL v2 -set timeout 30 +set timeout 10 spawn $env(SHELL) match_max 100000 if { $argc != 1 } { puts "TESTING ERROR: argument missing" - puts "Usage: wget.exp server-name" + puts "Usage: test-server.exp server-name" exit } @@ -19,19 +19,19 @@ send -- "pkill fdns\r" sleep 1 +puts " TESTING: --test-server\n" send -- "fdns --test-server=$argv 2>&1\r" expect { timeout {puts "TESTING ERROR 0\n";exit 1} - "Error:" {puts "TESTING ERROR 1\n";exit 1} - "no such server available" {puts "TESTING ERROR 2\n";exit 1} "response average" } expect { - timeout {puts "TESTING ERROR 3\n";exit 1} + timeout {puts "TESTING ERROR 2\n";exit 1} "Testing completed" } sleep 1 +puts " TESTING: starting proxy111\n" send -- "fdns --server=$argv 2>&1\r" expect { timeout {puts "TESTING ERROR 10\n";exit 1} @@ -50,76 +50,66 @@ expect { timeout {puts "TESTING ERROR 14\n";exit 1} "SSL connection opened" } - -# wait 10 seconds to detect any kind of connection problems -#expect { -# timeout {puts "connection OK\n"} -# "Error:" {puts "TESTING ERROR 2\n";exit 1} -# "SSL connection closed" {puts "TESTING ERROR 3\n";exit 1} -#} -sleep 1 - -spawn $env(SHELL) -send "rm /tmp/index.html\r" after 100 -send -- "firejail --dns=127.1.1.1 wget -O /tmp/index.html google.com\r" +spawn $env(SHELL) + +puts " TESTING: starting proxy222\n" +#send -- "fdns --server=$argv --proxy-addr=127.2.2.2 2>&1\r" +send -- "fdns --server=blahdns-de --proxy-addr=127.2.2.2 2>&1\r" expect { timeout {puts "TESTING ERROR 20\n";exit 1} - "Connecting to www.google.com" -} -expect { - timeout {puts "TESTING ERROR 21\n";exit 1} - "index.html" + "Error:" {puts "TESTING ERROR 21\n";exit 1} + "fdns starting" } expect { timeout {puts "TESTING ERROR 22\n";exit 1} - "saved" + "SSL connection opened" } -after 100 - -# do it again! -send "rm /tmp/index.html\r" -after 100 -send -- "firejail --dns=127.1.1.1 wget -O /tmp/index.html wordpress.com\r" expect { timeout {puts "TESTING ERROR 23\n";exit 1} - "Connecting to wordpress.com" + "SSL connection opened" } expect { timeout {puts "TESTING ERROR 24\n";exit 1} - "index.html" -} -expect { - timeout {puts "TESTING ERROR 25\n";exit 1} - "saved" + "SSL connection opened" } + after 100 -send "rm /tmp/index.html\r" -after 100 +spawn $env(SHELL) -# under development +puts " TESTING: queries\n" send -- "cp ../src/fdnstress/fdnstress /transfer/.\r" send -- "cp list20 /transfer/.\r" after 100 send -- "firejail --dns=127.1.1.1 /transfer/fdnstress --threads=1 /transfer/list20\r" expect { timeout {puts "TESTING ERROR 30\n";exit 1} + "Error:" {puts "TESTING ERROR 30.1\n";exit 1} + "SSL connection closed" {puts "TESTING ERROR 30.2\n";exit 1} "wordpress.com" } expect { timeout {puts "TESTING ERROR 31\n";exit 1} + "Error:" {puts "TESTING ERROR 30.1\n";exit 1} + "SSL connection closed" {puts "TESTING ERROR 30.2\n";exit 1} "linux.org" } expect { timeout {puts "TESTING ERROR 32\n";exit 1} + "Error:" {puts "TESTING ERROR 30.1\n";exit 1} + "SSL connection closed" {puts "TESTING ERROR 30.2\n";exit 1} "google.com" } expect { timeout {puts "TESTING ERROR 33\n";exit 1} + "Error:" {puts "TESTING ERROR 30.1\n";exit 1} + "SSL connection closed" {puts "TESTING ERROR 30.2\n";exit 1} "youtube.com" } expect { timeout {puts "TESTING ERROR 34\n";exit} + "Error:" {puts "TESTING ERROR 30.1\n";exit 1} + "SSL connection closed" {puts "TESTING ERROR 30.2\n";exit 1} "fedoraproject.org" } after 100