├── install.sh ├── monitor1.png ├── filtercheck.sh ├── test └── fdns │ ├── whitelist-fdns-test │ ├── blocklist-fdns-test │ ├── invalid-server.exp │ ├── monitor.exp │ ├── list-adblocker.exp │ ├── server-anycast.exp │ ├── test-servers-anycast.exp │ ├── test-url-list.exp │ ├── test-url.exp │ ├── list-security.exp │ ├── list-family.exp │ ├── unlisted.exp │ ├── list-all.exp │ ├── already-running.exp │ ├── list-anycast.exp │ ├── local-doh.exp │ ├── default-ping.exp │ ├── nofilter.exp │ ├── help-man.exp │ ├── print-requests.exp │ ├── default-wget.exp │ ├── default-nslookup.exp │ ├── list.exp │ ├── restart-worker.exp │ ├── test-servers.exp │ ├── ipv6.exp │ ├── wget.exp │ ├── whitelist.exp │ ├── multiserver.exp │ ├── forwarder.exp │ ├── whitelist-file.exp │ ├── workers.exp │ ├── test-user.sh │ ├── fallback.exp │ ├── keepalive.exp │ ├── blocklist-file.exp │ ├── test.sh │ ├── filter.exp │ ├── protocol.exp │ └── restart-workers.exp ├── .gitmodules ├── etc ├── apparmor │ ├── fdns-local │ └── usr.bin.fdns ├── resolver.seccomp ├── servers.local ├── hosts └── fdns.service ├── mkman.sh ├── src ├── dnsc │ ├── Makefile │ ├── dedup.c │ ├── whitelist.c │ ├── tld.c │ ├── dnsc.h │ ├── subs.c │ └── rsort.c ├── nxdomain │ ├── Makefile │ ├── testme │ ├── nxdomain.h │ └── resolver.c ├── fdns │ ├── Makefile │ ├── timetrace.h │ ├── hpack_static.h │ ├── restart.c │ ├── unlisted.c │ ├── log.c │ ├── timetrace.c │ ├── lint.h │ ├── forwarder.c │ ├── stats.c │ ├── hpack_static.c │ ├── dnsdb.c │ ├── whitelist.c │ ├── h2frame.h │ ├── fallback.c │ ├── net.c │ ├── procs.c │ ├── security.c │ ├── cache.c │ ├── dot.c │ └── quic.c ├── man │ ├── dnsc.txt │ └── nxdomain.txt ├── common.mk.in └── bash_completion │ └── fdns.bash_completion ├── mkasc.sh ├── .gitignore ├── ci └── printenv.sh ├── platform ├── debian │ ├── control.amd64 │ ├── control.i386 │ └── copyright ├── arch │ └── PKGBUILD └── fedora │ ├── fdns-local.spec │ ├── fdns-stable.spec │ ├── fdns-git.spec │ └── mkrpm.sh ├── aclocal.m4 ├── gcov.sh ├── mkdeb.sh ├── .github └── workflows │ └── codeql.yml ├── m4 ├── ax_check_compile_flag.m4 └── ax_check_openssl.m4 ├── configure.ac ├── Makefile.in ├── README.md └── README /install.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | echo "installing..." 3 | -------------------------------------------------------------------------------- /monitor1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/netblue30/fdns/HEAD/monitor1.png -------------------------------------------------------------------------------- /filtercheck.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | grep -n '127.0.0.1 ..$' `find etc/blocklists/list.*` 4 | exit 0 -------------------------------------------------------------------------------- /test/fdns/whitelist-fdns-test: -------------------------------------------------------------------------------- 1 | # whitelists for gentoo.org 2 | gentoo.org 3 | assets.gentoo.org 4 | security.gentoo.org 5 | -------------------------------------------------------------------------------- /.gitmodules: -------------------------------------------------------------------------------- 1 | [submodule "etc/blocklists"] 2 | path = etc/blocklists 3 | url = https://github.com/netblue30/fdns-blocklists 4 | -------------------------------------------------------------------------------- /test/fdns/blocklist-fdns-test: -------------------------------------------------------------------------------- 1 | # blocklists 2 | 127.0.0.1 google.com 3 | 127.0.0.1 youtube.com 4 | 127.0.0.1 facebook.com 5 | -------------------------------------------------------------------------------- /etc/apparmor/fdns-local: -------------------------------------------------------------------------------- 1 | # Site-specific additions and overrides for 'usr.bin.fdns'. 2 | # For more details, please see /etc/apparmor.d/local/README. 3 | -------------------------------------------------------------------------------- /mkman.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | sed "s/VERSION/$1/g" $2 > $3 4 | MONTH=`LC_ALL=C date -u --date="@${SOURCE_DATE_EPOCH:-$(date +%s)}" +%b` 5 | sed -i "s/MONTH/$MONTH/g" $3 6 | YEAR=`LC_ALL=C date -u --date="@${SOURCE_DATE_EPOCH:-$(date +%s)}" +%Y` 7 | sed -i "s/YEAR/$YEAR/g" $3 8 | -------------------------------------------------------------------------------- /src/dnsc/Makefile: -------------------------------------------------------------------------------- 1 | all: dnsc 2 | 3 | include ../common.mk 4 | 5 | %.o : %.c $(H_FILE_LIST) 6 | $(CC) $(CFLAGS) $(EXTRA_CFLAGS) $(INCLUDE) -c $< -o $@ 7 | 8 | dnsc: $(OBJS) 9 | $(CC) $(LDFLAGS) -o $@ $(OBJS) $(LIBS) $(EXTRA_LDFLAGS) 10 | 11 | clean:; rm -f *.o dnsc *.gcov *.gcda *.gcno 12 | 13 | distclean: clean 14 | -------------------------------------------------------------------------------- /mkasc.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | echo "Calculating SHA256 for all files in /transfer - fdns version $1" 4 | 5 | cd /transfer 6 | sha256sum * > fdns-$1-unsigned 7 | gpg --clearsign --digest-algo SHA256 < fdns-$1-unsigned > fdns-$1.asc 8 | gpg --verify fdns-$1.asc 9 | gpg --detach-sign --armor fdns-$1.tar.xz 10 | rm fdns-$1-unsigned 11 | -------------------------------------------------------------------------------- /src/nxdomain/Makefile: -------------------------------------------------------------------------------- 1 | all: nxdomain 2 | 3 | include ../common.mk 4 | 5 | %.o : %.c $(H_FILE_LIST) 6 | $(CC) $(CFLAGS) $(EXTRA_CFLAGS) $(INCLUDE) -c $< -o $@ 7 | 8 | nxdomain: $(OBJS) 9 | $(CC) $(LDFLAGS) -o $@ $(OBJS) $(LIBS) $(EXTRA_LDFLAGS) 10 | 11 | clean:; rm -f *.o nxdoamain *.gcov *.gcda *.gcno 12 | 13 | distclean: clean 14 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | *.o 2 | *.so 3 | *~ 4 | *.swp 5 | *.deb 6 | *.rpm 7 | *.gcda 8 | *.gcno 9 | Makefile 10 | autom4te.cache/ 11 | config.log 12 | config.status 13 | fdns-*.tar.xz 14 | fdns.1 15 | src/fdns/fdns 16 | src/dnsc/dnsc 17 | src/common.mk 18 | src/nxdomain/nxdomain 19 | test/src/fdnstress/fdnstress 20 | gcov-dir 21 | nxdomain.1 22 | dnsc.1 23 | -------------------------------------------------------------------------------- /src/fdns/Makefile: -------------------------------------------------------------------------------- 1 | all: fdns 2 | 3 | include ../common.mk 4 | 5 | %.o : %.c $(H_FILE_LIST) 6 | $(CC) $(CFLAGS) $(EXTRA_CFLAGS) $(INCLUDE) -c $< -o $@ 7 | 8 | fdns: $(OBJS) 9 | $(CC) $(LDFLAGS) -o $@ $(OBJS) -lssl -lcrypto -lrt -lseccomp -lm $(LIBS) $(EXTRA_LDFLAGS) 10 | 11 | clean:; rm -f *.o fdns *.gcov *.gcda *.gcno 12 | 13 | distclean: clean 14 | -------------------------------------------------------------------------------- /etc/resolver.seccomp: -------------------------------------------------------------------------------- 1 | recvmmsg,tgkill,futex,munmap,clock_gettime,fcntl,bind,brk,clock_nanosleep,close,connect,dup,exit_group,fstat,getpid,getrandom,getsockname,gettimeofday,ioctl,kill,mmap,_newselect,nanosleep,open,openat,poll,read,recvfrom,recvmsg,rt_sigprocmask,select,sendmmsg,sendto,setsockopt,sigreturn,socket,stat,time,uname,wait4,write,writev,fstat64,pselect6,newfstatat,gettid 2 | -------------------------------------------------------------------------------- /etc/servers.local: -------------------------------------------------------------------------------- 1 | # 2 | # Local Firejail DoH server list. This file is not overwriten at software install. 3 | # 4 | # Use this file to add new servers 5 | # 6 | # name: 42l 7 | # website: https://42l.fr 8 | # tags: non-profit, France, Europe 9 | # address: 185.216.27.142:443 10 | # host: doh.42l.fr/dns-query 11 | # keepalive: 40 12 | # end; 13 | # 14 | # or to unlist servers from default server list in /etc/fdns/servers 15 | # 16 | # unlist: quad9 17 | # 18 | 19 | -------------------------------------------------------------------------------- /test/fdns/invalid-server.exp: -------------------------------------------------------------------------------- 1 | #!/usr/bin/expect -f 2 | # This file is part of FDNS project 3 | # Copyright (C) 2019-2025 FDNS Authors 4 | # License GPL v2 5 | 6 | set timeout 10 7 | spawn $env(SHELL) 8 | match_max 100000 9 | 10 | send -- "pkill fdns\r" 11 | sleep 2 12 | 13 | send -- "fdns --server=blablabla\r" 14 | expect { 15 | timeout {puts "TESTING ERROR 0\n";exit} 16 | "cannot connect to server blablabla" 17 | } 18 | 19 | after 100 20 | puts "\nall done\n" 21 | -------------------------------------------------------------------------------- /ci/printenv.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | # Print information that may be useful for debugging CI. 3 | 4 | test -f /etc/os-release && . /etc/os-release 5 | 6 | cat < 5 | Installed-Size: 2184 6 | Depends: libc6,libseccomp-dev,libssl-dev 7 | Section: admin 8 | Priority: optional 9 | Homepage: https://github.com/netblue30/fdns 10 | Description: Firejail DNS-over-HTTPS Proxy Server. 11 | fdns is a DNS-over-HTTPS proxy server targeted at small networks and 12 | Linux desktops. To speed up the name resolution fdns caches the responses, 13 | and uses a configurable adblocker and privacy filter to cut down 14 | unnecessary traffic. The software is written in C, and is licensed under GPLv3. 15 | -------------------------------------------------------------------------------- /test/fdns/list-security.exp: -------------------------------------------------------------------------------- 1 | #!/usr/bin/expect -f 2 | # This file is part of FDNS project 3 | # Copyright (C) 2019-2025 FDNS Authors 4 | # License GPL v2 5 | 6 | set timeout 10 7 | spawn $env(SHELL) 8 | match_max 100000 9 | 10 | send -- "fdns --list=security\r" 11 | expect { 12 | timeout {puts "TESTING ERROR 0\n";exit} 13 | "Current zone:" 14 | } 15 | expect { 16 | timeout {puts "TESTING ERROR 1\n";exit} 17 | "cleanbrowsing" 18 | } 19 | expect { 20 | timeout {puts "TESTING ERROR 2\n";exit} 21 | "quad9" 22 | } 23 | expect { 24 | timeout {puts "TESTING ERROR 3\n";exit} 25 | "servers found" 26 | } 27 | 28 | 29 | after 100 30 | puts "\nall done\n" 31 | 32 | -------------------------------------------------------------------------------- /platform/debian/control.i386: -------------------------------------------------------------------------------- 1 | Package: fdns 2 | Version: FDNSVER-1 3 | Architecture: i386 4 | Maintainer: netblue30 5 | Installed-Size: 2024 6 | Depends: libc6,libseccomp-dev,libssl-dev 7 | Section: admin 8 | Priority: optional 9 | Homepage: https://github.com/netblue30/firejail 10 | Description: Firejail DNS-over-HTTPS Proxy Server. 11 | fdns is a DNS-over-HTTPS proxy server targeted at small networks and 12 | Linux desktops. To speed up the name resolution fdns caches the responses, 13 | and uses a configurable adblocker and privacy filter to cut down the 14 | unnecessary traffic. The software is written in C, and is licensed under GPLv3. 15 | -------------------------------------------------------------------------------- /test/fdns/list-family.exp: -------------------------------------------------------------------------------- 1 | #!/usr/bin/expect -f 2 | # This file is part of FDNS project 3 | # Copyright (C) 2019-2025 FDNS Authors 4 | # License GPL v2 5 | 6 | set timeout 10 7 | spawn $env(SHELL) 8 | match_max 100000 9 | 10 | send -- "fdns --list=family\r" 11 | expect { 12 | timeout {puts "TESTING ERROR 0\n";exit} 13 | "Current zone:" 14 | } 15 | expect { 16 | timeout {puts "TESTING ERROR 1\n";exit} 17 | "adguard-family" 18 | } 19 | expect { 20 | timeout {puts "TESTING ERROR 2\n";exit} 21 | "cleanbrowsing-family" 22 | } 23 | expect { 24 | timeout {puts "TESTING ERROR 3\n";exit} 25 | "servers found" 26 | } 27 | 28 | 29 | after 100 30 | puts "\nall done\n" 31 | 32 | -------------------------------------------------------------------------------- /test/fdns/unlisted.exp: -------------------------------------------------------------------------------- 1 | #!/usr/bin/expect -f 2 | # This file is part of FDNS project 3 | # Copyright (C) 2019-2025 FDNS Authors 4 | # License GPL v2 5 | 6 | set timeout 10 7 | spawn $env(SHELL) 8 | match_max 100000 9 | 10 | send -- "fdns --list=cloudflare\r" 11 | expect { 12 | timeout {puts "TESTING ERROR 0\n";exit} 13 | "anycast" 14 | } 15 | after 100 16 | 17 | send -- "fdns --list=testserver1\r" 18 | expect { 19 | timeout {puts "TESTING ERROR 1\n";exit} 20 | "no such server available" 21 | } 22 | after 100 23 | 24 | send -- "fdns --list=testserver2\r" 25 | expect { 26 | timeout {puts "TESTING ERROR 2\n";exit} 27 | "no such server available" 28 | } 29 | after 100 30 | 31 | puts "\nall done\n" 32 | 33 | -------------------------------------------------------------------------------- /test/fdns/list-all.exp: -------------------------------------------------------------------------------- 1 | #!/usr/bin/expect -f 2 | # This file is part of FDNS project 3 | # Copyright (C) 2019-2025 FDNS Authors 4 | # License GPL v2 5 | 6 | set timeout 10 7 | spawn $env(SHELL) 8 | match_max 100000 9 | 10 | send -- "fdns --list=all\r" 11 | expect { 12 | timeout {puts "TESTING ERROR 0\n";exit} 13 | "Current zone:" 14 | } 15 | expect { 16 | timeout {puts "TESTING ERROR 1\n";exit} 17 | "cleanbrowsing" 18 | } 19 | expect { 20 | timeout {puts "TESTING ERROR 3\n";exit} 21 | "cloudflare" 22 | } 23 | expect { 24 | timeout {puts "TESTING ERROR 4\n";exit} 25 | "quad9" 26 | } 27 | expect { 28 | timeout {puts "TESTING ERROR 6\n";exit} 29 | "servers found" 30 | } 31 | 32 | 33 | after 100 34 | puts "\nall done\n" 35 | 36 | -------------------------------------------------------------------------------- /test/fdns/already-running.exp: -------------------------------------------------------------------------------- 1 | #!/usr/bin/expect -f 2 | # This file is part of FDNS project 3 | # Copyright (C) 2019-2025 FDNS Authors 4 | # License GPL v2 5 | 6 | set timeout 10 7 | spawn $env(SHELL) 8 | match_max 100000 9 | 10 | send -- "pkill fdns\r" 11 | sleep 2 12 | 13 | send -- "fdns\r" 14 | expect { 15 | timeout {puts "TESTING ERROR 0\n";exit} 16 | "fdns starting" 17 | } 18 | expect { 19 | timeout {puts "TESTING ERROR 0.1\n";exit} 20 | "SSL connection opened" 21 | } 22 | sleep 1 23 | 24 | spawn $env(SHELL) 25 | send -- "fdns --nofilter\r" 26 | expect { 27 | timeout {puts "TESTING ERROR 1\n";exit} 28 | "a different DNS server is already running on" 29 | } 30 | after 100 31 | 32 | send -- "pkill fdns\r" 33 | after 100 34 | puts "\nall done\n" 35 | -------------------------------------------------------------------------------- /aclocal.m4: -------------------------------------------------------------------------------- 1 | # generated automatically by aclocal 1.16.1 -*- Autoconf -*- 2 | 3 | # Copyright (C) 1996-2018 Free Software Foundation, Inc. 4 | 5 | # This file is free software; the Free Software Foundation 6 | # gives unlimited permission to copy and/or distribute it, 7 | # with or without modifications, as long as this notice is preserved. 8 | 9 | # This program is distributed in the hope that it will be useful, 10 | # but WITHOUT ANY WARRANTY, to the extent permitted by law; without 11 | # even the implied warranty of MERCHANTABILITY or FITNESS FOR A 12 | # PARTICULAR PURPOSE. 13 | 14 | m4_ifndef([AC_CONFIG_MACRO_DIRS], [m4_defun([_AM_CONFIG_MACRO_DIRS], [])m4_defun([AC_CONFIG_MACRO_DIRS], [_AM_CONFIG_MACRO_DIRS($@)])]) 15 | m4_include([m4/ax_check_compile_flag.m4]) 16 | m4_include([m4/ax_check_openssl.m4]) 17 | -------------------------------------------------------------------------------- /test/fdns/list-anycast.exp: -------------------------------------------------------------------------------- 1 | #!/usr/bin/expect -f 2 | # This file is part of FDNS project 3 | # Copyright (C) 2019-2025FDNS Authors 4 | # License GPL v2 5 | 6 | set timeout 10 7 | spawn $env(SHELL) 8 | match_max 100000 9 | 10 | send -- "fdns --list=anycast\r" 11 | expect { 12 | timeout {puts "TESTING ERROR 0\n";exit} 13 | "Current zone:" 14 | } 15 | expect { 16 | timeout {puts "TESTING ERROR 1\n";exit} 17 | "adguard" 18 | } 19 | expect { 20 | timeout {puts "TESTING ERROR 2\n";exit} 21 | "cleanbrowsing" 22 | } 23 | expect { 24 | timeout {puts "TESTING ERROR 3\n";exit} 25 | "cloudflare" 26 | } 27 | expect { 28 | timeout {puts "TESTING ERROR 4\n";exit} 29 | "quad9" 30 | } 31 | expect { 32 | timeout {puts "TESTING ERROR 5\n";exit} 33 | "servers found" 34 | } 35 | 36 | 37 | after 100 38 | puts "\nall done\n" 39 | 40 | -------------------------------------------------------------------------------- /test/fdns/local-doh.exp: -------------------------------------------------------------------------------- 1 | #!/usr/bin/expect -f 2 | # This file is part of FDNS project 3 | # Copyright (C) 2019-2025 FDNS Authors 4 | # License GPL v2 5 | 6 | set timeout 10 7 | spawn $env(SHELL) 8 | match_max 100000 9 | 10 | send -- "pkill fdns\r" 11 | sleep 2 12 | 13 | send -- "fdns\r" 14 | set server_id $spawn_id 15 | expect { 16 | timeout {puts "TESTING ERROR 0\n";exit} 17 | "fdns starting" 18 | } 19 | expect { 20 | timeout {puts "TESTING ERROR 0.1\n";exit} 21 | "SSL connection opened" 22 | } 23 | sleep 1 24 | 25 | spawn $env(SHELL) 26 | send -- "firejail --dns=127.1.1.1 ping -c 3 doh.42l.fr\r" 27 | set ping_id $spawn_id 28 | sleep 2 29 | 30 | spawn $env(SHELL) 31 | set monitor_id $spawn_id 32 | send -- "fdns --monitor\r" 33 | expect { 34 | timeout {puts "TESTING ERROR 1\n";exit} 35 | "doh.42l.fr, encrypted" 36 | } 37 | after 100 38 | 39 | set spawn_id $ping_id 40 | send -- "pkill fdns\r" 41 | 42 | after 100 43 | puts "\nall done\n" 44 | 45 | 46 | -------------------------------------------------------------------------------- /test/fdns/default-ping.exp: -------------------------------------------------------------------------------- 1 | #!/usr/bin/expect -f 2 | # This file is part of FDNS project 3 | # Copyright (C) 2019-2025 FDNS Authors 4 | # License GPL v2 5 | 6 | set timeout 10 7 | spawn $env(SHELL) 8 | match_max 100000 9 | 10 | send -- "pkill fdns\r" 11 | sleep 2 12 | 13 | send -- "fdns\r" 14 | expect { 15 | timeout {puts "TESTING ERROR 0\n";exit} 16 | "fdns starting" 17 | } 18 | expect { 19 | timeout {puts "TESTING ERROR 0.1\n";exit} 20 | "SSL connection opened" 21 | } 22 | sleep 1 23 | 24 | spawn $env(SHELL) 25 | send -- "firejail --dns=127.1.1.1 ping -c 3 google.com\r" 26 | expect { 27 | timeout {puts "TESTING ERROR 1\n";exit} 28 | "icmp_seq=1" 29 | } 30 | expect { 31 | timeout {puts "TESTING ERROR 2\n";exit} 32 | "icmp_seq=2" 33 | } 34 | expect { 35 | timeout {puts "TESTING ERROR 3\n";exit} 36 | "icmp_seq=3" 37 | } 38 | expect { 39 | timeout {puts "TESTING ERROR 4\n";exit} 40 | "3 received" 41 | } 42 | after 100 43 | send -- "pkill fdns\r" 44 | 45 | after 100 46 | puts "\nall done\n" 47 | -------------------------------------------------------------------------------- /test/fdns/nofilter.exp: -------------------------------------------------------------------------------- 1 | #!/usr/bin/expect -f 2 | # This file is part of FDNS project 3 | # Copyright (C) 2019-2025 FDNS Authors 4 | # License GPL v2 5 | 6 | set timeout 10 7 | spawn $env(SHELL) 8 | match_max 100000 9 | 10 | send -- "pkill fdns\r" 11 | sleep 2 12 | 13 | send -- "fdns --nofilter\r" 14 | set server_id $spawn_id 15 | expect { 16 | timeout {puts "TESTING ERROR 0\n";exit} 17 | "fdns starting" 18 | } 19 | expect { 20 | timeout {puts "TESTING ERROR 0.1\n";exit} 21 | "SSL connection opened" 22 | } 23 | sleep 1 24 | 25 | spawn $env(SHELL) 26 | send -- "firejail --dns=127.1.1.1 ping -c 3 doubleclick.net\r" 27 | set ping_id $spawn_id 28 | 29 | spawn $env(SHELL) 30 | set monitor_id $spawn_id 31 | send -- "fdns --monitor\r" 32 | expect { 33 | timeout {puts "TESTING ERROR 1\n";exit} 34 | "doubleclick.net, dropped" {puts "TESTING ERROR 2\n";exit} 35 | "doubleclick.net" 36 | } 37 | after 100 38 | 39 | set spawn_id $ping_id 40 | send -- "pkill fdns\r" 41 | 42 | after 100 43 | puts "\nall done\n" 44 | 45 | -------------------------------------------------------------------------------- /src/fdns/timetrace.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2019-2025 FDNS Authors 3 | * 4 | * This file is part of fdns project 5 | * 6 | * This program is free software: you can redistribute it and/or modify 7 | * it under the terms of the GNU General Public License as published by 8 | * the Free Software Foundation, either version 3 of the License, or 9 | * (at your option) any later version. 10 | * 11 | * This program is distributed in the hope that it will be useful, 12 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14 | * GNU General Public License for more details. 15 | * 16 | * You should have received a copy of the GNU General Public License 17 | * along with this program. If not, see . 18 | */ 19 | #ifndef TIMETRACE_H 20 | #define TIMETRACE_H 21 | 22 | void timetrace_start(void); 23 | float timetrace_end(void); 24 | void init_time_delta(void); 25 | void print_time(void); 26 | 27 | #endif 28 | -------------------------------------------------------------------------------- /test/fdns/help-man.exp: -------------------------------------------------------------------------------- 1 | #!/usr/bin/expect -f 2 | # This file is part of FDNS project 3 | # Copyright (C) 2019-2025 FDNS Authors 4 | # License GPL v2 5 | 6 | set timeout 10 7 | spawn $env(SHELL) 8 | match_max 100000 9 | 10 | send -- "fdns --help\r" 11 | expect { 12 | timeout {puts "TESTING ERROR 0\n";exit} 13 | "fdns - DNS over HTTPS proxy server" 14 | } 15 | after 100 16 | 17 | send -- "fdns -?\r" 18 | expect { 19 | timeout {puts "TESTING ERROR 1\n";exit} 20 | "fdns - DNS over HTTPS proxy server" 21 | } 22 | after 100 23 | 24 | send -- "fdns --version\r" 25 | expect { 26 | timeout {puts "TESTING ERROR 2\n";exit} 27 | "FDNS version" 28 | } 29 | after 100 30 | 31 | send -- "rm -f tmp\r" 32 | after 100 33 | send -- "man fdns > tmp\r" 34 | sleep 2 35 | send -- "cat tmp\r" 36 | expect { 37 | timeout {puts "TESTING ERROR 3\n";exit} 38 | "fdns - Firejail DNS over HTTPS/TLS proxy" 39 | } 40 | after 100 41 | send -- "rm -f tmp" 42 | after 100 43 | send -- "pkill fdns\r" 44 | 45 | after 100 46 | puts "\nall done\n" 47 | 48 | -------------------------------------------------------------------------------- /test/fdns/print-requests.exp: -------------------------------------------------------------------------------- 1 | #!/usr/bin/expect -f 2 | # This file is part of FDNS project 3 | # Copyright (C) 2019-2025 FDNS Authors 4 | # License GPL v2 5 | 6 | set timeout 10 7 | spawn $env(SHELL) 8 | match_max 100000 9 | 10 | send -- "pkill fdns\r" 11 | sleep 2 12 | 13 | send -- "fdns\r" 14 | set server_id $spawn_id 15 | expect { 16 | timeout {puts "TESTING ERROR 0\n";exit} 17 | "fdns starting" 18 | } 19 | expect { 20 | timeout {puts "TESTING ERROR 0.1\n";exit} 21 | "SSL connection opened" 22 | } 23 | sleep 1 24 | 25 | spawn $env(SHELL) 26 | send -- "firejail --dns=127.1.1.1 ping -c 3 google.com\r" 27 | set ping_id $spawn_id 28 | 29 | spawn $env(SHELL) 30 | set monitor_id $spawn_id 31 | send -- "fdns --monitor\r" 32 | expect { 33 | timeout {puts "TESTING ERROR 1\n";exit} 34 | "google.com" 35 | } 36 | expect { 37 | timeout {puts "TESTING ERROR 2\n";exit} 38 | "in-addr.arpa (PTR), dropped" 39 | } 40 | after 100 41 | 42 | set spawn_id $ping_id 43 | send -- "pkill fdns\r" 44 | 45 | after 100 46 | puts "\nall done\n" 47 | -------------------------------------------------------------------------------- /test/fdns/default-wget.exp: -------------------------------------------------------------------------------- 1 | #!/usr/bin/expect -f 2 | # This file is part of FDNS project 3 | # Copyright (C) 2019-2025 FDNS Authors 4 | # License GPL v2 5 | 6 | set timeout 10 7 | spawn $env(SHELL) 8 | match_max 100000 9 | 10 | send -- "pkill fdns\r" 11 | sleep 2 12 | 13 | send -- "fdns --nofilter\r" 14 | expect { 15 | timeout {puts "TESTING ERROR 0\n";exit} 16 | "fdns starting" 17 | } 18 | expect { 19 | timeout {puts "TESTING ERROR 0.1\n";exit} 20 | "SSL connection opened" 21 | } 22 | sleep 1 23 | 24 | spawn $env(SHELL) 25 | send "rm /tmp/index.html\r" 26 | after 100 27 | send -- "firejail --dns=127.1.1.1 wget -O /tmp/index.html google.com\r" 28 | expect { 29 | timeout {puts "TESTING ERROR 1\n";exit} 30 | "Connecting to www.google.com" 31 | } 32 | expect { 33 | timeout {puts "TESTING ERROR 2\n";exit} 34 | "index.html" 35 | } 36 | expect { 37 | timeout {puts "TESTING ERROR 3\n";exit} 38 | "saved" 39 | } 40 | after 100 41 | send "rm /tmp/index.html\r" 42 | send -- "pkill fdns\r" 43 | 44 | after 100 45 | puts "\nall done\n" 46 | -------------------------------------------------------------------------------- /test/fdns/default-nslookup.exp: -------------------------------------------------------------------------------- 1 | #!/usr/bin/expect -f 2 | # This file is part of FDNS project 3 | # Copyright (C) 2019-2025 FDNS Authors 4 | # License GPL v2 5 | 6 | set timeout 10 7 | spawn $env(SHELL) 8 | match_max 100000 9 | 10 | send -- "pkill fdns\r" 11 | sleep 2 12 | 13 | send -- "fdns\r" 14 | expect { 15 | timeout {puts "TESTING ERROR 0\n";exit} 16 | "fdns starting" 17 | } 18 | expect { 19 | timeout {puts "TESTING ERROR 0.1\n";exit} 20 | "SSL connection opened" 21 | } 22 | sleep 1 23 | 24 | spawn $env(SHELL) 25 | send -- "firejail --dns=127.1.1.1 nslookup google.com\r" 26 | expect { 27 | timeout {puts "TESTING ERROR 1\n";exit} 28 | "Address" 29 | } 30 | expect { 31 | timeout {puts "TESTING ERROR 2\n";exit} 32 | "127.1.1.1#53" 33 | } 34 | expect { 35 | timeout {puts "TESTING ERROR 3\n";exit} 36 | "Non-authoritative answer:" 37 | } 38 | expect { 39 | timeout {puts "TESTING ERROR 4\n";exit} 40 | "google.com" 41 | } 42 | expect { 43 | timeout {puts "TESTING ERROR 5\n";exit} 44 | "Address" 45 | } 46 | after 100 47 | send -- "pkill fdns\r" 48 | 49 | after 100 50 | puts "\nall done\n" 51 | -------------------------------------------------------------------------------- /test/fdns/list.exp: -------------------------------------------------------------------------------- 1 | #!/usr/bin/expect -f 2 | # This file is part of FDNS project 3 | # Copyright (C) 2019-2025 FDNS Authors 4 | # License GPL v2 5 | 6 | set timeout 10 7 | spawn $env(SHELL) 8 | match_max 100000 9 | 10 | send -- "fdns --list=America\r" 11 | expect { 12 | timeout {puts "TESTING ERROR 0\n";exit} 13 | "Current zone:" 14 | } 15 | expect { 16 | timeout {puts "TESTING ERROR 1\n";exit} 17 | "adguard" 18 | } 19 | expect { 20 | timeout {puts "TESTING ERROR 2\n";exit} 21 | "dnscrypt-ca" 22 | } 23 | expect { 24 | timeout {puts "TESTING ERROR 3\n";exit} 25 | "nextdns" 26 | } 27 | expect { 28 | timeout {puts "TESTING ERROR 5\n";exit} 29 | "servers found" 30 | } 31 | 32 | send -- "fdns --list\r" 33 | expect { 34 | timeout {puts "TESTING ERROR 6\n";exit} 35 | "Current zone:" 36 | } 37 | expect { 38 | timeout {puts "TESTING ERROR 7\n";exit} 39 | "quad9" 40 | } 41 | expect { 42 | timeout {puts "TESTING ERROR 8\n";exit} 43 | "quad9-2" 44 | } 45 | expect { 46 | timeout {puts "TESTING ERROR 9\n";exit} 47 | "servers found" 48 | } 49 | 50 | 51 | after 100 52 | puts "\nall done\n" 53 | 54 | -------------------------------------------------------------------------------- /test/fdns/restart-worker.exp: -------------------------------------------------------------------------------- 1 | #!/usr/bin/expect -f 2 | # This file is part of FDNS project 3 | # Copyright (C) 2019-2025 FDNS Authors 4 | # License GPL v2 5 | 6 | set timeout 10 7 | spawn $env(SHELL) 8 | match_max 100000 9 | 10 | send -- "pkill fdns\r" 11 | sleep 2 12 | 13 | send -- "fdns\r" 14 | set server_id $spawn_id 15 | expect { 16 | timeout {puts "TESTING ERROR 0\n";exit} 17 | "fdns starting" 18 | } 19 | expect { 20 | timeout {puts "TESTING ERROR 0.1\n";exit} 21 | "SSL connection opened" 22 | } 23 | sleep 1 24 | 25 | spawn $env(SHELL) 26 | set ping_id $spawn_id 27 | 28 | send -- "kill -9 `ps a -o pid,cmd | grep fdns | grep id | awk '{print \$1}' | head -n 1`\r" 29 | after 100 30 | set spawn_id $server_id 31 | 32 | expect { 33 | timeout {puts "TESTING ERROR 1\n";exit} 34 | "resolver" 35 | } 36 | expect { 37 | timeout {puts "TESTING ERROR 2\n";exit} 38 | "terminated, restarting it..." 39 | } 40 | expect { 41 | timeout {puts "TESTING ERROR 3\n";exit} 42 | "SSL connection opened" 43 | } 44 | after 100 45 | 46 | set spawn_id $ping_id 47 | send -- "pkill fdns\r" 48 | 49 | after 100 50 | puts "\nall done\n" 51 | -------------------------------------------------------------------------------- /test/fdns/test-servers.exp: -------------------------------------------------------------------------------- 1 | #!/usr/bin/expect -f 2 | # This file is part of FDNS project 3 | # Copyright (C) 2019-2025 FDNS Authors 4 | # License GPL v2 5 | 6 | set timeout 10 7 | spawn $env(SHELL) 8 | match_max 100000 9 | 10 | send -- "fdns --test-server\r" 11 | expect { 12 | timeout {puts "TESTING ERROR 0\n";exit} 13 | "Testing server" 14 | } 15 | expect { 16 | timeout {puts "TESTING ERROR 1\n";exit} 17 | "SSL/TLS connection" 18 | } 19 | expect { 20 | timeout {puts "TESTING ERROR 2\n";exit} 21 | "query average" 22 | } 23 | expect { 24 | timeout {puts "TESTING ERROR 3\n";exit} 25 | "Testing server" 26 | } 27 | expect { 28 | timeout {puts "TESTING ERROR 4\n";exit} 29 | "SSL/TLS connection" 30 | } 31 | expect { 32 | timeout {puts "TESTING ERROR 5\n";exit} 33 | "query average" 34 | } 35 | expect { 36 | timeout {puts "TESTING ERROR 6\n";exit} 37 | "Testing server" 38 | } 39 | expect { 40 | timeout {puts "TESTING ERROR 7\n";exit} 41 | "SSL/TLS connection" 42 | } 43 | expect { 44 | timeout {puts "TESTING ERROR 8\n";exit} 45 | "query average" 46 | } 47 | 48 | 49 | 50 | 51 | after 100 52 | puts "\nall done\n" 53 | 54 | -------------------------------------------------------------------------------- /test/fdns/ipv6.exp: -------------------------------------------------------------------------------- 1 | #!/usr/bin/expect -f 2 | # This file is part of FDNS project 3 | # Copyright (C) 2019-2025 FDNS Authors 4 | # License GPL v2 5 | 6 | set timeout 10 7 | spawn $env(SHELL) 8 | match_max 100000 9 | 10 | send -- "pkill fdns\r" 11 | sleep 2 12 | 13 | send -- "fdns --ipv6\r" 14 | set server_id $spawn_id 15 | expect { 16 | timeout {puts "TESTING ERROR 0\n";exit} 17 | "fdns starting" 18 | } 19 | expect { 20 | timeout {puts "TESTING ERROR 0.1\n";exit} 21 | "SSL connection opened" 22 | } 23 | sleep 1 24 | 25 | spawn $env(SHELL) 26 | send -- "firejail --dns=127.1.1.1 ping -c 3 google.com\r" 27 | set ping_id $spawn_id 28 | sleep 2 29 | 30 | spawn $env(SHELL) 31 | set monitor_id $spawn_id 32 | send -- "fdns --monitor\r" 33 | expect { 34 | timeout {puts "TESTING ERROR 1\n";exit} 35 | "google.com (ipv6), dropped" {puts "TESTING ERROR 2\n";exit} 36 | "google.com" 37 | } 38 | expect { 39 | timeout {puts "TESTING ERROR 3\n";exit} 40 | "google.com (ipv6), dropped" {puts "TESTING ERROR 4\n";exit} 41 | "google.com (ipv6)" 42 | } 43 | after 100 44 | 45 | set spawn_id $ping_id 46 | send -- "pkill fdns\r" 47 | 48 | after 100 49 | puts "\nall done\n" 50 | 51 | 52 | -------------------------------------------------------------------------------- /test/fdns/wget.exp: -------------------------------------------------------------------------------- 1 | #!/usr/bin/expect -f 2 | # This file is part of FDNS project 3 | # Copyright (C) 2019-2025 FDNS Authors 4 | # License GPL v2 5 | 6 | set timeout 10 7 | spawn $env(SHELL) 8 | match_max 100000 9 | 10 | if { $argc != 1 } { 11 | puts "TESTING ERROR: argument missing" 12 | puts "Usage: wget.exp server-name" 13 | exit 14 | } 15 | 16 | puts "TESTING: $argv" 17 | 18 | send -- "pkill fdns\r" 19 | sleep 2 20 | 21 | send -- "fdns --server=$argv\r" 22 | expect { 23 | timeout {puts "TESTING ERROR 0\n";exit} 24 | "fdns starting" 25 | } 26 | expect { 27 | timeout {puts "TESTING ERROR 0.2\n";exit} 28 | "SSL connection opened" 29 | } 30 | sleep 1 31 | 32 | spawn $env(SHELL) 33 | send "rm /tmp/index.html\r" 34 | after 100 35 | send -- "firejail --dns=127.1.1.1 wget -O /tmp/index.html google.com\r" 36 | expect { 37 | timeout {puts "TESTING ERROR 1\n";exit} 38 | "Connecting to www.google.com" 39 | } 40 | expect { 41 | timeout {puts "TESTING ERROR 2\n";exit} 42 | "index.html" 43 | } 44 | expect { 45 | timeout {puts "TESTING ERROR 3\n";exit} 46 | "saved" 47 | } 48 | after 100 49 | send "rm /tmp/index.html\r" 50 | send -- "pkill fdns\r" 51 | 52 | after 100 53 | puts "\nall done\n" 54 | -------------------------------------------------------------------------------- /gcov.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | # This file is part of Firejail project 3 | # Copyright (C) 2014-2020 Firejail Authors 4 | # License GPL v2 5 | 6 | gcov_init() { 7 | USER=`whoami` 8 | fdns --help > /dev/null 9 | sudo chown $USER:$USER `find .` 10 | } 11 | 12 | generate() { 13 | lcov -q --capture -d src/fdns --output-file gcov-file-new 14 | lcov --add-tracefile gcov-file-old --add-tracefile gcov-file-new --output-file gcov-file 15 | rm -fr gcov-dir 16 | genhtml -q gcov-file --output-directory gcov-dir 17 | sudo rm `find . -name *.gcda` 18 | cp gcov-file gcov-file-old 19 | gcov_init 20 | } 21 | 22 | 23 | # disable apparmor temporarily 24 | sudo apparmor_parser -R /etc/apparmor.d/usr.bin.fdns 2>&1 > /dev/null 25 | gcov_init 26 | lcov -q --capture -d src/fdns --output-file gcov-file-old 27 | 28 | sudo test/fdns/forwarder.exp 29 | generate 30 | sleep 2 31 | 32 | sudo test/fdns/whitelist.exp 33 | generate 34 | sleep 2 35 | 36 | sudo test/fdns/whitelist-file.exp 37 | generate 38 | sleep 2 39 | 40 | sudo test/fdns/test-url-list.exp 41 | generate 42 | sleep 2 43 | 44 | make test 45 | generate 46 | sleep 2 47 | 48 | # enable apparmor back 49 | sudo apparmor_parser -r /etc/apparmor.d/usr.bin.fdns 2>&1 > /dev/null 50 | 51 | 52 | -------------------------------------------------------------------------------- /src/man/dnsc.txt: -------------------------------------------------------------------------------- 1 | .TH DNSC 1 "MONTH YEAR" "VERSION" "dnsc man page" 2 | .SH NAME 3 | nxdomain \- utility program for cleaning and compressing wildcard domain lists 4 | .SH SYNOPSIS 5 | .PP 6 | dnsc [options] hosts-file [hosts-file] 7 | 8 | .SH DESCRIPTION 9 | dnsc is a program for cleaning and compressing wildcard domain lists in /etc/hosts format. 10 | 11 | .SH OPTIONS 12 | .TP 13 | \fB\-\-cnt=number 14 | Above this number, the domain is reported in the short list. 15 | By default we use 0.1% of the number of input domains. 16 | 17 | .TP 18 | \fB\-\-debug 19 | Print debug info 20 | 21 | .TP 22 | \fB\-\-help, \-?, \-h 23 | Print command-line options and exit. 24 | 25 | .TP 26 | \fB\-\-short 27 | Print only the short list. 28 | 29 | .SH EXAMPLE 30 | $ dnscn openphish.feed 31 | 32 | .SH LICENSE 33 | This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 3 of the License, or (at your option) any later version. 34 | .PP 35 | Homepage: https://firejaildns.wordpress.com 36 | .br 37 | Development: https://github.com/netblue30/fdns 38 | 39 | .SH SEE ALSO 40 | .BR fdns (1), 41 | .BR nxdomain (1) 42 | 43 | 44 | -------------------------------------------------------------------------------- /platform/arch/PKGBUILD: -------------------------------------------------------------------------------- 1 | # Maintainer: Helle Vaanzinn < glitsj16 AT riseup DOT net > 2 | 3 | pkgname=fdns 4 | pkgver=0.9.62.8 5 | pkgrel=1 6 | pkgdesc="Firejail DNS-over-HTTPS proxy server" 7 | arch=(x86_64) 8 | license=(GPL2) 9 | url="https://github.com/netblue30/fdns" 10 | depends=('libseccomp' 'openssl') 11 | optdepends=('apparmor: support for apparmor profiles' 12 | 'bash-completion: bash completion' 13 | 'firejail: seamless integration support' 14 | 'systemd: run fdns as a systemd service') 15 | validpgpkeys=('F951164995F5C4006A73411E2CCB36ADFC5849A7') 16 | source=("https://github.com/netblue30/fdns/releases/download/v${pkgver}/${pkgname}-${pkgver}.tar.xz"{,.asc}) 17 | sha256sums=('92195841f34ac916474757a4cf62ae82207b8707d767fb22f72f34f99987d463' 18 | 'SKIP') 19 | 20 | build() { 21 | cd "${srcdir}/${pkgname}-${pkgver}" 22 | ./configure --prefix=/usr 23 | make 24 | } 25 | 26 | package() { 27 | cd "${srcdir}/${pkgname}-${pkgver}" 28 | make DESTDIR="$pkgdir" install 29 | 30 | # license 31 | install -Dm0644 "${pkgdir}/usr/share/doc/${pkgname}/COPYING" \ 32 | "${pkgdir}/usr/share/licenses/${pkgname}/LICENSE" 33 | rm -f "${pkgdir}/usr/share/doc/${pkgname}/COPYING" 34 | } 35 | -------------------------------------------------------------------------------- /platform/fedora/fdns-local.spec: -------------------------------------------------------------------------------- 1 | Name: fdns 2 | Version: 0.9.69 3 | Release: 1.git%{?dist} 4 | Summary: Firejail DNS-over-HTTPS Proxy Server 5 | 6 | License: GPLv3+ 7 | URL: https://github.com/netblue30/fdns 8 | Source0: fdns-%{version}.tar.xz 9 | 10 | BuildRequires: gcc 11 | BuildRequires: git 12 | BuildRequires: libseccomp-devel 13 | BuildRequires: openssl-devel 14 | BuildRequires: systemd-rpm-macros 15 | 16 | %description 17 | fdns is a DNS-over-HTTPS proxy server targeted at small networks and 18 | Linux desktops. To speed up the name resolution fdns caches the responses, 19 | and uses a configurable adblocker and privacy filter to cut down 20 | unnecessary traffic. The software is written in C, and is licensed under GPLv3. 21 | 22 | 23 | %prep 24 | %autosetup -S git 25 | 26 | 27 | %build 28 | %configure --with-systemd=%{_unitdir} 29 | %make_build 30 | 31 | 32 | %install 33 | make install DESTDIR=$RPM_BUILD_ROOT 34 | 35 | 36 | %files 37 | %license COPYING 38 | %doc COPYING README RELNOTES 39 | %{_bindir}/fdns 40 | %{_bindir}/nxdomain 41 | %config %{_sysconfdir}/fdns 42 | %{_unitdir}/fdns.service 43 | %{_datadir}/bash-completion/completions/fdns 44 | %{_mandir}/man1/fdns.1.gz 45 | %{_mandir}/man1/nxdomain.1.gz 46 | -------------------------------------------------------------------------------- /test/fdns/whitelist.exp: -------------------------------------------------------------------------------- 1 | #!/usr/bin/expect -f 2 | # This file is part of FDNS project 3 | # Copyright (C) 2019-2025 FDNS Authors 4 | # License GPL v2 5 | 6 | set timeout 10 7 | spawn $env(SHELL) 8 | match_max 100000 9 | 10 | send -- "pkill fdns\r" 11 | sleep 2 12 | 13 | send -- "fdns --whitelist=gentoo.org --whitelist=assets.gentoo.org --whitelist=security.gentoo.orgfdns\r" 14 | expect { 15 | timeout {puts "TESTING ERROR 0\n";exit} 16 | "fdns starting" 17 | } 18 | expect { 19 | timeout {puts "TESTING ERROR 0.1\n";exit} 20 | "SSL connection opened" 21 | } 22 | sleep 1 23 | 24 | spawn $env(SHELL) 25 | send -- "firejail --dns=127.1.1.1 ping -c 3 assets.gentoo.org\r" 26 | expect { 27 | timeout {puts "TESTING ERROR 1\n";exit} 28 | "icmp_seq=1" 29 | } 30 | expect { 31 | timeout {puts "TESTING ERROR 2\n";exit} 32 | "icmp_seq=2" 33 | } 34 | expect { 35 | timeout {puts "TESTING ERROR 3\n";exit} 36 | "icmp_seq=3" 37 | } 38 | expect { 39 | timeout {puts "TESTING ERROR 4\n";exit} 40 | "3 received" 41 | } 42 | sleep 1 43 | 44 | send -- "firejail --dns=127.1.1.1 ping -c 3 google.com\r" 45 | expect { 46 | timeout {puts "TESTING ERROR 5\n";exit} 47 | "Name or service not known" 48 | } 49 | 50 | sleep 1 51 | send -- "pkill fdns\r" 52 | 53 | after 100 54 | puts "\nall done\n" 55 | 56 | 57 | -------------------------------------------------------------------------------- /src/fdns/hpack_static.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2019-2025 FDNS Authors 3 | * 4 | * This file is part of fdns project based on Dridi Boukelmoune implementation, 5 | * see below. 6 | * 7 | * This program is free software: you can redistribute it and/or modify 8 | * it under the terms of the GNU General Public License as published by 9 | * the Free Software Foundation, either version 3 of the License, or 10 | * (at your option) any later version. 11 | * 12 | * This program is distributed in the hope that it will be useful, 13 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 14 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 15 | * GNU General Public License for more details. 16 | * 17 | * You should have received a copy of the GNU General Public License 18 | * along with this program. If not, see . 19 | */ 20 | /*- 21 | * Written by Dridi Boukelmoune 22 | * 23 | * This file is in the public domain. 24 | * 25 | * HPACK: Static Table Definition (RFC 7540 Appendix A) 26 | */ 27 | #ifndef HPACK_STATIC_H 28 | #define HPACK_STATIC_H 29 | 30 | typedef struct hpac_static_t { 31 | char * name; 32 | char *value; 33 | } HpackStatic; 34 | 35 | HpackStatic *hpack_static_get(unsigned id); 36 | 37 | #endif 38 | -------------------------------------------------------------------------------- /platform/fedora/fdns-stable.spec: -------------------------------------------------------------------------------- 1 | Name: fdns 2 | Version: 0.9.68 3 | Release: 1%{?dist} 4 | Summary: Firejail DNS-over-HTTPS Proxy Server 5 | 6 | License: GPLv3+ 7 | URL: https://github.com/netblue30/fdns 8 | Source0: https://github.com/netblue30/fdns/archive/v%{version}.tar.gz 9 | 10 | BuildRequires: gcc 11 | BuildRequires: git 12 | BuildRequires: libseccomp-devel 13 | BuildRequires: openssl-devel 14 | BuildRequires: systemd-rpm-macros 15 | 16 | %description 17 | fdns is a DNS-over-HTTPS proxy server targeted at small networks and 18 | Linux desktops. To speed up the name resolution fdns caches the responses, 19 | and uses a configurable adblocker and privacy filter to cut down 20 | unnecessary traffic. The software is written in C, and is licensed under GPLv3. 21 | 22 | 23 | %prep 24 | %autosetup -S git 25 | 26 | 27 | %build 28 | %configure --with-systemd=%{_unitdir} 29 | %make_build 30 | 31 | 32 | %install 33 | make install DESTDIR=$RPM_BUILD_ROOT 34 | 35 | 36 | %files 37 | %license COPYING 38 | %doc COPYING README RELNOTES 39 | %{_bindir}/fdns 40 | %{_bindir}/nxdomain 41 | %config %{_sysconfdir}/fdns 42 | %{_unitdir}/fdns.service 43 | %{_datadir}/bash-completion/completions/fdns 44 | %{_mandir}/man1/fdns.1.gz 45 | %{_mandir}/man1/nxdomain.1.gz 46 | -------------------------------------------------------------------------------- /platform/fedora/fdns-git.spec: -------------------------------------------------------------------------------- 1 | Name: fdns 2 | Version: 0.9.69 3 | Release: 1.git%{?dist} 4 | Summary: Firejail DNS-over-HTTPS Proxy Server 5 | 6 | License: GPLv3+ 7 | URL: https://github.com/netblue30/fdns 8 | Source0: https://github.com/netblue30/fdns/archive/master.tar.gz 9 | 10 | BuildRequires: gcc 11 | BuildRequires: git 12 | BuildRequires: libseccomp-devel 13 | BuildRequires: openssl-devel 14 | BuildRequires: systemd-rpm-macros 15 | 16 | %description 17 | fdns is a DNS-over-HTTPS proxy server targeted at small networks and 18 | Linux desktops. To speed up the name resolution fdns caches the responses, 19 | and uses a configurable adblocker and privacy filter to cut down 20 | unnecessary traffic. The software is written in C, and is licensed under GPLv3. 21 | 22 | 23 | %prep 24 | %autosetup -n %{name}-master -S git 25 | 26 | 27 | %build 28 | %configure --with-systemd=%{_unitdir} 29 | %make_build 30 | 31 | 32 | %install 33 | make install DESTDIR=$RPM_BUILD_ROOT 34 | 35 | 36 | %files 37 | %license COPYING 38 | %doc COPYING README RELNOTES 39 | %{_bindir}/fdns 40 | %{_bindir}/nxdomain 41 | %config %{_sysconfdir}/fdns 42 | %{_unitdir}/fdns.service 43 | %{_datadir}/bash-completion/completions/fdns 44 | %{_mandir}/man1/fdns.1.gz 45 | %{_mandir}/man1/nxdomain.1.gz 46 | -------------------------------------------------------------------------------- /test/fdns/multiserver.exp: -------------------------------------------------------------------------------- 1 | #!/usr/bin/expect -f 2 | # This file is part of FDNS project 3 | # Copyright (C) 2019-2025 FDNS Authors 4 | # License GPL v2 5 | 6 | set timeout 10 7 | spawn $env(SHELL) 8 | match_max 100000 9 | 10 | send -- "pkill fdns\r" 11 | sleep 2 12 | 13 | send -- "fdns --daemonize --nofilter\r" 14 | sleep 6 15 | send -- "fdns --proxy-addr=127.2.2.2 --daemonize --nofilter\r" 16 | sleep 6 17 | 18 | send -- "fdns --proxies\r" 19 | expect { 20 | timeout {puts "TESTING ERROR 1\n";exit} 21 | "127.1.1.1" 22 | } 23 | sleep 1 24 | 25 | send -- "fdns --proxies\r" 26 | expect { 27 | timeout {puts "TESTING ERROR 2\n";exit} 28 | "127.2.2.2" 29 | } 30 | 31 | sleep 2 32 | send -- "firejail --dns=127.1.1.1 ping -c 3 google.com\r" 33 | expect { 34 | timeout {puts "TESTING ERROR 4\n";exit} 35 | "3 received" 36 | } 37 | 38 | sleep 2 39 | send -- "firejail --dns=127.2.2.2 ping -c 3 google.com\r" 40 | expect { 41 | timeout {puts "TESTING ERROR 5\n";exit} 42 | "3 received" 43 | } 44 | 45 | set main_id $spawn_id 46 | sleep 2 47 | 48 | spawn $env(SHELL) 49 | send -- "fdns --monitor=127.2.2.2\r" 50 | expect { 51 | timeout {puts "TESTING ERROR 7\n";exit} 52 | "google.com, encrypted" 53 | } 54 | after 100 55 | 56 | set spawn_id $main_id 57 | send -- "pkill fdns\r" 58 | 59 | after 100 60 | puts "\nall done\n" 61 | 62 | 63 | -------------------------------------------------------------------------------- /platform/debian/copyright: -------------------------------------------------------------------------------- 1 | 2 | This is the Debian/Ubuntu prepackaged version of fdns. 3 | 4 | fdns is a DNS-over-HTTPS proxy server targeted at small networks and 5 | Linux desktops. To speed up the name resolution fdns caches the responses, 6 | and uses a configurable adblocker and privacy filter to cut down the 7 | unnecessary traffic. The software is written in C, and is licensed under GPLv3. 8 | 9 | Copyright (C) 2019-2025 FDNS Authors (see README file for more details) 10 | 11 | This program is free software; you can redistribute it and/or modify 12 | it under the terms of the GNU General Public License as published by 13 | the Free Software Foundation; either version 3 of the License, or 14 | (at your option) any later version. 15 | 16 | This program is distributed in the hope that it will be useful, 17 | but WITHOUT ANY WARRANTY; without even the implied warranty of 18 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 19 | GNU General Public License for more details. 20 | 21 | You should have received a copy of the GNU General Public License 22 | along with this program. If not, see . 23 | 24 | The complete text of the GNU General Public License can be found 25 | in /usr/share/common-licenses/GPL-3. 26 | 27 | Homepage: https://github.com/netblue30/fdns. 28 | -------------------------------------------------------------------------------- /etc/apparmor/usr.bin.fdns: -------------------------------------------------------------------------------- 1 | # vim:syntax=apparmor 2 | #include 3 | 4 | /usr/bin/fdns (attach_disconnected) { 5 | #include 6 | #include 7 | #include 8 | #include 9 | 10 | capability kill, 11 | capability net_bind_service, 12 | capability setgid, 13 | capability setuid, 14 | capability sys_admin, 15 | capability sys_chroot, 16 | 17 | signal send set=kill peer=/usr/bin/fdns//null-/usr/bin/fdns, 18 | 19 | /usr/bin/fdns mrix, 20 | 21 | # we need to be able to create /dev/shm/fdns-stats file 22 | /dev/** rw, 23 | 24 | # we need to be able to create /run/fdns directory and inspect /proc 25 | /run/** rw, 26 | /proc/** r, 27 | 28 | /etc/fdns/** r, 29 | 30 | # we need to be able to read these (symlinks) - Arch Linux 31 | /etc/ca-certificates/extracted/tls-ca-bundle.pem r, 32 | /var/db/nscd/hosts r, 33 | /var/db/nscd/passwd r, 34 | 35 | # mount required for setting up the mount namespace in the resolver processes - Debian 10 36 | mount, 37 | 38 | # problems on Ubuntu 18.04, unix (bind) is required - allow all unix socket functionality 39 | unix, 40 | 41 | deny ptrace (r, rw, read, readby, trace, tracedby), 42 | 43 | # Site-specific additions and overrides. See local/README for details. 44 | #include 45 | } 46 | -------------------------------------------------------------------------------- /src/common.mk.in: -------------------------------------------------------------------------------- 1 | # common definitions for all makefiles 2 | 3 | CC=@CC@ 4 | prefix=@prefix@ 5 | exec_prefix=@exec_prefix@ 6 | libdir=@libdir@ 7 | sysconfdir=@sysconfdir@ 8 | 9 | VERSION=@PACKAGE_VERSION@ 10 | NAME=@PACKAGE_NAME@ 11 | HAVE_FATAL_WARNINGS=@HAVE_FATAL_WARNINGS@ 12 | HAVE_GCOV=@HAVE_GCOV@ 13 | HAVE_SECCOMP=@HAVE_SECCOMP@ 14 | 15 | H_FILE_LIST = $(sort $(wildcard *.[h])) 16 | C_FILE_LIST = $(sort $(wildcard *.c)) 17 | OBJS = $(C_FILE_LIST:.c=.o) 18 | BINOBJS = $(foreach file, $(OBJS), $file) 19 | 20 | ifdef HAVE_GCOV 21 | CFLAGS += -ggdb $(HAVE_FATAL_WARNINGS) -DVERSION='"$(VERSION)"' $(HAVE_GCOV) $(HAVE_SECCOMP) -DPREFIX='"$(prefix)"' -DSYSCONFDIR='"$(sysconfdir)/fdns"' -DLIBDIR='"$(libdir)"' -fstack-protector-all -D_FORTIFY_SOURCE=2 -fPIE -pie -Wformat -Wformat-security 22 | else 23 | CFLAGS += -ggdb $(HAVE_FATAL_WARNINGS) -O2 -DVERSION='"$(VERSION)"' $(HAVE_GCOV) $(HAVE_SECCOMP) -DPREFIX='"$(prefix)"' -DSYSCONFDIR='"$(sysconfdir)/fdns"' -DLIBDIR='"$(libdir)"' -fstack-protector-all -D_FORTIFY_SOURCE=2 -fPIE -pie -Wformat -Wformat-security 24 | endif 25 | 26 | ifdef HAVE_SECCOMP 27 | LDFLAGS += -pie -Wl,-z,relro -Wl,-z,now -lpthread -lseccomp 28 | else 29 | LDFLAGS += -pie -Wl,-z,relro -Wl,-z,now -lpthread 30 | endif 31 | 32 | EXTRA_LDFLAGS +=@EXTRA_LDFLAGS@ 33 | 34 | ifdef NO_EXTRA_CFLAGS 35 | else 36 | EXTRA_CFLAGS +=@EXTRA_CFLAGS@ 37 | endif -------------------------------------------------------------------------------- /test/fdns/forwarder.exp: -------------------------------------------------------------------------------- 1 | #!/usr/bin/expect -f 2 | # This file is part of FDNS project 3 | # Copyright (C) 2019-2025 FDNS Authors 4 | # License GPL v2 5 | 6 | set timeout 10 7 | spawn $env(SHELL) 8 | match_max 100000 9 | 10 | send -- "pkill fdns\r" 11 | sleep 2 12 | 13 | send -- "fdns --daemonize --forwarder=gentoo.org@127.2.2.2 --nofilter\r" 14 | sleep 6 15 | send -- "fdns --proxy-addr=127.2.2.2 --daemonize --nofilter\r" 16 | sleep 6 17 | 18 | send -- "fdns --proxies\r" 19 | expect { 20 | timeout {puts "TESTING ERROR 1\n";exit} 21 | "127.1.1.1" 22 | } 23 | sleep 1 24 | 25 | send -- "fdns --proxies\r" 26 | expect { 27 | timeout {puts "TESTING ERROR 2\n";exit} 28 | "127.2.2.2" 29 | } 30 | 31 | sleep 2 32 | send -- "firejail --dns=127.1.1.1 ping -c 3 assets.gentoo.org\r" 33 | expect { 34 | timeout {puts "TESTING ERROR 3\n";exit} 35 | "3 received" 36 | } 37 | 38 | 39 | set main_id $spawn_id 40 | sleep 2 41 | spawn $env(SHELL) 42 | send -- "fdns --monitor\r" 43 | expect { 44 | timeout {puts "TESTING ERROR 4\n";exit} 45 | "assets.gentoo.org, forwarded" 46 | } 47 | after 100 48 | 49 | 50 | sleep 1 51 | spawn $env(SHELL) 52 | send -- "fdns --monitor=127.2.2.2\r" 53 | expect { 54 | timeout {puts "TESTING ERROR 7\n";exit} 55 | "assets.gentoo.org, encrypted" 56 | } 57 | after 100 58 | 59 | 60 | set spawn_id $main_id 61 | send -- "pkill fdns\r" 62 | 63 | after 100 64 | puts "\nall done\n" 65 | 66 | 67 | -------------------------------------------------------------------------------- /src/bash_completion/fdns.bash_completion: -------------------------------------------------------------------------------- 1 | # bash completion for fdns -*- shell-script -*- 2 | #******************************************************************** 3 | # Script based on completions/configure script in bash-completion package in 4 | # Debian. The original package is release under GPL v2 license, the webpage is 5 | # http://bash-completion.alioth.debian.org 6 | #******************************************************************* 7 | 8 | _fdns() 9 | { 10 | local cur prev words cword split 11 | _init_completion -s || return 12 | 13 | case $prev in 14 | --help|--version) 15 | return 16 | ;; 17 | esac 18 | 19 | $split && return 0 20 | 21 | # if $COMP_CONFIGURE_HINTS is not null, then completions of the form 22 | # --option=SETTING will include 'SETTING' as a contextual hint 23 | [[ "$cur" != -* ]] && return 0 24 | 25 | if [[ -n $COMP_CONFIGURE_HINTS ]]; then 26 | COMPREPLY=( $( compgen -W "$( $1 --help 2>&1 | \ 27 | awk '/^ --[A-Za-z]/ { print $1; \ 28 | if ($2 ~ /--[A-Za-z]/) print $2 }' | sed -e 's/[[,].*//g' )" \ 29 | -- "$cur" ) ) 30 | [[ $COMPREPLY == *=* ]] && compopt -o nospace 31 | else 32 | COMPREPLY=( $( compgen -W '$( _parse_help "$1" )' -- "$cur" ) ) 33 | [[ $COMPREPLY == *= ]] && compopt -o nospace 34 | fi 35 | } && 36 | complete -F _fdns fdns 37 | -------------------------------------------------------------------------------- /test/fdns/whitelist-file.exp: -------------------------------------------------------------------------------- 1 | #!/usr/bin/expect -f 2 | # This file is part of FDNS project 3 | # Copyright (C) 2019-2025 FDNS Authors 4 | # License GPL v2 5 | 6 | set timeout 10 7 | spawn $env(SHELL) 8 | match_max 100000 9 | 10 | send -- "pkill fdns\r" 11 | sleep 2 12 | 13 | send -- "rm /etc/fdns/whitelist-fdns-test\r" 14 | after 100 15 | send -- "cp whitelist-fdns-test /etc/fdns/.\r" 16 | after 100 17 | 18 | send -- "fdns --whitelist-file=/etc/fdns/whitelist-fdns-test\r" 19 | expect { 20 | timeout {puts "TESTING ERROR 0\n";exit} 21 | "fdns starting" 22 | } 23 | expect { 24 | timeout {puts "TESTING ERROR 0.1\n";exit} 25 | "SSL connection opened" 26 | } 27 | sleep 3 28 | 29 | spawn $env(SHELL) 30 | send -- "firejail --dns=127.1.1.1 ping -c 3 assets.gentoo.org\r" 31 | expect { 32 | timeout {puts "TESTING ERROR 1\n";exit} 33 | "icmp_seq=1" 34 | } 35 | expect { 36 | timeout {puts "TESTING ERROR 2\n";exit} 37 | "icmp_seq=2" 38 | } 39 | expect { 40 | timeout {puts "TESTING ERROR 3\n";exit} 41 | "icmp_seq=3" 42 | } 43 | expect { 44 | timeout {puts "TESTING ERROR 4\n";exit} 45 | "3 received" 46 | } 47 | sleep 1 48 | 49 | send -- "firejail --dns=127.1.1.1 ping -c 3 google.com\r" 50 | expect { 51 | timeout {puts "TESTING ERROR 5\n";exit} 52 | "Name or service not known" 53 | } 54 | 55 | sleep 1 56 | send -- "pkill fdns\r" 57 | after 100 58 | send -- "rm /etc/fdns/whitelist-fdns-test\r" 59 | 60 | after 100 61 | puts "\nall done\n" 62 | 63 | 64 | -------------------------------------------------------------------------------- /test/fdns/workers.exp: -------------------------------------------------------------------------------- 1 | #!/usr/bin/expect -f 2 | # This file is part of FDNS project 3 | # Copyright (C) 2019-2025 FDNS Authors 4 | # License GPL v2 5 | 6 | set timeout 10 7 | spawn $env(SHELL) 8 | match_max 100000 9 | 10 | send -- "pkill fdns\r" 11 | sleep 2 12 | 13 | send -- "fdns --resolvers=10\r" 14 | expect { 15 | timeout {puts "TESTING ERROR 0\n";exit} 16 | "fdns starting" 17 | } 18 | expect { 19 | timeout {puts "TESTING ERROR 1\n";exit} 20 | "SSL connection opened" 21 | } 22 | expect { 23 | timeout {puts "TESTING ERROR 2\n";exit} 24 | "SSL connection opened" 25 | } 26 | expect { 27 | timeout {puts "TESTING ERROR 3\n";exit} 28 | "SSL connection opened" 29 | } 30 | expect { 31 | timeout {puts "TESTING ERROR 4\n";exit} 32 | "SSL connection opened" 33 | } 34 | expect { 35 | timeout {puts "TESTING ERROR 5\n";exit} 36 | "SSL connection opened" 37 | } 38 | expect { 39 | timeout {puts "TESTING ERROR 6\n";exit} 40 | "SSL connection opened" 41 | } 42 | expect { 43 | timeout {puts "TESTING ERROR 7\n";exit} 44 | "SSL connection opened" 45 | } 46 | expect { 47 | timeout {puts "TESTING ERROR 8\n";exit} 48 | "SSL connection opened" 49 | } 50 | expect { 51 | timeout {puts "TESTING ERROR 9\n";exit} 52 | "SSL connection opened" 53 | } 54 | expect { 55 | timeout {puts "TESTING ERROR 10\n";exit} 56 | "SSL connection opened" 57 | } 58 | sleep 1 59 | 60 | spawn $env(SHELL) 61 | send -- "pkill fdns\r" 62 | 63 | after 100 64 | puts "\nall done\n" 65 | -------------------------------------------------------------------------------- /test/fdns/test-user.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | # This file is part of fdns project 3 | # Copyright (C) 2019-2025 FDNS Authors 4 | # License GPL v2 5 | 6 | export MALLOC_CHECK_=3 7 | export MALLOC_PERTURB_=$(($RANDOM % 255 + 1)) 8 | 9 | echo "TESTING: help/man (test/fdns/help-man.exp)" 10 | ./help-man.exp 11 | rm -f tmp 12 | 13 | echo "TESTING: list (test/fdns/list.exp)" 14 | ./list.exp 15 | 16 | echo "TESTING: unlisted (test/fdns/unlisted.exp)" 17 | ./unlisted.exp 18 | 19 | echo "TESTING: list=anycast (test/fdns/list-anycast.exp)" 20 | ./list-anycast.exp 21 | 22 | echo "TESTING: list=all (test/fdns/list-all.exp)" 23 | ./list-all.exp 24 | 25 | echo "TESTING: list=family (test/fdns/list-family.exp)" 26 | ./list-family.exp 27 | 28 | echo "TESTING: list=security (test/fdns/list-security.exp)" 29 | ./list-security.exp 30 | 31 | echo "TESTING: list=adblocker (test/fdns/list-adblocker.exp)" 32 | ./list-adblocker.exp 33 | 34 | echo "TESTING: test-url (test/fdns/test-url.exp)" 35 | ./test-url.exp 36 | 37 | echo "TESTING: test-url-list (test/fdns/test-url-list.exp)" 38 | ./test-url-list.exp 39 | 40 | echo "TESTING: test-servers=anycast (test/fdns/test-servers-anycast.exp)" 41 | ./test-servers-anycast.exp 42 | 43 | echo "TESTING: test-servers (test/fdns/test-servers.exp)" 44 | ./test-servers.exp 45 | 46 | echo "TESTING: monitor (test/fdns/monitor.exp)" 47 | ./monitor.exp 48 | 49 | # todo: fix it! 50 | # echo "TESTING: LAN rx packet (test/fdns/ptest.exp)" 51 | # ./ptest.exp 52 | -------------------------------------------------------------------------------- /test/fdns/fallback.exp: -------------------------------------------------------------------------------- 1 | #!/usr/bin/expect -f 2 | # This file is part of FDNS project 3 | # Copyright (C) 2019-2025 FDNS Authors 4 | # License GPL v2 5 | 6 | set timeout 10 7 | spawn $env(SHELL) 8 | match_max 100000 9 | 10 | send -- "pkill fdns\r" 11 | sleep 2 12 | 13 | send -- "fdns --fallback-only --debug --resolvers=1 --details\r" 14 | expect { 15 | timeout {puts "TESTING ERROR 0\n";exit} 16 | "fdns starting" 17 | } 18 | expect { 19 | timeout {puts "TESTING ERROR 0.1\n";exit} 20 | "filter entries added" 21 | } 22 | sleep 1 23 | 24 | spawn $env(SHELL) 25 | send -- "firejail --dns=127.1.1.1 ping -c 3 gentoo.org\r" 26 | expect { 27 | timeout {puts "TESTING ERROR 1\n";exit} 28 | "icmp_seq=1" 29 | } 30 | expect { 31 | timeout {puts "TESTING ERROR 2\n";exit} 32 | "icmp_seq=2" 33 | } 34 | expect { 35 | timeout {puts "TESTING ERROR 3\n";exit} 36 | "icmp_seq=3" 37 | } 38 | expect { 39 | timeout {puts "TESTING ERROR 4\n";exit} 40 | "3 received" 41 | } 42 | 43 | sleep 1 44 | spawn $env(SHELL) 45 | send -- "firejail --dns=127.1.1.1 ping -c 3 assets.gentoo.org\r" 46 | expect { 47 | timeout {puts "TESTING ERROR 1\n";exit} 48 | "icmp_seq=1" 49 | } 50 | expect { 51 | timeout {puts "TESTING ERROR 2\n";exit} 52 | "icmp_seq=2" 53 | } 54 | expect { 55 | timeout {puts "TESTING ERROR 3\n";exit} 56 | "icmp_seq=3" 57 | } 58 | expect { 59 | timeout {puts "TESTING ERROR 4\n";exit} 60 | "3 received" 61 | } 62 | 63 | sleep 1 64 | send -- "pkill fdns\r" 65 | 66 | after 100 67 | puts "\nall done\n" 68 | 69 | 70 | -------------------------------------------------------------------------------- /src/fdns/restart.c: -------------------------------------------------------------------------------- 1 | /* 2 | *Copyright (C) 2019-2025 FDNS Authors 3 | * 4 | *This file is part of fdns project 5 | * 6 | *This program is free software: 7 | you can redistribute it and / or modify 8 | *it under the terms of the GNU General Public License as published by 9 | *the Free Software Foundation, either version 3 of the License, or 10 | *(at your option) any later version. 11 | * 12 | *This program is distributed in the hope that it will be useful, 13 | * but WITHOUT ANY WARRANTY; 14 | without even the implied warranty of 15 | *MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 16 | *GNU General Public License for more details. 17 | * 18 | *You should have received a copy of the GNU General Public License 19 | *along with this program. If not, see < https : //www.gnu.org/licenses/>. 20 | */ 21 | #include "fdns.h" 22 | #include 23 | 24 | void restart(void) { 25 | // find default proxy pid 26 | pid_t default_pid = 0; 27 | procs_list(&default_pid); 28 | if (!default_pid) { 29 | fprintf(stderr, "Error: no default proxy found\n"); 30 | exit(1); 31 | } 32 | 33 | // extract command line 34 | char *fname; 35 | if (asprintf(&fname, "/proc/%d/cmdline", default_pid) == -1) 36 | errExit("asprintf"); 37 | FILE *fp = fopen(fname, "r"); 38 | if (!fp) { 39 | fprintf(stderr, "Error: cannot open %s file\n", fname); 40 | exit(1); 41 | } 42 | 43 | fprintf(stderr, "Restarting %d proxy\n", default_pid); 44 | int rv = kill(default_pid, SIGUSR1); 45 | if (rv) { 46 | fprintf(stderr, "Error: cannot kill existing default proxy, not enough permissions\n"); 47 | exit(1); 48 | } 49 | 50 | free(fname); 51 | fclose(fp); 52 | } 53 | -------------------------------------------------------------------------------- /test/fdns/keepalive.exp: -------------------------------------------------------------------------------- 1 | #!/usr/bin/expect -f 2 | # This file is part of FDNS project 3 | # Copyright (C) 2019-2025 FDNS Authors 4 | # License GPL v2 5 | 6 | set timeout 20 7 | spawn $env(SHELL) 8 | match_max 100000 9 | 10 | send -- "pkill fdns\r" 11 | sleep 2 12 | 13 | send -- "fdns --server=https://dns.quad9.net/dns-query --fallback-server=9.9.9.9 --proxy-addr=127.9.9.9 --keepalive=7\r" 14 | expect { 15 | timeout {puts "TESTING ERROR 0\n";exit} 16 | "fdns starting" 17 | } 18 | expect { 19 | timeout {puts "TESTING ERROR 0.1\n";exit} 20 | "configuring fallback server 9.9.9.9" 21 | } 22 | expect { 23 | timeout {puts "TESTING ERROR 0.2\n";exit} 24 | "SSL connection opened" 25 | } 26 | expect { 27 | timeout {puts "TESTING ERROR 1\n";exit} 28 | "keepalive 7" 29 | } 30 | expect { 31 | timeout {puts "TESTING ERROR 2\n";exit} 32 | "keepalive 7" 33 | } 34 | expect { 35 | timeout {puts "TESTING ERROR 3\n";exit} 36 | "keepalive 7" 37 | } 38 | expect { 39 | timeout {puts "TESTING ERROR 4\n";exit} 40 | "keepalive 7" 41 | } 42 | sleep 2 43 | 44 | spawn $env(SHELL) 45 | send -- "fdns --test-server=dot://9.9.9.9 --details\r" 46 | expect { 47 | timeout {puts "TESTING ERROR 10\n";exit} 48 | "URL: dot://9.9.9.9" 49 | } 50 | expect { 51 | timeout {puts "TESTING ERROR 11\n";exit} 52 | "Port: 853" 53 | } 54 | expect { 55 | timeout {puts "TESTING ERROR 12\n";exit} 56 | "Network trace:" 57 | } 58 | expect { 59 | timeout {puts "TESTING ERROR 13\n";exit} 60 | "DoT query average" 61 | } 62 | expect { 63 | timeout {puts "TESTING ERROR 14\n";exit} 64 | "Testing completed" 65 | } 66 | 67 | after 100 68 | send -- "pkill fdns\r" 69 | 70 | after 100 71 | puts "\nall done\n" 72 | -------------------------------------------------------------------------------- /src/fdns/unlisted.c: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2019-2025 FDNS Authors 3 | * 4 | * This file is part of fdns project 5 | * 6 | * This program is free software: you can redistribute it and/or modify 7 | * it under the terms of the GNU General Public License as published by 8 | * the Free Software Foundation, either version 3 of the License, or 9 | * (at your option) any later version. 10 | * 11 | * This program is distributed in the hope that it will be useful, 12 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14 | * GNU General Public License for more details. 15 | * 16 | * You should have received a copy of the GNU General Public License 17 | * along with this program. If not, see . 18 | */ 19 | #include "fdns.h" 20 | 21 | typedef struct unlisted_t { 22 | struct unlisted_t *next; 23 | char *name; 24 | } UnlistedElem; 25 | 26 | static UnlistedElem *unlisted = NULL; 27 | 28 | void *unlisted_find(const char *name) { 29 | assert(name); 30 | UnlistedElem *ptr = unlisted; 31 | 32 | while (ptr) { 33 | if (strcmp(name, ptr->name) == 0) 34 | return ptr; 35 | ptr = ptr->next; 36 | } 37 | 38 | return NULL; 39 | } 40 | 41 | void unlisted_add(const char *name) { 42 | assert(name); 43 | if (server_print_unlist && arg_id == -1 && arg_debug) 44 | printf("Unlisting %s\n", name); 45 | 46 | UnlistedElem *ptr = malloc(sizeof(UnlistedElem)); 47 | if (!ptr) 48 | errExit("malloc"); 49 | memset(ptr, 0, sizeof(UnlistedElem)); 50 | ptr->name = strdup(name); 51 | if (!ptr->name) 52 | errExit("strdup"); 53 | 54 | if (unlisted == NULL) 55 | unlisted = ptr; 56 | else { 57 | ptr->next = unlisted; 58 | unlisted = ptr; 59 | } 60 | } 61 | -------------------------------------------------------------------------------- /src/man/nxdomain.txt: -------------------------------------------------------------------------------- 1 | .TH NXDOMAIN 1 "MONTH YEAR" "VERSION" "nxdomain man page" 2 | .SH NAME 3 | nxdomain \- simple utility program to clean up hosts files 4 | .SH SYNOPSIS 5 | .PP 6 | nxdomain [options] file-in [file-out] 7 | 8 | .SH DESCRIPTION 9 | nxdomain is a program that removes dead domains from a host list. 10 | The program accepts regular hosts files with IP addresses of 127.0.0.1 or 0.0.0.0, 11 | or lists of domain names without IP address, one domain per line. 12 | The program is sending a maximum of 30 queries per second. 13 | The default upstream resolver is Cloudflare 1.1.1.1. 14 | .br 15 | 16 | .SH OPTIONS 17 | .TP 18 | \fB\-\-help, \-?, \-h 19 | Print command-line options and exit. 20 | 21 | .TP 22 | \fB\-\-chunk=number 23 | Number of domains in a chunk of input data, default 200. 24 | 25 | 26 | .TP 27 | \fB\-\-server=IP_ADDRESS 28 | Use this IP address for your server, default Cloudflare 1.1.1.1. 29 | .br 30 | 31 | .br 32 | Other unfiltered servers: 33 | .br 34 | Quad9 - 9.9.9.10 35 | .br 36 | AdGuard - 94.140.14.140 37 | .br 38 | 39 | .TP 40 | \fB\-\-timeout=seconds 41 | Number of seconds to wait for a response form the server, default 5 seconds. 42 | 43 | .TP 44 | \fB\-\-timeout-only 45 | Check only the domains that timed out in a previous run. 46 | 47 | .SH EXAMPLE 48 | $ nxdomain adblocker.hostsfile adblocker.hostfile.new 49 | 50 | .SH LICENSE 51 | This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 3 of the License, or (at your option) any later version. 52 | .PP 53 | Homepage: https://firejaildns.wordpress.com 54 | .br 55 | Development: https://github.com/netblue30/fdns 56 | 57 | .SH SEE ALSO 58 | .BR dnsc (1), 59 | .BR fdns (1) 60 | 61 | 62 | -------------------------------------------------------------------------------- /etc/hosts: -------------------------------------------------------------------------------- 1 | # 2 | # User blocklist file loaded by default by fdns at startup. 3 | # This file is not overwritten at install time. 4 | # 5 | # Example: 6 | # 127.0.0.1 domain-name.com 7 | # 8 | # Add your personal list here. This list is not overwritten by a new 9 | # software install. 10 | # 11 | # 127.0.0.1 domain-name.com #drop any site under domain-name.com 12 | # 13 | # Note: 14 | # glibc library installed on Linux does not support wildcard domains. 15 | # Copying /etc/hosts in /etc/fdns/hosts will work, but not the other 16 | # way around. 17 | # 18 | # Some suggestions: 19 | # 20 | 21 | ## Firefox browser 22 | #127.0.0.1 getpocket.com 23 | #127.0.0.1 incoming.telemetry.mozilla.org 24 | #127.0.0.1 location.services.mozilla.com 25 | #127.0.0.1 safebrowsing.googleapis.com 26 | #127.0.0.1 contile.services.mozilla.com 27 | #127.0.0.1 snippets.cdn.mozilla.net 28 | #127.0.0.1 getpocket.cdn.mozilla.net 29 | #127.0.0.1 normandy.cdn.mozilla.net 30 | #127.0.0.1 links.email.mozilla.org 31 | #127.0.0.1 discovery.addons.mozilla.org 32 | #127.0.0.1 input.mozilla.org 33 | #127.0.0.1 contile.services.mozilla.com 34 | #127.0.0.1 push.services.mozilla.com 35 | # 36 | ## Brave browser 37 | #127.0.0.1 matomo.brave.com 38 | #127.0.0.1 metric-proxy.brave.com 39 | #127.0.0.1 p3a.brave.com 40 | #127.0.0.1 rewards.brave.com 41 | #127.0.0.1 variations.brave.com 42 | # 43 | ## etc. 44 | #127.0.0.1 improving.duckduckgo.com 45 | #127.0.0.1 fundingchoicesmessages.google.com 46 | #127.0.0.1 fonts.googleapis.com 47 | #127.0.0.1 fonts.gstatic.com 48 | #127.0.0.1 accounts.google.com 49 | #127.0.0.1 connect.facebook.net 50 | #127.0.0.1 coinpayments.net 51 | #127.0.0.1 paypalobjects.com 52 | #127.0.0.1 appleid.cdn-apple.com 53 | #127.0.0.1 cdn.syndication.twimg.com 54 | #127.0.0.1 polyfill.io 55 | #127.0.0.1 puzzmo.com 56 | # 57 | -------------------------------------------------------------------------------- /src/nxdomain/nxdomain.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2019-2025 FDNS Authors 3 | * 4 | * This file is part of fdns project 5 | * 6 | * This program is free software: you can redistribute it and/or modify 7 | * it under the terms of the GNU General Public License as published by 8 | * the Free Software Foundation, either version 3 of the License, or 9 | * (at your option) any later version. 10 | * 11 | * This program is distributed in the hope that it will be useful, 12 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14 | * GNU General Public License for more details. 15 | * 16 | * You should have received a copy of the GNU General Public License 17 | * along with this program. If not, see . 18 | */ 19 | 20 | #ifndef NXDOMAIN_H 21 | #define NXDOMAIN_H 22 | 23 | #define _GNU_SOURCE 24 | #include 25 | #include 26 | #include 27 | #include 28 | #include 29 | #include 30 | #include 31 | #include 32 | 33 | #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) 34 | 35 | // macro to print ip addresses in a printf statement 36 | #define PRINT_IP(A) \ 37 | ((int) (((A) >> 24) & 0xFF)), ((int) (((A) >> 16) & 0xFF)), ((int) (((A) >> 8) & 0xFF)), ((int) ( (A) & 0xFF)) 38 | 39 | #define FILE_CHUNK_SIZE 200 // maximum number of domains in a chunk of data 40 | #define MAX_CHUNKS 3 // maximum number of parallel chunks for processing 41 | 42 | // main.c 43 | extern char *arg_server; 44 | extern int arg_timeout; 45 | 46 | // resolver.c 47 | // timeout in seconds 48 | int resolver(const char *domain, int timeout); 49 | 50 | #endif 51 | -------------------------------------------------------------------------------- /test/fdns/blocklist-file.exp: -------------------------------------------------------------------------------- 1 | #!/usr/bin/expect -f 2 | # This file is part of FDNS project 3 | # Copyright (C) 2019-2025 FDNS Authors 4 | # License GPL v2 5 | 6 | set timeout 10 7 | spawn $env(SHELL) 8 | match_max 100000 9 | 10 | send -- "pkill fdns\r" 11 | sleep 2 12 | 13 | send -- "rm /etc/fdns/blocklist-fdns-test\r" 14 | after 100 15 | # copy blocklist in /etc/fdns since apparmor might be enabled! 16 | send -- "cp blocklist-fdns-test /etc/fdns/.\r" 17 | after 100 18 | 19 | send -- "fdns --blocklist-file=/etc/fdns/blocklist-fdns-test --resolvers=1\r" 20 | expect { 21 | timeout {puts "TESTING ERROR 0\n";exit} 22 | "fdns starting" 23 | } 24 | expect { 25 | timeout {puts "TESTING ERROR 0.1\n";exit} 26 | "SSL connection opened" 27 | } 28 | sleep 1 29 | 30 | spawn $env(SHELL) 31 | send -- "firejail --dns=127.1.1.1 ping -c 3 google.com\r" 32 | expect { 33 | timeout {puts "TESTING ERROR 1\n";exit} 34 | "Name or service not known" 35 | } 36 | sleep 1 37 | 38 | send -- "firejail --dns=127.1.1.1 ping -c 3 youtube.com\r" 39 | expect { 40 | timeout {puts "TESTING ERROR 2\n";exit} 41 | "Name or service not known" 42 | } 43 | sleep 1 44 | 45 | send -- "firejail --dns=127.1.1.1 ping -c 3 facebook.com\r" 46 | expect { 47 | timeout {puts "TESTING ERROR 3\n";exit} 48 | "Name or service not known" 49 | } 50 | sleep 1 51 | 52 | send -- "firejail --dns=127.1.1.1 ping -c 3 assets.gentoo.org\r" 53 | expect { 54 | timeout {puts "TESTING ERROR 4\n";exit} 55 | "icmp_seq=1" 56 | } 57 | expect { 58 | timeout {puts "TESTING ERROR 5\n";exit} 59 | "icmp_seq=2" 60 | } 61 | expect { 62 | timeout {puts "TESTING ERROR 6\n";exit} 63 | "icmp_seq=3" 64 | } 65 | expect { 66 | timeout {puts "TESTING ERROR 7\n";exit} 67 | "3 received" 68 | } 69 | sleep 1 70 | send -- "pkill fdns\r" 71 | after 100 72 | send -- "rm /etc/fdns/blocklist-fdns-test\r" 73 | 74 | 75 | 76 | after 100 77 | puts "\nall done\n" 78 | 79 | 80 | -------------------------------------------------------------------------------- /src/fdns/log.c: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2019-2025 FDNS Authors 3 | * 4 | * This file is part of fdns project 5 | * 6 | * This program is free software: you can redistribute it and/or modify 7 | * it under the terms of the GNU General Public License as published by 8 | * the Free Software Foundation, either version 3 of the License, or 9 | * (at your option) any later version. 10 | * 11 | * This program is distributed in the hope that it will be useful, 12 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14 | * GNU General Public License for more details. 15 | * 16 | * You should have received a copy of the GNU General Public License 17 | * along with this program. If not, see . 18 | */ 19 | #include "fdns.h" 20 | #include "timetrace.h" 21 | 22 | static LogMsg msg; 23 | static int disabled = 0; 24 | 25 | void log_disable(void) { 26 | disabled = 1; 27 | } 28 | 29 | // remote logging (resolver processes to frontend process) 30 | void rlogprintf(const char *format, ...) { 31 | if (disabled) 32 | // if (arg_id == -1) 33 | return; 34 | 35 | // initialize packet 36 | memset(&msg, 0, sizeof(LogMsgHeader)); 37 | 38 | // printf 39 | va_list valist; 40 | va_start(valist, format); 41 | vsnprintf(msg.buf, MAXBUF, format, valist); 42 | va_end(valist); 43 | 44 | // set packet size 45 | msg.h.len = sizeof(LogMsgHeader) + strlen(msg.buf) + 1; // + '\0' 46 | 47 | // send packet 48 | ssize_t rv = write(arg_fd, &msg, msg.h.len); 49 | if (rv == -1) 50 | errExit("write"); 51 | 52 | fflush(0); 53 | } 54 | 55 | // local logging (monitor process) 56 | void logprintf(const char *format, ...) { 57 | if (disabled) 58 | return; 59 | 60 | va_list valist; 61 | va_start(valist, format); 62 | 63 | print_time(); 64 | vprintf(format, valist); 65 | 66 | va_end(valist); 67 | fflush(0); 68 | } 69 | -------------------------------------------------------------------------------- /mkdeb.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | # based on http://tldp.org/HOWTO/html_single/Debian-Binary-Package-Building-HOWTO/ 3 | # a code archive should already be available 4 | 5 | set -e 6 | 7 | TOP=`pwd` 8 | CODE_ARCHIVE="$1-$2.tar.xz" 9 | CODE_DIR="$1-$2" 10 | INSTALL_DIR="${INSTALL_DIR}${CODE_DIR}/debian" 11 | DEBIAN_CTRL_DIR="${DEBIAN_CTRL_DIR}${CODE_DIR}/debian/DEBIAN" 12 | 13 | echo "*****************************************" 14 | echo "code archive: $CODE_ARCHIVE" 15 | echo "code directory: $CODE_DIR" 16 | echo "install directory: $INSTALL_DIR" 17 | echo "debian control directory: $DEBIAN_CTRL_DIR" 18 | echo "*****************************************" 19 | 20 | tar -xJvf $CODE_ARCHIVE 21 | #mkdir -p $INSTALL_DIR 22 | cd $CODE_DIR 23 | ./configure --prefix=/usr --enable-apparmor 24 | make -j2 25 | mkdir debian 26 | DESTDIR=debian make install-strip 27 | 28 | cd .. 29 | echo "*****************************************" 30 | SIZE=`du -s $INSTALL_DIR` 31 | echo "install size $SIZE" 32 | echo "*****************************************" 33 | 34 | pwd 35 | mkdir -p $INSTALL_DIR/usr/lib/systemd/system 36 | install -m644 $INSTALL_DIR/etc/fdns/fdns.service $INSTALL_DIR/usr/lib/systemd/system/fdns.service 37 | mv $INSTALL_DIR/usr/share/doc/fdns/RELNOTES $INSTALL_DIR/usr/share/doc/fdns/changelog.Debian 38 | gzip -9 -n $INSTALL_DIR/usr/share/doc/fdns/changelog.Debian 39 | rm $INSTALL_DIR/usr/share/doc/fdns/COPYING 40 | install -m644 platform/debian/copyright $INSTALL_DIR/usr/share/doc/fdns/. 41 | mkdir -p $DEBIAN_CTRL_DIR 42 | sed "s/FDNSVER/$2/g" platform/debian/control.$(dpkg-architecture -qDEB_HOST_ARCH) > $DEBIAN_CTRL_DIR/control 43 | 44 | find $INSTALL_DIR/etc -type f | sed "s,^$INSTALL_DIR,," | LC_ALL=C sort > $DEBIAN_CTRL_DIR/conffiles 45 | chmod 644 $DEBIAN_CTRL_DIR/conffiles 46 | find $INSTALL_DIR -type d | xargs chmod 755 47 | cd $CODE_DIR 48 | fakeroot dpkg-deb --build debian 49 | lintian debian.deb 50 | mv debian.deb ../fdns_$2_1_$(dpkg-architecture -qDEB_HOST_ARCH).deb 51 | cd .. 52 | rm -fr $CODE_DIR 53 | -------------------------------------------------------------------------------- /.github/workflows/codeql.yml: -------------------------------------------------------------------------------- 1 | name: "CodeQL" 2 | 3 | on: 4 | push: 5 | branches: [ 'master' ] 6 | pull_request: 7 | # The branches below must be a subset of the branches above 8 | branches: [ 'master' ] 9 | schedule: 10 | - cron: '42 15 * * 0' 11 | 12 | jobs: 13 | analyze: 14 | name: Analyze 15 | runs-on: ubuntu-latest 16 | permissions: 17 | actions: read 18 | contents: read 19 | security-events: write 20 | 21 | strategy: 22 | fail-fast: false 23 | matrix: 24 | language: [ 'cpp' ] 25 | # CodeQL supports [ 'cpp', 'csharp', 'go', 'java', 'javascript', 'python', 'ruby' ] 26 | # Learn more about CodeQL language support at https://aka.ms/codeql-docs/language-support 27 | 28 | steps: 29 | - name: Checkout repository 30 | uses: actions/checkout@v3 31 | with: 32 | submodules: true 33 | 34 | - name: Install dependencies 35 | run: sudo apt-get install -y libseccomp-dev libssl-dev 36 | 37 | # Initializes the CodeQL tools for scanning. 38 | - name: Initialize CodeQL 39 | uses: github/codeql-action/init@v2 40 | with: 41 | languages: ${{ matrix.language }} 42 | # If you wish to specify custom queries, you can do so here or in a config file. 43 | # By default, queries listed here will override any specified in a config file. 44 | # Prefix the list here with "+" to use these queries and those in the config file. 45 | 46 | # Details on CodeQL's query packs refer to : https://docs.github.com/en/code-security/code-scanning/automatically-scanning-your-code-for-vulnerabilities-and-errors/configuring-code-scanning#using-queries-in-ql-packs 47 | queries: +security-and-quality 48 | 49 | 50 | - name: Build 51 | run: |- 52 | ./configure --enable-analyzer --enable-fatal-warnings 53 | make 54 | sudo make install 55 | 56 | - name: Perform CodeQL Analysis 57 | uses: github/codeql-action/analyze@v2 58 | with: 59 | category: "/language:${{matrix.language}}" 60 | -------------------------------------------------------------------------------- /src/fdns/timetrace.c: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2019-2025 FDNS Authors 3 | * 4 | * This file is part of fdns project 5 | * 6 | * This program is free software: you can redistribute it and/or modify 7 | * it under the terms of the GNU General Public License as published by 8 | * the Free Software Foundation, either version 3 of the License, or 9 | * (at your option) any later version. 10 | * 11 | * This program is distributed in the hope that it will be useful, 12 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14 | * GNU General Public License for more details. 15 | * 16 | * You should have received a copy of the GNU General Public License 17 | * along with this program. If not, see . 18 | */ 19 | #include "timetrace.h" 20 | #include 21 | #include 22 | #include 23 | #include 24 | 25 | //************************** 26 | // time trace based on getticks function 27 | //************************** 28 | static int gm_delta = 0; 29 | struct timespec start_time; // start time 30 | 31 | // time difference in milliseconds 32 | static inline float msdelta(struct timespec *end, struct timespec *start) { 33 | unsigned sec = end->tv_sec - start->tv_sec; 34 | long nsec = end->tv_nsec - start->tv_nsec; 35 | 36 | return (float) sec * 1000 + (float) nsec / 1000000; 37 | } 38 | 39 | 40 | void timetrace_start(void) { 41 | clock_gettime(CLOCK_MONOTONIC, &start_time); 42 | } 43 | 44 | float timetrace_end(void) { 45 | struct timespec end_time; // end time 46 | clock_gettime(CLOCK_MONOTONIC, &end_time); 47 | return msdelta(&end_time, &start_time); 48 | } 49 | 50 | // calculate GMT / local time difference 51 | void init_time_delta(void) { 52 | time_t t = time(NULL); 53 | struct tm *ts =gmtime(&t); 54 | int gmh = ts->tm_hour; 55 | ts =localtime(&t); 56 | gm_delta = ts->tm_hour - gmh; 57 | } 58 | 59 | // print a timestamp - local time 60 | void print_time(void) { 61 | time_t t = time(NULL); 62 | struct tm *ts = gmtime(&t); 63 | printf("%02d:%02d:%02d ", ts->tm_hour + gm_delta, ts->tm_min, ts->tm_sec); 64 | } 65 | -------------------------------------------------------------------------------- /src/dnsc/dedup.c: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2019-2025 FDNS Authors 3 | * 4 | * This file is part of fdns project 5 | * 6 | * This program is free software: you can redistribute it and/or modify 7 | * it under the terms of the GNU General Public License as published by 8 | * the Free Software Foundation, either version 3 of the License, or 9 | * (at your option) any later version. 10 | * 11 | * This program is distributed in the hope that it will be useful, 12 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14 | * GNU General Public License for more details. 15 | * 16 | * You should have received a copy of the GNU General Public License 17 | * along with this program. If not, see . 18 | */ 19 | 20 | #include "dnsc.h" 21 | 22 | #define MAXHASH 256 23 | static Node *hlist[MAXHASH] = {NULL}; 24 | 25 | static void dedup_add(const char *name) { 26 | assert(name); 27 | 28 | Node *ptr = malloc(sizeof(Node)); 29 | if (!ptr) 30 | errExit("malloc"); 31 | memset(ptr, 0, sizeof(Node)); 32 | ptr->name = strdup(name); 33 | if (!ptr->name) 34 | errExit("strdup"); 35 | 36 | unsigned hval = hash(name, MAXHASH); 37 | ptr->next = hlist[hval]; 38 | hlist[hval] = ptr; 39 | } 40 | 41 | // ret 1 if found 42 | int dedup_search(const char *name) { 43 | Node *hptr = hlist[hash(name, MAXHASH)]; 44 | while (hptr) { 45 | if (strcmp(hptr->name, name) == 0) 46 | return 1; 47 | hptr = hptr->next; 48 | } 49 | 50 | return 0; 51 | } 52 | 53 | #define MAXBUF 1024 54 | void dedup_init(const char *fname) { 55 | assert(fname); 56 | 57 | FILE *fp = fopen(fname, "r"); 58 | if (!fp) { 59 | fprintf(stderr, "Error: cannot open %s\n", fname); 60 | exit(1); 61 | } 62 | 63 | char buf[MAXBUF]; 64 | int n = 0; 65 | while (fgets(buf, MAXBUF, fp)) { 66 | n++; 67 | char *ptr = strchr(buf, '\n'); 68 | if (ptr) 69 | *ptr = '\0'; 70 | ptr = buf; 71 | if (*ptr == '\0') 72 | continue; 73 | 74 | while (*ptr == ' ' || *ptr == '\t') 75 | ptr++; 76 | if (*ptr == '#') 77 | continue; 78 | 79 | // 127.0.0.1 domain.com 80 | ptr = strchr(ptr, ' '); 81 | if (ptr == NULL || *(ptr + 1) == '\0') { 82 | fprintf(stderr, "Error: invalid line %d in %s file\n", n, fname); 83 | exit(1); 84 | } 85 | ptr++; 86 | if (strchr(ptr, ' ') || strchr(ptr, '\t')) { 87 | fprintf(stderr, "Error: invalid line %d in %s file\n", n, fname); 88 | exit(1); 89 | } 90 | dedup_add(ptr); 91 | } 92 | 93 | fclose(fp); 94 | } 95 | 96 | 97 | -------------------------------------------------------------------------------- /src/fdns/lint.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2019-2025 FDNS Authors 3 | * 4 | * This file is part of fdns project 5 | * 6 | * This program is free software: you can redistribute it and/or modify 7 | * it under the terms of the GNU General Public License as published by 8 | * the Free Software Foundation, either version 3 of the License, or 9 | * (at your option) any later version. 10 | * 11 | * This program is distributed in the hope that it will be useful, 12 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14 | * GNU General Public License for more details. 15 | * 16 | * You should have received a copy of the GNU General Public License 17 | * along with this program. If not, see . 18 | */ 19 | #ifndef LINT_H 20 | #define LINT_H 21 | 22 | #include 23 | #include 24 | #include 25 | #include 26 | #include 27 | #include 28 | 29 | typedef struct __attribute__((__packed__)) dns_header_t { 30 | uint16_t id; 31 | uint16_t flags; 32 | uint16_t questions; 33 | uint16_t answer; 34 | uint16_t authority; 35 | uint16_t additional; 36 | } DnsHeader; 37 | 38 | typedef struct dns_question_t { 39 | // maximum domain name including the first label length byte and terminating '\0' 40 | #define DNS_MAX_DOMAIN_NAME 255 41 | char domain[DNS_MAX_DOMAIN_NAME]; 42 | uint16_t type; // RR type requested 43 | unsigned len; // question length 44 | unsigned dlen; // domain name length (len - 6) 45 | } DnsQuestion; 46 | 47 | typedef struct __attribute__((__packed__)) dns_rr_t { 48 | uint16_t type; 49 | uint16_t cls; 50 | uint32_t ttl; 51 | uint16_t rlen; 52 | } DnsRR; 53 | 54 | // error checking 55 | #define DNSERR_OK 0 56 | #define DNSERR_INVALID_HEADER_LENGTH 1 57 | #define DNSERR_INVALID_HEADER_QR 2 58 | #define DNSERR_INVALID_DOMAIN 3 59 | #define DNSERR_INVALID_CLASS 4 60 | #define DNSERR_NXDOMAIN 5 61 | #define DNSERR_MULTIPLE_QUESTIONS 6 62 | #define DNSERR_INVALID_PKT_LEN 7 63 | #define DNSERR_INVALID_RLEN 8 64 | #define DNSERR_REBINDING_ATTACK 9 65 | #define DNSERR_CNAME_CLOAKING 10 66 | #define DNSERR_INVALID_CNAME 11 67 | #define DNSERR_MAX 12 // always the last one 68 | int lint_error(void); 69 | const char *lint_err2str(void); 70 | 71 | DnsHeader *lint_header(uint8_t **pkt, uint8_t *last); 72 | DnsQuestion *lint_question(uint8_t **pkt, uint8_t *last); 73 | int lint_rx(uint8_t *pkt, unsigned len); 74 | const char *lint_get_cname(void); 75 | 76 | extern int filter_cname(const char *cname); 77 | 78 | #endif 79 | -------------------------------------------------------------------------------- /test/fdns/test.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | # This file is part of fdns project 3 | # Copyright (C) 2019-2025 FDNS Authors 4 | # License GPL v2 5 | 6 | # we are coming into this file as root user; by the end we will switch back to the regular $USER 7 | 8 | export MALLOC_CHECK_=3 9 | export MALLOC_PERTURB_=$(($RANDOM % 255 + 1)) 10 | 11 | echo "TESTING: ********************" 12 | printf "TESTING: running as " 13 | whoami 14 | echo "TESTING: ********************" 15 | 16 | echo "TESTING: protocol (test/fdns/protocol.exp)" 17 | ./protocol.exp 18 | 19 | echo "TESTING: keepalive and custom servers (test/fdns/keepalive.exp)" 20 | ./keepalive.exp 21 | 22 | echo "TESTING: already running (test/fdns/already-running.exp)" 23 | ./already-running.exp 24 | 25 | echo "TESTING: default wget (test/fdns/default-wget.exp)" 26 | ./default-wget.exp 27 | rm -f /tmp/index.html 28 | 29 | echo "TESTING: invalid server (test/fdns/invalid-server.exp)" 30 | ./invalid-server.exp 31 | 32 | echo "TESTING: default ping (test/fdns/default-ping.exp)" 33 | ./default-ping.exp 34 | 35 | echo "TESTING: default nslookup (test/fdns/default-nslookup.exp)" 36 | ./default-nslookup.exp 37 | 38 | echo "TESTING: print-requests (test/fdns/print-requests.exp)" 39 | ./print-requests.exp 40 | 41 | echo "TESTING: ipv6 (test/fdns/ipv6.exp)" 42 | ./ipv6.exp 43 | 44 | echo "TESTING: filter (test/fdns/filter.exp)" 45 | ./filter.exp 46 | 47 | echo "TESTING: nofilter (test/fdns/nofilter.exp)" 48 | ./nofilter.exp 49 | 50 | echo "TESTING: server=anycast (test/fdns/server-anycast.exp)" 51 | ./server-anycast.exp 52 | 53 | echo "TESTING: multiserver (test/fdns/multiserver.exp)" 54 | ./multiserver.exp 55 | 56 | echo "TESTING: local doh (test/fdns/local-doh.exp)" 57 | ./local-doh.exp 58 | 59 | echo "TESTING: forwarder (test/fdns/forwarder.exp)" 60 | ./forwarder.exp 61 | 62 | echo "TESTING: whitelist (test/fdns/whitelist.exp)" 63 | ./whitelist.exp 64 | 65 | echo "TESTING: whitelist-file (test/fdns/whitelist-file.exp)" 66 | ./whitelist-file.exp 67 | 68 | echo "TESTING: blocklist-file (test/fdns/blocklist-file.exp)" 69 | ./blocklist-file.exp 70 | 71 | echo "TESTING: restart worker (test/fdns/restart-worker.exp)" 72 | ./restart-worker.exp 73 | 74 | echo "TESTING: workers (test/fdns/workers.exp)" 75 | ./workers.exp 76 | 77 | echo "TESTING: restart workers (test/fdns/restart-workers.exp)" 78 | ./restart-workers.exp 79 | 80 | # 81 | # Start server and switch back to the regular user 82 | # 83 | echo "TESTING: starting user-level tests, please wait 5 seconds" 84 | 85 | fdns --daemonize --server=cloudflare 86 | sleep 5 87 | sudo -u $USER ./test-user.sh 88 | 89 | -------------------------------------------------------------------------------- /test/fdns/filter.exp: -------------------------------------------------------------------------------- 1 | #!/usr/bin/expect -f 2 | # This file is part of FDNS project 3 | # Copyright (C) 2019-2025 FDNS Authors 4 | # License GPL v2 5 | 6 | set timeout 10 7 | spawn $env(SHELL) 8 | match_max 100000 9 | 10 | send -- "pkill fdns --resolvers=1\r" 11 | sleep 2 12 | 13 | send -- "fdns\r" 14 | set server_id $spawn_id 15 | expect { 16 | timeout {puts "TESTING ERROR 0\n";exit} 17 | "fdns starting" 18 | } 19 | expect { 20 | timeout {puts "TESTING ERROR 0.1\n";exit} 21 | "SSL connection opened" 22 | } 23 | sleep 1 24 | 25 | 26 | spawn $env(SHELL) 27 | send -- "firejail --dns=127.1.1.1 ping -c 3 doubleclick.net\r" 28 | sleep 2 29 | send -- "firejail --dns=127.1.1.1 dig +time=2 +tries=1 doubleclick.net\r" 30 | sleep 2 31 | send -- "firejail --dns=127.1.1.1 nslookup -query=NS -retry=1 -timeout=2 doubleclick.net\r" 32 | sleep 2 33 | send -- "firejail --dns=127.1.1.1 nslookup -query=CNAME -retry=1 -timeout=2 doubleclick.net\r" 34 | sleep 2 35 | send -- "firejail --dns=127.1.1.1 nslookup -query=SOA -retry=1 -timeout=2 doubleclick.net\r" 36 | sleep 2 37 | send -- "firejail --dns=127.1.1.1 nslookup -query=NULL --retry=1 -timeout=2 doubleclick.net\r" 38 | sleep 2 39 | send -- "firejail --dns=127.1.1.1 nslookup -query=MX -retry=1 -timeout=2 doubleclick.net\r" 40 | sleep 2 41 | send -- "firejail --dns=127.1.1.1 nslookup -query=TXT -retry=1 -timeout=2 doubleclick.net\r" 42 | sleep 2 43 | send -- "firejail --dns=127.1.1.1 nslookup -query=KEY -retry=1 -timeout=2 doubleclick.net\r" 44 | sleep 2 45 | send -- "firejail --dns=127.1.1.1 nslookup -query=LOC -retry=1 -timeout=2 doubleclick.net\r" 46 | sleep 2 47 | send -- "firejail --dns=127.1.1.1 nslookup -query=SRV -retry=1 -timeout=2 doubleclick.net\r" 48 | sleep 2 49 | send -- "firejail --dns=127.1.1.1 nslookup -query=ANY -retry=1 -timeout=2 doubleclick.net\r" 50 | sleep 2 51 | send -- "firejail --dns=127.1.1.1 nslookup -query=URI -retry=1 -timeout=2 doubleclick.net\r" 52 | sleep 2 53 | send -- "firejail --dns=127.1.1.1 nslookup -query=PRIVATE -retry=1 -timeout=2 doubleclick.net\r" 54 | sleep 2 55 | 56 | set ping_id $spawn_id 57 | 58 | spawn $env(SHELL) 59 | set monitor_id $spawn_id 60 | send -- "fdns --monitor\r" 61 | expect { 62 | timeout {puts "TESTING ERROR 1\n";exit} 63 | "doubleclick.net, dropped" 64 | } 65 | expect { 66 | timeout {puts "TESTING ERROR 2\n";exit} 67 | "invalid DNS section counts" 68 | } 69 | expect { 70 | timeout {puts "TESTING ERROR 3\n";exit} 71 | "Error LANrx: RR type" 72 | } 73 | expect { 74 | timeout {puts "TESTING ERROR 4\n";exit} 75 | "Error LANrx: RR type" 76 | } 77 | expect { 78 | timeout {puts "TESTING ERROR 5\n";exit} 79 | "Error LANrx: RR type" 80 | } 81 | after 100 82 | 83 | set spawn_id $ping_id 84 | send -- "pkill fdns\r" 85 | 86 | after 100 87 | puts "\nall done\n" 88 | 89 | -------------------------------------------------------------------------------- /platform/fedora/mkrpm.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | set -e 4 | 5 | usage() { 6 | cat </dev/null; then 49 | echo "Please install spectool: sudo dnf install rpmdevtools" 50 | exit 1 51 | fi 52 | if ! command -v rpmbuild >/dev/null; then 53 | echo "Please install rpmbuild: sudo dnf install rpm-build" 54 | exit 1 55 | fi 56 | 57 | echo "Which version of fdns do you want to build?" 58 | select version in local git stable help; do 59 | case $version in 60 | stable) 61 | setup "$(dirname "$0")"/fdns-stable.spec 62 | break 63 | ;; 64 | git) 65 | setup "$(dirname "$0")"/fdns-git.spec 66 | break 67 | ;; 68 | local) 69 | setup "$(dirname "$0")"/fdns-local.spec 70 | cd "$(git rev-parse --show-toplevel)" 71 | if [[ ! -e Makefile ]]; then 72 | ./configure 73 | fi 74 | make dist 75 | mv fdns-*.tar.xz "$SOURCEDIR" 76 | cd - 77 | break 78 | ;; 79 | help) 80 | echo "stable: The latest stable version of fdns." 81 | echo "git: The master branch on github." 82 | echo "local: The source files in this local git clone." 83 | ;; 84 | esac 85 | done 86 | 87 | if [ "$1" == "--use-mock" ]; then 88 | rpmbuild --define "_topdir $TOPDIR" -bs "$SPECDIR"/fdns.spec 89 | 90 | mock "$SRPMDIR"/*.rpm 91 | else 92 | rpmbuild --define "_topdir $TOPDIR" -ba "$SPECDIR"/fdns.spec 93 | 94 | cp "$RPMDIR"/*/*.rpm "$SRPMDIR"/*.rpm . 95 | fi 96 | -------------------------------------------------------------------------------- /src/dnsc/whitelist.c: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2019-2025 FDNS Authors 3 | * 4 | * This file is part of fdns project 5 | * 6 | * This program is free software: you can redistribute it and/or modify 7 | * it under the terms of the GNU General Public License as published by 8 | * the Free Software Foundation, either version 3 of the License, or 9 | * (at your option) any later version. 10 | * 11 | * This program is distributed in the hope that it will be useful, 12 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14 | * GNU General Public License for more details. 15 | * 16 | * You should have received a copy of the GNU General Public License 17 | * along with this program. If not, see . 18 | */ 19 | 20 | #include "dnsc.h" 21 | 22 | #define HMAX 1024 23 | typedef struct hnode_t { 24 | struct hnode_t *next; 25 | const char *name; 26 | int cnt; 27 | } HNode; 28 | 29 | HNode *ht[HMAX] = {NULL}; 30 | 31 | static void whitelist_add(const char *name) { 32 | HNode *hnode = malloc(sizeof(HNode)); 33 | if (!hnode) 34 | errExit("malloc"); 35 | memset(hnode, 0, sizeof(HNode)); 36 | 37 | hnode->name = strdup(name); 38 | if (!hnode->name) 39 | errExit("strdup"); 40 | 41 | unsigned h = hash(name, HMAX); 42 | hnode->next = ht[h]; 43 | ht[h] = hnode; 44 | } 45 | 46 | void *whitelist_find(const char *name) { 47 | unsigned h = hash(name, HMAX); 48 | HNode *hnode = ht[h]; 49 | 50 | while (hnode) { 51 | if (strcmp(hnode->name, name) == 0) { 52 | hnode->cnt++; 53 | return hnode; 54 | } 55 | hnode = hnode->next; 56 | } 57 | 58 | return NULL; 59 | } 60 | 61 | void whitelist_load(void) { 62 | char *fname = SYSCONFDIR "/whitelist"; 63 | if (arg_debug) 64 | fprintf(stderr, "Loading domain whitelist from %s\n", fname); 65 | 66 | FILE *fp = fopen(fname, "r"); 67 | if (!fp) { 68 | fprintf(stderr, "Error: cannot open whitelist file\n"); 69 | exit(1); 70 | } 71 | 72 | char buf[LINE_MAX]; 73 | while (fgets(buf, LINE_MAX, fp)) { 74 | if (*buf == '#') 75 | continue; 76 | char *ptr = strchr(buf, '\n'); 77 | if (ptr) 78 | *ptr = '\0'; 79 | if (*buf == '\0') 80 | continue; 81 | 82 | char *start = buf; 83 | if (strncmp(buf, "127.0.0.1 ", 10) == 0) 84 | start += 10; 85 | else if (strncmp(buf, "0.0.0.0 ", 8) == 0) 86 | start += 8; 87 | 88 | whitelist_add(start); 89 | } 90 | fclose(fp); 91 | } 92 | 93 | void whitelist_print(int total_domains) { 94 | printf("# Whitelisted domains:\n"); 95 | int i; 96 | for (i = 0; i < HMAX; i++) { 97 | HNode *hnode = ht[i]; 98 | while (hnode) { 99 | if (hnode->cnt) 100 | printf("# %6d (%.02f%%) %s\n", 101 | hnode->cnt, 102 | ((double) hnode->cnt / (double) total_domains) * 100, 103 | hnode->name); 104 | hnode = hnode->next; 105 | } 106 | } 107 | } 108 | -------------------------------------------------------------------------------- /src/fdns/forwarder.c: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2019-2025 FDNS Authors 3 | * 4 | * This file is part of fdns project 5 | * 6 | * This program is free software: you can redistribute it and/or modify 7 | * it under the terms of the GNU General Public License as published by 8 | * the Free Software Foundation, either version 3 of the License, or 9 | * (at your option) any later version. 10 | * 11 | * This program is distributed in the hope that it will be useful, 12 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14 | * GNU General Public License for more details. 15 | * 16 | * You should have received a copy of the GNU General Public License 17 | * along with this program. If not, see . 18 | */ 19 | #include "fdns.h" 20 | #include 21 | 22 | Forwarder *fwd_list = NULL; 23 | Forwarder *fwd_active = NULL; 24 | 25 | void forwarder_set(const char *str) { 26 | assert(str); 27 | if (*str == '.') 28 | str++; 29 | if (*str == '\0') { 30 | fprintf(stderr, "Error: invalid forwarding domain\n"); 31 | exit(1); 32 | } 33 | 34 | Forwarder *f = malloc(sizeof(Forwarder)); 35 | if (!f) 36 | errExit("malloc"); 37 | memset(f, 0, sizeof(Forwarder)); 38 | 39 | // extract name 40 | f->name = strdup(str); 41 | if (!f->name) 42 | errExit("strdup"); 43 | char *ptr = strchr(f->name, '@'); 44 | if (!ptr || ptr == f->name) { 45 | fprintf(stderr, "Error: invalid forwarding %s\n", str); 46 | exit(1); 47 | } 48 | *ptr = '\0'; 49 | f->name_len = strlen(f->name); 50 | 51 | // extract ip address 52 | ptr++; 53 | uint32_t ip; 54 | if (atoip(ptr, &ip)) { 55 | fprintf(stderr, "Error: invalid IP address %s\n", ptr); 56 | exit(1); 57 | } 58 | f->ip = ptr; 59 | 60 | // create socket 61 | f->sock = net_remote_dns_socket(&f->saddr, f->ip); 62 | f->slen = sizeof(f->saddr); 63 | if (arg_id == 0) { 64 | printf("forwarding \"%s\" to %s\n", f->name, f->ip); 65 | fflush(0); 66 | } 67 | 68 | f->next = fwd_list; 69 | fwd_list = f; 70 | #ifdef HAVE_GCOV 71 | __gcov_flush(); 72 | #endif 73 | } 74 | 75 | // args: domain name and domain name length 76 | // return 1 if found 77 | int forwarder_check(const char *domain, unsigned len) { 78 | assert(domain); 79 | assert(len != 0); 80 | fwd_active = NULL; 81 | if (fwd_list == NULL) 82 | return 0; 83 | 84 | Forwarder *f = fwd_list; 85 | 86 | while (f) { 87 | if (len < f->name_len) { 88 | f = f->next; 89 | continue; 90 | } 91 | 92 | // exact match 93 | if (len == f->name_len) { 94 | if (strcmp(domain, f->name) == 0) { 95 | fwd_active = f; 96 | return 1; 97 | } 98 | } 99 | 100 | // check the ending of the domain name and affix a '.' 101 | int delta = len - f->name_len; 102 | if (strcmp(domain + delta, f->name) == 0) { 103 | if (*(domain + delta - 1) == '.') { 104 | fwd_active = f; 105 | return 1; 106 | } 107 | } 108 | 109 | f = f->next; 110 | #ifdef HAVE_GCOV 111 | __gcov_flush(); 112 | #endif 113 | } 114 | 115 | return 0; 116 | } 117 | 118 | -------------------------------------------------------------------------------- /src/fdns/stats.c: -------------------------------------------------------------------------------- 1 | #include "fdns.h" 2 | #include "math.h" 3 | 4 | typedef struct stats_node_t { 5 | struct stats_node_t *next; 6 | char *name; 7 | float qtime; 8 | float probability; 9 | } StatsNode; 10 | 11 | static StatsNode *stats_list = NULL; 12 | static int stats_cnt = 0; 13 | static double simple_average = 0; 14 | 15 | void stats_add(const char *name, float qtime) { 16 | assert(name); 17 | assert(qtime); 18 | 19 | // allocate 20 | stats_cnt++; 21 | StatsNode *snew = malloc(sizeof(StatsNode)); 22 | if (!snew) 23 | errExit("malloc"); 24 | memset(snew, 0, sizeof(StatsNode)); 25 | snew->name = strdup(name); 26 | if (!snew->name) 27 | errExit("strdup"); 28 | snew->qtime = qtime; 29 | 30 | // find 31 | StatsNode **pptr = &stats_list; 32 | while (*pptr != NULL) { 33 | assert(pptr); 34 | if ((*pptr)->qtime > qtime) 35 | break; 36 | pptr = &((*pptr)->next); 37 | } 38 | 39 | // add 40 | snew->next = *pptr; 41 | *pptr = snew; 42 | simple_average += snew->qtime; 43 | } 44 | 45 | 46 | static char *down_list = ""; 47 | static int down_cnt = 0; 48 | void stats_down(const char *name) { 49 | assert(name); 50 | 51 | down_cnt++; 52 | char *ptr; 53 | if (asprintf(&ptr, "\t%s\n%s", name, down_list) == -1) 54 | errExit("asprintf"); 55 | down_list = ptr; 56 | } 57 | 58 | 59 | void stats_print(void) { 60 | StatsNode *ptr = stats_list; 61 | assert(ptr); 62 | assert(stats_cnt); 63 | 64 | if (down_cnt) { 65 | printf("\n"); 66 | printf("Servers down:\n%s\n\n", down_list); 67 | } 68 | 69 | if (stats_cnt <= 1) 70 | return; 71 | printf("Simple average %.02f ms\n\n", simple_average / stats_cnt); 72 | printf("Run-time statistics:\n"); 73 | 74 | // calculate probability 75 | float total = stats_cnt * (stats_cnt - 1); 76 | int i = 0; 77 | while (ptr) { 78 | ptr->probability = ((float) (stats_cnt - 1 - i) * 2 / total); 79 | i++; 80 | ptr = ptr->next; 81 | } 82 | 83 | // adjust probability for random limit 84 | ptr = stats_list; 85 | int cnt = 0; 86 | float sum = 0; 87 | while (ptr) { 88 | if (ptr->qtime > QTIME_RANDOM_LIMIT) 89 | break; 90 | sum += ptr->probability; 91 | cnt++; 92 | ptr = ptr->next; 93 | } 94 | if (cnt) { 95 | sum /= cnt; 96 | ptr = stats_list; 97 | for (i = 0; i < cnt; i++, ptr = ptr->next) 98 | ptr->probability = sum; 99 | } 100 | 101 | // calculate average; 102 | float average = 0; 103 | ptr = stats_list; 104 | while (ptr) { 105 | average += ptr->qtime * ptr->probability; 106 | ptr = ptr->next; 107 | } 108 | 109 | // calculate standard deviation 110 | ptr = stats_list; 111 | float stdev = 0; 112 | while (ptr) { 113 | stdev += ptr->probability * powf(ptr->qtime - average, 2); 114 | ptr = ptr->next; 115 | } 116 | stdev = sqrtf(stdev); 117 | 118 | // print 119 | printf("%-5s%-25s%-12s probability (cumulative)\n", "", "", "query time"); 120 | ptr = stats_list; 121 | float cumulative = 0; 122 | i = 1; 123 | while (ptr) { 124 | cumulative += ptr->probability; 125 | char sindex[20]; 126 | sprintf(sindex, "%d", i); 127 | char sqtime[20]; 128 | sprintf(sqtime, "%.02f ms", ptr->qtime); 129 | printf("%-5s%-25s%-12s %.02f%% (%.02f%%)\n", 130 | sindex, 131 | ptr->name, 132 | sqtime, 133 | ptr->probability * 100, 134 | cumulative * 100); 135 | ptr = ptr->next; 136 | i++; 137 | } 138 | printf("\n"); 139 | printf("Query time average %.02f ms\n", average); 140 | printf("Standard deviation %.02f ms\n", stdev); 141 | printf("\n"); 142 | } 143 | 144 | -------------------------------------------------------------------------------- /m4/ax_check_compile_flag.m4: -------------------------------------------------------------------------------- 1 | # =========================================================================== 2 | # https://www.gnu.org/software/autoconf-archive/ax_check_compile_flag.html 3 | # =========================================================================== 4 | # 5 | # SYNOPSIS 6 | # 7 | # AX_CHECK_COMPILE_FLAG(FLAG, [ACTION-SUCCESS], [ACTION-FAILURE], [EXTRA-FLAGS], [INPUT]) 8 | # 9 | # DESCRIPTION 10 | # 11 | # Check whether the given FLAG works with the current language's compiler 12 | # or gives an error. (Warnings, however, are ignored) 13 | # 14 | # ACTION-SUCCESS/ACTION-FAILURE are shell commands to execute on 15 | # success/failure. 16 | # 17 | # If EXTRA-FLAGS is defined, it is added to the current language's default 18 | # flags (e.g. CFLAGS) when the check is done. The check is thus made with 19 | # the flags: "CFLAGS EXTRA-FLAGS FLAG". This can for example be used to 20 | # force the compiler to issue an error when a bad flag is given. 21 | # 22 | # INPUT gives an alternative input source to AC_COMPILE_IFELSE. 23 | # 24 | # NOTE: Implementation based on AX_CFLAGS_GCC_OPTION. Please keep this 25 | # macro in sync with AX_CHECK_{PREPROC,LINK}_FLAG. 26 | # 27 | # LICENSE 28 | # 29 | # Copyright (c) 2008 Guido U. Draheim 30 | # Copyright (c) 2011 Maarten Bosmans 31 | # 32 | # This program is free software: you can redistribute it and/or modify it 33 | # under the terms of the GNU General Public License as published by the 34 | # Free Software Foundation, either version 3 of the License, or (at your 35 | # option) any later version. 36 | # 37 | # This program is distributed in the hope that it will be useful, but 38 | # WITHOUT ANY WARRANTY; without even the implied warranty of 39 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General 40 | # Public License for more details. 41 | # 42 | # You should have received a copy of the GNU General Public License along 43 | # with this program. If not, see . 44 | # 45 | # As a special exception, the respective Autoconf Macro's copyright owner 46 | # gives unlimited permission to copy, distribute and modify the configure 47 | # scripts that are the output of Autoconf when processing the Macro. You 48 | # need not follow the terms of the GNU General Public License when using 49 | # or distributing such scripts, even though portions of the text of the 50 | # Macro appear in them. The GNU General Public License (GPL) does govern 51 | # all other use of the material that constitutes the Autoconf Macro. 52 | # 53 | # This special exception to the GPL applies to versions of the Autoconf 54 | # Macro released by the Autoconf Archive. When you make and distribute a 55 | # modified version of the Autoconf Macro, you may extend this special 56 | # exception to the GPL to apply to your modified version as well. 57 | 58 | #serial 5 59 | 60 | AC_DEFUN([AX_CHECK_COMPILE_FLAG], 61 | [AC_PREREQ(2.64)dnl for _AC_LANG_PREFIX and AS_VAR_IF 62 | AS_VAR_PUSHDEF([CACHEVAR],[ax_cv_check_[]_AC_LANG_ABBREV[]flags_$4_$1])dnl 63 | AC_CACHE_CHECK([whether _AC_LANG compiler accepts $1], CACHEVAR, [ 64 | ax_check_save_flags=$[]_AC_LANG_PREFIX[]FLAGS 65 | _AC_LANG_PREFIX[]FLAGS="$[]_AC_LANG_PREFIX[]FLAGS $4 $1" 66 | AC_COMPILE_IFELSE([m4_default([$5],[AC_LANG_PROGRAM()])], 67 | [AS_VAR_SET(CACHEVAR,[yes])], 68 | [AS_VAR_SET(CACHEVAR,[no])]) 69 | _AC_LANG_PREFIX[]FLAGS=$ax_check_save_flags]) 70 | AS_VAR_IF(CACHEVAR,yes, 71 | [m4_default([$2], :)], 72 | [m4_default([$3], :)]) 73 | AS_VAR_POPDEF([CACHEVAR])dnl 74 | ])dnl AX_CHECK_COMPILE_FLAGS 75 | -------------------------------------------------------------------------------- /src/fdns/hpack_static.c: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2019-2025 FDNS Authors 3 | * 4 | * This file is part of fdns project based on Dridi Boukelmoune implementation, 5 | * see below. 6 | * 7 | * This program is free software: you can redistribute it and/or modify 8 | * it under the terms of the GNU General Public License as published by 9 | * the Free Software Foundation, either version 3 of the License, or 10 | * (at your option) any later version. 11 | * 12 | * This program is distributed in the hope that it will be useful, 13 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 14 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 15 | * GNU General Public License for more details. 16 | * 17 | * You should have received a copy of the GNU General Public License 18 | * along with this program. If not, see . 19 | */ 20 | 21 | #include "hpack_static.h" 22 | #include 23 | 24 | static HpackStatic hps[] = { 25 | {"", ""}, // null entry 0 26 | {":authority", ""}, 27 | {":method", "GET"}, 28 | {":method", "POST"}, 29 | {":path", "/"}, 30 | {":path", "/index.html"}, 31 | {":scheme", "http"}, 32 | {":scheme", "https"}, 33 | {":status", "200"}, 34 | {":status", "204"}, 35 | {":status", "206"}, 36 | {":status", "304"}, 37 | {":status", "400"}, 38 | {":status", "404"}, 39 | {":status", "500"}, 40 | {"accept-charset", ""}, 41 | {"accept-encoding", "gzip, deflate"}, 42 | {"accept-language", ""}, 43 | {"accept-ranges", ""}, 44 | {"accept", ""}, 45 | {"access-control-allow-origin", ""}, 46 | {"age", ""}, 47 | {"allow", ""}, 48 | {"authorization", ""}, 49 | {"cache-control", ""}, 50 | {"content-disposition", ""}, 51 | {"content-encoding", ""}, 52 | {"content-language", ""}, 53 | {"content-length", ""}, 54 | {"content-location", ""}, 55 | {"content-range", ""}, 56 | {"content-type", ""}, 57 | {"cookie", ""}, 58 | {"date", ""}, 59 | {"etag", ""}, 60 | {"expect", ""}, 61 | {"expires", ""}, 62 | {"from", ""}, 63 | {"host", ""}, 64 | {"if-match", ""}, 65 | {"if-modified-since", ""}, 66 | {"if-none-match", ""}, 67 | {"if-range", ""}, 68 | {"if-unmodified-since", ""}, 69 | {"last-modified", ""}, 70 | {"link", ""}, 71 | {"location", ""}, 72 | {"max-forwards", ""}, 73 | {"proxy-authenticate", ""}, 74 | {"proxy-authorization", ""}, 75 | {"range", ""}, 76 | {"referer", ""}, 77 | {"refresh", ""}, 78 | {"retry-after", ""}, 79 | {"server", ""}, 80 | {"set-cookie", ""}, 81 | {"strict-transport-security", ""}, 82 | {"transfer-encoding", ""}, 83 | {"user-agent", ""}, 84 | {"vary", ""}, 85 | {"via", ""}, 86 | {"www-authenticate", ""}, 87 | }; 88 | 89 | HpackStatic *hpack_static_get(unsigned id) { 90 | if (id <= 0 || id > sizeof(hps) / sizeof(HpackStatic)) 91 | return NULL; 92 | return &hps[id]; 93 | } 94 | 95 | -------------------------------------------------------------------------------- /test/fdns/protocol.exp: -------------------------------------------------------------------------------- 1 | #!/usr/bin/expect -f 2 | # This file is part of FDNS project 3 | # Copyright (C) 2019-2025 FDNS Authors 4 | # License GPL v2 5 | 6 | set timeout 10 7 | spawn $env(SHELL) 8 | match_max 100000 9 | 10 | send -- "pkill fdns\r" 11 | sleep 2 12 | 13 | send -- "fdns --list\r" 14 | expect { 15 | timeout {puts "TESTING ERROR 0\n";exit} 16 | "Error" {puts "TESTING ERROR 1\n";exit} 17 | "adguard" 18 | } 19 | sleep 1 20 | 21 | send -- "fdns --list=dot\r" 22 | expect { 23 | timeout {puts "TESTING ERROR 2\n";exit} 24 | "Error" {puts "TESTING ERROR 3\n";exit} 25 | "adguard-dot" 26 | } 27 | sleep 1 28 | 29 | send -- "fdns --list=quic\r" 30 | expect { 31 | timeout {puts "TESTING ERROR 2\n";exit} 32 | "Error" {puts "TESTING ERROR 3\n";exit} 33 | "adguard-quic" 34 | } 35 | sleep 1 36 | 37 | 38 | send -- "fdns --nofilter --server=adguard --details\r" 39 | expect { 40 | timeout {puts "TESTING ERROR 4\n";exit} 41 | "Error" {puts "TESTING ERROR 5\n";exit} 42 | "Testing server" 43 | } 44 | expect { 45 | timeout {puts "TESTING ERROR 6\n";exit} 46 | "Error" {puts "TESTING ERROR 7\n\n";exit} 47 | "HTTP Header" 48 | } 49 | expect { 50 | timeout {puts "TESTING ERROR 8\n";exit} 51 | "Error" {puts "TESTING ERROR 9\n";exit} 52 | "DoH/DNS bandwidth ratio" 53 | } 54 | expect { 55 | timeout {puts "TESTING ERROR 10\n";exit} 56 | "Error" {puts "TESTING ERROR 11\n";exit} 57 | "fdns starting" 58 | } 59 | expect { 60 | timeout {puts "TESTING ERROR 12\n";exit} 61 | "Error" {puts "TESTING ERROR 13\n";exit} 62 | "SSL connection opened" 63 | } 64 | sleep 1 65 | spawn $env(SHELL) 66 | sleep 1 67 | send -- "pkill fdns\r" 68 | sleep 1 69 | 70 | 71 | 72 | 73 | 74 | send -- "fdns --nofilter --server=adguard-dot --details\r" 75 | expect { 76 | timeout {puts "TESTING ERROR 14\n";exit} 77 | "Error" {puts "TESTING ERROR 15\n";exit} 78 | "Testing server" 79 | } 80 | expect { 81 | timeout {puts "TESTING ERROR 16\n";exit} 82 | "Error" {puts "TESTING ERROR 17n";exit} 83 | "URL: dot://" 84 | } 85 | expect { 86 | timeout {puts "TESTING ERROR 18\n";exit} 87 | "Error" {puts "TESTING ERROR 19\n";exit} 88 | "Network trace" 89 | } 90 | expect { 91 | timeout {puts "TESTING ERROR 20\n";exit} 92 | "Error" {puts "TESTING ERROR 21\n";exit} 93 | "DoT/DNS bandwidth ratio" 94 | } 95 | expect { 96 | timeout {puts "TESTING ERROR 22\n";exit} 97 | "Error" {puts "TESTING ERROR 23\n";exit} 98 | "fdns starting" 99 | } 100 | expect { 101 | timeout {puts "TESTING ERROR 24\n";exit} 102 | "Error" {puts "TESTING ERROR 25\n";exit} 103 | "SSL connection opened" 104 | } 105 | sleep 1 106 | spawn $env(SHELL) 107 | sleep 1 108 | send -- "pkill fdns\r" 109 | sleep 1 110 | 111 | 112 | send -- "fdns --nofilter --server=adguard-quic --details\r" 113 | expect { 114 | timeout {puts "TESTING ERROR 14\n";exit} 115 | "Error" {puts "TESTING ERROR 15\n";exit} 116 | "Testing server" 117 | } 118 | expect { 119 | timeout {puts "TESTING ERROR 16\n";exit} 120 | "Error" {puts "TESTING ERROR 17n";exit} 121 | "URL: quic://" 122 | } 123 | expect { 124 | timeout {puts "TESTING ERROR 18\n";exit} 125 | "Error" {puts "TESTING ERROR 19\n";exit} 126 | "Network trace" 127 | } 128 | #expect { 129 | # timeout {puts "TESTING ERROR 20\n";exit} 130 | # "Error" {puts "TESTING ERROR 21\n";exit} 131 | # "DoT/DNS bandwidth ratio" 132 | #} 133 | expect { 134 | timeout {puts "TESTING ERROR 22\n";exit} 135 | "Error" {puts "TESTING ERROR 23\n";exit} 136 | "fdns starting" 137 | } 138 | expect { 139 | timeout {puts "TESTING ERROR 24\n";exit} 140 | "Error" {puts "TESTING ERROR 25\n";exit} 141 | "SSL connection opened" 142 | } 143 | sleep 1 144 | spawn $env(SHELL) 145 | sleep 1 146 | send -- "pkill fdns\r" 147 | sleep 1 148 | 149 | 150 | 151 | 152 | after 100 153 | puts "\nall done\n" 154 | -------------------------------------------------------------------------------- /src/dnsc/tld.c: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2019-2025 FDNS Authors 3 | * 4 | * This file is part of fdns project 5 | * 6 | * This program is free software: you can redistribute it and/or modify 7 | * it under the terms of the GNU General Public License as published by 8 | * the Free Software Foundation, either version 3 of the License, or 9 | * (at your option) any later version. 10 | * 11 | * This program is distributed in the hope that it will be useful, 12 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14 | * GNU General Public License for more details. 15 | * 16 | * You should have received a copy of the GNU General Public License 17 | * along with this program. If not, see . 18 | */ 19 | 20 | #include "dnsc.h" 21 | // https://ntldstats.com/tld 22 | // https://interisle.net/PhishingLandscape2022.pdf 23 | // https://www.spamhaus.org/statistics/tlds/ 24 | // https://en.wikipedia.org/wiki/.top 25 | // https://unit42.paloaltonetworks.com/top-level-domains-cybercrime/ 26 | // https://en.wikipedia.org/wiki/GRS_Domains#gTLDs_managed 27 | // https://www.techspot.com/news/97856-meta-suing-freenom-cybercriminals-favorite-domain-registrar.html 28 | // https://en.wikipedia.org/wiki/.tk 29 | // https://en.wikipedia.org/wiki/.cc 30 | // kaperski - phishing attack timelife: https://securelist.com/phishing-page-life-cycle/105171/ 31 | // cfd: contracts for difference -> Clothing & Fashion Design 32 | // sbs: Special Broadcasting Service (an Australian public-service broadcaster) -> side by side 33 | 34 | typedef struct bnode_t { 35 | char *name; 36 | int len; 37 | } BNode; 38 | 39 | static BNode blacklist[] = { 40 | // {"app", 3}, 41 | // {"dev", 3}, 42 | // {"cloud", 5}, ... breaks rumble 43 | 44 | {"accountant", 10}, 45 | {"am", 2}, 46 | {"bazar", 5}, 47 | {"best", 4}, 48 | {"beauty", 6}, 49 | {"bid", 3}, 50 | {"boats", 5}, 51 | {"bond", 4}, 52 | {"buzz", 4}, 53 | {"bd", 2}, 54 | {"cam", 3}, 55 | {"casa", 4}, 56 | {"cd", 2}, 57 | {"cf", 2}, 58 | {"cfd", 3}, 59 | {"club", 4}, 60 | {"cm", 2}, 61 | {"cn", 2}, 62 | {"co.cc", 5}, 63 | {"coin", 4}, 64 | {"cricket", 7}, 65 | {"cyou", 4}, 66 | {"date", 4}, 67 | {"degree", 6}, 68 | {"download", 8}, 69 | {"email", 5}, 70 | {"faith", 5}, 71 | {"fyi", 3}, 72 | {"ga", 2}, 73 | {"gq", 2}, 74 | {"haus", 4}, 75 | {"hair", 4}, 76 | {"help", 4}, 77 | {"icu", 3}, 78 | {"ke", 2}, 79 | {"link", 4}, 80 | {"live", 4}, 81 | {"loan", 4}, 82 | {"men", 3}, 83 | {"mov", 3}, 84 | {"market", 6}, 85 | {"makeup", 6}, 86 | {"ml", 2}, 87 | {"pw", 2}, 88 | {"party", 5}, 89 | {"quest", 5}, 90 | {"rest", 4}, 91 | {"racing",6}, 92 | {"review", 6}, 93 | {"rip", 3}, 94 | {"rodeo", 5}, 95 | {"sbs", 3}, 96 | {"science", 7}, 97 | {"stream", 6}, 98 | {"su", 2}, 99 | {"support", 7}, 100 | {"tk", 2}, 101 | {"tokyo", 5}, 102 | {"top", 3}, 103 | {"trade", 5}, 104 | {"uno", 3}, 105 | {"webcam", 6}, 106 | {"win", 3}, 107 | {"ws", 2}, 108 | {"xin", 3}, 109 | {"xyz", 3}, 110 | {"zip", 3}, 111 | {"zone", 4}, 112 | {"zw", 2}, 113 | {NULL, 0} 114 | }; 115 | 116 | char *tld_find(const char *name) { 117 | //printf("tld_find %s\n", name); 118 | int i = 0; 119 | int len = strlen(name); 120 | while (blacklist[i].name) { 121 | int delta = len - blacklist[i].len; 122 | //printf("delta %d, %s len %d/%d\n", delta, blacklist[i].name, blacklist[i].len, len); 123 | if (delta == 0 && strcmp(name, blacklist[i].name) == 0) 124 | return blacklist[i].name; 125 | 126 | if (delta > 0 && strcmp(name + delta, blacklist[i].name) == 0) { 127 | if (name[delta - 1] == '.') 128 | return blacklist[i].name; 129 | } 130 | i++; 131 | } 132 | 133 | return NULL; 134 | } 135 | 136 | -------------------------------------------------------------------------------- /test/fdns/restart-workers.exp: -------------------------------------------------------------------------------- 1 | #!/usr/bin/expect -f 2 | # This file is part of FDNS project 3 | # Copyright (C) 2019-2025 FDNS Authors 4 | # License GPL v2 5 | 6 | set timeout 50 7 | spawn $env(SHELL) 8 | match_max 100000 9 | 10 | send -- "pkill fdns\r" 11 | sleep 2 12 | 13 | send -- "fdns --resolvers=10\r" 14 | set server_id $spawn_id 15 | expect { 16 | timeout {puts "TESTING ERROR 0\n";exit} 17 | "fdns starting" 18 | } 19 | expect { 20 | timeout {puts "TESTING ERROR 1.0\n";exit} 21 | "SSL connection opened" 22 | } 23 | expect { 24 | timeout {puts "TESTING ERROR 1.1\n";exit} 25 | "SSL connection opened" 26 | } 27 | expect { 28 | timeout {puts "TESTING ERROR 1.2\n";exit} 29 | "SSL connection opened" 30 | } 31 | expect { 32 | timeout {puts "TESTING ERROR 1.3\n";exit} 33 | "SSL connection opened" 34 | } 35 | expect { 36 | timeout {puts "TESTING ERROR 1.4\n";exit} 37 | "SSL connection opened" 38 | } 39 | expect { 40 | timeout {puts "TESTING ERROR 1.5\n";exit} 41 | "SSL connection opened" 42 | } 43 | expect { 44 | timeout {puts "TESTING ERROR 1.6\n";exit} 45 | "SSL connection opened" 46 | } 47 | expect { 48 | timeout {puts "TESTING ERROR 1.7\n";exit} 49 | "SSL connection opened" 50 | } 51 | expect { 52 | timeout {puts "TESTING ERROR 1.8\n";exit} 53 | "SSL connection opened" 54 | } 55 | expect { 56 | timeout {puts "TESTING ERROR 1.9\n";exit} 57 | "SSL connection opened" 58 | } 59 | sleep 1 60 | 61 | spawn $env(SHELL) 62 | set ping_id $spawn_id 63 | puts "TESTING: Killing workers, please wait 30 seconds\n" 64 | send -- "kill -9 `ps a -o pid,cmd | grep fdns | grep id | awk '{print \$1}' | head -n 10`\r" 65 | after 100 66 | set spawn_id $server_id 67 | 68 | expect { 69 | timeout {puts "TESTING ERROR 2\n";exit} 70 | "Restarting resolver process" 71 | } 72 | expect { 73 | timeout {puts "TESTING ERROR 3\n";exit} 74 | "Restarting resolver process" 75 | } 76 | expect { 77 | timeout {puts "TESTING ERROR 4\n";exit} 78 | "Restarting resolver process" 79 | } 80 | expect { 81 | timeout {puts "TESTING ERROR 5\n";exit} 82 | "Restarting resolver process" 83 | } 84 | expect { 85 | timeout {puts "TESTING ERROR 6\n";exit} 86 | "Restarting resolver process" 87 | } 88 | expect { 89 | timeout {puts "TESTING ERROR 7\n";exit} 90 | "Restarting resolver process" 91 | } 92 | expect { 93 | timeout {puts "TESTING ERROR 8\n";exit} 94 | "Restarting resolver process" 95 | } 96 | expect { 97 | timeout {puts "TESTING ERROR 9\n";exit} 98 | "Restarting resolver process" 99 | } 100 | expect { 101 | timeout {puts "TESTING ERROR 10\n";exit} 102 | "Restarting resolver process" 103 | } 104 | expect { 105 | timeout {puts "TESTING ERROR 11\n";exit} 106 | "Restarting resolver process" 107 | } 108 | expect { 109 | timeout {puts "TESTING ERROR 12\n";exit} 110 | "SSL connection opened" 111 | } 112 | expect { 113 | timeout {puts "TESTING ERROR 13\n";exit} 114 | "SSL connection opened" 115 | } 116 | expect { 117 | timeout {puts "TESTING ERROR 14\n";exit} 118 | "SSL connection opened" 119 | } 120 | expect { 121 | timeout {puts "TESTING ERROR 15\n";exit} 122 | "SSL connection opened" 123 | } 124 | expect { 125 | timeout {puts "TESTING ERROR 16\n";exit} 126 | "SSL connection opened" 127 | } 128 | expect { 129 | timeout {puts "TESTING ERROR 17\n";exit} 130 | "SSL connection opened" 131 | } 132 | expect { 133 | timeout {puts "TESTING ERROR 18\n";exit} 134 | "SSL connection opened" 135 | } 136 | expect { 137 | timeout {puts "TESTING ERROR 19\n";exit} 138 | "SSL connection opened" 139 | } 140 | expect { 141 | timeout {puts "TESTING ERROR 20\n";exit} 142 | "SSL connection opened" 143 | } 144 | expect { 145 | timeout {puts "TESTING ERROR 21\n";exit} 146 | "SSL connection opened" 147 | } 148 | after 100 149 | 150 | set spawn_id $ping_id 151 | send -- "pkill fdns\r" 152 | 153 | after 100 154 | puts "\nall done\n" 155 | -------------------------------------------------------------------------------- /src/fdns/dnsdb.c: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2019-2025 FDNS Authors 3 | * 4 | * This file is part of fdns project 5 | * 6 | * This program is free software: you can redistribute it and/or modify 7 | * it under the terms of the GNU General Public License as published by 8 | * the Free Software Foundation, either version 3 of the License, or 9 | * (at your option) any later version. 10 | * 11 | * This program is distributed in the hope that it will be useful, 12 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14 | * GNU General Public License for more details. 15 | * 16 | * You should have received a copy of the GNU General Public License 17 | * along with this program. If not, see . 18 | */ 19 | #include "fdns.h" 20 | 21 | // database of DNS requests; this code is not re-entrant 22 | 23 | typedef struct db_elem_t { 24 | uint8_t active; 25 | uint8_t timeout; 26 | #define ID_SIZE 2 // 2 bytes matching DNS id 27 | uint8_t *buf[ID_SIZE]; 28 | struct db_elem_t *next; 29 | struct sockaddr_in addr; 30 | int pool_index; 31 | } DbElem; 32 | 33 | #define MAX_HASH_ARRAY 256 34 | static DbElem db[MAX_HASH_ARRAY]; 35 | 36 | void dnsdb_init(void) { 37 | memset(&db[0], 0, sizeof(db)); 38 | } 39 | 40 | static inline int hash(const uint8_t *buf) { 41 | uint8_t h = 0xac; 42 | int i; 43 | for (i = 0; i < ID_SIZE; i++, buf++) 44 | h ^= *buf; 45 | if(arg_debug) 46 | printf("hash %u\n", h); 47 | return (int) h; 48 | } 49 | 50 | struct sockaddr_in *dnsdb_retrieve(int pool_index, uint8_t *buf) { 51 | assert(pool_index < MAX_FALLBACK_POOL); 52 | assert(buf); 53 | if (arg_debug) 54 | printf("dbretrieve %u %u, pool index %d\n", buf[0], buf[1], pool_index); 55 | int h = hash(buf); 56 | assert(h < MAX_HASH_ARRAY); 57 | 58 | DbElem *ptr = &db[h]; 59 | assert(ptr); 60 | do { 61 | if (ptr->active && memcmp(ptr->buf, buf, ID_SIZE) == 0 && pool_index == ptr->pool_index) { 62 | ptr->active = 0; 63 | return &ptr->addr; 64 | } 65 | ptr = ptr->next; 66 | } 67 | while (ptr); 68 | if(arg_debug) 69 | printf("search failed\n"); 70 | return NULL; 71 | } 72 | 73 | void dnsdb_store(int pool_index, uint8_t *buf, struct sockaddr_in *addr) { 74 | assert(pool_index < MAX_FALLBACK_POOL); 75 | assert(buf); 76 | assert(addr); 77 | if (arg_debug) 78 | printf("dbstore %u, %u, pool_index %d\n", buf[0], buf[1], pool_index); 79 | 80 | int h = hash(buf); 81 | assert(h < MAX_HASH_ARRAY); 82 | 83 | DbElem *ptr = &db[h]; 84 | assert(ptr); 85 | DbElem *found = NULL; 86 | do { 87 | if (!ptr->active || (ptr->active && memcmp(buf, ptr->buf, ID_SIZE) == 0 && ptr->pool_index == pool_index)) { 88 | found = ptr; 89 | break; 90 | } 91 | ptr = ptr->next; 92 | } 93 | while (ptr); 94 | 95 | if (!found) { 96 | if(arg_debug) 97 | printf("allocating new db element\n"); 98 | // allocate a new element and place it at the end of the list 99 | found = malloc(sizeof(DbElem)); 100 | if (!found) 101 | errExit("malloc"); 102 | memset(found, 0, sizeof(DbElem)); 103 | ptr = &db[h]; 104 | while (ptr->next) 105 | ptr = ptr->next; 106 | ptr->next = found; 107 | } 108 | 109 | // set the hash table entry 110 | memcpy(found->buf, buf, ID_SIZE); 111 | memcpy(&found->addr, addr, sizeof(struct sockaddr_in)); 112 | found->active = 1; 113 | found->timeout = FALLBACK_TIMEOUT; 114 | found->pool_index = pool_index; 115 | } 116 | 117 | int dnsdb_timeout(void) { 118 | int i; 119 | int cnt = 0; 120 | for (i = 0; i < MAX_HASH_ARRAY; i++) { 121 | DbElem *ptr = &db[i]; 122 | while (ptr) { 123 | if (ptr->active) { 124 | if (--ptr->timeout <= 0) 125 | ptr->active = 0; 126 | else 127 | cnt++; 128 | } 129 | ptr = ptr->next; 130 | } 131 | } 132 | return cnt; 133 | } 134 | 135 | -------------------------------------------------------------------------------- /src/fdns/whitelist.c: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2019-2025 FDNS Authors 3 | * 4 | * This file is part of fdns project 5 | * 6 | * This program is free software: you can redistribute it and/or modify 7 | * it under the terms of the GNU General Public License as published by 8 | * the Free Software Foundation, either version 3 of the License, or 9 | * (at your option) any later version. 10 | * 11 | * This program is distributed in the hope that it will be useful, 12 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14 | * GNU General Public License for more details. 15 | * 16 | * You should have received a copy of the GNU General Public License 17 | * along with this program. If not, see . 18 | */ 19 | #include "fdns.h" 20 | 21 | // domain list 22 | typedef struct dentry_t { 23 | struct dentry_t *next; 24 | size_t len; // strlen(domain) 25 | const char *domain; 26 | } DEntry; 27 | 28 | static DEntry *wlist = NULL; // whitelist 29 | 30 | // is active? 31 | int whitelist_active(void) { 32 | return (wlist)? 1: 0; 33 | } 34 | 35 | 36 | // count entries 37 | int whitelist_cnt(void) { 38 | DEntry *dlist = wlist; 39 | int cnt = 0; 40 | while (dlist) { 41 | cnt++; 42 | dlist = dlist->next; 43 | } 44 | 45 | return cnt; 46 | } 47 | 48 | void whitelist_add(const char *domain) { 49 | assert(domain); 50 | DEntry **dlist = &wlist; 51 | assert(dlist); 52 | 53 | // skip www. 54 | const char *dm = domain; 55 | if (strncmp(domain, "www.", 4) == 0) 56 | dm = domain + 4; 57 | 58 | // in list already? 59 | DEntry *d = *dlist; 60 | while (d != NULL) { 61 | if (strcmp(dm, d->domain) == 0) 62 | return; 63 | d = d->next; 64 | } 65 | 66 | DEntry *dnew = malloc(sizeof(DEntry)); 67 | if (!dnew) 68 | errExit("malloc"); 69 | dnew->domain = strdup(dm); 70 | if (!dnew->domain) 71 | errExit("strdup"); 72 | dnew->len = strlen(dnew->domain); 73 | dnew->next = *dlist; 74 | *dlist = dnew; 75 | 76 | if (arg_id == 0) { 77 | printf("whitelist %s\n", domain); 78 | fflush(0); 79 | } 80 | } 81 | 82 | // load file 83 | void whitelist_load_file(const char *fname) { 84 | assert(fname); 85 | 86 | FILE *fp = fopen(fname, "r"); 87 | if (!fp) { 88 | fprintf(stderr, "Error: cannot open %s\n", fname); 89 | fprintf(stderr, "If AppArmor is enabled, please place the file in %s directory\n", SYSCONFDIR); 90 | exit(1); 91 | } 92 | 93 | char buf[MAXBUF]; 94 | while (fgets(buf, MAXBUF, fp)) { 95 | // cleanup 96 | char *ptr = strchr(buf, '\n'); 97 | if (ptr) 98 | *ptr = '\0'; 99 | ptr = buf; 100 | while (*ptr == ' ' || *ptr == '\t') 101 | ptr++; 102 | char *start = ptr; 103 | if (*ptr == '\0' || *ptr == '#') // empty line, comments 104 | continue; 105 | ptr = buf + strlen(buf) - 1; 106 | while (*ptr == ' ' || *ptr == '\t') { 107 | *ptr = '\0'; 108 | ptr--; 109 | } 110 | 111 | whitelist_add(start); 112 | } 113 | 114 | fclose(fp); 115 | } 116 | 117 | // re-generate the command line 118 | void whitelist_command(char **argv) { 119 | assert(argv); 120 | 121 | int i = 0; 122 | DEntry *d = wlist; 123 | while (d) { 124 | if (asprintf(&argv[i], "--whitelist=%s", d->domain) == -1) 125 | errExit("asprintf"); 126 | d = d->next; 127 | i++; 128 | } 129 | } 130 | 131 | // 1 not found, 0 found 132 | // full domain name matching 133 | // Example: fdns --whitelist=gentoo.org --whitelist=security.gentoo.org 134 | int whitelist_blocked(const char *domain) { 135 | assert(domain); 136 | 137 | // skip www. 138 | const char *dm = domain; 139 | if (strncmp(domain, "www.", 4) == 0) 140 | dm = domain + 4; 141 | 142 | DEntry *d = wlist; 143 | while (d) { 144 | if (strcmp(d->domain, dm) == 0) 145 | return 0; 146 | d = d->next; 147 | } 148 | 149 | return 1; 150 | } 151 | -------------------------------------------------------------------------------- /src/nxdomain/resolver.c: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2019-2025 FDNS Authors 3 | * 4 | * This file is part of fdns project 5 | * 6 | * This program is free software: you can redistribute it and/or modify 7 | * it under the terms of the GNU General Public License as published by 8 | * the Free Software Foundation, either version 3 of the License, or 9 | * (at your option) any later version. 10 | * 11 | * This program is distributed in the hope that it will be useful, 12 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14 | * GNU General Public License for more details. 15 | * 16 | * You should have received a copy of the GNU General Public License 17 | * along with this program. If not, see . 18 | */ 19 | 20 | 21 | #include "nxdomain.h" 22 | #include 23 | #include 24 | #include 25 | #include 26 | #define MAXBUF (10 * 1024) 27 | 28 | static uint16_t id = 0; 29 | 30 | // timeout in seconds 31 | // return 0 if domain OK 32 | // return 1 if NXDOMAIN 33 | // return 2 if timeout 34 | int resolver(const char *domain, int timeout) { 35 | // init socket 36 | int sock = socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP); 37 | if (sock == -1) 38 | errExit("socket"); 39 | struct sockaddr_in addr; 40 | memset(&addr, 0, sizeof(struct sockaddr_in)); 41 | addr.sin_family = AF_INET; 42 | addr.sin_port = htons(53); 43 | addr.sin_addr.s_addr = inet_addr(arg_server); 44 | socklen_t addr_len = sizeof(addr); 45 | 46 | // manufacture a dns query 47 | uint8_t dnsmsg_start[] = { 48 | 0x00, 0x00, // id 49 | 0x01, 0x00, // flags 50 | 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 51 | }; 52 | 53 | uint8_t dnsmsg[MAXBUF]; 54 | int len = sizeof(dnsmsg_start); 55 | memcpy(dnsmsg, dnsmsg_start, len); 56 | id++; 57 | memcpy(dnsmsg, &id, sizeof(uint16_t)); 58 | uint8_t *ptr = dnsmsg + len; 59 | *ptr++ = 0; 60 | strcpy((char *) ptr, domain); 61 | while(*ptr) 62 | ptr++; 63 | 64 | unsigned char cnt = 0; 65 | do { 66 | ptr--; 67 | if (*ptr != '.') 68 | cnt++; 69 | else { 70 | *ptr = cnt; 71 | cnt = 0; 72 | } 73 | } 74 | while (*ptr != 0); 75 | *ptr = cnt - 1; 76 | 77 | len++; 78 | while (*ptr != 0) { 79 | ptr++; 80 | len++; 81 | } 82 | ptr++; 83 | // len++; 84 | *ptr++ = 0; 85 | *ptr++ = 1; 86 | *ptr++ = 0; 87 | *ptr++ = 1; 88 | len += 4; 89 | 90 | //dbg_memory(dnsmsg, len, "tx"); 91 | 92 | // send query 93 | sendto(sock, dnsmsg, len, 0, (struct sockaddr *) &addr, addr_len); 94 | 95 | struct timeval t = { timeout, 0}; // 10 seconds timeout 96 | int retval = 0; 97 | while (1) { 98 | fd_set fds; 99 | FD_ZERO(&fds); 100 | FD_SET(sock, &fds); 101 | int nfds = sock; 102 | nfds++; 103 | errno = 0; 104 | 105 | int rv = select(nfds, &fds, NULL, NULL, &t); 106 | if (rv == -1) { 107 | if (errno == EINTR) { 108 | // select() man page reads: 109 | // "... the sets and timeout become undefined, so 110 | // do not rely on their contents after an error. " 111 | t.tv_sec = 10; 112 | t.tv_usec = 0; 113 | continue; 114 | } 115 | errExit("select"); 116 | } 117 | if (rv == 0) { // timeout 118 | close(sock); 119 | return 2; 120 | } 121 | 122 | if (FD_ISSET(sock, &fds)) { 123 | uint8_t buf[MAXBUF]; 124 | struct sockaddr_in remote; 125 | memset(&remote, 0, sizeof(remote)); 126 | socklen_t remote_len = sizeof(struct sockaddr_in); 127 | ssize_t len = recvfrom(sock, buf, MAXBUF, 0, (struct sockaddr *) &remote, &remote_len); 128 | if (len != -1) { // todo: parse errno - EINTR 129 | //dbg_memory(buf, len, "rx"); 130 | if (memcmp(buf, &id, sizeof(uint16_t)) != 0) 131 | continue; 132 | 133 | // check for NXDOMAIN 134 | if ((buf[3] & 0x3) == 0x3) { 135 | fflush(0); 136 | retval = 1; 137 | } 138 | } 139 | } 140 | break; 141 | } 142 | close(sock); 143 | return retval; 144 | } 145 | -------------------------------------------------------------------------------- /src/dnsc/dnsc.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2019-2025 FDNS Authors 3 | * 4 | * This file is part of fdns project 5 | * 6 | * This program is free software: you can redistribute it and/or modify 7 | * it under the terms of the GNU General Public License as published by 8 | * the Free Software Foundation, either version 3 of the License, or 9 | * (at your option) any later version. 10 | * 11 | * This program is distributed in the hope that it will be useful, 12 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14 | * GNU General Public License for more details. 15 | * 16 | * You should have received a copy of the GNU General Public License 17 | * along with this program. If not, see . 18 | */ 19 | 20 | #ifndef DNSC_H 21 | #define DNSC_H 22 | 23 | #define _GNU_SOURCE 24 | #include 25 | #include 26 | #include 27 | #include 28 | #include 29 | #include 30 | #include 31 | #include 32 | 33 | #define errExit(msg) do { char msgout[500]; sprintf(msgout, "Error %s:%s(%d)", msg, __FUNCTION__, __LINE__); perror(msgout); exit(1);} while (0) 34 | 35 | // memory printout 36 | static inline void dbg_memory(void *ptr, ssize_t len, const char *name) { 37 | if (name) 38 | printf("%s:\n", name); 39 | 40 | const uint8_t *ptr2 = (uint8_t *) ptr; 41 | ssize_t i; 42 | for ( i = 0; i < len; i++) { 43 | if (i % 16 == 0) 44 | printf("%04lx: ", i); 45 | if ((i + 8) % 16 == 0) 46 | printf("- "); 47 | printf("%02x ", ptr2[i]); 48 | if (i % 16 == 15) 49 | printf("\n"); 50 | } 51 | printf("\n"); 52 | } 53 | 54 | // djb2 hash function by Dan Bernstein 55 | static inline unsigned hash(const char *str, unsigned array_cnt) { 56 | unsigned hash = 5381; 57 | int c; 58 | 59 | while ((c = *str++) != '\0') 60 | hash = ((hash << 5) + hash) ^ c; // hash * 33 ^ c 61 | 62 | return (hash & (array_cnt - 1)); 63 | } 64 | 65 | // read a text file; \0 is added at the end of the text 66 | static inline char *read_file_malloc(const char *fname) { 67 | assert(fname); 68 | 69 | int fd = open(fname, O_RDONLY); 70 | if (fd == -1) 71 | return NULL; 72 | off_t len = lseek(fd, 0, SEEK_END); 73 | lseek(fd, 0, SEEK_SET); 74 | 75 | char *rv = malloc(len + 1); // add a \0 at the end of the string 76 | if (!rv) 77 | errExit("malloc"); 78 | memset(rv, 0, len); 79 | 80 | ssize_t cnt = 0; 81 | char *ptr = rv; 82 | while (cnt != len) { 83 | ssize_t cnt2 = read(fd, ptr + cnt, len - cnt); 84 | if (cnt2 == -1) { 85 | close(fd); 86 | return NULL; 87 | } 88 | cnt += cnt2; 89 | ptr += cnt2; 90 | } 91 | rv[cnt] = '\0'; 92 | //printf("#%s#\n", rv); 93 | close(fd); 94 | return rv; 95 | } 96 | 97 | 98 | #include 99 | static inline int is_dir(const char *fname) { 100 | assert(fname); 101 | if (*fname == '\0') 102 | return 0; 103 | 104 | // if fname doesn't end in '/', add one 105 | struct stat s; 106 | if (stat(fname, &s) == 0) { 107 | if (S_ISDIR(s.st_mode)) 108 | return 1; 109 | } 110 | 111 | return 0; 112 | } 113 | 114 | // main.c 115 | extern int arg_short; 116 | extern int arg_debug; 117 | typedef struct node_t { 118 | struct node_t *next; 119 | char *name; 120 | int len; // len of name string 121 | int cnt; // number of occurrences 122 | 123 | // subdomain filtering 124 | int scnt; 125 | char *s1; 126 | char *s2; 127 | char *s3; 128 | char *s4; 129 | } Node; 130 | extern Node *domains; 131 | int get_limit(void); 132 | extern char execpath[LINE_MAX+ 1]; 133 | 134 | // rsort.c 135 | void rsort_load(const char *fname); 136 | char **rsort(void); 137 | 138 | // subs.c 139 | extern int tld_cnt; 140 | void subs_print(int total_domains); 141 | 142 | // whitelist.c 143 | void *whitelist_find(const char *name); 144 | void whitelist_load(void); 145 | void whitelist_print(int total_domains); 146 | 147 | // tld.c 148 | char *tld_find(const char *name); 149 | 150 | // tech.c 151 | void tech_check(const char *name); 152 | void tech_print(int total_domains); 153 | 154 | // tld_top.c 155 | void tld_top_print(int total_domains); 156 | 157 | // dedup.c 158 | int dedup_search(const char *name); 159 | void dedup_init(const char *fname); 160 | 161 | #endif 162 | -------------------------------------------------------------------------------- /src/fdns/h2frame.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2019-2025 FDNS Authors 3 | * 4 | * This file is part of fdns project 5 | * 6 | * This program is free software: you can redistribute it and/or modify 7 | * it under the terms of the GNU General Public License as published by 8 | * the Free Software Foundation, either version 3 of the License, or 9 | * (at your option) any later version. 10 | * 11 | * This program is distributed in the hope that it will be useful, 12 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14 | * GNU General Public License for more details. 15 | * 16 | * You should have received a copy of the GNU General Public License 17 | * along with this program. If not, see . 18 | */ 19 | 20 | #ifndef H2FRAME_H 21 | #define H2FRAME_H 22 | #include "fdns.h" 23 | #include "timetrace.h" 24 | 25 | // 26 | // http2 header definitions 27 | // 28 | typedef struct h2frame_t { 29 | uint8_t len[3]; 30 | 31 | #define H2_TYPE_DATA 0x00 32 | #define H2_TYPE_HEADERS 0x01 33 | #define H2_TYPE_PRIORITY 0x02 34 | #define H2_TYPE_RESET 0x03 35 | #define H2_TYPE_SETTINGS 0x04 36 | #define H2_TYPE_PUSH_PROMISE 0x05 37 | #define H2_TYPE_PING 0x06 38 | #define H2_TYPE_GOAWAY 0x07 39 | #define H2_TYPE_WIN_UPDATE 0x08 40 | #define H2_TYPE_MAX 0x08 // the last one 41 | uint8_t type; 42 | 43 | #define H2_FLAG_END_STREAM 0x01 44 | #define H2_FLAG_END_HEADERS 0x04 45 | #define H2_FLAG_PADDED 0x08 46 | #define H2_FLAG_PRIORITY 0x20 47 | uint8_t flag; 48 | 49 | uint8_t stream[4]; 50 | } H2Frame; 51 | 52 | static inline char *h2frame_type2str(uint8_t type) { 53 | switch (type) { 54 | case H2_TYPE_DATA: 55 | return "DATA"; 56 | case H2_TYPE_HEADERS: 57 | return "HEADERS"; 58 | case H2_TYPE_PRIORITY: 59 | return "PRIORITY"; 60 | case H2_TYPE_RESET: 61 | return "RESET"; 62 | case H2_TYPE_SETTINGS: 63 | return "SETTINGS"; 64 | case H2_TYPE_PUSH_PROMISE: 65 | return "PUSH-PROMISE"; 66 | case H2_TYPE_PING: 67 | return "PING"; 68 | case H2_TYPE_GOAWAY: 69 | return "GOAWAY"; 70 | case H2_TYPE_WIN_UPDATE: 71 | return "WINDOW-UPDATE"; 72 | }; 73 | return "UNKNOWN"; 74 | } 75 | 76 | static inline uint32_t h2frame_extract_stream(H2Frame *frm) { 77 | uint32_t rv = frm->stream[0] << 24 | frm->stream[1] << 16 | frm->stream[2] << 8 | frm->stream[3]; 78 | return rv; 79 | } 80 | 81 | static inline uint32_t h2frame_extract_length(H2Frame *frm) { 82 | uint32_t rv = frm->len[0] << 16 | frm->len[1] << 8 | frm->len[2]; 83 | return rv; 84 | } 85 | 86 | static inline void h2frame_set_stream(H2Frame *frm, uint32_t stream) { 87 | frm->stream[3] = stream & 0xFF; 88 | frm->stream[2] = (stream >> 8) & 0xFF; 89 | frm->stream[1] = (stream >> 16) & 0xFF; 90 | frm->stream[0] = (stream >> 24) & 0x7F; 91 | } 92 | 93 | static inline void h2frame_set_length(H2Frame *frm, uint32_t length) { 94 | frm->len[2] = length & 0xFF; 95 | frm->len[1] = (length >> 8) & 0xFF; 96 | frm->len[0] = (length >> 16) & 0xFF; 97 | } 98 | 99 | static inline void h2frame_print(int id, const char *direction, H2Frame *frm) { 100 | assert(frm); 101 | assert(direction); 102 | 103 | uint32_t len = h2frame_extract_length(frm); 104 | uint32_t stream = h2frame_extract_stream(frm); 105 | print_time(); 106 | if (frm->type != H2_TYPE_WIN_UPDATE) 107 | printf("(%d) h2 %s s %u, len %u, 0x%02u %s, 0x%02u (", 108 | id, 109 | direction, 110 | stream, 111 | len, 112 | frm->type, h2frame_type2str(frm->type), 113 | frm->flag); 114 | else { 115 | uint8_t *wstart = (uint8_t *) frm + sizeof(H2Frame); 116 | uint32_t window; 117 | memcpy(&window, wstart, 4); 118 | window &= 0x7fffffff; 119 | window = ntohl(window); 120 | printf("(%d) h2 %s s %u, len %u, 0x%02u %s(%u), 0x%02u (", 121 | id, 122 | direction, 123 | stream, 124 | len, 125 | frm->type, h2frame_type2str(frm->type), window, 126 | frm->flag); 127 | } 128 | 129 | if (frm->flag & H2_FLAG_END_STREAM) 130 | printf("end stream,"); 131 | if (frm->flag & H2_FLAG_END_HEADERS) 132 | printf("end headers,"); 133 | if (frm->flag & H2_FLAG_PADDED) 134 | printf("padded,"); 135 | if (frm->flag & H2_FLAG_PRIORITY) 136 | printf("priority,"); 137 | printf(")\n"); 138 | fflush(0); 139 | } 140 | 141 | #endif 142 | -------------------------------------------------------------------------------- /src/fdns/fallback.c: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2019-2025 FDNS Authors 3 | * 4 | * This file is part of fdns project 5 | * 6 | * This program is free software: you can redistribute it and/or modify 7 | * it under the terms of the GNU General Public License as published by 8 | * the Free Software Foundation, either version 3 of the License, or 9 | * (at your option) any later version. 10 | * 11 | * This program is distributed in the hope that it will be useful, 12 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14 | * GNU General Public License for more details. 15 | * 16 | * You should have received a copy of the GNU General Public License 17 | * along with this program. If not, see . 18 | */ 19 | 20 | #include "fdns.h" 21 | #include "timetrace.h" 22 | #include 23 | 24 | static DnsServer *fcurrent = NULL; // current fallback server 25 | 26 | static DnsServer fallback[] = { 27 | { .name = "adguard", .address = "94.140.14.14"}, // adblock 28 | { .name = "cleanbrowsing", .address = "185.228.168.9"}, // security 29 | { .name = "cloudflare", .address = "1.1.1.2"}, // security 30 | { .name = "nextdns", .address = "45.90.28.141" }, // security 31 | { .name = "quad9", .address = "9.9.9.9"} // security 32 | }; 33 | 34 | 35 | 36 | DnsServer *server_fallback_get(void) { 37 | if (arg_fallback_server) { 38 | if (fcurrent == NULL) { 39 | fcurrent = malloc(sizeof(DnsServer)); 40 | if (!fcurrent) 41 | errExit("malloc"); 42 | memset(fcurrent, 0, sizeof(DnsServer)); 43 | fcurrent->name = "custom"; 44 | fcurrent->address = arg_fallback_server; 45 | printf("configuring fallback server %s\n", fcurrent->address); 46 | } 47 | return fcurrent; 48 | } 49 | 50 | if (fcurrent) 51 | return fcurrent; 52 | 53 | // init socket 54 | int i; 55 | int cnt = sizeof(fallback) / sizeof(DnsServer); 56 | int sock = socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP); 57 | if (sock == -1) 58 | errExit("socket"); 59 | struct sockaddr_in addr_fallback[cnt]; 60 | for (i = 0; i < cnt; i++) { 61 | memset(&addr_fallback[i], 0, sizeof(struct sockaddr_in)); 62 | addr_fallback[i].sin_family = AF_INET; 63 | addr_fallback[i].sin_port = htons(53); 64 | addr_fallback[i].sin_addr.s_addr = inet_addr(fallback[i].address); 65 | } 66 | socklen_t addr_fallback_len = sizeof(addr_fallback[0]); 67 | 68 | // send example.com queries 69 | uint8_t dnsmsg[] = { 70 | 0x00, 0x00, 0x01, 0x00, 0x00, 0x01, 0x00, 71 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x07, 0x65, 0x78, 0x61, 0x6d, 0x70, 0x6c, 0x65, 0x03, 0x63, 0x6f, 72 | 0x6d, 0x00, 0x00, 0x01, 0x00, 0x01 73 | }; 74 | timetrace_start(); 75 | // send to a random server 76 | sendto(sock, dnsmsg, sizeof(dnsmsg), 0, (struct sockaddr *) &addr_fallback[rand() % cnt], addr_fallback_len); 77 | // ... and to all of them just in case the random one didn't respond 78 | for (i = 0; i < cnt; i++) 79 | sendto(sock, dnsmsg, sizeof(dnsmsg), 0, (struct sockaddr *) &addr_fallback[i], addr_fallback_len); 80 | 81 | struct timeval t = { 1, 0}; // one second timeout 82 | while (1) { 83 | fd_set fds; 84 | FD_ZERO(&fds); 85 | FD_SET(sock, &fds); 86 | int nfds = sock + 1; 87 | nfds++; 88 | errno = 0; 89 | 90 | int rv = select(nfds, &fds, NULL, NULL, &t); 91 | if (rv == -1) { 92 | if (errno == EINTR) { 93 | // select() man page reads: 94 | // "... the sets and timeout become undefined, so 95 | // do not rely on their contents after an error. " 96 | t.tv_sec = 1; 97 | t.tv_usec = 0; 98 | continue; 99 | } 100 | errExit("select"); 101 | } 102 | if (rv == 0) // timeout 103 | break; 104 | 105 | if (FD_ISSET(sock, &fds)) { 106 | uint8_t buf[MAXBUF]; 107 | float ms = timetrace_end(); 108 | struct sockaddr_in remote; 109 | memset(&remote, 0, sizeof(remote)); 110 | socklen_t remote_len = sizeof(struct sockaddr_in); 111 | ssize_t len = recvfrom(sock, buf, MAXBUF, 0, (struct sockaddr *) &remote, &remote_len); 112 | if (len != -1) { // todo: parse errno - EINTR 113 | for (i = 0; i < cnt; i++) { 114 | if (remote.sin_addr.s_addr == addr_fallback[i].sin_addr.s_addr) { 115 | printf("Testing fallback server: %s (%s) - %.02f ms\n", fallback[i].name, fallback[i].address, ms); 116 | fcurrent = &fallback[i]; 117 | break; 118 | } 119 | } 120 | } 121 | } 122 | break; 123 | } 124 | 125 | close(sock); 126 | if (fcurrent == NULL) { 127 | fprintf(stderr, "Warning: fallback test failed, using Quad9 (9.9.9.9) server\n"); 128 | fcurrent = &fallback[0]; 129 | } 130 | 131 | return fcurrent; 132 | } 133 | -------------------------------------------------------------------------------- /src/fdns/net.c: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2019-2025 FDNS Authors 3 | * 4 | * This file is part of fdns project 5 | * 6 | * This program is free software: you can redistribute it and/or modify 7 | * it under the terms of the GNU General Public License as published by 8 | * the Free Software Foundation, either version 3 of the License, or 9 | * (at your option) any later version. 10 | * 11 | * This program is distributed in the hope that it will be useful, 12 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14 | * GNU General Public License for more details. 15 | * 16 | * You should have received a copy of the GNU General Public License 17 | * along with this program. If not, see . 18 | */ 19 | #include "fdns.h" 20 | #include 21 | #include 22 | #include 23 | 24 | // the number of bits in a network mask 25 | static inline uint8_t mask2bits(uint32_t mask) { 26 | uint32_t tmp = 0x80000000; 27 | int i; 28 | uint8_t rv = 0; 29 | 30 | for (i = 0; i < 32; i++, tmp >>= 1) { 31 | if (tmp & mask) 32 | rv++; 33 | else 34 | break; 35 | } 36 | return rv; 37 | } 38 | 39 | void net_check_proxy_addr(const char *str) { 40 | if (arg_debug) 41 | printf("%d: Checking proxy address %s\n", arg_id, str); 42 | if (str == NULL || *str == '\0') 43 | goto errout; 44 | 45 | // simple addr check 46 | uint32_t ip; 47 | if (atoip(str, &ip)) 48 | goto errout; 49 | 50 | // see if this address belongs to any local interface 51 | struct ifaddrs *ifaddr, *ifa; 52 | 53 | if (getifaddrs(&ifaddr) == -1) 54 | errExit("getifaddrs"); 55 | 56 | int found = 0; 57 | for (ifa = ifaddr; ifa != NULL; ifa = ifa->ifa_next) { 58 | if (ifa->ifa_addr == NULL) 59 | continue; 60 | 61 | if (ifa->ifa_addr->sa_family == AF_INET) { 62 | struct sockaddr_in *si = (struct sockaddr_in *) ifa->ifa_netmask; 63 | uint32_t ifmask = ntohl(si->sin_addr.s_addr); 64 | si = (struct sockaddr_in *) ifa->ifa_addr; 65 | uint32_t ifip = ntohl(si->sin_addr.s_addr); 66 | 67 | int status = 0; 68 | if (ifa->ifa_flags & IFF_RUNNING && ifa->ifa_flags & IFF_UP) 69 | status = 1; 70 | 71 | if (arg_debug) 72 | printf("%d: Checking interface %s, %d.%d.%d.%d/%u\n", 73 | arg_id, ifa->ifa_name, PRINT_IP(ifip), mask2bits(ifmask)); 74 | 75 | // is the address in the loopback network range? 76 | if (strcmp(ifa->ifa_name, "lo") == 0 && (ip & ifmask) == (ifip & ifmask)) { 77 | found = 1; 78 | if (!status) 79 | logprintf("Warning: interface %s is down\n", ifa->ifa_name); 80 | break; 81 | } 82 | // is the address an ethernet address? 83 | else if (ip == ifip) { 84 | found = 1; 85 | if (!status) 86 | logprintf("Warning: interface %s is down\n", ifa->ifa_name); 87 | break; 88 | } 89 | } 90 | } 91 | 92 | freeifaddrs(ifaddr); 93 | if (found) 94 | return; 95 | 96 | // exit with error 97 | errout: 98 | fprintf(stderr, "Error: invalid proxy address\n"); 99 | exit(1); 100 | } 101 | 102 | 103 | 104 | int net_local_dns_socket(int reuse) { 105 | int slocal = socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP); 106 | if (slocal == -1) 107 | errExit("socket"); 108 | 109 | if (reuse) { 110 | int opt = 1; 111 | if (setsockopt(slocal, SOL_SOCKET, SO_REUSEADDR, (const char *)&opt, sizeof(opt)) < 0) 112 | errExit("setsockopt(SO_REUSEADDR)"); 113 | 114 | #ifdef SO_REUSEPORT 115 | if (setsockopt(slocal, SOL_SOCKET, SO_REUSEPORT, (const char *)&opt, sizeof(opt)) < 0) 116 | errExit("setsockopt(SO_REUSEPORT)"); 117 | #endif 118 | } 119 | 120 | // configure proxy server local address:port 121 | struct sockaddr_in addr_local; 122 | memset(&addr_local, 0, sizeof(addr_local)); 123 | addr_local.sin_family = AF_INET; 124 | char *tmp = (arg_proxy_addr) ? arg_proxy_addr : DEFAULT_PROXY_ADDR; 125 | addr_local.sin_addr.s_addr = inet_addr(tmp); //INADDR_LOOPBACK, INADDR_ANY 126 | addr_local.sin_port = htons(53); 127 | 128 | int rv = bind(slocal, (struct sockaddr *) &addr_local, sizeof(addr_local)); 129 | if (rv == -1) 130 | return -1; 131 | 132 | return slocal; 133 | } 134 | 135 | int net_remote_dns_socket(struct sockaddr_in *addr, const char *ipstr) { 136 | // Remote dns server socket 137 | // this is the fallback server 138 | int sremote = socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP); 139 | if (sremote == -1) 140 | errExit("socket"); 141 | 142 | memset(addr, 0, sizeof(struct sockaddr_in)); 143 | addr->sin_family = AF_INET; 144 | addr->sin_port = htons(53); 145 | addr->sin_addr.s_addr = inet_addr(ipstr); 146 | 147 | return sremote; 148 | } 149 | -------------------------------------------------------------------------------- /m4/ax_check_openssl.m4: -------------------------------------------------------------------------------- 1 | # =========================================================================== 2 | # https://www.gnu.org/software/autoconf-archive/ax_check_openssl.html 3 | # =========================================================================== 4 | # 5 | # SYNOPSIS 6 | # 7 | # AX_CHECK_OPENSSL([action-if-found[, action-if-not-found]]) 8 | # 9 | # DESCRIPTION 10 | # 11 | # Look for OpenSSL in a number of default spots, or in a user-selected 12 | # spot (via --with-openssl). Sets 13 | # 14 | # OPENSSL_INCLUDES to the include directives required 15 | # OPENSSL_LIBS to the -l directives required 16 | # OPENSSL_LDFLAGS to the -L or -R flags required 17 | # 18 | # and calls ACTION-IF-FOUND or ACTION-IF-NOT-FOUND appropriately 19 | # 20 | # This macro sets OPENSSL_INCLUDES such that source files should use the 21 | # openssl/ directory in include directives: 22 | # 23 | # #include 24 | # 25 | # LICENSE 26 | # 27 | # Copyright (c) 2009,2010 Zmanda Inc. 28 | # Copyright (c) 2009,2010 Dustin J. Mitchell 29 | # 30 | # Copying and distribution of this file, with or without modification, are 31 | # permitted in any medium without royalty provided the copyright notice 32 | # and this notice are preserved. This file is offered as-is, without any 33 | # warranty. 34 | 35 | #serial 10 36 | 37 | AU_ALIAS([CHECK_SSL], [AX_CHECK_OPENSSL]) 38 | AC_DEFUN([AX_CHECK_OPENSSL], [ 39 | found=false 40 | AC_ARG_WITH([openssl], 41 | [AS_HELP_STRING([--with-openssl=DIR], 42 | [root of the OpenSSL directory])], 43 | [ 44 | case "$withval" in 45 | "" | y | ye | yes | n | no) 46 | AC_MSG_ERROR([Invalid --with-openssl value]) 47 | ;; 48 | *) ssldirs="$withval" 49 | ;; 50 | esac 51 | ], [ 52 | # if pkg-config is installed and openssl has installed a .pc file, 53 | # then use that information and don't search ssldirs 54 | AC_CHECK_TOOL([PKG_CONFIG], [pkg-config]) 55 | if test x"$PKG_CONFIG" != x""; then 56 | OPENSSL_LDFLAGS=`$PKG_CONFIG openssl --libs-only-L 2>/dev/null` 57 | if test $? = 0; then 58 | OPENSSL_LIBS=`$PKG_CONFIG openssl --libs-only-l 2>/dev/null` 59 | OPENSSL_INCLUDES=`$PKG_CONFIG openssl --cflags-only-I 2>/dev/null` 60 | found=true 61 | fi 62 | fi 63 | 64 | # no such luck; use some default ssldirs 65 | if ! $found; then 66 | ssldirs="/usr/local/ssl /usr/lib/ssl /usr/ssl /usr/pkg /usr/local /usr" 67 | fi 68 | ] 69 | ) 70 | 71 | 72 | # note that we #include , so the OpenSSL headers have to be in 73 | # an 'openssl' subdirectory 74 | 75 | if ! $found; then 76 | OPENSSL_INCLUDES= 77 | for ssldir in $ssldirs; do 78 | AC_MSG_CHECKING([for openssl/ssl.h in $ssldir]) 79 | if test -f "$ssldir/include/openssl/ssl.h"; then 80 | OPENSSL_INCLUDES="-I$ssldir/include" 81 | OPENSSL_LDFLAGS="-L$ssldir/lib" 82 | OPENSSL_LIBS="-lssl -lcrypto" 83 | found=true 84 | AC_MSG_RESULT([yes]) 85 | break 86 | else 87 | AC_MSG_RESULT([no]) 88 | fi 89 | done 90 | 91 | # if the file wasn't found, well, go ahead and try the link anyway -- maybe 92 | # it will just work! 93 | fi 94 | 95 | # try the preprocessor and linker with our new flags, 96 | # being careful not to pollute the global LIBS, LDFLAGS, and CPPFLAGS 97 | 98 | AC_MSG_CHECKING([whether compiling and linking against OpenSSL works]) 99 | echo "Trying link with OPENSSL_LDFLAGS=$OPENSSL_LDFLAGS;" \ 100 | "OPENSSL_LIBS=$OPENSSL_LIBS; OPENSSL_INCLUDES=$OPENSSL_INCLUDES" >&AS_MESSAGE_LOG_FD 101 | 102 | save_LIBS="$LIBS" 103 | save_LDFLAGS="$LDFLAGS" 104 | save_CPPFLAGS="$CPPFLAGS" 105 | LDFLAGS="$LDFLAGS $OPENSSL_LDFLAGS" 106 | LIBS="$OPENSSL_LIBS $LIBS" 107 | CPPFLAGS="$OPENSSL_INCLUDES $CPPFLAGS" 108 | AC_LINK_IFELSE( 109 | [AC_LANG_PROGRAM([#include ], [SSL_new(NULL)])], 110 | [ 111 | AC_MSG_RESULT([yes]) 112 | $1 113 | ], [ 114 | AC_MSG_RESULT([no]) 115 | $2 116 | ]) 117 | CPPFLAGS="$save_CPPFLAGS" 118 | LDFLAGS="$save_LDFLAGS" 119 | LIBS="$save_LIBS" 120 | 121 | AC_SUBST([OPENSSL_INCLUDES]) 122 | AC_SUBST([OPENSSL_LIBS]) 123 | AC_SUBST([OPENSSL_LDFLAGS]) 124 | ]) 125 | -------------------------------------------------------------------------------- /src/fdns/procs.c: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2019-2025 FDNS Authors 3 | * 4 | * This file is part of fdns project 5 | * 6 | * This program is free software: you can redistribute it and/or modify 7 | * it under the terms of the GNU General Public License as published by 8 | * the Free Software Foundation, either version 3 of the License, or 9 | * (at your option) any later version. 10 | * 11 | * This program is distributed in the hope that it will be useful, 12 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14 | * GNU General Public License for more details. 15 | * 16 | * You should have received a copy of the GNU General Public License 17 | * along with this program. If not, see . 18 | */ 19 | 20 | #include "fdns.h" 21 | #include 22 | 23 | static void procs_dir_cleanup(void) { 24 | DIR *dir; 25 | if (!(dir = opendir("/run/fdns"))) { 26 | // sleep 2 seconds and try again 27 | sleep(2); 28 | if (!(dir = opendir("/run/fdns"))) { 29 | fprintf(stderr, "Error: cannot open /proc directory\n"); 30 | exit(1); 31 | } 32 | } 33 | 34 | struct dirent *entry; 35 | while ((entry = readdir(dir))) { 36 | if (*entry->d_name == '.') 37 | continue; 38 | if (strcmp(entry->d_name, "empty") == 0) 39 | continue; 40 | 41 | char *fname; 42 | if (asprintf(&fname, "/proc/%s", entry->d_name) == -1) 43 | errExit("asprintf"); 44 | if (access(fname, R_OK)) { 45 | printf("cleaning %s process\n", entry->d_name); 46 | fflush(0); 47 | 48 | char *runfname; 49 | if (asprintf(&runfname, "/run/fdns/%s", entry->d_name) == -1) 50 | errExit("asprintf"); 51 | int rv = unlink(runfname); 52 | (void) rv; 53 | free(runfname); 54 | } 55 | free(fname); 56 | } 57 | 58 | closedir(dir); 59 | } 60 | 61 | 62 | void procs_exit(void) { 63 | pid_t pid = getpid(); 64 | char *runfname; 65 | if (asprintf(&runfname, "/run/fdns/%d", pid) == -1) 66 | errExit("asprintf"); 67 | int rv = unlink(runfname); 68 | (void) rv; 69 | free(runfname); 70 | } 71 | 72 | void procs_add(void) { 73 | assert(getuid() == 0); 74 | 75 | struct stat s; 76 | if (stat(PATH_RUN_FDNS, &s) ) { 77 | if (mkdir(PATH_RUN_FDNS, 0755) == -1) { 78 | fprintf(stderr, "Error: cannot create %s directory\n", PATH_RUN_FDNS); 79 | exit(1); 80 | } 81 | } 82 | if (stat(PATH_CHROOT, &s) ) { 83 | if (mkdir(PATH_CHROOT, 0755) == -1) { 84 | fprintf(stderr, "Error: cannot create %s directory\n", PATH_RUN_FDNS); 85 | exit(1); 86 | } 87 | } 88 | procs_dir_cleanup(); 89 | 90 | pid_t pid = getpid(); 91 | char *fname; 92 | if (asprintf(&fname, "/run/fdns/%d", pid) == -1) 93 | errExit("asprintf"); 94 | 95 | FILE *fp = fopen(fname, "w"); 96 | if (fp == NULL) { 97 | fprintf(stderr, "Error: cannot create %s file\n", fname); 98 | exit(1); 99 | } 100 | 101 | char *tmp = (arg_proxy_addr) ? arg_proxy_addr : DEFAULT_PROXY_ADDR; 102 | fprintf(fp, "%s\n", tmp); 103 | fclose(fp); 104 | free(fname); 105 | atexit(procs_exit); 106 | } 107 | 108 | char *procs_list(pid_t *default_proxy_pid) { 109 | DIR *dir; 110 | char *rv = NULL; 111 | if (!(dir = opendir("/run/fdns"))) { 112 | // sleep 2 seconds and try again 113 | sleep(2); 114 | if (!(dir = opendir("/run/fdns"))) 115 | return NULL; 116 | } 117 | 118 | struct dirent *entry; 119 | while ((entry = readdir(dir))) { 120 | if (*entry->d_name == '.') 121 | continue; 122 | 123 | char *fname; 124 | if (asprintf(&fname, "/proc/%s", entry->d_name) == -1) 125 | errExit("asprintf"); 126 | if (access(fname, R_OK) == 0) { 127 | char *runfname; 128 | if (asprintf(&runfname, "/run/fdns/%s", entry->d_name) == -1) 129 | errExit("asprintf"); 130 | printf("pid %s: ", entry->d_name); 131 | FILE *fp = fopen(runfname, "r"); 132 | if (fp) { 133 | char buf[MAXBUF]; 134 | if (fgets(buf, MAXBUF, fp)) { 135 | char *ptr = strchr(buf, '\n'); 136 | if (ptr) 137 | *ptr = '\0'; 138 | 139 | if (strcmp(buf, DEFAULT_PROXY_ADDR) == 0) { 140 | if (rv) 141 | free(rv); 142 | rv = strdup(DEFAULT_PROXY_ADDR); 143 | if (!rv) 144 | errExit("strdup"); 145 | if (default_proxy_pid) 146 | *default_proxy_pid = atoi(entry->d_name); 147 | } 148 | else if (!rv) { 149 | rv = strdup(buf); 150 | if (!rv) 151 | errExit("strdup"); 152 | if (default_proxy_pid) 153 | *default_proxy_pid = atoi(entry->d_name); 154 | } 155 | 156 | shmem_print_server(buf); 157 | // if (strcmp(buf, DEFAULT_PROXY_ADDR) == 0) 158 | // printf(" (default)"); 159 | } 160 | fclose(fp); 161 | } 162 | printf("\n"); 163 | free(runfname); 164 | } 165 | free(fname); 166 | } 167 | closedir(dir); 168 | return rv; 169 | } 170 | -------------------------------------------------------------------------------- /src/fdns/security.c: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2019-2025 FDNS Authors 3 | * 4 | * This file is part of fdns project 5 | * 6 | * This program is free software: you can redistribute it and/or modify 7 | * it under the terms of the GNU General Public License as published by 8 | * the Free Software Foundation, either version 3 of the License, or 9 | * (at your option) any later version. 10 | * 11 | * This program is distributed in the hope that it will be useful, 12 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14 | * GNU General Public License for more details. 15 | * 16 | * You should have received a copy of the GNU General Public License 17 | * along with this program. If not, see . 18 | */ 19 | #include "fdns.h" 20 | 21 | #include 22 | #include 23 | //#include 24 | #include 25 | #include 26 | #include 27 | #include 28 | #ifdef HAVE_SECCOMP 29 | #include 30 | #endif 31 | 32 | void daemonize(void) { 33 | if (daemon(0, 0) == -1) 34 | errExit("daemon"); 35 | } 36 | 37 | // chroot into PATH_RUN_FDNS "/empty" and switch user to nobody 38 | void chroot_drop_privs(const char *username, const char *dir) { 39 | struct stat s; 40 | int rv; 41 | assert(username); 42 | 43 | // find user/group id 44 | struct passwd *pw; 45 | if ((pw = getpwnam(username)) == 0) { 46 | fprintf(stderr, "Error: can't find user %s\n", username); 47 | exit(1); 48 | } 49 | 50 | // check /run/fdns directory 51 | if (stat(dir, &s)) { 52 | fprintf(stderr, "Error: cannot find %s directory\n", dir); 53 | exit(1); 54 | } 55 | 56 | // chroot 57 | rv = chroot(dir); 58 | if (rv == -1) 59 | errExit("chroot"); 60 | rv = chdir("/"); 61 | if (rv == -1) 62 | errExit("chdir"); 63 | 64 | // drop privs 65 | if (setgroups(0, NULL) < 0) { 66 | fprintf(stderr, "Error: failed to drop supplementary groups\n"); 67 | exit(1); 68 | } 69 | if (setgid(pw->pw_gid) < 0 || setuid(pw->pw_uid) < 0) { 70 | fprintf(stderr, "Error: failed to switch the user\n"); 71 | exit(1); 72 | } 73 | } 74 | 75 | //************************************************* 76 | // seccomp: resolver process 77 | //************************************************* 78 | #ifndef HAVE_SECCOMP 79 | void seccomp_resolver(void) {}; 80 | int seccomp_load_filter_list(void) { 81 | return 0; 82 | } 83 | #else 84 | static uint32_t arch_token; // system architecture as detected by libseccomp 85 | 86 | static void trap_handler_resolver(int sig, siginfo_t *siginfo, void *ucontext) { 87 | (void) ucontext; 88 | if (sig == SIGSYS) { 89 | fprintf(stderr, "Error: fdns resolver process %d killed by seccomp - syscall %d", arg_id, siginfo->si_syscall); 90 | char *syscall_name = seccomp_syscall_resolve_num_arch(arch_token, siginfo->si_syscall); 91 | if (syscall_name) 92 | fprintf(stderr, " (%s)", syscall_name); 93 | fprintf(stderr, "\n"); 94 | 95 | rlogprintf("Error: fdns resolver process %d killed by seccomp - syscall %d (%s)\n", arg_id, siginfo->si_syscall, syscall_name); 96 | free(syscall_name); 97 | } 98 | } 99 | 100 | static char *syscall_list; 101 | int seccomp_load_filter_list(void) { 102 | struct stat s; 103 | if (stat(PATH_ETC_RESOLVER_SECCOMP, &s) == -1) 104 | goto errout; 105 | 106 | syscall_list = malloc(s.st_size + 10); 107 | if (!syscall_list) 108 | errExit("malloc"); 109 | memset(syscall_list, 0, s.st_size + 10); 110 | 111 | FILE *fp = fopen(PATH_ETC_RESOLVER_SECCOMP, "r"); 112 | if (!fp) 113 | goto errout; 114 | 115 | if (fgets(syscall_list, s.st_size + 10, fp) == NULL) 116 | goto errout; 117 | char *tmp = strchr(syscall_list, '\n'); 118 | if (tmp) 119 | *tmp = '\0'; 120 | fclose(fp); 121 | return 1; 122 | 123 | errout: 124 | fprintf(stderr, "Warning: cannot find seccomp filter %s\n", PATH_ETC_RESOLVER_SECCOMP); 125 | rlogprintf("Warning: cannot find seccomp filter %s\n", PATH_ETC_RESOLVER_SECCOMP); 126 | return 0; 127 | } 128 | 129 | void seccomp_resolver(void) { 130 | char *tmp = syscall_list; 131 | 132 | arch_token = seccomp_arch_native(); 133 | scmp_filter_ctx ctx = seccomp_init(SCMP_ACT_TRAP); 134 | if (!ctx) 135 | goto errout; 136 | 137 | struct sigaction sa; 138 | sa.sa_sigaction = &trap_handler_resolver; 139 | sa.sa_flags = SA_SIGINFO; 140 | sigfillset(&sa.sa_mask); // mask all other signals during the handler execution 141 | if (sigaction(SIGSYS, &sa, NULL) == -1) 142 | fprintf(stderr, "Warning: cannot handle sigaction/SIGSYS\n"); 143 | 144 | char *syscall = strtok(tmp, ","); 145 | while(syscall) { 146 | if (seccomp_rule_add(ctx, SCMP_ACT_ALLOW, seccomp_syscall_resolve_name(syscall), 0) == -1) 147 | fprintf(stderr, "Warning: syscall %s not added\n", syscall); 148 | syscall = strtok(NULL, ","); 149 | } 150 | 151 | int rc = seccomp_load(ctx); 152 | //seccomp_export_bpf(ctx, STDOUT_FILENO); 153 | //seccomp_export_pfc(ctx, STDOUT_FILENO); 154 | 155 | if (rc) 156 | goto errout; 157 | 158 | seccomp_release(ctx); 159 | return; 160 | 161 | errout: 162 | fprintf(stderr, "Warning: cannot initialize seccomp\n"); 163 | rlogprintf("Warning: cannot initialize seccomp\n"); 164 | } 165 | 166 | #endif 167 | -------------------------------------------------------------------------------- /src/fdns/cache.c: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2019-2025 FDNS Authors 3 | * 4 | * This file is part of fdns project 5 | * 6 | * This program is free software: you can redistribute it and/or modify 7 | * it under the terms of the GNU General Public License as published by 8 | * the Free Software Foundation, either version 3 of the License, or 9 | * (at your option) any later version. 10 | * 11 | * This program is distributed in the hope that it will be useful, 12 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14 | * GNU General Public License for more details. 15 | * 16 | * You should have received a copy of the GNU General Public License 17 | * along with this program. If not, see . 18 | */ 19 | #include "fdns.h" 20 | #include "timetrace.h" 21 | 22 | // debug statistics 23 | //#define DEBUG_STATS 24 | #ifdef DEBUG_STATS 25 | static unsigned sentries = 0; // entries 26 | static unsigned scnt = 0; // print counter 27 | #endif 28 | 29 | typedef struct cache_entry_t { 30 | struct cache_entry_t *next; 31 | int16_t ttl; 32 | uint16_t len; 33 | int qtype; // query type: 0 - ipv4,, 1 - ipv6 34 | char name[CACHE_NAME_LEN + 1]; 35 | #define MAX_REPLY 900 36 | uint8_t reply[MAX_REPLY]; 37 | } CacheEntry; // not more than 1024 38 | 39 | #define MAX_HASH_ARRAY 256 40 | static CacheEntry *clist[MAX_HASH_ARRAY]; 41 | static int ccnt = 0; 42 | static char cname[CACHE_NAME_LEN + 1] = {0}; 43 | static int cname_type; // 0 - ipv4, 1 - ipv6 44 | static uint8_t creply[MAX_REPLY]; 45 | 46 | static inline void clean_entry(CacheEntry *ptr) { 47 | ptr->next = NULL; 48 | ptr->ttl = 0; 49 | ptr->qtype = 0; 50 | ptr->name[0] = '\0'; 51 | } 52 | 53 | // djb2 hash function by Dan Bernstein 54 | static inline int hash(const char *str, int qtype) { 55 | uint32_t hash = 5381; 56 | int c; 57 | 58 | while ((c = *str++) != '\0') 59 | hash = ((hash << 5) + hash) ^ c; // hash * 33 ^ c 60 | 61 | return (int) ((hash & (MAX_HASH_ARRAY - 1)) ^ qtype); 62 | } 63 | 64 | void cache_init(void) { 65 | memset(&clist[0], 0, sizeof(clist)); 66 | memset(cname, 0, sizeof(cname)); 67 | ccnt = 0; 68 | } 69 | 70 | void cache_set_name(const char *name, int qtype) { 71 | assert(name); 72 | strncpy(cname, name, CACHE_NAME_LEN); 73 | cname[CACHE_NAME_LEN] = '\0'; 74 | cname_type = qtype; 75 | } 76 | 77 | const char *cache_get_name(void) { 78 | return cname; 79 | } 80 | 81 | void cache_set_reply(uint8_t *reply, ssize_t len, int ttl) { 82 | assert(reply); 83 | assert(ttl > 0); 84 | if (len == 0 || len > MAX_REPLY || *cname == '\0') { 85 | *cname = '\0'; 86 | return; 87 | } 88 | 89 | int h = hash(cname, cname_type); 90 | CacheEntry *ptr = malloc(sizeof(CacheEntry)); 91 | if (!ptr) 92 | errExit("malloc"); 93 | clean_entry(ptr); 94 | ccnt++; 95 | #ifdef DEBUG_STATS 96 | sentries++; 97 | #endif 98 | ptr->len = len; 99 | ptr->qtype = cname_type; 100 | assert(sizeof(cname) == sizeof(ptr->name)); 101 | memcpy(ptr->name, cname, sizeof(cname)); 102 | memcpy(ptr->reply, reply, len); 103 | ptr->ttl = (int16_t) ttl; 104 | 105 | ptr->next = clist[h]; 106 | clist[h] = ptr; 107 | *cname = '\0'; 108 | } 109 | 110 | uint8_t *cache_check(uint16_t id, const char *name, ssize_t *lenptr, int qtype) { 111 | assert(name); 112 | int h = hash(name, qtype); 113 | CacheEntry *ptr = clist[h]; 114 | while (ptr) { 115 | if (strcmp(ptr->name, name) == 0 && ptr->qtype == qtype) { 116 | // store the reply locally 117 | assert(ptr->len); 118 | assert(ptr->len < MAX_REPLY); 119 | memcpy(creply, ptr->reply, ptr->len); 120 | // set id 121 | id = htons(id); 122 | memcpy(creply, &id, 2); 123 | // set length 124 | *lenptr = ptr->len; 125 | 126 | return creply; 127 | } 128 | 129 | ptr = ptr->next; 130 | } 131 | 132 | return NULL; 133 | } 134 | 135 | void cache_timeout(void) { 136 | int i; 137 | 138 | int cnt = 0; 139 | for (i = 0; i < MAX_HASH_ARRAY; i++) { 140 | CacheEntry *ptr = clist[i]; 141 | CacheEntry *last = NULL; 142 | 143 | while (ptr) { 144 | ptr->ttl--; 145 | if (ptr->ttl <= 0) { 146 | if (last == NULL) 147 | clist[i] = ptr->next; 148 | else 149 | last->next = ptr->next; 150 | CacheEntry *tmp = ptr; 151 | ptr = ptr->next; 152 | free(tmp); 153 | #ifdef DEBUG_STATS 154 | sentries--; 155 | #endif 156 | } 157 | else { 158 | last = ptr; 159 | ptr = ptr->next; 160 | } 161 | cnt++; 162 | } 163 | } 164 | ccnt = cnt; 165 | 166 | #ifdef DEBUG_STATS 167 | scnt++; 168 | if (scnt >= 60) { 169 | printf("*** (%d) cache entries %u, mem %lu, cache ttl %d\n", arg_id, sentries, (unsigned) sentries * sizeof(CacheEntry) + (unsigned) sizeof(clist), CACHE_TTL_DEFAULT); 170 | fflush(0); 171 | scnt = 0; 172 | } 173 | #endif 174 | } 175 | 176 | static int ctimer = CACHE_PRINT_TIMEOUT; 177 | void print_cache(void) { 178 | if (arg_daemonize) 179 | return; 180 | if (--ctimer > 0) 181 | return; 182 | if (ccnt == 0) 183 | return; 184 | 185 | ctimer = CACHE_PRINT_TIMEOUT; 186 | print_time(); 187 | printf("(%d) Cache: ", arg_id); 188 | 189 | int i; 190 | for (i = 0; i < MAX_HASH_ARRAY; i++) { 191 | CacheEntry *ptr = clist[i]; 192 | 193 | while (ptr) { 194 | printf("%s, ", ptr->name); 195 | ptr = ptr->next; 196 | } 197 | } 198 | 199 | if (ccnt == 1) 200 | printf("(%d domain)\n", ccnt); 201 | else 202 | printf("(%d domains)\n", ccnt); 203 | fflush(0); 204 | } 205 | -------------------------------------------------------------------------------- /etc/fdns.service: -------------------------------------------------------------------------------- 1 | [Unit] 2 | Description=Firejail DoH Proxy Server 3 | Documentation=man:fdns(1) 4 | Wants=network-online.target nss-lookup.target 5 | Before=nscd.service nss-lookup.target ntpdate.service 6 | 7 | [Service] 8 | Type=oneshot 9 | # start fdns as a local server listening on 127.1.1.1 loopback address 10 | ExecStart=/usr/bin/fdns --daemonize 11 | # start fdns as a network server listening on all interfaces and on 127.0.0.1 loopback address 12 | #ExecStart=/usr/bin/fdns --proxy-addr-any --daemonize 13 | # start fdns as a network server listening on a specific network interface address 14 | # --proxy-addr is broken when enabling RestrictAddressFamilies, see #15 15 | #ExecStart=/usr/bin/fdns --proxy-addr=192.168.1.200 --daemonize 16 | # For more options like --allow-all-queries see man 1 fdns. 17 | RemainAfterExit=true 18 | 19 | # Log all queries to /tmp/fdns-log.txt. 20 | # [NOTE] /tmp is often a tmpfs, you need to change this path if you want persistent logs. 21 | #StandardOutput=append:/tmp/fdns-log.txt 22 | 23 | ############################################################### 24 | ## HARDENING NOTES: 25 | ## - depending on your systemd version not all options are supported - 26 | ## unsupported options are ignored and can be checked via `systemctl status fdns.service`; 27 | ## - lines commented with `#*` are required by systemd; 28 | ## - see man 5 systemd.exec for details on the options; 29 | ## - firejail equivalents are commented with `# FJ`; 30 | ############################################################### 31 | 32 | AmbientCapabilities= 33 | LockPersonality=true 34 | MountFlags=private 35 | ProtectClock=true 36 | ProtectControlGroups=true 37 | ProtectHostname=true 38 | ProtectKernelLogs=true 39 | ProtectKernelModules=true 40 | ProtectKernelTunables=true 41 | RestrictNamespaces=ipc mnt pid uts 42 | RestrictRealtime=true 43 | RestrictSUIDSGID=true 44 | SecureBits=noroot-locked 45 | 46 | # FJ: --caps.keep=kill,net-bind,setgid,setuid,sys-admin,sys-chroot 47 | CapabilityBoundingSet=CAP_KILL CAP_NET_BIND_SERVICE CAP_SETGID CAP_SETUID CAP_SYS_ADMIN CAP_SYS_CHROOT 48 | 49 | # FJ: disable-mnt 50 | InaccessiblePaths=-/media 51 | InaccessiblePaths=-/mnt 52 | InaccessiblePaths=-/run/media 53 | #*InaccessiblePaths=-/run/mount 54 | 55 | # firejail's hardcoded blacklist 56 | InaccessiblePaths=-/boot 57 | InaccessiblePaths=-/dev/kmsg 58 | InaccessiblePaths=-/dev/port 59 | InaccessiblePaths=-/lib/modules 60 | InaccessiblePaths=-/proc/bus 61 | InaccessiblePaths=-/proc/config.gz 62 | InaccessiblePaths=-/proc/irq 63 | InaccessiblePaths=-/proc/kallsyms 64 | InaccessiblePaths=-/proc/kcore 65 | InaccessiblePaths=-/proc/kmem 66 | #*InaccessiblePaths=-/proc/kmsg 67 | InaccessiblePaths=-/proc/mem 68 | InaccessiblePaths=-/proc/sched_debug 69 | InaccessiblePaths=-/proc/sys/efi/vars 70 | InaccessiblePaths=-/proc/sys/fs/binfmt_misc 71 | #*InaccessiblePaths=-/proc/sys/kernel/core_pattern 72 | InaccessiblePaths=-/proc/sys/kernel/hotplug 73 | #*InaccessiblePaths=-/proc/sys/kernel/modprobe 74 | InaccessiblePaths=-/proc/sys/security 75 | #*InaccessiblePaths=-/proc/sys/vm/panic_on_oom 76 | InaccessiblePaths=-/proc/sysrq-trigger 77 | InaccessiblePaths=-/proc/timer_list 78 | InaccessiblePaths=-/proc/timer_stats 79 | InaccessiblePaths=-/selinux 80 | InaccessiblePaths=-/sys/firmware 81 | InaccessiblePaths=-/sys/fs 82 | InaccessiblePaths=-/sys/hypervisor 83 | InaccessiblePaths=-/sys/kernel/debug 84 | InaccessiblePaths=-/sys/kernel/uevent_helper 85 | InaccessiblePaths=-/sys/kernel/vmcoreinfo 86 | InaccessiblePaths=-/sys/module 87 | InaccessiblePaths=-/sys/power 88 | #*InaccessiblePaths=-/usr/lib/debug 89 | InaccessiblePaths=-/usr/src/linux 90 | 91 | # FJ: --memory-deny-write-execute 92 | MemoryDenyWriteExecute=true 93 | 94 | # FJ: --nonewprivs 95 | NoNewPrivileges=true 96 | 97 | # FJ: --blacklist=/home --blacklist=/root --blacklist=/run/user 98 | ProtectHome=true 99 | 100 | # FJ: --private-dev --no… 101 | PrivateDevices=true 102 | 103 | # FJ: --private-etc=ca-certificates,crypto-policies,fdns,ld.so.cache,ld.so.preload,localtime,nsswitch.conf,passwd,pki,ssl 104 | BindReadOnlyPaths=-/etc/ca-certificates 105 | BindReadOnlyPaths=-/etc/crypto-policies 106 | BindReadOnlyPaths=-/etc/fdns 107 | BindReadOnlyPaths=-/etc/ld.so.cache 108 | BindReadOnlyPaths=-/etc/ld.so.preload 109 | BindReadOnlyPaths=-/etc/localtime 110 | BindReadOnlyPaths=-/etc/nsswitch.conf 111 | BindReadOnlyPaths=-/etc/passwd 112 | BindReadOnlyPaths=-/etc/pki 113 | BindReadOnlyPaths=-/etc/ssl 114 | TemporaryFileSystem=/etc 115 | 116 | # FJ: --private-tmp 117 | PrivateTmp=true 118 | 119 | # FJ: --protocol=unix,inet,inet6 (Breaks --proxy-addr, see #15) 120 | RestrictAddressFamilies=AF_UNIX AF_INET AF_INET6 121 | 122 | # FJ: --read-only=/ (except: /dev, /proc, /sys) 123 | ProtectSystem=strict 124 | 125 | # FJ: mkdir + --read-write=/run/fdns 126 | RuntimeDirectory=fdns 127 | ReadWritePaths=/run/fdns 128 | 129 | # seccomp filter enabled only in fdns daemon 130 | ## FJ: --seccomp.keep=@system-service,seccomp 131 | #SystemCallFilter=@system-service seccomp 132 | ## FJ: --seccomp.drop=@aio,@chown,@ipc,@keyring,@memlock,@privileged,@resources,@sync,@timer 133 | #SystemCallFilter=~@aio @chown @ipc @keyring @memlock @privileged @resources @sync @timer 134 | ## FJ: --seccomp.keep=@mount,@setuid 135 | #SystemCallFilter=@mount @setuid 136 | ## FJ: --seccomp.block-secondary 137 | #SystemCallArchitectures=native 138 | 139 | # If you use a static server (--server=name|url), you can enable ip address whitelisting 140 | # by adding the IP of your DoH-Server below and uncomment the next lines. 141 | #IPAddressDeny=any 142 | #IPAddressAllow=localhost 143 | #IPAddressAllow= ... 144 | # 145 | 146 | [Install] 147 | WantedBy=multi-user.target 148 | ############################################################### 149 | -------------------------------------------------------------------------------- /configure.ac: -------------------------------------------------------------------------------- 1 | # 2 | # Note: 3 | # 4 | # If for any reason autoconf fails, run "autoreconf -i --install " and try again. 5 | # This is how the error looks like on Arch Linux: 6 | # ./configure: line 3064: syntax error near unexpected token `newline' 7 | # ./configure: line 3064: `AX_CHECK_COMPILE_FLAG(' 8 | # 9 | # We rely solely on autoconf, without automake. Apparently, in this case 10 | # the macros from m4 directory are not picked up by default by automake. 11 | # "autoreconf -i --install" seems to fix the problem. 12 | # 13 | # Config options for various distros: 14 | # - Debian: ./configure --prefix=/usr --enable-apparmor --with-systemd=/lib/systemd/system 15 | # - Fedora: ./configure --prefix=/usr --with-systemd=/usr/lib/systemd/system 16 | # - Arch:./configure --prefix=/usr --enable-apparmor --with-systemd=/usr/lib/systemd/system 17 | # 18 | # Run "pkg-config systemd --variable=systemdsystemunitdir" to find out where systemd unit 19 | # files are stored on your system. 20 | 21 | 22 | AC_PREREQ([2.71]) 23 | AC_INIT([fdns],[0.9.77],[netblue30@protonmail.com],[],[https://firejaildns.wordpress.com]) 24 | AC_CONFIG_SRCDIR([src/fdns/main.c]) 25 | 26 | AC_CONFIG_MACRO_DIR([m4]) 27 | 28 | AC_PROG_CC 29 | AC_PROG_INSTALL 30 | AC_PROG_RANLIB 31 | 32 | HAVE_SPECTRE="no" 33 | AX_CHECK_COMPILE_FLAG( 34 | [-mindirect-branch=thunk], 35 | [HAVE_SPECTRE="yes" && EXTRA_CFLAGS+=" -mindirect-branch=thunk"] 36 | ) 37 | AX_CHECK_COMPILE_FLAG( 38 | [-mretpoline], 39 | [HAVE_SPECTRE="yes" && EXTRA_CFLAGS+=" -mretpoline"] 40 | ) 41 | AX_CHECK_COMPILE_FLAG( 42 | [-fstack-clash-protection], 43 | [EXTRA_CFLAGS+=" -fstack-clash-protection"] 44 | ) 45 | AX_CHECK_COMPILE_FLAG( 46 | [-fstack-protector-strong], 47 | [EXTRA_CFLAGS+=" -fstack-protector-strong"] 48 | ) 49 | 50 | AC_ARG_ENABLE([analyzer], 51 | [AS_HELP_STRING([--enable-analyzer], [enable GCC static analyzer])]) 52 | AS_IF([test "x$enable_analyzer" = "xyes"], [ 53 | EXTRA_CFLAGS="$EXTRA_CFLAGS -fanalyzer -Wno-analyzer-malloc-leak" 54 | ]) 55 | 56 | AC_ARG_ENABLE([sanitizer], 57 | [AS_HELP_STRING([--enable-sanitizer=@<:@address | memory | undefined@:>@], 58 | [enable a compiler-based sanitizer (debug)])], 59 | [], 60 | [enable_sanitizer=no]) 61 | 62 | AS_IF([test "x$enable_sanitizer" != "xno" ], [ 63 | AX_CHECK_COMPILE_FLAG([-fsanitize=$enable_sanitizer], [ 64 | EXTRA_CFLAGS="$EXTRA_CFLAGS -fsanitize=$enable_sanitizer -fno-omit-frame-pointer" 65 | EXTRA_LDFLAGS="$EXTRA_LDFLAGS -fsanitize=$enable_sanitizer" 66 | ], [AC_MSG_ERROR([sanitizer not supported: $enable_sanitizer])]) 67 | ]) 68 | 69 | AC_SUBST([EXTRA_CFLAGS]) 70 | AC_SUBST([EXTRA_LDFLAGS]) 71 | 72 | HAVE_APPARMOR="" 73 | AC_ARG_ENABLE([apparmor], 74 | AS_HELP_STRING([--enable-apparmor], [enable apparmor])) 75 | AS_IF([test "x$enable_apparmor" = "xyes"], [ 76 | HAVE_APPARMOR="-DHAVE_APPARMOR" 77 | AC_SUBST(HAVE_APPARMOR) 78 | ]) 79 | 80 | HAVE_FATAL_WARNINGS="" 81 | AC_ARG_ENABLE([fatal_warnings], 82 | AS_HELP_STRING([--enable-fatal-warnings], [-W -Wall -Werror])) 83 | AS_IF([test "x$enable_fatal_warnings" = "xyes"], [ 84 | HAVE_FATAL_WARNINGS="-W -Wall -Werror" 85 | AC_SUBST(HAVE_FATAL_WARNINGS) 86 | ]) 87 | 88 | HAVE_GCOV="" 89 | AC_ARG_ENABLE([gcov], 90 | AS_HELP_STRING([--enable-gcov], [Gcov instrumentation])) 91 | AS_IF([test "x$enable_gcov" = "xyes"], [ 92 | HAVE_GCOV="--coverage -DHAVE_GCOV " 93 | EXTRA_LDFLAGS+=" -lgcov --coverage " 94 | AC_SUBST(HAVE_GCOV) 95 | ]) 96 | 97 | # checking pthread library 98 | AC_CHECK_LIB([pthread], [main], [], AC_MSG_ERROR([*** POSIX thread support not installed ***])) 99 | AC_CHECK_HEADER(pthread.h,,AC_MSG_ERROR([*** POSIX thread support not installed ***])) 100 | 101 | # bring in openssl library 102 | #AC_CHECK_LIB([seccomp], [main], [], AC_MSG_ERROR([*** libseccomp not installed ***])) 103 | #AC_CHECK_HEADER(seccomp.h,,AC_MSG_ERROR([*** libseccomp development headers not installed ***])) 104 | #echo libseccomp library found 105 | AX_CHECK_OPENSSL([ 106 | echo OpenSSL library found], [ 107 | AC_MSG_ERROR([*** OpenSSL development headers not installed - packages: libssl-dev (Debian/Ubuntu), openssl-devel (Fedora) ***]) 108 | ]) 109 | 110 | HAVE_SECCOMP="-DHAVE_SECCOMP" 111 | AC_ARG_ENABLE([seccomp], 112 | AS_HELP_STRING([--disable-seccomp], [disable seccomp])) 113 | AS_IF([test "x$enable_seccomp" == "xno"], [ 114 | HAVE_SECCOMP="" 115 | ]) 116 | AC_SUBST(HAVE_SECCOMP) 117 | 118 | if test "$HAVE_SECCOMP" = "-DHAVE_SECCOMP"; then 119 | AC_CHECK_LIB([seccomp], [main], [], AC_MSG_ERROR([*** libseccomp not installed ***])) 120 | AC_CHECK_HEADER(seccomp.h,,AC_MSG_ERROR([*** libseccomp development headers not installed ***])) 121 | fi 122 | 123 | 124 | # set sysconfdir 125 | if test "$prefix" = /usr; then 126 | test "$sysconfdir" = '${prefix}/etc' && sysconfdir="/etc" 127 | fi 128 | 129 | # set systemd unit file 130 | SYSTEMD_DIR="$sysconfdir/fdns" 131 | AC_ARG_WITH([systemd], 132 | AS_HELP_STRING([--with-systemd=DIR],[Pathname to systemd unit directory. Without this option, a copy of the unit file is installed in ${sysconfdir}/fdns directory]), 133 | [SYSTEMD_DIR="$withval"]) 134 | AC_SUBST(SYSTEMD_DIR) 135 | 136 | AC_CONFIG_FILES([Makefile src/common.mk]) 137 | AC_OUTPUT 138 | 139 | echo 140 | echo "Configuration options:" 141 | echo " prefix: $prefix" 142 | echo " sysconfdir: $sysconfdir" 143 | echo " systemd directory: $SYSTEMD_DIR" 144 | echo " Spectre compiler patch: $HAVE_SPECTRE" 145 | echo " apparmor: $HAVE_APPARMOR" 146 | echo " seccomp: $HAVE_SECCOMP" 147 | echo " EXTRA_LDFLAGS: $EXTRA_LDFLAGS" 148 | echo " EXTRA_CFLAGS: $EXTRA_CFLAGS" 149 | echo " fatal warnings: $HAVE_FATAL_WARNINGS" 150 | echo " gcc analyzer: $enable_analyzer" 151 | echo " Gcov instrumentation: $HAVE_GCOV" 152 | echo 153 | -------------------------------------------------------------------------------- /Makefile.in: -------------------------------------------------------------------------------- 1 | all: apps man 2 | APPS = src/fdns src/nxdomain src/dnsc 3 | MANPAGES = fdns.1 nxdomain.1 dnsc.1 4 | 5 | prefix=@prefix@ 6 | exec_prefix=@exec_prefix@ 7 | bindir=@bindir@ 8 | libdir=@libdir@ 9 | datarootdir=@datarootdir@ 10 | mandir=@mandir@ 11 | sysconfdir=@sysconfdir@ 12 | 13 | VERSION=@PACKAGE_VERSION@ 14 | NAME=@PACKAGE_NAME@ 15 | PACKAGE_TARNAME=@PACKAGE_TARNAME@ 16 | DOCDIR=@docdir@ 17 | HAVE_APPARMOR=@HAVE_APPARMOR@ 18 | SYSTEMD_DIR=@SYSTEMD_DIR@ 19 | CPPCHECK ?= cppcheck 20 | 21 | .PHONY: apps $(APPS) 22 | apps: $(APPS) 23 | $(APPS): $(LIBS) 24 | $(MAKE) -C $@ 25 | 26 | $(MANPAGES): $(wildcard src/man/*.txt) 27 | ./mkman.sh $(VERSION) src/man/$(basename $@).txt $@ 28 | 29 | man: $(MANPAGES) 30 | 31 | clean: 32 | rm -f gcov-file gcov-file-new gcov-file-old 33 | for dir in $(APPS); do \ 34 | $(MAKE) -C $$dir clean; \ 35 | done 36 | rm -f $(MANPAGES) $(MANPAGES:%=%.gz) 37 | cd test/compile; ./compile.sh --clean; cd ../.. 38 | 39 | distclean: clean 40 | for dir in $(APPS); do \ 41 | $(MAKE) -C $$dir distclean; \ 42 | done 43 | rm -fr Makefile autom4te.cache config.log config.status config.h src/common.mk 44 | 45 | realinstall: 46 | # fdns executable 47 | install -m 0755 -d $(DESTDIR)/$(bindir) 48 | install -c -m 0755 src/fdns/fdns $(DESTDIR)/$(bindir)/. 49 | install -c -m 0755 src/nxdomain/nxdomain $(DESTDIR)/$(bindir)/. 50 | install -c -m 0755 src/dnsc/dnsc $(DESTDIR)/$(bindir)/. 51 | # documents 52 | install -m 0755 -d $(DESTDIR)/$(DOCDIR) 53 | install -c -m 0644 COPYING $(DESTDIR)/$(DOCDIR)/. 54 | install -c -m 0644 README $(DESTDIR)/$(DOCDIR)/. 55 | install -c -m 0644 RELNOTES $(DESTDIR)/$(DOCDIR)/. 56 | install -c -m 0644 etc/blocklists/README.md $(DESTDIR)/$(DOCDIR)/README-blocklists 57 | # etc files 58 | install -m 0755 -d $(DESTDIR)$(sysconfdir)/fdns 59 | install -m 0644 -t $(DESTDIR)$(sysconfdir)/fdns etc/resolver.seccomp 60 | install -m 0644 -t $(DESTDIR)$(sysconfdir)/fdns etc/servers 61 | install -m 0644 -t $(DESTDIR)$(sysconfdir)/fdns etc/blocklists/list.adblocker 62 | install -m 0644 -t $(DESTDIR)$(sysconfdir)/fdns etc/blocklists/list.trackers 63 | install -m 0644 -t $(DESTDIR)$(sysconfdir)/fdns etc/blocklists/list.coinblocker 64 | install -m 0644 -t $(DESTDIR)$(sysconfdir)/fdns etc/blocklists/list.phishing 65 | install -m 0644 -t $(DESTDIR)$(sysconfdir)/fdns etc/blocklists/list.tld-blacklist 66 | install -m 0644 -t $(DESTDIR)$(sysconfdir)/fdns etc/blocklists/list.malware 67 | install -m 0644 -t $(DESTDIR)$(sysconfdir)/fdns etc/blocklists/list.dyndns 68 | install -m 0644 -t $(DESTDIR)$(sysconfdir)/fdns etc/blocklists/whitelist 69 | # install server customization file 70 | sh -c "if [ ! -f $(DESTDIR)/$(sysconfdir)/fdns/servers.local ]; then install -c -m 0644 etc/servers.local $(DESTDIR)/$(sysconfdir)/fdns/servers.local; fi;" 71 | # install user hosts customization file 72 | sh -c "if [ ! -f $(DESTDIR)/$(sysconfdir)/fdns/hosts ]; then install -c -m 0644 etc/hosts $(DESTDIR)/$(sysconfdir)/fdns/hosts; fi;" 73 | 74 | # systemd service unit 75 | install -Dm0644 etc/fdns.service $(DESTDIR)$(SYSTEMD_DIR)/fdns.service 76 | # man pages 77 | echo "**********************************" 78 | echo $(MANPAGES) 79 | echo "**********************************" 80 | install -m 0755 -d $(DESTDIR)/$(mandir)/man1 81 | for man in $(MANPAGES); do \ 82 | rm -f $$man.gz; \ 83 | gzip -9n $$man; \ 84 | case "$$man" in \ 85 | *.1) install -c -m 0644 $$man.gz $(DESTDIR)/$(mandir)/man1/; ;; \ 86 | esac; \ 87 | done 88 | rm -f $(MANPAGES) $(MANPAGES:%=%.gz) 89 | # bash completion 90 | install -m 0755 -d $(DESTDIR)/$(datarootdir)/bash-completion/completions 91 | install -c -m 0644 src/bash_completion/fdns.bash_completion $(DESTDIR)/$(datarootdir)/bash-completion/completions/fdns 92 | ifeq ($(HAVE_APPARMOR),-DHAVE_APPARMOR) 93 | # apparmor profile 94 | sh -c "if [ ! -d $(DESTDIR)/$(sysconfdir)/apparmor.d ]; then install -d -m 755 $(DESTDIR)/$(sysconfdir)/apparmor.d; fi;" 95 | sh -c "if [ ! -d $(DESTDIR)/$(sysconfdir)/apparmor.d/local ]; then install -d -m 755 $(DESTDIR)/$(sysconfdir)/apparmor.d/local; fi;" 96 | install -c -m 0644 etc/apparmor/usr.bin.fdns $(DESTDIR)/$(sysconfdir)/apparmor.d/. 97 | install -c -m 0644 etc/apparmor/fdns-local $(DESTDIR)/$(sysconfdir)/apparmor.d/local/usr.bin.fdns 98 | endif 99 | 100 | install: all 101 | $(MAKE) realinstall 102 | 103 | install-strip: all 104 | strip src/fdns/fdns 105 | strip src/nxdomain/nxdomain 106 | strip src/dnsc/dnsc 107 | $(MAKE) realinstall 108 | 109 | uninstall: 110 | rm -f $(DESTDIR)/$(bindir)/fdns 111 | rm -f $(DESTDIR)/$(bindir)/nxdomain 112 | rm -fr $(DESTDIR)/$(datarootdir)/doc/fdns 113 | for man in $(MANPAGES); do \ 114 | rm -f $(DESTDIR)/$(mandir)/man1/$$man*; \ 115 | done 116 | rm -f $(DESTDIR)/$(datarootdir)/bash-completion/completions/fdns 117 | 118 | DISTFILES = "src etc platform test configure configure.ac Makefile.in install.sh mkasc.sh mkman.sh mkdeb.sh COPYING README RELNOTES" 119 | 120 | dist: 121 | mv config.status config.status.old 122 | make distclean 123 | mv config.status.old config.status 124 | rm -fr $(NAME)-$(VERSION) $(NAME)-$(VERSION).tar.xz 125 | mkdir $(NAME)-$(VERSION) 126 | cp -a "$(DISTFILES)" $(NAME)-$(VERSION) 127 | rm -rf $(NAME)-$(VERSION)/src/tools 128 | tar -cJvf $(NAME)-$(VERSION).tar.xz $(NAME)-$(VERSION) 129 | rm -fr $(NAME)-$(VERSION) 130 | 131 | asc:; ./mkasc.sh $(VERSION) 132 | 133 | deb: dist 134 | ./mkdeb.sh $(NAME) $(VERSION) 135 | 136 | cppcheck: clean 137 | $(CPPCHECK) --force --error-exitcode=1 --enable=warning,performance --check-level=exhaustive . 138 | 139 | filtercheck:; ./filtercheck.sh 140 | 141 | scan-build: clean 142 | NO_EXTRA_CFLAGS="yes" scan-build make 143 | 144 | test-fdns: 145 | cd test/fdns;su -c ./test.sh | grep TESTING 146 | 147 | test: test-fdns 148 | echo "TEST COMPLETE" 149 | 150 | test-compile: dist 151 | cd test/compile; ./compile.sh $(NAME)-$(VERSION) 152 | 153 | gcov: 154 | su -c ./gcov.sh 155 | 156 | .PHONY: codespell 157 | codespell: 158 | @printf 'Running %s...\n' $@ 159 | @codespell --ignore-regex 'UE|als|chage|creat|doas|ether|isplay|readby|[Ss]hotcut' \ 160 | -S *.gz,*.o,*.so \ 161 | -S COPYING,m4 \ 162 | -S ./contrib/syscalls.sh \ 163 | -S ./etc \ 164 | -S ./.git \ 165 | -S ./test \ 166 | . 167 | 168 | .PHONY: print-env 169 | print-env: 170 | ./ci/printenv.sh 171 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Firejail DNS-over-HTTPS Proxy Server 2 |
 
3 | 4 | 5 | 6 | 13 | 20 | 21 |
7 | 8 | Network Security Introduction 10 |
Network Security Introduction 11 |
12 |
14 | 15 | Firejail Encrypted DNS HowTo 17 |
Firejail Encrypted DNS HowTo 18 |
19 |
22 | 23 |
 
24 | 25 | FDNS is an encrypted DNS proxy designed for small networks and Linux desktops. Lean and mean, it protects your computer from some of the most common cyber threats, while also improving privacy and the system performance. 26 | 27 | FDNS is written in C and licensed under GPLv3. We use only DoH/DoT/DoQ services from non-logging and non-censoring providers, while preferring small operators such as open-source enthusiasts and privacy-oriented non-profit organizations. 28 | 29 |
 
30 | 31 | 32 | 33 | 34 |
 
35 | 36 |

Features

37 |
    38 |
  • Network of 300+ non-logging/non-censoring service providers spread across the globe. Access to specialized services such as family filtering, adblocking, and security.
  • 39 |
  • Blocking ads, trackers, coinminers, phishing.
  • 40 |
  • DNS resolver cache and DNS firewall targeting various DNS attack techniques.
  • 41 |
  • Highly scalable multi-process design and built-in support for various security technologies such as seccomp, Linux namespaces, and AppArmor.
  • 42 |
  • Seamless integration with Firejail Security Sandbox.
  • 43 |
44 | 45 | ![FDNS monitor](monitor1.png) 46 | 47 |
 
48 | 49 | 50 |

Build and Install

51 | 52 | ````` 53 | sudo apt install build-essential make git 54 | sudo apt install libseccomp-dev libssl-dev 55 | git clone --recursive https://github.com/netblue30/fdns 56 | cd fdns 57 | ./configure --prefix=/usr --enable-apparmor 58 | make 59 | sudo make install 60 | (to uninstall) sudo make uninstall 61 | (to update the repo) git submodule update --remote --merge 62 | ````` 63 | 64 |
 
65 |

About us

66 |
 
67 | 68 | FDNS is a community project. We are not affiliated with any company, and we don’t have any commercial goals. Our focus is the Linux desktop. Home users and Linux beginners are our target market. The software is built by a large international team of volunteers on GitHub. Expert or regular Linux user, you are welcome to join us! 69 | 70 | Security bugs are taken seriously, please email them to netblue30 at protonmail.com. 71 | 72 | 80 |
 
81 | 82 |

Development release 0.9.77:

83 | 84 | Current DNS over QUIC server list: 85 | ````` 86 | $ fdns --list=all | grep quic 87 | adguard-unfiltered-quic - anycast, quic, America, AsiaPacific, Europe 88 | adguard-unfiltered2-quic - anycast, quic, America, AsiaPacific, Europe 89 | adguard-quic - anycast, quic, adblocker, America, AsiaPacific, Europe 90 | adguard2-quic - anycast, quic, adblocker, America, AsiaPacific, Europe 91 | adguard-family-quic - quic, family, America, AsiaPacific, Europe 92 | adguard-family2-quic - quic, family, America, AsiaPacific, Europe 93 | blissdns-quic - quic, adblocker, US, America 94 | brahmaworld-quic - quic, adblocker, Sweden, Europe 95 | dnsdoh.art-quic - quic, adblocker, Finland, Europe 96 | dynx-quic - quic, adblocker, Germany, Europe 97 | hagezi-quic - dot, adblocker, Germany, Europe 98 | hagezi2-quic - dot, adblocker, Germany, Europe 99 | hagezi3-quic - dot, adblocker, Finland, Europe 100 | nextdns-quic - quic, America, AsiaPacific, Europe 101 | nextdns2-quic - quic, America, AsiaPacific, Europe 102 | nextdns3-quic - anycast, quic, America, AsiaPacific, Europe 103 | noridev-quic - quic, adblocker, Korea, AsiaPacific, FarEast 104 | qquackdns-quic - quic, Korea, AsiaPacific, FarEast 105 | rbn-quic - quic, adblocker, Germany, Europe 106 | rezjahul-quic - quic, adblocker, Singapore, AsiaPacific, FarEast 107 | surfshark-quic - anycast, quic, America, AsiaPacific, Europe 108 | sz-dns-quic - quic, America, AsiaPacific, Europe 109 | tridns-ca-quic - quic, America, Canada 110 | tridns-ch-quic - quic, Europe, Switzerland 111 | tridns-sg-quic - quic, Singapore, AsiaPacific. FarEast 112 | tiarap2-quic - quic, Singapore, FarEast, AsiaPacific 113 | zdn-quic - quic, adblocker, Romania, Europe 114 | ````` 115 | A short comparison of DoH, DoT, and DoQ: 116 | ````` 117 | $ fdns --test-server 118 | Current zone: America 119 | 120 | Testing server adguard-unfiltered 121 | Tags: anycast, America, AsiaPacific, Europe 122 | SSL/TLS connection: 122.96 ms 123 | DoH query average: 22.22 ms 124 | DoH/Do53 bandwidth ratio: 2.13 125 | 126 | Testing server adguard-unfiltered-dot 127 | Tags: anycast, dot, America, AsiaPacific, Europe 128 | SSL/TLS connection: 146.72 ms 129 | DoT query average: 20.88 ms 130 | DoT/Do53 bandwidth ratio: 1.32 131 | 132 | Testing server adguard-unfiltered-quic 133 | Tags: anycast, quic, America, AsiaPacific, Europe 134 | SSL/TLS connection: 485.44 ms 135 | DoQ query average: 22.92 ms 136 | [...] 137 | ````` 138 | Larger connection time, but similar query time averages. We will add more DoQ servers as the technology matures. 139 | 140 |
 
141 | -------------------------------------------------------------------------------- /src/fdns/dot.c: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2019-2025 FDNS Authors 3 | * 4 | * This file is part of fdns project 5 | * 6 | * This program is free software: you can redistribute it and/or modify 7 | * it under the terms of the GNU General Public License as published by 8 | * the Free Software Foundation, either version 3 of the License, or 9 | * (at your option) any later version. 10 | * 11 | * This program is distributed in the hope that it will be useful, 12 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14 | * GNU General Public License for more details. 15 | * 16 | * You should have received a copy of the GNU General Public License 17 | * along with this program. If not, see . 18 | */ 19 | 20 | #define _GNU_SOURCE 21 | #include 22 | #include 23 | #include 24 | #include 25 | #include 26 | #include 27 | #include 28 | #include "fdns.h" 29 | #include "timetrace.h" 30 | #include "lint.h" 31 | 32 | static void dot_header_stats(void); 33 | static double dot_bandwidth(void); 34 | static void dot_init(void); 35 | static void dot_close(void); 36 | static int dot_connect(void); 37 | static int dot_send_exampledotcom(uint8_t *req); 38 | static int dot_send_query(uint8_t *req, int cnt); 39 | static int dot_send_ping(void); 40 | static int dot_exchange(uint8_t *response, uint32_t stream); 41 | static void dot_print_url(void); 42 | DnsTransport dot_transport = { 43 | "dot", 44 | "DoT", 45 | dot_init, 46 | dot_close, 47 | dot_connect, 48 | dot_send_exampledotcom, 49 | dot_send_query, 50 | dot_send_ping, 51 | dot_exchange, 52 | dot_header_stats, 53 | dot_bandwidth, 54 | dot_print_url 55 | }; 56 | 57 | 58 | static int dot_rx = 0; // received bytes, including IP/TCP/TLS headers 59 | static int dot_rx_dns = 0; // received DNS bytes over H2 plus IP/UDP 60 | static int first_query = 1; // don't include the first query in network byte count 61 | 62 | static void dot_print_url(void) { 63 | DnsServer *srv = server_get(); 64 | assert(srv); 65 | printf(" URL: dot://%s\n", srv->host); 66 | } 67 | 68 | static void dot_header_stats(void) { 69 | } 70 | 71 | // DoT/DNS ratio 72 | static double dot_bandwidth(void) { 73 | if (dot_rx_dns == 0) 74 | return 0; 75 | return (double) dot_rx / (double) dot_rx_dns; 76 | } 77 | 78 | static void dot_init(void) { 79 | first_query = 1; 80 | } 81 | 82 | static void dot_close(void) { 83 | first_query = 1; 84 | } 85 | 86 | static uint8_t buf_query[MAXBUF]; 87 | // returns -1 if error 88 | static int dot_connect(void) { 89 | return 0; 90 | } 91 | 92 | // the result message is placed in res, the length of the message is returned 93 | // returns -1 if error 94 | static int dot_send_exampledotcom(uint8_t *req) { 95 | uint8_t dnsmsg[] = { 96 | 0x00, 0x00, 0x01, 0x00, 0x00, 0x01, 0x00, 97 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x07, 0x65, 0x78, 0x61, 0x6d, 0x70, 0x6c, 0x65, 0x03, 0x63, 0x6f, 98 | 0x6d, 0x00, 0x00, 0x02, 0x00, 0x01 99 | }; 100 | 101 | // two bytes length field 102 | uint16_t len = htons(sizeof(dnsmsg)); 103 | memcpy(buf_query, &len, 2); 104 | memcpy(buf_query + 2, dnsmsg, sizeof(dnsmsg)); 105 | 106 | if (arg_debug || arg_debug_transport) { 107 | print_time(); 108 | printf("(%d) tx len %d dot\n", arg_id, (int) sizeof(dnsmsg) + 2); 109 | } 110 | ssl_tx(buf_query, sizeof(dnsmsg) + 2); 111 | int rv = dot_exchange(req, 0); 112 | first_query = 0; 113 | return rv; 114 | } 115 | 116 | 117 | // the result message is placed in req, the length of the message is returned 118 | // returns -1 if error 119 | static int dot_send_query(uint8_t *req, int cnt) { 120 | if (cnt <= 0 || cnt > DNS_MAX_DOMAIN_NAME) 121 | return 0; 122 | 123 | // two bytes length field 124 | uint16_t len = htons(cnt); 125 | memcpy(buf_query, &len, 2); 126 | memcpy(buf_query + 2, req, cnt); 127 | 128 | if (arg_debug || arg_debug_transport) { 129 | print_time(); 130 | printf("(%d) tx len %d dot\n", arg_id, cnt + 2); 131 | } 132 | ssl_tx(buf_query, cnt + 2); 133 | int rv = dot_exchange(req, 0); 134 | first_query = 0; 135 | return rv; 136 | } 137 | 138 | // returns -1 if error 139 | static int dot_send_ping(void) { 140 | return dot_send_exampledotcom(buf_query); 141 | } 142 | 143 | // copy rx data in response and return the length 144 | // return -1 if error 145 | static int dot_exchange(uint8_t *response, uint32_t stream) { 146 | assert(response); 147 | (void) stream; 148 | 149 | uint8_t buf[MAXBUF]; 150 | int total_len = ssl_rx_timeout((uint8_t *) buf, MAXBUF, TRANSPORT_TIMEOUT); 151 | if (total_len == 0) 152 | goto errout; 153 | 154 | if (arg_debug) 155 | print_mem(buf, total_len); 156 | 157 | if (arg_debug || arg_debug_transport) { 158 | print_time(); 159 | printf("(%d) rx len %d dot\n", arg_id, total_len); 160 | } 161 | 162 | dot_rx += 20 + 20 + 5 + (int) ((float) total_len * 1.2); // ip + tcp + dot 163 | 164 | uint16_t len; 165 | memcpy(&len, buf, 2); 166 | len = ntohs(len); 167 | if (len > (MAXBUF - 2)) 168 | goto errout; 169 | 170 | if ((arg_debug || arg_details) && first_query) { 171 | printf("\n Network trace:\n"); 172 | printf("-----> rx %d bytes: IP + TCP + TLS\n", 20 + 20 + 5 + (int) ((float) total_len * 1.2)); 173 | } 174 | 175 | if ((total_len - 2) > len) { 176 | // read some more data 177 | int newlen = ssl_rx_timeout(buf + total_len, MAXBUF - total_len, TRANSPORT_TIMEOUT); 178 | if (len == 0) 179 | goto errout; 180 | if (arg_debug || arg_debug_transport) { 181 | print_time(); 182 | printf("(%d) rx len %d dot\n", arg_id, total_len); 183 | } 184 | total_len += newlen; 185 | dot_rx += 20 + 20 + 5 + (int) ((float) newlen * 1.2); // ip + tcp + dot 186 | if ((arg_debug || arg_details) && first_query) 187 | printf("-----> rx %d bytes: IP + TCP + TLS\n", 20 + 20 + 5 + (int) ((float) newlen * 1.2)); 188 | } 189 | if ((arg_debug || arg_details) && first_query) 190 | printf("\n"); 191 | 192 | // bailout! 193 | if ((total_len - 2) > len) 194 | goto errout; 195 | 196 | if (arg_debug) 197 | print_mem(buf + 2, len); 198 | 199 | dot_rx_dns += 20 + 8 + len; // ip + tcp + dot + dns 200 | 201 | // copy response in buf_query_data 202 | if (len != 0) { 203 | memcpy(response, buf + 2, len); 204 | return len; 205 | } 206 | 207 | 208 | errout: 209 | rlogprintf("Error: dot timeout\n"); 210 | return -1; 211 | } 212 | 213 | 214 | 215 | -------------------------------------------------------------------------------- /README: -------------------------------------------------------------------------------- 1 | What is fdns? 2 | ----------- 3 | fdns is a DNS-over-HTTPS proxy server targeted at small networks and 4 | Linux desktops. To speed up the name resolution fdns caches the responses, 5 | and uses a configurable adblocker and privacy filter to cut down the 6 | unnecessary traffic. 7 | 8 | Copyright (C) 2025 fdns Authors 9 | 10 | All files are licensed under GPLv3, with the exception of 11 | etc/bloclists/list.coinblocker licensed under AGPLv3. 12 | 13 | 14 | Build and Install 15 | ----------------- 16 | Dependencies: OpenSSL library, libseccomp 17 | - Debian/Ubuntu: sudo apt-get install libseccomp-dev libssl-dev 18 | - Fedora/CentOS: sudo yum install libseccomp-devel openssl-devel 19 | - Arch Linux: the libraries are already included by the base meta package 20 | 21 | Build and install: ./configure && make && sudo make install 22 | If AppArmor is present in the system, enable fdns profile by running 23 | # /sbin/apparmor_parser -r /etc/apparmor.d/usr.bin.fdns 24 | 25 | 26 | Development 27 | ----------- 28 | The project is developed at https://github.com/netblue30/fdns, 29 | website at https://firejaildns.wordpress.com 30 | 31 | Maintainers/committers: 32 | - netblue30 (netblue30@protonmail.com) 33 | - glitsj16 (https://github.com/glitsj16) 34 | - Fred-Barclay (https://github.com/Fred-Barclay) 35 | - rusty-snake (https://github.com/rusty-snake) 36 | - sblighting (https://github.com/sblighting) 37 | - startx2017 (https://github.com/startx2017) 38 | 39 | 40 | Documentation and Support 41 | ------------------------- 42 | For bugs/questions/whatever please use the issues section 43 | at https://github.com/netblue30/fdns/issues 44 | 45 | 46 | fdns Authors 47 | ------------ 48 | Fdns manitainers/committers as described above. 49 | 50 | Airat Makhmutov (https://github.com/rautyrauty) 51 | - add ALT OpenSSL certificate 52 | - --server-list option enhancement 53 | 54 | Deng XingJing (https://github.com/MicroMilo) 55 | - memory allocation fix 56 | - fix file descriptor leak in dedup_init 57 | 58 | Niko Itäjärvi (https://github.com/nwps) 59 | - server list fixes 60 | 61 | sblighting (https://github.com/sblighting) 62 | - hash table for the local DNS cache 63 | - disable doh on local network 64 | 65 | Daniel Schildt (https://github.com/d2s) 66 | - documentation 67 | 68 | rcplab (https://github.com/rcplab) 69 | - containerpi and twnic 70 | 71 | shiraneyo (https://github.com/shiraneyo) 72 | - update resolver.seccomp for kernel 5.11 73 | 74 | w3goodies (https://github.com/w3goodies) 75 | - fixed dnsforfamily DoT server 76 | 77 | Zorvalt (https://github.com/Zorvalt) 78 | - fix server load_file() overwriting global list 79 | 80 | Jorge (https://github.com/jorgectf) 81 | - Add CodeQL workflow 82 | 83 | Jan Weidenhaupt (https://github.com/jamowei) 84 | - fix systemd service file 85 | 86 | Incorporating code from Firejail Security Sandbox, https://github.com/netblue30/firejail 87 | Copyright © 2014-2020 Firejail Authors, license GPLv2 88 | 89 | Incorporating code from Privacy Badger, htps://github.com/EFForg/privacybadger 90 | Copyright © Electronic Frontier Foundation and other contributors, license GPLv3 91 | 92 | Incorporating code from Steven Black's hosts project, https://github.com/StevenBlack/hosts 93 | Copyright © Steven Black, license MIT 94 | 95 | Incorporating code from Geoffrey Frogeye first-party trackers host list at 96 | https://hostfiles.frogeye.fr/firstparty-trackers-hosts.txt, 97 | Copyright © Geoffrey Frogeye, license MIT 98 | 99 | Incorporating code from Sophos XG Block Lists project at 100 | https://github.com/austinheap/sophos-xg-block-lists (adguard.txt). 101 | Copyright © Austin Heap (https://github.com/austinheap), license MIT 102 | 103 | Incorporating code from AdGuard CNAME disguised trackers list 104 | https://github.com/AdguardTeam/cname-trackers 105 | Copyright 2021 Adguard Software Ltd, MIT license 106 | 107 | Incorporating code from NextDNS CNAME cloaking block list 108 | https://github.com/nextdns/cname-cloaking-blocklist/blob/master/domains 109 | Copyright (c) 2022 NextDNS, MIT license 110 | 111 | Incorporating phising list from Phishing.Database, autohor mitchellkrogza 112 | from https://github.com/mitchellkrogza/Phishing.Database/blob/master/phishing-domains-ACTIVE.txt 113 | MIT License 114 | 115 | Incorporating several lists from The Block List Project, author blocklistproject 116 | from https://github.com/blocklistproject/Lists 117 | Unlicense license 118 | 119 | Incorporating Peter Lowe adserver list 120 | from https://pgl.yoyo.org/adservers/serverlist.php?hostformat=adblockplus&showintro=0&mimetype=plaintext 121 | McRae General Public License (version 4.r53) - https://pgl.yoyo.org/license/ 122 | 123 | Incorporating several lists by lightswitch05 124 | from https://www.github.developerdan.com/hosts/ 125 | Apache License, Version 2.0 126 | 127 | Incorporating malware domains from URLhouse 128 | https://urlhaus.abuse.ch/api 129 | license CC0 (https://creativecommons.org/share-your-work/public-domain/cc0) 130 | 131 | Licensing and Copyright 132 | ----------------------- 133 | This project is published under GPLv3 license. Please see COPYING file 134 | for more details. Copyright © 2021 FDNS Authors 135 | 136 | 137 | Cryptographic Software Notice 138 | ----------------------------- 139 | 140 | This project includes code designed for use with cryptographic software. 141 | The country in which you currently reside may have restrictions on the 142 | import, possession, use, and/or re-export to another country, of 143 | encryption software. BEFORE using any encryption software, please check 144 | your country's laws, regulations and policies concerning encryption 145 | software, to see if this is permitted. 146 | 147 | The U.S. Government Department of Commerce, Bureau of Industry and 148 | Security (BIS), has classified this software as Export Commodity 149 | Control Number (ECCN) 5D002.C.1, which includes information security 150 | software using or performing cryptographic functions with asymmetric 151 | algorithms. The form and manner of this distribution makes it 152 | eligible for export under the License Exception ENC Technology 153 | Software Unrestricted (TSU) exception (see the BIS Export Administration 154 | Regulations, Section 740.13) for both object code and source code. 155 | 156 | Manufacturers and software components: 157 | (only listed for components related to cryptographic functions) 158 | 159 | - OpenSSL Project, openssl library, 160 | source code at https://www.openssl.org/source 161 | 162 | -------------------------------------------------------------------------------- /src/fdns/quic.c: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2019-2025 FDNS Authors 3 | * 4 | * This file is part of fdns project 5 | * 6 | * This program is free software: you can redistribute it and/or modify 7 | * it under the terms of the GNU General Public License as published by 8 | * the Free Software Foundation, either version 3 of the License, or 9 | * (at your option) any later version. 10 | * 11 | * This program is distributed in the hope that it will be useful, 12 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14 | * GNU General Public License for more details. 15 | * 16 | * You should have received a copy of the GNU General Public License 17 | * along with this program. If not, see . 18 | */ 19 | 20 | #define _GNU_SOURCE 21 | #include 22 | #include 23 | #include 24 | #include 25 | #include 26 | #include 27 | #include 28 | #include "fdns.h" 29 | #include "timetrace.h" 30 | #include "lint.h" 31 | 32 | static void quic_header_stats(void); 33 | static double quic_bandwidth(void); 34 | static void quic_init(void); 35 | static void quic_close(void); 36 | static int quic_connect(void); 37 | static int quic_send_exampledotcom(uint8_t *req); 38 | static int quic_send_query(uint8_t *req, int cnt); 39 | static int quic_send_ping(void); 40 | static int quic_exchange(uint8_t *response, uint32_t stream); 41 | static void quic_print_url(void); 42 | DnsTransport quic_transport = { 43 | "quic", 44 | "DoQ", 45 | quic_init, 46 | quic_close, 47 | quic_connect, 48 | quic_send_exampledotcom, 49 | quic_send_query, 50 | quic_send_ping, 51 | quic_exchange, 52 | quic_header_stats, 53 | quic_bandwidth, 54 | quic_print_url 55 | }; 56 | 57 | 58 | static int quic_rx = 0; // received bytes, including IP/UDP/QUIC headers 59 | static int quic_rx_dns = 0; // received DNS bytes over H2 plus IP/UDP 60 | static int first_query = 1; // don't include the first query in network byte count 61 | 62 | static void quic_print_url(void) { 63 | DnsServer *srv = server_get(); 64 | assert(srv); 65 | printf(" URL: quic://%s\n", srv->host); 66 | } 67 | 68 | static void quic_header_stats(void) { 69 | } 70 | 71 | // DoQ/DNS ratio 72 | static double quic_bandwidth(void) { 73 | // if (quic_rx_dns == 0) 74 | return 0; 75 | // return (double) quic_rx / (double) quic_rx_dns; 76 | } 77 | 78 | static void quic_init(void) { 79 | first_query = 1; 80 | } 81 | 82 | static void quic_close(void) { 83 | first_query = 1; 84 | } 85 | 86 | static uint8_t buf_query[MAXBUF]; 87 | // returns -1 if error 88 | static int quic_connect(void) { 89 | return 0; 90 | } 91 | 92 | // the result message is placed in res, the length of the message is returned 93 | // returns -1 if error 94 | static int quic_send_exampledotcom(uint8_t *req) { 95 | uint8_t dnsmsg[] = { 96 | 0x00, 0x00, 0x01, 0x00, 0x00, 0x01, 0x00, 97 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x07, 0x65, 0x78, 0x61, 0x6d, 0x70, 0x6c, 0x65, 0x03, 0x63, 0x6f, 98 | 0x6d, 0x00, 0x00, 0x02, 0x00, 0x01 99 | }; 100 | 101 | // two bytes length field 102 | uint16_t len = htons(sizeof(dnsmsg)); 103 | memcpy(buf_query, &len, 2); 104 | memcpy(buf_query + 2, dnsmsg, sizeof(dnsmsg)); 105 | 106 | if (arg_debug || arg_debug_transport) { 107 | print_time(); 108 | printf("(%d) tx len %d quic\n", arg_id, (int) sizeof(dnsmsg) + 2); 109 | } 110 | ssl_tx(buf_query, sizeof(dnsmsg) + 2); 111 | int rv = quic_exchange(req, 0); 112 | first_query = 0; 113 | return rv; 114 | } 115 | 116 | 117 | // the result message is placed in req, the length of the message is returned 118 | // returns -1 if error 119 | static int quic_send_query(uint8_t *req, int cnt) { 120 | if (cnt <= 0 || cnt > DNS_MAX_DOMAIN_NAME) 121 | return 0; 122 | 123 | // two bytes length field 124 | uint16_t len = htons(cnt); 125 | memcpy(buf_query, &len, 2); 126 | memcpy(buf_query + 2, req, cnt); 127 | 128 | if (arg_debug || arg_debug_transport) { 129 | print_time(); 130 | printf("(%d) tx len %d quic\n", arg_id, cnt + 2); 131 | } 132 | ssl_tx(buf_query, cnt + 2); 133 | int rv = quic_exchange(req, 0); 134 | first_query = 0; 135 | return rv; 136 | } 137 | 138 | // returns -1 if error 139 | static int quic_send_ping(void) { 140 | return quic_send_exampledotcom(buf_query); 141 | } 142 | 143 | // copy rx data in response and return the length 144 | // return -1 if error 145 | static int quic_exchange(uint8_t *response, uint32_t stream) { 146 | assert(response); 147 | (void) stream; 148 | 149 | uint8_t buf[MAXBUF]; 150 | int total_len = ssl_rx_timeout((uint8_t *) buf, MAXBUF, TRANSPORT_TIMEOUT); 151 | if (total_len == 0) 152 | goto errout; 153 | 154 | if (arg_debug) 155 | print_mem(buf, total_len); 156 | 157 | if (arg_debug || arg_debug_transport) { 158 | print_time(); 159 | printf("(%d) rx len %d quic\n", arg_id, total_len); 160 | } 161 | 162 | quic_rx += 20 + 8 + 5 + (int) ((float) total_len * 1.2); // ip + udp + quic 163 | 164 | uint16_t len; 165 | memcpy(&len, buf, 2); 166 | len = ntohs(len); 167 | if (len > (MAXBUF - 2)) 168 | goto errout; 169 | 170 | if ((arg_debug || arg_details) && first_query) { 171 | printf("\n Network trace:\n"); 172 | printf("-----> rx %d bytes: IP + UDP + QUIC\n", 20 + 8 + 5 + (int) ((float) total_len * 1.2)); 173 | } 174 | 175 | if ((total_len - 2) > len) { 176 | // read some more data 177 | int newlen = ssl_rx_timeout(buf + total_len, MAXBUF - total_len, TRANSPORT_TIMEOUT); 178 | if (len == 0) 179 | goto errout; 180 | if (arg_debug || arg_debug_transport) { 181 | print_time(); 182 | printf("(%d) rx len %d quic\n", arg_id, total_len); 183 | } 184 | total_len += newlen; 185 | quic_rx += 20 + 8 + 5 + (int) ((float) newlen * 1.2); // ip + udp + quic 186 | if ((arg_debug || arg_details) && first_query) 187 | printf("-----> rx %d bytes: IP + TCP + TLS\n", 20 + 8 + 5 + (int) ((float) newlen * 1.2)); 188 | } 189 | if ((arg_debug || arg_details) && first_query) 190 | printf("\n"); 191 | 192 | // bailout! 193 | if ((total_len - 2) > len) 194 | goto errout; 195 | 196 | if (arg_debug) 197 | print_mem(buf + 2, len); 198 | 199 | quic_rx_dns += 20 + 8 + len; // ip + tcp + dot + dns 200 | 201 | // copy response in buf_query_data 202 | if (len != 0) { 203 | memcpy(response, buf + 2, len); 204 | return len; 205 | } 206 | 207 | 208 | errout: 209 | rlogprintf("Error: doq timeout\n"); 210 | return -1; 211 | } 212 | 213 | 214 | 215 | -------------------------------------------------------------------------------- /src/dnsc/subs.c: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2019-2025 FDNS Authors 3 | * 4 | * This file is part of fdns project 5 | * 6 | * This program is free software: you can redistribute it and/or modify 7 | * it under the terms of the GNU General Public License as published by 8 | * the Free Software Foundation, either version 3 of the License, or 9 | * (at your option) any later version. 10 | * 11 | * This program is distributed in the hope that it will be useful, 12 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14 | * GNU General Public License for more details. 15 | * 16 | * You should have received a copy of the GNU General Public License 17 | * along with this program. If not, see . 18 | */ 19 | 20 | #include "dnsc.h" 21 | 22 | #define MAX_REORDER 1024 23 | static Node *subs_node[MAX_REORDER] = {NULL}; 24 | static int subs_index = 0; 25 | int tld_cnt = 0; 26 | 27 | static int callback(const void *n1, const void *n2) { 28 | Node **ptr1 = (Node **) n1; 29 | Node *node1 = *ptr1; 30 | Node **ptr2 = (Node **) n2; 31 | Node *node2 = *ptr2; 32 | 33 | return (node1->cnt < node2->cnt); 34 | } 35 | 36 | // return 1 if found 37 | static int subs_find(const char *name) { 38 | if (subs_index == 0) 39 | return 0; 40 | 41 | int len = strlen(name); 42 | int i; 43 | for (i = 0; i < subs_index; i++) { 44 | int delta = len - subs_node[i]->len; 45 | if (delta == 0 && strcmp(name, subs_node[i]->name) == 0) 46 | return 1; 47 | 48 | if (delta > 0 && strcmp(name + delta, subs_node[i]->name) == 0) { 49 | if (name[delta - 1] == '.') 50 | return 1; 51 | } 52 | // if (strstr(name, subs_node[i]->name)) { 53 | // return 1; 54 | // } 55 | } 56 | 57 | return 0; 58 | } 59 | 60 | 61 | static void subs_add(char *name, int cnt) { 62 | if (subs_find(name)) 63 | return; 64 | 65 | Node *n = malloc(sizeof(Node)); 66 | if (!n) 67 | errExit("malloc"); 68 | memset(n, 0, sizeof(Node)); 69 | n->name = name; 70 | n->cnt = cnt; 71 | n->len = strlen(name); 72 | subs_node[subs_index++] = n; 73 | } 74 | 75 | static void extract_subs1(int limit) { 76 | if (!domains) 77 | return; 78 | Node *ptr = domains; 79 | while (ptr && ptr->s1 == NULL) 80 | ptr = ptr->next; 81 | if (ptr == NULL) 82 | return; 83 | char *search = ptr->s1; 84 | void *w = whitelist_find(search); 85 | char *name = ptr->name; 86 | int cnt = 0; 87 | while (ptr) { 88 | if (ptr->s1) { 89 | if (strcmp(search, ptr->s1) == 0) 90 | cnt += ptr->cnt; 91 | else { 92 | if (!w && cnt > limit && strcmp(search, name) != 0 && 93 | tld_find(search)) { 94 | subs_add(search, cnt); 95 | tld_cnt += cnt; 96 | } 97 | cnt = ptr->cnt; 98 | search = ptr->s1; 99 | w = whitelist_find(search); 100 | name = ptr->name; 101 | } 102 | } 103 | ptr = ptr->next; 104 | } 105 | 106 | if (!w && cnt > limit && tld_find(search)) { 107 | subs_add(search, cnt); 108 | tld_cnt += cnt; 109 | } 110 | } 111 | 112 | static void extract_subs2(int limit) { 113 | if (!domains) 114 | return; 115 | Node *ptr = domains; 116 | while (ptr && ptr->s2 == NULL) 117 | ptr = ptr->next; 118 | if (ptr == NULL) 119 | return; 120 | char *search = ptr->s2; 121 | void *w = whitelist_find(search); 122 | int cnt = 0; 123 | while (ptr) { 124 | if (ptr->s2) { 125 | if (strcmp(search, ptr->s2) == 0) 126 | cnt += ptr->cnt; 127 | else { 128 | if (!w && cnt > limit) 129 | subs_add(search, cnt); 130 | cnt = ptr->cnt; 131 | search = ptr->s2; 132 | w = whitelist_find(search); 133 | } 134 | } 135 | ptr = ptr->next; 136 | } 137 | 138 | if (!w && cnt > limit) 139 | subs_add(search, cnt); 140 | } 141 | 142 | static void extract_subs3(int limit) { 143 | if (!domains) 144 | return; 145 | Node *ptr = domains; 146 | while (ptr && ptr->s3 == NULL) 147 | ptr = ptr->next; 148 | 149 | if (ptr == NULL) 150 | return; 151 | char *search = ptr->s3; 152 | void *w = whitelist_find(search); 153 | int cnt = 0; 154 | while (ptr) { 155 | if (ptr->s3) { 156 | if (strcmp(search, ptr->s3) == 0) 157 | cnt += ptr->cnt; 158 | else { 159 | if (!w && cnt > limit) 160 | subs_add(search, cnt); 161 | cnt = ptr->cnt; 162 | search = ptr->s3; 163 | w = whitelist_find(search); 164 | } 165 | } 166 | ptr = ptr->next; 167 | } 168 | 169 | if (!w && cnt > limit) 170 | subs_add(search, cnt); 171 | } 172 | 173 | static void extract_subs4(int limit) { 174 | if (!domains) 175 | return; 176 | Node *ptr = domains; 177 | while (ptr && ptr->s4 == NULL) 178 | ptr = ptr->next; 179 | 180 | if (ptr == NULL) 181 | return; 182 | char *search = ptr->s4; 183 | void *w = whitelist_find(search); 184 | int cnt = 0; 185 | while (ptr) { 186 | if (ptr->s4) { 187 | if (strcmp(search, ptr->s4) == 0) 188 | cnt += ptr->cnt; 189 | else { 190 | if (!w && cnt > limit) 191 | subs_add(search, cnt); 192 | cnt = ptr->cnt; 193 | search = ptr->s4; 194 | w = whitelist_find(search); 195 | } 196 | } 197 | ptr = ptr->next; 198 | } 199 | 200 | if (!w && cnt > limit) 201 | subs_add(search, cnt); 202 | } 203 | 204 | void subs_print(int total_domains) { 205 | if (!domains) 206 | return; 207 | int limit = get_limit(); 208 | extract_subs1(limit); 209 | extract_subs2(limit); 210 | extract_subs3(limit); 211 | extract_subs4(limit); 212 | if (subs_index == 0) 213 | return; 214 | qsort(subs_node, subs_index, sizeof(Node *), callback); 215 | 216 | // count subs 217 | int subs_total = 0; 218 | int i; 219 | for (i = 0; i < subs_index; i++) 220 | subs_total += subs_node[i]->cnt; 221 | 222 | // print subs 223 | printf("# Short list: heavily compressed list covering %.02f%% of the input\n", ((double) subs_total / (double) total_domains) * 100); 224 | for (i = 0; i < subs_index; i++) { 225 | if (arg_short) { 226 | if (strlen(subs_node[i]->name) < 30) 227 | printf("127.0.0.1 %-30s # %d (%.02f%%)\n", subs_node[i]->name, subs_node[i]->cnt, ((double) subs_node[i]->cnt / (double) total_domains) * 100); 228 | else 229 | printf("127.0.0.1 %s # %d (%.02f%%)\n", subs_node[i]->name, subs_node[i]->cnt, ((double) subs_node[i]->cnt / (double) total_domains) * 100); 230 | } 231 | else 232 | printf("#%02d: %6d (%.02f%%) %s\n", i + 1, subs_node[i]->cnt, ((double) subs_node[i]->cnt / (double) total_domains) * 100, subs_node[i]->name); 233 | } 234 | 235 | // free mem 236 | for (i = 0; i < subs_index; i++) 237 | free(subs_node[i]); 238 | } -------------------------------------------------------------------------------- /src/dnsc/rsort.c: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2019-2025 FDNS Authors 3 | * 4 | * This file is part of fdns project 5 | * 6 | * This program is free software: you can redistribute it and/or modify 7 | * it under the terms of the GNU General Public License as published by 8 | * the Free Software Foundation, either version 3 of the License, or 9 | * (at your option) any later version. 10 | * 11 | * This program is distributed in the hope that it will be useful, 12 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14 | * GNU General Public License for more details. 15 | * 16 | * You should have received a copy of the GNU General Public License 17 | * along with this program. If not, see . 18 | */ 19 | 20 | #include "dnsc.h" 21 | #include 22 | 23 | static int callback(const void *p1, const void *p2) { 24 | char *str1 = *((char **) p1); 25 | char *str2 = *((char **) p2); 26 | //printf("%s %s\n", str1, str2); 27 | 28 | int len1 = strlen(str1); 29 | if (len1 == 0) 30 | return -1; 31 | int len2 = strlen(str2); 32 | if (len2 == 0) 33 | return 1; 34 | int min = (len1 < len2) ? len1 : len2; 35 | 36 | char *ptr1 = str1 + len1 - 1; 37 | char *ptr2 = str2 + len2 - 1; 38 | 39 | int i; 40 | for (i = 0; i < min; i++, ptr1--, ptr2--) { 41 | if (*ptr1 == *ptr2) 42 | continue; 43 | if (*ptr1 < *ptr2) 44 | return -1; 45 | return 1; 46 | } 47 | 48 | if (len1 == len2) 49 | return 0; 50 | else if (len1 > len2) 51 | return 1; 52 | return -1; 53 | } 54 | 55 | static char *line_filter(char *buf) { 56 | if (*buf == '#') 57 | return NULL; 58 | if (*buf == '*') // handle dnstwist.it *original line 59 | return NULL; 60 | if (strncmp(buf, "fuzzer,domain,", 14) == 0) // handle dnstwist.it first line 61 | return NULL; 62 | char *ptr = strchr(buf, '\n'); 63 | if (ptr) 64 | *ptr = '\0'; 65 | ptr = strchr(buf, '#'); 66 | if (ptr) 67 | *ptr = '\0'; 68 | 69 | // clean line start 70 | char *start = buf; 71 | while (*start == ' ' || *start == '\t') 72 | start++; 73 | 74 | if (*start == '\0') 75 | return NULL; 76 | 77 | //printf("%d: start %s\n", __LINE__, start); 78 | // alienvolt, phishtank etc. 79 | ptr = strstr(start, "http"); 80 | if (ptr) { 81 | ptr += 4; 82 | if (*ptr == 's') 83 | ptr++; 84 | if (strncmp(ptr, "://", 3) == 0) 85 | ptr += 3; 86 | else 87 | goto getout; 88 | start = ptr; 89 | } 90 | getout: 91 | 92 | //printf("%d: start %s\n", __LINE__, start); 93 | if (strncmp(start, "127.0.0.1", 9) == 0 || strncmp(start, "0.0.0.0", 7) == 0) { 94 | char *oldstart = start; 95 | while (*start != ' ' && *start != '\t' && *start != '\0') 96 | start++; 97 | if (*start != '\0') { 98 | while ((*start == ' ' || *start == '\t') && *start != '\0') 99 | start++; 100 | if (*start == '\0') 101 | goto errout; 102 | } 103 | else 104 | start = oldstart; 105 | } 106 | 107 | if (*start == '\0') 108 | return NULL; 109 | // dealing with urls such as http://[2602:fc59:b0:64::b869:8e3e]/m/login.php 110 | if (*start == '[') 111 | return NULL; 112 | 113 | while ((*start == '|' || *start == '!') && *start != '\0') 114 | start++; 115 | if (*start == '\0') 116 | return NULL; 117 | 118 | // clean port numbers 119 | ptr = strchr(start, ':'); 120 | if (ptr) 121 | *ptr = '\0'; 122 | 123 | // clean line end 124 | ptr = strchr(start, ','); 125 | if (ptr) 126 | *ptr = '\0'; 127 | ptr = strchr(start, '#'); 128 | if (ptr) 129 | *ptr = '\0'; 130 | ptr = strchr(start, '+'); 131 | if (ptr) 132 | *ptr = '\0'; 133 | ptr = strchr(start, '='); 134 | if (ptr) 135 | *ptr = '\0'; 136 | ptr = strchr(start, '%'); 137 | if (ptr) 138 | *ptr = '\0'; 139 | ptr = strchr(start, '/'); 140 | if (ptr) 141 | *ptr = '\0'; 142 | ptr = strchr(start, '?'); 143 | if (ptr) 144 | *ptr = '\0'; 145 | ptr = strchr(start, '"'); 146 | if (ptr) 147 | *ptr = '\0'; 148 | ptr = strchr(start, ' '); 149 | if (ptr) 150 | *ptr = '\0'; 151 | ptr = strchr(start, ';'); 152 | if (ptr) 153 | *ptr = '\0'; 154 | ptr = strchr(start, '\t'); 155 | if (ptr) 156 | *ptr = '\0'; 157 | ptr = strchr(start, '^'); 158 | if (ptr) 159 | *ptr = '\0'; 160 | 161 | // remove an ending dot 162 | int len = strlen(start); 163 | if (*(start + len - 1) == '.') 164 | *(start + len - 1) = '\0'; 165 | 166 | return start; 167 | errout: 168 | fprintf(stderr, "Error: %s\n", buf); 169 | exit(1); 170 | } 171 | 172 | 173 | char **line_in = NULL; //malloc(sizeof(char *) * (cnt + 1)); 174 | int line_in_cnt = 0; 175 | int line_in_size = 0; 176 | #define LINE_CHUNK 4096 177 | 178 | void rsort_load(const char *fname) { 179 | // int cnt_start = line_in_cnt; 180 | if (is_dir(fname)) 181 | return; 182 | printf("# loading %s", fname); 183 | fflush(0); 184 | 185 | // read file 186 | char *storage = read_file_malloc(fname); 187 | if (!storage) { 188 | printf("..... file not found!\n"); 189 | fprintf(stderr, "Warning: cannot read %s file\n", fname); 190 | return; 191 | } 192 | 193 | int len = strlen(storage); 194 | if (len == 0) { 195 | printf(" (0)\n"); 196 | free(storage); 197 | return; 198 | } 199 | 200 | // if the file doesn't end in '\n', add a '\n' at the end 201 | if (*(storage + len - 1) != '\n') { 202 | char *newstorage = malloc(len + 2); 203 | if (!newstorage) { 204 | goto errout; 205 | } 206 | sprintf(newstorage, "%s\n", storage); 207 | free(storage); 208 | storage = newstorage; 209 | } 210 | 211 | // check dnstwist.it 212 | int dnstwist_it = 0; 213 | if (strncmp(storage, "fuzzer,domain,dns_a,", 20) == 0) 214 | dnstwist_it = 1; 215 | 216 | int domains = 0; 217 | char *ptr; 218 | while ((ptr = strsep(&storage, "\n")) != NULL) { 219 | if ((line_in_cnt + 10) >= line_in_size) { 220 | char **new_line_in = realloc(line_in, sizeof(char *) * (line_in_cnt + LINE_CHUNK)); 221 | if (!new_line_in) { 222 | goto errout; 223 | } 224 | line_in = new_line_in; 225 | line_in_size += LINE_CHUNK; 226 | } 227 | 228 | if (dnstwist_it) { 229 | char *ptr1 = strchr(ptr, ','); 230 | if (!ptr1) 231 | goto errout; 232 | line_in[line_in_cnt] = ptr1 + 1; 233 | } 234 | else 235 | line_in[line_in_cnt] = ptr; 236 | 237 | char *start = line_filter(line_in[line_in_cnt]); 238 | if (start) { 239 | line_in[line_in_cnt++] = start; 240 | domains++; 241 | } 242 | else 243 | line_in[line_in_cnt] = NULL; 244 | } 245 | 246 | line_in[line_in_cnt] = NULL; // we allocated 10 more above! 247 | printf(" (%d)\n", domains); 248 | free(storage); 249 | return; 250 | 251 | errout: 252 | printf("\n"); 253 | fprintf(stderr, "Error: file %s\n", fname); 254 | free(storage); 255 | exit(1); 256 | } 257 | 258 | char **rsort(void) { 259 | // convert to lower-case 260 | int i; 261 | for (i = 0; i < line_in_cnt; i++) { 262 | char *ptr = line_in[i]; 263 | while (*ptr) { 264 | *ptr = tolower(*ptr); 265 | ptr++; 266 | } 267 | } 268 | 269 | // sorting 270 | qsort(&line_in[0], line_in_cnt, sizeof(char *), callback); 271 | return line_in; 272 | } 273 | 274 | --------------------------------------------------------------------------------