├── 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 |
--------------------------------------------------------------------------------