├── README.md ├── proxy_dns_fcgi ├── .gitignore ├── Makefile └── proxy_dns_fcgi.c └── proxy_dns_gw ├── .gitignore ├── Makefile └── proxy_dns_gw.c /README.md: -------------------------------------------------------------------------------- 1 | Introduction 2 | ------------ 3 | 4 | This is proxy_dns, a way to tunnel DNS inside HTTP. It provides two things: 5 | 6 | 1. a FastCGI endpoint that sits between a web server (we use nginx, but Apache 7 | would also work) and a DNS server (we use BIND, but Unbound would also work.) 8 | 2. a DNS proxy server that is the target of an /etc/resolv.conf (on UNIX) or 9 | DHCP "name server" declaration; it resolves DNS by using upstream HTTP. 10 | 11 | The great advantage to this approach is that HTTP usually makes it through 12 | even the worst coffee shop or hotel room firewalls, since commerce may be at 13 | stake. We also benefit from HTTP's persistent TCP connection pool concept, 14 | which DNS on TCP/53 does not have. Lastly, HTTPS will work, giving nominal privacy. 15 | 16 | This software is as yet unpackaged, but is portable to FreeBSD 10 and Debian 7 17 | and very probably other BSD-similar and Linux-similar systems. This software 18 | is written entirely in C and has been compiled with GCC and Clang with "full 19 | warnings" enabled. 20 | 21 | Construction 22 | ------------ 23 | 24 | More or less, do this: 25 | 26 | (cd proxy_dns_gw; make) 27 | (cd proxy_dns_fcgi; make) 28 | 29 | It is possible that the Makefile will need tweaking, since -lresolv is 30 | required on Linux but is both not required and will not work on BSD due 31 | to differences in their "libc" implementations. 32 | 33 | Server Installation 34 | ------------------- 35 | 36 | The proxy_dns_fcgi service currently just follows /etc/resolv.conf, so you 37 | will need a working name server configuration on your web server. The server 38 | should be reachable by UDP and TCP, and you should have a clear ICMP path to 39 | it, as well as full MTU (1500 octets or larger) and the ability to receive 40 | fragmented UDP (to make EDNS0 usable.) 41 | 42 | 1. place the proxy_dns_fcgi executable somewhere that nginx can reach it. 43 | 2. start this executable and look for a /tmp/proxy_dns_fcgi.sock file. 44 | 3. edit nginx.conf to contain something equivilent to the following: 45 | 46 | location /proxy_dns { 47 | root /; 48 | fastcgi_pass unix:/tmp/proxy_dns_fcgi.sock; 49 | include fastcgi_params; 50 | } 51 | 52 | or, edit httpd.conf to contain something equivilent to the following: 53 | 54 | Listen 24.104.150.237:80 55 | Listen [2001:559:8000::B]:80 56 | 57 | LoadModule proxy_module libexec/apache24/mod_proxy.so 58 | LoadModule proxy_fcgi_module libexec/apache24/mod_proxy_fcgi.so 59 | 60 | 61 | ServerName proxy-dns.tisf.net 62 | ProxyPass /proxy_dns \ 63 | unix:/tmp/proxy_dns_fcgi.sock|fcgi://localhost/ \ 64 | enablereuse=on 65 | 66 | 67 | 4. reload the configuration of, or restart, your nginx server. 68 | 5. test the integration by visiting the /proxy_dns page with a browser. 69 | 70 | Client Installation 71 | ------------------- 72 | 73 | The proxy_dns_gw service must be told what IP address to listen on for DNS 74 | (noting, it will open both a UDP and a TCP listener on that address), so if 75 | you want it to listen on both ::1 and 127.0.0.1, you will have to start two 76 | listeners, by giving proxy_dns_gw two arguments "-l ::1" and "-l 127.0.0.1". 77 | 78 | It must also be told where to connect for its DNS proxy service. If your 79 | FastCGI service (see previous section) is running on a web server 80 | proxy-dns.vix.su, then you will have to specify "-s http://proxy-dns.vix.su" 81 | (or "-s https://proxy-dns.vix.su" if you are using TLS to protect your HTTP.) 82 | 83 | 1. place the proxy_dns_gw executable somewhere that will survive a reboot. 84 | 2. start this executable at least once with appropriate "-s" and "-l" options. 85 | 3. use "netstat -an" to determine whether it has opened listener sockets. 86 | 87 | Testing 88 | ------- 89 | 90 | Make sure you have a working "dig" command. If you started your client side 91 | dns_proxy service on 127.0.0.1, then you should be able to say: 92 | 93 | dig @127.0.0.1 www.vix.su aaaa 94 | 95 | and get a result back. You can watch this simultaneously on the server side 96 | dns_proxy by running a command similar to this: 97 | 98 | tail -f /var/log/nginx-access.log 99 | 100 | Protocol 101 | -------- 102 | 103 | The protocol used by the dns_proxy service is alarmingly simple. There's no 104 | JSON or XML encoding; the DNS query and response are sent as raw binary via 105 | the "libcurl" library on the client side and the "libfcgi" library on the 106 | server side. The URI is always "/proxy_dns", which means, it contains no 107 | parameters. The result is always marked non-cacheable. The request is always 108 | POST. If you send the fcgi server a GET, it will return a human-readable page 109 | showing its web server environment. There is one new HTTP header: 110 | 111 | Proxy-DNS-Transport: xyz 112 | 113 | where xyz is either UDP or TCP, which is the client's indication of how it 114 | received the underlying DNS query, and which the server will use when sending 115 | the query to the far-end DNS server. This means if a stub DNS client asks for 116 | TCP, then that's what the far-end DNS server will see, and likewise for UDP. 117 | 118 | The proxy service does not interpret the DNS query or response in any way. 119 | It could be DNS, EDNS, or something not yet invented at the time of this 120 | writing. The only requirement is that each request message solicits exactly 121 | one response message. If anything at all goes wrong with the proxy service, 122 | the stub client will hear a DNS SERVFAIL response. 123 | 124 | To Do List 125 | ---------- 126 | 127 | This software was written in C in order to be small, self contained, and 128 | portable to Windows and Mac/OS some day. The protocol was designed to be 129 | very simple in order that higher-performing implementations could be 130 | written for high availability production servers. Still, shortcuts were 131 | taken, and should be addressed: 132 | 133 | 1. threads on the proxy_dns_fcgi side are a problem. should use "libevent". 134 | 2. select() on the proxy_dns_gw side is a problem. should use "libcurl" more. 135 | 136 | Authors 137 | ------- 138 | 139 | This software was conceived and drafted by Paul Vixie during WIDE-2015-03, 140 | and is hereby placed into the public domain, and also placed into the care 141 | of BII, a Beijing-based non-profit Internet technology company. 142 | 143 | Note that there is a follow-up work using Golang to implement DNS over HTTP, 144 | Please visit https://github.com/BII-Lab/DNSoverHTTPinGO for more information. 145 | -------------------------------------------------------------------------------- /proxy_dns_fcgi/.gitignore: -------------------------------------------------------------------------------- 1 | proxy_dns_fcgi.o 2 | proxy_dns_fcgi 3 | -------------------------------------------------------------------------------- /proxy_dns_fcgi/Makefile: -------------------------------------------------------------------------------- 1 | CFLAGS= -I/usr/local/include 2 | LDFLAGS= -L/usr/local/lib 3 | RESOLV= 4 | #RESOLV= -lresolv 5 | 6 | CWARN =-W -Wall -Wcast-qual -Wpointer-arith -Wwrite-strings \ 7 | -Wmissing-prototypes -Wbad-function-cast -Wnested-externs \ 8 | -Wunused -Wshadow -Wmissing-noreturn -Wswitch-enum -Wformat-nonliteral 9 | # -Wunreachable-code is often wrong 10 | #CWARN +=-Wunreachable-code 11 | # try shipping without any warnings 12 | CWARN +=-Werror 13 | 14 | CDEBUG = -g 15 | CFLAGS += $(CDEBUG) $(CWARN) 16 | 17 | ALL= proxy_dns_fcgi 18 | 19 | all: $(ALL) 20 | 21 | clean: 22 | rm -f $(ALL) 23 | rm -f *.o 24 | 25 | proxy_dns_fcgi: proxy_dns_fcgi.o Makefile 26 | cc $(LDFLAGS) -pthread -o proxy_dns_fcgi proxy_dns_fcgi.o \ 27 | -lfcgi $(RESOLV) -lpthread 28 | 29 | proxy_dns_fcgi.o: proxy_dns_fcgi.c Makefile 30 | cc $(CFLAGS) -pthread -c proxy_dns_fcgi.c 31 | -------------------------------------------------------------------------------- /proxy_dns_fcgi/proxy_dns_fcgi.c: -------------------------------------------------------------------------------- 1 | /* proxy_dns_fcgi - gateway from FastCGI to DNS 2 | * 2015-03-13 Paul Vixie [original] 3 | */ 4 | 5 | /* Known defects: 6 | * Forks threads for parallelism, which is too limited. Needs "libevent". 7 | */ 8 | 9 | /* Externals. */ 10 | 11 | #define _GNU_SOURCE 12 | 13 | #include 14 | #include 15 | #include 16 | 17 | #include 18 | #include 19 | #include 20 | #include 21 | #include 22 | #include 23 | #include 24 | #include 25 | 26 | /* Private data structures. */ 27 | 28 | #define N_THREADS 4 29 | 30 | /* 31 | * Values for role component of FCGI_BeginRequestBody (from www.fastcgi.com) 32 | */ 33 | #define FCGI_RESPONDER 1 34 | #define FCGI_AUTHORIZER 2 35 | #define FCGI_FILTER 3 36 | 37 | typedef struct ctx { 38 | int fcgi_fd; 39 | } *ctx_t; 40 | 41 | static const char sockpath[] = "/tmp/proxy_dns_fcgi.sock"; 42 | 43 | /* Forward. */ 44 | 45 | static void *start_fcgi_worker(void *uap); 46 | 47 | /* Public. */ 48 | 49 | int 50 | main(void) { 51 | pthread_t threads[N_THREADS]; 52 | struct ctx info; 53 | int fcgifd, i; 54 | 55 | fcgifd = FCGX_OpenSocket(sockpath, 128); 56 | if (fcgifd < 0) { 57 | perror("Error opening socket\n"); 58 | exit(1); 59 | } 60 | info.fcgi_fd = fcgifd; 61 | chmod(sockpath, 0777); 62 | 63 | for (i = 0; i < N_THREADS; i++) 64 | pthread_create(&threads[i], NULL, start_fcgi_worker, 65 | (void *) &info); 66 | 67 | for (i = 0; i < N_THREADS; i++) 68 | pthread_join(threads[i], NULL); 69 | 70 | return (0); 71 | } 72 | 73 | void * 74 | start_fcgi_worker(void *uap) { 75 | ctx_t info = (ctx_t) uap; 76 | struct __res_state res; 77 | FCGX_Request request; 78 | 79 | memset(&res, 0, sizeof res); 80 | (void) res_ninit(&res); 81 | FCGX_Init(); 82 | FCGX_InitRequest(&request, info->fcgi_fd, 0); 83 | 84 | /* Repeat until killed. */ 85 | for (;;) { 86 | u_char dnsreq[NS_MAXMSG], dnsresp[NS_MAXMSG]; 87 | const char *method, *len_str, *transport; 88 | int reqlen, resplen; 89 | char *errmsg = NULL; 90 | int status = 0; 91 | 92 | FCGX_Accept_r(&request); 93 | method = FCGX_GetParam("REQUEST_METHOD", request.envp); 94 | len_str = FCGX_GetParam("CONTENT_LENGTH", request.envp); 95 | transport = FCGX_GetParam("HTTP_PROXY_DNS_TRANSPORT", 96 | request.envp); 97 | reqlen = atoi((len_str != NULL) ? len_str : "0"); 98 | 99 | /*fprintf(stderr, "request (%d bytes, transport %s)\n", 100 | reqlen, transport);*/ 101 | 102 | if (request.role != FCGI_RESPONDER) { 103 | asprintf(&errmsg, "bad role = %d\r\n", 104 | request.role); 105 | status = 500; 106 | goto fini; 107 | } 108 | /* GET means display the environment in text. */ 109 | if (method != NULL && strcasecmp(method, "GET") == 0) { 110 | FCGX_ParamArray p; 111 | 112 | FCGX_PutStr("Content-type: text/plain\r\n\r\n", 113 | 28, request.out); 114 | for (p = request.envp; 115 | *p != NULL; 116 | p++) 117 | FCGX_FPrintF(request.out, 118 | "env '%s'\r\n", *p); 119 | FCGX_PutStr("EOM\r\n", 5, request.out); 120 | goto fini; 121 | } 122 | /* Otherwise must be a POST with good size. */ 123 | if (strcasecmp(method, "POST") != 0 || 124 | reqlen < NS_HFIXEDSZ || 125 | (size_t)reqlen > sizeof dnsreq || 126 | FCGX_GetStr((char *)dnsreq, reqlen, 127 | request.in) < reqlen) 128 | { 129 | asprintf(&errmsg, "bad reqlen = %d\r\n", 130 | reqlen); 131 | status = 400; 132 | goto fini; 133 | } 134 | /* Our transport must be the same as remote's. */ 135 | if (transport != NULL && 136 | strcasecmp(transport, "TCP") == 0) 137 | res.options |= RES_USEVC; 138 | else if (transport != NULL && 139 | strcasecmp(transport, "UDP") == 0) 140 | res.options &= ~RES_USEVC; 141 | else { 142 | asprintf(&errmsg, "bad transport %s\r\n", 143 | transport == NULL ? "Null" 144 | : transport); 145 | status = 400; 146 | goto fini; 147 | } 148 | /* Finally, send this query to our system's RDNS. */ 149 | resplen = res_nsend(&res, dnsreq, reqlen, 150 | dnsresp, sizeof dnsresp); 151 | if (resplen < 0) { 152 | asprintf(&errmsg, "send failed: %s\r\n", 153 | strerror(errno)); 154 | status = 503; 155 | goto fini; 156 | } 157 | /* Send RDNS response to remote, unmodified. */ 158 | if (FCGX_PutStr("Status: 200 OK\r\n", 16, request.out) < 0 || 159 | FCGX_PutStr( 160 | "Content-type: application/octet-stream\r\n\r\n", 161 | 42, request.out) < 0 || 162 | FCGX_PutStr((char *)dnsresp, resplen, 163 | request.out) < 0) 164 | { 165 | status = 500; 166 | goto fini; 167 | } 168 | fini: 169 | assert((errmsg != NULL) == (status != 0)); 170 | if (errmsg != NULL) { 171 | char *msg; 172 | 173 | fprintf(stderr, "%d: %s", status, errmsg); 174 | asprintf(&msg, "Status: %d %s\r\n", status, errmsg); 175 | FCGX_PutStr(msg, strlen(msg), request.out); 176 | FCGX_PutStr("Content-Type: text/plain\r\n\r\n", 177 | 28, request.out); 178 | FCGX_PutStr(errmsg, strlen(errmsg), request.out); 179 | free(errmsg); 180 | errmsg = NULL; 181 | } 182 | FCGX_Finish_r(&request); 183 | } 184 | return (NULL); 185 | } 186 | -------------------------------------------------------------------------------- /proxy_dns_gw/.gitignore: -------------------------------------------------------------------------------- 1 | proxy_dns_gw.o 2 | proxy_dns_gw 3 | -------------------------------------------------------------------------------- /proxy_dns_gw/Makefile: -------------------------------------------------------------------------------- 1 | CURLINCL = `curl-config --cflags` 2 | CURLLIBS = `curl-config --static-libs` 3 | 4 | CFLAGS= -I/usr/local/include 5 | LDFLAGS= -L/usr/local/lib 6 | RESOLV= 7 | #RESOLV= -lresolv 8 | 9 | CWARN =-W -Wall -Wcast-qual -Wpointer-arith -Wwrite-strings \ 10 | -Wmissing-prototypes -Wbad-function-cast -Wnested-externs \ 11 | -Wunused -Wshadow -Wmissing-noreturn -Wswitch-enum -Wformat-nonliteral 12 | # -Wunreachable-code is often wrong 13 | #CWARN +=-Wunreachable-code 14 | # try shipping without any warnings 15 | CWARN +=-Werror 16 | 17 | CDEBUG = -g 18 | CFLAGS += $(CDEBUG) $(CWARN) 19 | 20 | ALL= proxy_dns_gw 21 | 22 | all: $(ALL) 23 | 24 | clean: 25 | rm -f $(ALL) 26 | rm -f *.o *.core 27 | 28 | proxy_dns_gw: proxy_dns_gw.o Makefile 29 | cc $(LDFLAGS) -o proxy_dns_gw proxy_dns_gw.o \ 30 | $(CURLLIBS) $(RESOLV) 31 | 32 | .c.o: 33 | cc $(CFLAGS) $(CURLINCL) -c $< 34 | -------------------------------------------------------------------------------- /proxy_dns_gw/proxy_dns_gw.c: -------------------------------------------------------------------------------- 1 | /* External. */ 2 | 3 | #define _GNU_SOURCE 4 | 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | #include 11 | 12 | #include 13 | #include 14 | #include 15 | #include 16 | #include 17 | #include 18 | #include 19 | 20 | #include 21 | 22 | /* Parameters. */ 23 | 24 | #define DEBUGGING_OUTPUT 1 25 | #define TCP_DNS_TIMEOUT 3 26 | 27 | /* Private. */ 28 | 29 | typedef union sockaddr_union { 30 | struct sockaddr_in6 sa6; 31 | struct sockaddr_in sa4; 32 | struct sockaddr sa; 33 | } *sockaddr_union_t; 34 | 35 | typedef struct upstream { 36 | int socket; 37 | union sockaddr_union from; 38 | socklen_t fromlen; 39 | struct curl_slist *headers; 40 | char *url, *transport; 41 | u_char *dnsreq, *resp; 42 | size_t reqlen, resplen; 43 | char errorbuffer[CURL_ERROR_SIZE]; 44 | } *upstream_t; 45 | 46 | typedef struct timeout { 47 | struct timeout *next; 48 | time_t when; 49 | int socket; 50 | } *timeout_t; 51 | 52 | typedef struct listener { 53 | struct listener *next; 54 | int udp, tcp; 55 | } *listener_t; 56 | 57 | enum conntype { e_none, e_udp, e_tcp }; 58 | 59 | static listener_t listeners = NULL; 60 | static int ourmax = -1; 61 | static int ncurl = 0, debug = 0; 62 | static timeout_t timeouts = NULL; 63 | static const char *server = NULL; 64 | static fd_set ourfds; 65 | 66 | #if DEBUGGING_OUTPUT 67 | #define DPRINTF(l, x) if (debug >= l) fprintf x; else {} 68 | #else 69 | #define DPRINTF(l, x) 0 70 | #endif 71 | 72 | /* Forward. */ 73 | 74 | static void upstream_complete(upstream_t arg); 75 | static void udp_input(CURLM *, int fd); 76 | static void tcp_session(int listener); 77 | static void tcp_input(CURLM *, int fd); 78 | static void tcp_close(int fd); 79 | static int launch_request(CURLM *, const u_char *, size_t, 80 | int, const char *, union sockaddr_union, socklen_t); 81 | static size_t write_callback(char *ptr, size_t size, size_t count, 82 | void *userdata); 83 | static listener_t get_sockets(const char *, int default_port); 84 | static enum conntype our_listener_p(int fd); 85 | static int get_sockaddr(const char *, int, sockaddr_union_t, 86 | socklen_t *, int *); 87 | static int add_timeout(time_t when, int socket); 88 | static void update_timeout(time_t when, int socket); 89 | static int remove_timeout(int socket); 90 | static long do_timeouts(time_t as_of); 91 | static upstream_t upstream_create(int, union sockaddr_union, socklen_t, 92 | const u_char *, size_t); 93 | static void upstream_destroy(upstream_t *arg); 94 | static void debug_dump(int level, const char *after); 95 | #if DEBUGGING_OUTPUT 96 | static char *fdlist(int, fd_set *); 97 | #endif 98 | 99 | /* Public. */ 100 | 101 | int 102 | main(int argc, char **argv) { 103 | listener_t listener; 104 | CURLM *curlm; 105 | int ch; 106 | 107 | while ((ch = getopt(argc, argv, "dl:s:")) != -1) { 108 | switch (ch) { 109 | case 'd': 110 | debug++; 111 | break; 112 | case 'l': 113 | listener = get_sockets(optarg, NS_DEFAULTPORT); 114 | if (listener == NULL) { 115 | perror("get_sockets"); 116 | exit(1); 117 | } 118 | listener->next = listeners; 119 | listeners = listener; 120 | listener = NULL; 121 | break; 122 | case 's': 123 | if (server != NULL) { 124 | fprintf(stderr, "-s may only appear once\n"); 125 | exit(1); 126 | } 127 | server = strdup(optarg); 128 | break; 129 | default: 130 | fprintf(stderr, 131 | "usage: proxy_dns_gw " 132 | "-l addr[,port] " 133 | "-s server" 134 | "\n"); 135 | exit(1); 136 | } 137 | } 138 | argc -= optind, argv += optind; 139 | 140 | if (listeners == NULL) { 141 | fprintf(stderr, "-l must be specified\n"); 142 | exit(1); 143 | } 144 | if (server == NULL) { 145 | fprintf(stderr, "-s must be specified\n"); 146 | exit(1); 147 | } 148 | 149 | FD_ZERO(&ourfds); 150 | ourmax = -1; 151 | for (listener = listeners; listener != NULL; listener = listener->next) 152 | { 153 | fcntl(listener->tcp, F_SETFL, 154 | fcntl(listener->tcp, F_GETFL) | O_NONBLOCK); 155 | fcntl(listener->udp, F_SETFL, 156 | fcntl(listener->udp, F_GETFL) | O_NONBLOCK); 157 | listen(listener->tcp, 10); 158 | FD_SET(listener->udp, &ourfds); 159 | FD_SET(listener->tcp, &ourfds); 160 | ourmax = MAX(ourmax, MAX(listener->udp, listener->tcp)); 161 | } 162 | 163 | curl_global_init(CURL_GLOBAL_DEFAULT); 164 | curlm = curl_multi_init(); 165 | for (;;) { 166 | long curl_timeout, our_timeout; 167 | fd_set input, output, except; 168 | int maxfd, curl_maxfd, n; 169 | struct timeval to, *top; 170 | struct CURLMsg *msg; 171 | 172 | input = ourfds; 173 | FD_ZERO(&output); 174 | FD_ZERO(&except); 175 | 176 | /* Find highest FD among our listeners, sessions, and curl. */ 177 | curl_multi_fdset(curlm, &input, &output, &except, &curl_maxfd); 178 | maxfd = MAX(curl_maxfd, ourmax); 179 | 180 | /* Timeout is ours or curl's, whichever is sooner. */ 181 | curl_multi_timeout(curlm, &curl_timeout); 182 | 183 | /* "Note: if libcurl returns a -1 timeout here, it just means 184 | * that libcurl currently has no stored timeout value. You 185 | * must not wait too long (more * than a few seconds perhaps) 186 | * before you call curl_multi_perform() again." 187 | * 188 | * (from curl_multi_timeout(3) as of 2015-03-21) 189 | */ 190 | #if BOGUS 191 | if (curl_timeout < 0L && ncurl > 0) 192 | curl_timeout = 2500; /* ms; so, 2.5sec */ 193 | #endif 194 | our_timeout = do_timeouts(time(NULL)); 195 | if (curl_timeout < 0L && our_timeout < 0L) { 196 | /* Noone has a timeout; wait until input. */ 197 | top = NULL; 198 | } else { 199 | long timeout; 200 | 201 | if (curl_timeout < 0L) 202 | timeout = our_timeout; 203 | else if (our_timeout < 0L) 204 | timeout = curl_timeout; 205 | else 206 | timeout = MIN(our_timeout, curl_timeout); 207 | 208 | top = &to; 209 | to.tv_sec = timeout / 1000; 210 | to.tv_usec = (timeout % 1000) * 1000; 211 | } 212 | #if DEBUGGING_OUTPUT 213 | if (debug >= 1) { 214 | char *in = fdlist(maxfd + 1, &input), 215 | *out = fdlist(maxfd + 1, &output), 216 | *exc = fdlist(maxfd + 1, &except), 217 | *tim = NULL; 218 | 219 | if (top == NULL) 220 | tim = strdup("Nil"); 221 | else 222 | asprintf(&tim, "%lu.%06lu", 223 | (u_long) top->tv_sec, 224 | (u_long) top->tv_usec); 225 | 226 | fprintf(stderr, "select(%d, %s, %s, %s, %s)\n", 227 | maxfd + 1, in, out, exc, tim); 228 | free(in); 229 | free(out); 230 | free(exc); 231 | free(tim); 232 | } 233 | #endif 234 | n = select(maxfd + 1, &input, &output, &except, top); 235 | if (n < 0) { 236 | perror("select"); 237 | exit(1); 238 | } 239 | DPRINTF(2, (stderr, "select = %d\n", n)); 240 | curl_multi_perform(curlm, &n); 241 | while ((msg = curl_multi_info_read(curlm, &n)) != NULL) { 242 | if (msg->msg == CURLMSG_DONE) { 243 | upstream_t arg; 244 | long rcode; 245 | 246 | assert(curl_easy_getinfo(msg->easy_handle, 247 | CURLINFO_PRIVATE, 248 | (char *) &arg) 249 | == CURLE_OK); 250 | assert(curl_easy_getinfo(msg->easy_handle, 251 | CURLINFO_RESPONSE_CODE, 252 | (char *) &rcode) 253 | == CURLE_OK); 254 | curl_multi_remove_handle(curlm, msg-> 255 | easy_handle); 256 | #if DEBUGGING_OUTPUT 257 | if (msg->data.result != CURLE_OK) { 258 | fprintf(stderr, "error: '%s'\n", 259 | arg->errorbuffer); 260 | } 261 | #endif 262 | if (rcode != 200) { 263 | fprintf(stderr, "failure, code %d:\n" 264 | "---\n%-*.*s===\n", 265 | (int) rcode, 266 | (int) arg->resplen, 267 | (int) arg->resplen, 268 | arg->resp); 269 | free(arg->resp); 270 | arg->resp = NULL; 271 | arg->resplen = 0; 272 | } 273 | upstream_complete(arg); 274 | upstream_destroy(&arg); 275 | ncurl--; 276 | } else { 277 | DPRINTF(1, (stderr, "info_read !done (%d)\n", 278 | msg->data.result)); 279 | } 280 | } 281 | 282 | for (n = 0; n <= ourmax; n++) { 283 | if (FD_ISSET(n, &ourfds) && FD_ISSET(n, &input)) { 284 | if (our_listener_p(n) == e_udp) 285 | udp_input(curlm, n); 286 | else if (our_listener_p(n) == e_tcp) 287 | tcp_session(n); 288 | else 289 | tcp_input(curlm, n); 290 | } 291 | } 292 | } 293 | curl_multi_cleanup(curlm); 294 | return (EXIT_SUCCESS); 295 | } 296 | 297 | /* Private. */ 298 | 299 | static void 300 | upstream_complete(upstream_t arg) { 301 | enum conntype ct; 302 | int n; 303 | 304 | assert(arg != NULL); 305 | assert((arg->resp != NULL) == (arg->resplen != 0)); 306 | 307 | DPRINTF(2, (stderr, "upstream_complete(%p, %d, %s)\n", 308 | arg, arg->socket, arg->url)); 309 | 310 | /* Calling us with nothing in the output buffer means SERVFAIL. */ 311 | if (arg->resp == NULL) { 312 | arg->resp = arg->dnsreq; 313 | arg->resplen = arg->reqlen; 314 | ((HEADER *)arg->resp)->rcode = SERVFAIL; 315 | ((HEADER *)arg->resp)->qr = 1; 316 | ((HEADER *)arg->resp)->ra = 1; 317 | arg->dnsreq = NULL; 318 | arg->reqlen = 0; 319 | } 320 | 321 | ct = our_listener_p(arg->socket); 322 | if (ct == e_udp) { 323 | n = sendto(arg->socket, arg->resp, arg->resplen, 324 | 0, &arg->from.sa, arg->fromlen); 325 | } else if (ct == e_none) { 326 | struct iovec iov[2]; 327 | struct msghdr msg; 328 | u_char msglen[2]; 329 | 330 | ns_put16(arg->resplen, msglen); 331 | memset(&msg, 0, sizeof msg); 332 | msg.msg_name = &arg->from; 333 | msg.msg_namelen = arg->fromlen; 334 | msg.msg_iov = iov; 335 | msg.msg_iovlen = 2; 336 | iov[0].iov_base = msglen; 337 | iov[0].iov_len = sizeof msglen; 338 | iov[1].iov_base = arg->resp; 339 | iov[1].iov_len = arg->resplen; 340 | 341 | n = sendmsg(arg->socket, &msg, 0); 342 | } else { 343 | abort(); 344 | } 345 | if (n < 0) 346 | perror("send"); 347 | } 348 | 349 | static void 350 | udp_input(CURLM *curlm, int fd) { 351 | union sockaddr_union from; 352 | u_char dnsreq[NS_MAXMSG]; 353 | socklen_t fromlen; 354 | ssize_t reqlen; 355 | 356 | DPRINTF(1, (stderr, "udp_input(%d)\n", fd)); 357 | 358 | while (fromlen = sizeof from, 359 | (reqlen = recvfrom(fd, dnsreq, sizeof dnsreq, 0, 360 | &from.sa, &fromlen)) > 0) 361 | { 362 | (void) launch_request(curlm, dnsreq, reqlen, 363 | fd, "UDP", from, fromlen); 364 | } 365 | } 366 | 367 | static void 368 | tcp_session(int listener) { 369 | struct sockaddr from; 370 | socklen_t fromlen; 371 | int tcp_client; 372 | 373 | fromlen = sizeof from; 374 | tcp_client = accept(listener, &from, &fromlen); 375 | if (tcp_client < 0) { 376 | perror("accept"); 377 | return; 378 | } 379 | assert(!FD_ISSET(tcp_client, &ourfds)); 380 | FD_SET(tcp_client, &ourfds); 381 | if (tcp_client > ourmax) 382 | ourmax = tcp_client; 383 | 384 | DPRINTF(1, (stderr, "tcp_session(%d -> %d)\n", listener, tcp_client)); 385 | 386 | fcntl(tcp_client, F_SETFL, 387 | fcntl(tcp_client, F_GETFL) | O_NONBLOCK); 388 | 389 | if (add_timeout(time(NULL) + TCP_DNS_TIMEOUT, tcp_client) < 0) { 390 | perror("add_timeout"); 391 | tcp_close(tcp_client); 392 | return; 393 | } 394 | debug_dump(2, "tcp_session"); 395 | } 396 | 397 | static void 398 | tcp_input(CURLM *curlm, int tcp_client) { 399 | u_char reqlen[NS_INT16SZ], req[NS_MAXMSG]; 400 | union sockaddr_union from; 401 | socklen_t fromlen; 402 | int len1, len2; 403 | 404 | len1 = read(tcp_client, reqlen, sizeof reqlen); 405 | if (len1 == 0) { 406 | tcp_close(tcp_client); 407 | remove_timeout(tcp_client); 408 | return; 409 | } 410 | if (len1 < 0) { 411 | fprintf(stderr, "read(tcp_client #1): %s\n", 412 | strerror(errno)); 413 | goto abend; 414 | } 415 | if (len1 != sizeof reqlen) { 416 | fprintf(stderr, "read(tcp_client #1): %d octets\n", 417 | len1); 418 | goto abend; 419 | } 420 | len1 = ns_get16(reqlen); 421 | 422 | DPRINTF(1, (stderr, "tcp_input(%d -> %d)\n", 423 | tcp_client, len1)); 424 | 425 | len2 = read(tcp_client, req, sizeof req); 426 | if (len2 < 0) { 427 | fprintf(stderr, "read(tcp_client #2): %s\n", 428 | strerror(errno)); 429 | goto abend; 430 | } 431 | if (len2 != len1) { 432 | fprintf(stderr, "read(tcp_client #2): %d (vs %d)\n", 433 | len1, len2); 434 | goto abend; 435 | } 436 | fromlen = sizeof from; 437 | if (getpeername(tcp_client, &from.sa, &fromlen) < 0) { 438 | fprintf(stderr, "getpeername(tcp_client): %s\n", 439 | strerror(errno)); 440 | goto abend; 441 | } 442 | if (launch_request(curlm, req, len2, tcp_client, 443 | "TCP", from, fromlen) < 0) 444 | goto abend; 445 | 446 | update_timeout(time(NULL) + TCP_DNS_TIMEOUT, tcp_client); 447 | debug_dump(2, "tcp_input#1"); 448 | return; 449 | 450 | abend: 451 | tcp_close(tcp_client); 452 | remove_timeout(tcp_client); 453 | debug_dump(2, "tcp_input#2"); 454 | } 455 | 456 | static void 457 | tcp_close(int fd) { 458 | assert(FD_ISSET(fd, &ourfds)); 459 | if (fd == ourmax) { 460 | FD_CLR(ourmax, &ourfds); 461 | do { 462 | ourmax--; 463 | } while (ourmax > 0 && !FD_ISSET(ourmax, &ourfds)); 464 | } 465 | DPRINTF(2, (stderr, "tcp_close(%d), outmax is now %d\n", fd, ourmax)); 466 | close(fd); 467 | debug_dump(2, "tcp_close"); 468 | } 469 | 470 | static int 471 | launch_request(CURLM *curlm, const u_char *dnsreq, size_t reqlen, 472 | int outputsock, const char *transport, 473 | union sockaddr_union from, socklen_t fromlen) 474 | { 475 | upstream_t arg = NULL; 476 | CURL *curl = NULL; 477 | CURLMcode res; 478 | int x; 479 | 480 | arg = upstream_create(outputsock, from, fromlen, dnsreq, reqlen); 481 | if (arg == NULL) { 482 | fprintf(stderr, "upstream_create failed\n"); 483 | return (-1); 484 | } 485 | 486 | curl = curl_easy_init(); 487 | if (curl == NULL) { 488 | fprintf(stderr, "curl_easy_init failed\n"); 489 | goto servfail; 490 | } 491 | 492 | x = asprintf(&arg->url, "%s/proxy_dns", server); 493 | if (x < 0) { 494 | perror("asprintf"); 495 | goto servfail; 496 | } 497 | 498 | x = asprintf(&arg->transport, "Proxy-DNS-Transport: %s", transport); 499 | if (x < 0) { 500 | perror("asprintf #2"); 501 | goto servfail; 502 | } 503 | 504 | curl_easy_setopt(curl, CURLOPT_PRIVATE, arg); 505 | curl_easy_setopt(curl, CURLOPT_ERRORBUFFER, arg->errorbuffer); 506 | curl_easy_setopt(curl, CURLOPT_URL, arg->url); 507 | curl_easy_setopt(curl, CURLOPT_SSL_VERIFYPEER, 0L); 508 | curl_easy_setopt(curl, CURLOPT_SSL_VERIFYHOST, 0L); 509 | arg->headers = curl_slist_append(arg->headers, "Accept: " 510 | "application/octet-stream"); 511 | arg->headers = curl_slist_append(arg->headers, "Content-Type: " 512 | "application/octet-stream"); 513 | arg->headers = curl_slist_append(arg->headers, arg->transport); 514 | curl_easy_setopt(curl, CURLOPT_HTTPHEADER, arg->headers); 515 | curl_easy_setopt(curl, CURLOPT_POSTFIELDS, dnsreq); 516 | curl_easy_setopt(curl, CURLOPT_POSTFIELDSIZE, reqlen); 517 | curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, write_callback); 518 | curl_easy_setopt(curl, CURLOPT_WRITEDATA, (char *)arg); 519 | 520 | res = curl_multi_add_handle(curlm, curl); 521 | if (res != CURLM_OK) { 522 | fprintf(stderr, "curl_multi_add_handle() failed: %s\n", 523 | curl_multi_strerror(res)); 524 | goto servfail; 525 | } 526 | 527 | ncurl++; 528 | DPRINTF(2, (stderr, "launch_request: ncurl %d, arg %p, easy %p\n", 529 | ncurl, arg, curl)); 530 | return (0); 531 | 532 | servfail: 533 | if (curl != NULL) { 534 | curl_easy_cleanup(curl); 535 | curl = NULL; 536 | } 537 | 538 | upstream_complete(arg); 539 | upstream_destroy(&arg); 540 | return (-1); 541 | } 542 | 543 | static size_t 544 | write_callback(char *ptr, size_t size, size_t count, void *userdata) { 545 | upstream_t arg = userdata; 546 | size_t len = size * count; 547 | 548 | arg->resp = realloc(arg->resp, arg->resplen + len); 549 | memcpy(arg->resp + arg->resplen, ptr, len); 550 | arg->resplen += len; 551 | return (len); 552 | } 553 | 554 | static listener_t 555 | get_sockets(const char *spec, int default_port) { 556 | union sockaddr_union su; 557 | listener_t new; 558 | char *p, *addr; 559 | socklen_t len; 560 | int udp, tcp, pf, port; 561 | const int on = 1; 562 | 563 | if ((p = strchr(spec, '/')) == NULL) 564 | p = strchr(spec, ','); 565 | if (p != NULL) 566 | port = atoi(p + 1); 567 | else 568 | port = default_port; 569 | if (port == 0) { 570 | fprintf(stderr, "port number '%s' is not valid\n", p + 1); 571 | return (NULL); 572 | } 573 | addr = strndup(spec, p - spec); 574 | if (!get_sockaddr(addr, port, &su, &len, &pf)) { 575 | fprintf(stderr, "address '%s' is not valid\n", addr); 576 | free(addr); 577 | return (NULL); 578 | } 579 | free(addr); 580 | 581 | udp = socket(pf, SOCK_DGRAM, 0); 582 | if (udp == -1) { 583 | perror("socket(udp)"); 584 | return (NULL); 585 | } 586 | if (bind(udp, &su.sa, len) == -1) { 587 | perror("bind(udp)"); 588 | close(udp); 589 | return (NULL); 590 | } 591 | tcp = socket(pf, SOCK_STREAM, 0); 592 | if (tcp == -1) { 593 | perror("socket(tcp)"); 594 | close(udp); 595 | close(tcp); 596 | return (NULL); 597 | } 598 | #ifdef SO_REUSEADDR 599 | (void) setsockopt(tcp, SOL_SOCKET, SO_REUSEADDR, &on, sizeof on); 600 | #endif 601 | #ifdef SO_REUSEPORT 602 | (void) setsockopt(tcp, SOL_SOCKET, SO_REUSEPORT, &on, sizeof on); 603 | #endif 604 | if (bind(tcp, &su.sa, len) == -1) { 605 | perror("bind(tcp)"); 606 | close(udp); 607 | close(tcp); 608 | return (NULL); 609 | } 610 | new = malloc(sizeof *new); 611 | if (new == NULL) { 612 | perror("malloc"); 613 | close(udp); 614 | close(tcp); 615 | return (NULL); 616 | } 617 | memset(new, 0, sizeof *new); 618 | new->udp = udp; 619 | new->tcp = tcp; 620 | new->next = NULL; 621 | return (new); 622 | } 623 | 624 | static enum conntype 625 | our_listener_p(int fd) { 626 | listener_t listener; 627 | 628 | for (listener = listeners; listener != NULL; listener = listener->next) 629 | { 630 | if (fd == listener->udp) 631 | return (e_udp); 632 | if (fd == listener->tcp) 633 | return (e_tcp); 634 | } 635 | return (e_none); 636 | } 637 | 638 | static int 639 | get_sockaddr(const char *input, int port, 640 | sockaddr_union_t sup, 641 | socklen_t *lenp, int *pfp) 642 | { 643 | memset(sup, 0, sizeof *sup); 644 | if (inet_pton(AF_INET6, input, &sup->sa6.sin6_addr) > 0) { 645 | *lenp = sizeof sup->sa6; 646 | sup->sa6.sin6_family = AF_INET6; 647 | #ifdef BSD4_4 648 | sup->sa6.sin6_len = *lenp; 649 | #endif 650 | sup->sa6.sin6_port = htons(port); 651 | *pfp = PF_INET6; 652 | } else if (inet_pton(AF_INET, input, &sup->sa4.sin_addr) > 0) { 653 | *lenp = sizeof sup->sa4; 654 | sup->sa4.sin_family = AF_INET; 655 | #ifdef BSD4_4 656 | sup->sa4.sin_len = *lenp; 657 | #endif 658 | sup->sa4.sin_port = htons(port); 659 | *pfp = PF_INET; 660 | } else { 661 | return (0); 662 | } 663 | return (1); 664 | } 665 | 666 | static int 667 | add_timeout(time_t when, int socket) { 668 | timeout_t new, cur, prev; 669 | 670 | new = malloc(sizeof *new); 671 | if (new == NULL) 672 | return (-1); 673 | new->next = NULL; 674 | new->when = when; 675 | new->socket = socket; 676 | 677 | prev = NULL; 678 | for (cur = timeouts; cur != NULL; cur = cur->next) 679 | prev = cur; 680 | 681 | if (prev == NULL) 682 | timeouts = new; 683 | else 684 | prev->next = new; 685 | 686 | return (0); 687 | } 688 | 689 | static void 690 | update_timeout(time_t when, int socket) { 691 | timeout_t cur1, cur2, prev1, prev2; 692 | 693 | prev1 = NULL; 694 | for (cur1 = timeouts; cur1 != NULL; cur1 = cur1->next) { 695 | if (cur1->socket == socket) 696 | break; 697 | prev1 = cur1; 698 | } 699 | assert(cur1 != NULL); 700 | cur1->when = when; 701 | 702 | prev2 = cur1; 703 | for (cur2 = cur1->next; cur2 != NULL; cur2 = cur2->next) { 704 | if (cur2->when > cur1->when) 705 | break; 706 | prev2 = cur2; 707 | } 708 | assert(prev2 != NULL); 709 | 710 | if (cur1->next != cur2) { 711 | /* Delete. */ 712 | if (prev1 == NULL) { 713 | assert(timeouts == cur1); 714 | timeouts = cur1->next; 715 | } else { 716 | assert(prev1->next == cur1); 717 | prev1->next = cur1->next; 718 | } 719 | /* Insert. */ 720 | cur1->next = cur2; 721 | prev2->next = cur1; 722 | } 723 | } 724 | 725 | static int 726 | remove_timeout(int socket) { 727 | timeout_t cur, prev; 728 | 729 | prev = NULL; 730 | for (cur = timeouts; cur != NULL; cur = cur->next) { 731 | if (cur->socket == socket) 732 | break; 733 | prev = cur; 734 | } 735 | if (cur == NULL) 736 | return (0); 737 | if (prev == NULL) 738 | timeouts = cur->next; 739 | else 740 | prev->next = cur->next; 741 | free(cur); 742 | return (1); 743 | } 744 | 745 | static long 746 | do_timeouts(time_t as_of) { 747 | while (timeouts != NULL && timeouts->when <= as_of) { 748 | tcp_close(timeouts->socket); 749 | remove_timeout(timeouts->socket); 750 | } 751 | if (timeouts != NULL) 752 | return ((as_of - timeouts->when) * 1000); 753 | return (-1L); 754 | } 755 | 756 | static upstream_t 757 | upstream_create(int outputsock, 758 | union sockaddr_union from, socklen_t fromlen, 759 | const u_char *dnsreq, size_t reqlen) 760 | { 761 | upstream_t arg = malloc(sizeof *arg); 762 | 763 | if (arg == NULL) { 764 | perror("upstream_create: malloc #1"); 765 | return (NULL); 766 | } 767 | arg->dnsreq = malloc(reqlen); 768 | if (arg->dnsreq == NULL) { 769 | perror("upstream_create: malloc #2"); 770 | free(arg); 771 | return (NULL); 772 | } 773 | memcpy(arg->dnsreq, dnsreq, reqlen); 774 | arg->reqlen = reqlen; 775 | arg->socket = outputsock; 776 | arg->from = from; 777 | arg->fromlen = fromlen; 778 | arg->headers = NULL; 779 | arg->url = NULL; 780 | arg->resp = NULL; 781 | arg->resplen = 0; 782 | return (arg); 783 | } 784 | 785 | static void 786 | upstream_destroy(upstream_t *argp) { 787 | if ((*argp)->headers != NULL) { 788 | curl_slist_free_all((*argp)->headers); 789 | (*argp)->headers = NULL; 790 | } 791 | if ((*argp)->transport != NULL) { 792 | free((*argp)->transport); 793 | (*argp)->transport = NULL; 794 | } 795 | if ((*argp)->url != NULL) { 796 | free((*argp)->url); 797 | (*argp)->url = NULL; 798 | } 799 | if ((*argp)->resp != NULL) { 800 | free((*argp)->resp); 801 | (*argp)->resp = NULL; 802 | } 803 | free(*argp); 804 | *argp = NULL; 805 | } 806 | 807 | static void 808 | debug_dump(int level, const char *after) { 809 | #if DEBUGGING_OUTPUT 810 | time_t now = time(NULL); 811 | timeout_t to; 812 | int fd; 813 | 814 | if (debug < level) 815 | return; 816 | fprintf(stderr, "debug_dump(%s)...\n", after); 817 | fprintf(stderr, "fd 0..%d:", ourmax); 818 | for (fd = 0; fd <= ourmax; fd++) 819 | if (FD_ISSET(fd, &ourfds)) 820 | fprintf(stderr, " %d", fd); 821 | fputc('\n', stderr); 822 | fprintf(stderr, "to @%lu:", now); 823 | for (to = timeouts; to != NULL; to = to->next) 824 | fprintf(stderr, " %d@%ld", to->socket, now - to->when); 825 | fputc('\n', stderr); 826 | fputs("---\n", stderr); 827 | #else 828 | after++; 829 | #endif 830 | } 831 | 832 | static char * 833 | fdlist(int nfds, fd_set *fdset) { 834 | const char *sep = ""; 835 | char *pres = strdup("["), *tmp; 836 | int fd; 837 | 838 | for (fd = 0; fd < nfds; fd++) 839 | if (FD_ISSET(fd, fdset)) { 840 | asprintf(&tmp, "%s%s%d", pres, sep, fd); 841 | free(pres); 842 | pres = tmp; 843 | tmp = NULL; 844 | sep = " "; 845 | } 846 | asprintf(&tmp, "%s]", pres); 847 | free(pres); 848 | pres = tmp; 849 | tmp = NULL; 850 | return (pres); 851 | } 852 | --------------------------------------------------------------------------------