forked from aarond10/https_dns_proxy
-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathdns_server_tcp.c
More file actions
370 lines (310 loc) · 11.6 KB
/
dns_server_tcp.c
File metadata and controls
370 lines (310 loc) · 11.6 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
//NOLINTNEXTLINE(bugprone-reserved-identifier,cert-dcl37-c,cert-dcl51-cpp)
#define _GNU_SOURCE // needed for having accept4()
#include <errno.h>
#include <fcntl.h>
#include <unistd.h>
#include "dns_server_tcp.h"
#include "logging.h"
// the following macros require to have client pointer to tcp_client_s structure
// else: compilation failure will occur
#define LOG_CLIENT(level, format, args...) LOG(level, "C-%u: " format, client->id, ## args)
#define DLOG_CLIENT(format, args...) DLOG("C-%u: " format, client->id, ## args)
#define ILOG_CLIENT(format, args...) ILOG("C-%u: " format, client->id, ## args)
#define WLOG_CLIENT(format, args...) WLOG("C-%u: " format, client->id, ## args)
#define ELOG_CLIENT(format, args...) ELOG("C-%u: " format, client->id, ## args)
#define FLOG_CLIENT(format, args...) FLOG("C-%u: " format, client->id, ## args)
enum {
LISTEN_BACKLOG = 5,
IDLE_TIMEOUT_S = 120, // "two minutes" according to RFC1035 4.2.2
RESEND_DELAY_US = 500, // 0.0005 sec
};
struct tcp_client_s {
struct dns_server_tcp_s * d;
uint64_t id;
int sock;
struct sockaddr_storage raddr;
socklen_t addr_len;
char * input_buffer;
uint32_t input_buffer_size;
uint32_t input_buffer_used;
ev_io read_watcher;
ev_timer timer_watcher;
struct tcp_client_s * next;
} __attribute__((packed)) __attribute__((aligned(128)));
struct dns_server_tcp_s {
struct ev_loop *loop;
dns_req_received_cb cb;
void *cb_data;
int sock;
socklen_t addrlen;
ev_io accept_watcher;
uint64_t client_id;
uint16_t client_count;
uint16_t client_limit;
struct tcp_client_s * clients;
} __attribute__((packed)) __attribute__((aligned(128)));
static void remove_client(struct tcp_client_s * client) {
dns_server_tcp_t *d = client->d;
DLOG_CLIENT("Removing client, socket %d", client->sock);
if (d->client_count == d->client_limit) {
ev_io_start(d->loop, &d->accept_watcher); // continue accepting new client connections
}
d->client_count--;
ev_io_stop(d->loop, &client->read_watcher);
ev_timer_stop(d->loop, &client->timer_watcher);
free(client->input_buffer);
close(client->sock);
if (d->clients == client) {
d->clients = client->next;
}
else {
for (struct tcp_client_s * cur = d->clients; cur != NULL; cur = cur->next) {
if (cur->next == client) {
cur->next = client->next;
break;
}
}
}
free(client);
}
static int get_dns_request(struct tcp_client_s *client,
char ** dns_req, uint16_t * req_size) {
// check if whole request is available
*req_size = ntohs(*((uint16_t*)client->input_buffer));
uint16_t data_size = sizeof(uint16_t) + *req_size;
if (data_size > client->input_buffer_used) {
return 0; // Partial request
}
// copy whole request
*dns_req = (char *)malloc(*req_size); // To free buffer after https request is complete.
if (*dns_req == NULL) {
FLOG_CLIENT("Out of mem");
}
memcpy(*dns_req, client->input_buffer + sizeof(uint16_t), *req_size);
// move down data of next request(s) if any
client->input_buffer_used -= data_size;
memmove(client->input_buffer, client->input_buffer + data_size, client->input_buffer_used);
return 1;
}
static void read_cb(struct ev_loop __attribute__((unused)) *loop,
ev_io *w, int __attribute__((unused)) revents) {
struct tcp_client_s *client = (struct tcp_client_s *)w->data;
dns_server_tcp_t *d = client->d;
// Receive data
char buf[DNS_REQUEST_BUFFER_SIZE]; // if there would be more data, callback will be called again
ssize_t len = recv(w->fd, buf, DNS_REQUEST_BUFFER_SIZE, 0);
if (len <= 0) {
if (len == 0 || errno == ECONNRESET) {
DLOG_CLIENT("Connection closed");
} else if (errno == EAGAIN || errno == EWOULDBLOCK) {
return;
} else {
WLOG_CLIENT("Read error: %s", strerror(errno));
}
remove_client(client);
return;
}
// Append data into input buffer
const uint32_t free_space = client->input_buffer_size - client->input_buffer_used;
const uint32_t needed_space = client->input_buffer_used + (uint32_t)len;
DLOG_CLIENT("Received %d byte, free: %u", len, free_space);
if (free_space < len) {
for (client->input_buffer_size = 64; // lower value does not make much sense
client->input_buffer_size < needed_space;
client->input_buffer_size *= 2) {
if (client->input_buffer_size > 2*UINT16_MAX) {
FLOG_CLIENT("Unrealistic input buffer size: %u", client->input_buffer_size);
}
}
DLOG_CLIENT("Resize input buffer to %u", client->input_buffer_size);
client->input_buffer = (char *) realloc((void*) client->input_buffer, // NOLINT(bugprone-suspicious-realloc-usage) if realloc fails, program stops
client->input_buffer_size);
if (client->input_buffer == NULL) {
FLOG_CLIENT("Out of mem");
}
}
memcpy(client->input_buffer + client->input_buffer_used, buf, (size_t)len);
client->input_buffer_used = needed_space;
// Split requests
char *dns_req = NULL;
uint16_t req_size = 0;
uint8_t request_received = 0;
while (get_dns_request(client, &dns_req, &req_size)) {
if (req_size < DNS_HEADER_LENGTH) {
WLOG_CLIENT("Malformed request received, too short: %u", req_size);
free(dns_req);
remove_client(client);
return;
}
d->cb(d, 1, d->cb_data, (struct sockaddr*)&client->raddr, dns_req, req_size);
request_received = 1;
}
if (request_received) {
ev_timer_again(d->loop, &client->timer_watcher);
}
}
static void timer_cb(struct ev_loop __attribute__((unused)) *loop,
ev_timer *w, int __attribute__((unused)) revents) {
struct tcp_client_s *client = (struct tcp_client_s *)w->data;
DLOG_CLIENT("TCP client timeouted");
remove_client(client);
}
static void accept_cb(struct ev_loop __attribute__((unused)) *loop,
ev_io *w, int __attribute__((unused)) revents) {
dns_server_tcp_t *d = (dns_server_tcp_t *)w->data;
struct sockaddr_storage client_addr;
socklen_t client_addr_len = sizeof(client_addr);
int client_sock = accept4(w->fd, (struct sockaddr *)&client_addr,
&client_addr_len, SOCK_NONBLOCK);
if (client_sock == -1 && errno != EAGAIN && errno != EWOULDBLOCK) {
ELOG("Failed to accept TCP client: %s", strerror(errno));
return;
}
d->client_id++;
d->client_count++;
if (d->client_count == d->client_limit) {
ev_io_stop(d->loop, &d->accept_watcher); // suspend accepting new client connections
}
struct tcp_client_s *client = (struct tcp_client_s *)calloc(1, sizeof(struct tcp_client_s));
if (client == NULL) {
FLOG("Out of mem");
}
client->d = d;
client->id = d->client_id;
client->sock = client_sock;
memcpy(&client->raddr, &client_addr, client_addr_len);
client->addr_len = client_addr_len;
client->input_buffer = NULL;
client->next = d->clients;
d->clients = client;
ev_io_init(&client->read_watcher, read_cb, client->sock, EV_READ);
client->read_watcher.data = client;
ev_io_start(d->loop, &client->read_watcher);
ev_init(&client->timer_watcher, timer_cb);
client->timer_watcher.repeat = IDLE_TIMEOUT_S;
client->timer_watcher.data = client;
ev_timer_again(d->loop, &client->timer_watcher);
DLOG_CLIENT("Accepted client %u of %u, socket %d", d->client_count, d->client_limit, client->sock);
}
// Creates and bind a listening non-blocking TCP socket for incoming requests.
static int get_tcp_listen_sock(struct addrinfo *listen_addrinfo) {
int sock = socket(listen_addrinfo->ai_family, SOCK_STREAM, 0);
if (sock < 0) {
FLOG("Error creating TCP socket: %s (%d)", strerror(errno), errno);
}
int yes = 1;
if (setsockopt(sock, SOL_SOCKET, SO_REUSEADDR, &yes, sizeof(yes)) == -1) {
ELOG("Reuse address failed: %s (%d)", strerror(errno), errno);
}
uint16_t port = 0;
char ipstr[INET6_ADDRSTRLEN];
if (listen_addrinfo->ai_family == AF_INET) {
port = ntohs(((struct sockaddr_in*) listen_addrinfo->ai_addr)->sin_port);
inet_ntop(AF_INET, &((struct sockaddr_in *)listen_addrinfo->ai_addr)->sin_addr, ipstr, sizeof(ipstr));
} else if (listen_addrinfo->ai_family == AF_INET6) {
port = ntohs(((struct sockaddr_in6*) listen_addrinfo->ai_addr)->sin6_port);
inet_ntop(AF_INET6, &((struct sockaddr_in6 *)listen_addrinfo->ai_addr)->sin6_addr, ipstr, sizeof(ipstr));
} else {
FLOG("Unknown address family: %d", listen_addrinfo->ai_family);
}
int res = bind(sock, listen_addrinfo->ai_addr, listen_addrinfo->ai_addrlen);
if (res < 0) {
FLOG("Error binding on %s:%d TCP: %s (%d)", ipstr, port,
strerror(errno), errno);
}
if (listen(sock, LISTEN_BACKLOG) == -1) {
FLOG("Error listening on %s:%d TCP: %s (%d)", ipstr, port,
strerror(errno), errno);
}
int flags = fcntl(sock, F_GETFL, 0);
if (flags == -1) {
FLOG("Error getting TCP socket flags on %s:%d: %s (%d)", ipstr, port,
strerror(errno), errno);
}
if (fcntl(sock, F_SETFL, flags | O_NONBLOCK) == -1) {
FLOG("Error setting TCP socket to non-blocking on %s:%d: %s (%d)", ipstr, port,
strerror(errno), errno);
}
ILOG("Listening on %s:%d TCP", ipstr, port);
return sock;
}
dns_server_tcp_t * dns_server_tcp_create(
struct ev_loop *loop, struct addrinfo *listen_addrinfo,
dns_req_received_cb cb, void *data, uint16_t tcp_client_limit) {
dns_server_tcp_t * d = (dns_server_tcp_t *) malloc(sizeof(dns_server_tcp_t));
if (d == NULL) {
FLOG("Out of mem");
}
d->loop = loop;
d->cb = cb;
d->cb_data = data;
d->sock = get_tcp_listen_sock(listen_addrinfo);
d->addrlen = listen_addrinfo->ai_addrlen;
d->client_id = 0;
d->client_count = 0;
d->client_limit = tcp_client_limit;
d->clients = NULL;
ev_io_init(&d->accept_watcher, accept_cb, d->sock, EV_READ);
d->accept_watcher.data = d;
ev_io_start(d->loop, &d->accept_watcher);
return d;
}
void dns_server_tcp_respond(dns_server_tcp_t *d,
struct sockaddr *raddr, char *resp, size_t resp_len)
{
if (resp_len < DNS_HEADER_LENGTH || resp_len > UINT16_MAX) {
WLOG("Malformed response received, invalid length: %u", resp_len);
return;
}
// find client data
struct tcp_client_s *client = NULL;
for (struct tcp_client_s * cur = d->clients; cur != NULL; cur = cur->next) {
if (memcmp(raddr, &(cur->raddr), cur->addr_len) == 0) {
client = cur;
break;
}
}
if (client == NULL) {
uint16_t response_id = ntohs(*((uint16_t*)resp));
WLOG("Could not find client, can not send DNS response: %04hX", response_id);
return;
}
DLOG_CLIENT("Sending %u bytes", resp_len);
// send length of response
uint16_t resp_size = htons((uint16_t)resp_len);
ssize_t len = send(client->sock, &resp_size, sizeof(uint16_t), MSG_MORE | MSG_NOSIGNAL);
if (len != sizeof(uint16_t)) {
WLOG_CLIENT("Send error: %s, len: %d", strerror(errno), len);
remove_client(client);
return;
}
// send the response
ssize_t sent = 0;
for (uint8_t i = 0; i < UINT8_MAX; ++i) // endless loop guard
{
len = send(client->sock, resp + sent, resp_len - (size_t)sent, MSG_NOSIGNAL);
if (len < 0) {
if (errno != EAGAIN && errno != EWOULDBLOCK) {
WLOG_CLIENT("Send error: %s", strerror(errno));
remove_client(client);
return;
}
continue;
}
sent += len;
if (sent == (ssize_t)resp_len) {
break;
}
usleep(RESEND_DELAY_US);
}
ev_timer_again(d->loop, &client->timer_watcher);
}
void dns_server_tcp_stop(dns_server_tcp_t *d) {
while (d->clients) {
remove_client(d->clients); //NOLINT(clang-analyzer-unix.Malloc) false use after free detection
}
ev_io_stop(d->loop, &d->accept_watcher);
}
void dns_server_tcp_cleanup(dns_server_tcp_t *d) {
close(d->sock);
}