b89f4be902e6bb155ee3fc9015d4e8bfd74456e9
[librecmc/librecmc-fossil.git] /
1 From 709646d59d96cb73a7e70347f37de9823e4e5f14 Mon Sep 17 00:00:00 2001
2 From: Leonid Evdokimov <leon@darkk.net.ru>
3 Date: Fri, 13 Apr 2012 01:57:23 +0400
4 Subject: [PATCH 03/12] Initial support for UDP + TPROXY redirection. No more
5  dest_ip in redudp.
6
7  * TPROXY requires Linux 2.6.29+ (see man 7 ip[1]).
8  * all redsocks code is running as root to bind to arbitrary port.
9  * Non-Linux and old-Linux builds are broken at the moment.
10
11 [1] http://www.kernel.org/doc/man-pages/online/pages/man7/ip.7.html
12 ---
13  dnstc.c  |   2 +-
14  redudp.c | 197 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++-----
15  redudp.h |   2 +
16  utils.c  |  43 +++++++++++++-
17  utils.h  |   2 +-
18  5 files changed, 227 insertions(+), 19 deletions(-)
19
20 diff --git a/dnstc.c b/dnstc.c
21 index 43881d8..5f9fedd 100644
22 --- a/dnstc.c
23 +++ b/dnstc.c
24 @@ -68,7 +68,7 @@ static void dnstc_pkt_from_client(int fd, short what, void *_arg)
25         ssize_t pktlen, outgoing;
26  
27         assert(fd == EVENT_FD(&self->listener));
28 -       pktlen = red_recv_udp_pkt(fd, buf.raw, sizeof(buf), &clientaddr);
29 +       pktlen = red_recv_udp_pkt(fd, buf.raw, sizeof(buf), &clientaddr, NULL);
30         if (pktlen == -1)
31                 return;
32  
33 diff --git a/redudp.c b/redudp.c
34 index 9516a50..262af3e 100644
35 --- a/redudp.c
36 +++ b/redudp.c
37 @@ -15,6 +15,7 @@
38   */
39  
40  #include <stdlib.h>
41 +#include <search.h>
42  #include <string.h>
43  #include <sys/types.h>
44  #include <sys/uio.h>
45 @@ -33,30 +34,157 @@
46  #include "redudp.h"
47  
48  #define redudp_log_error(client, prio, msg...) \
49 -       redsocks_log_write_plain(__FILE__, __LINE__, __func__, 0, &(client)->clientaddr, &(client)->instance->config.destaddr, prio, ## msg)
50 +       redsocks_log_write_plain(__FILE__, __LINE__, __func__, 0, &(client)->clientaddr, get_destaddr(client), prio, ## msg)
51  #define redudp_log_errno(client, prio, msg...) \
52 -       redsocks_log_write_plain(__FILE__, __LINE__, __func__, 1, &(client)->clientaddr, &(client)->instance->config.destaddr, prio, ## msg)
53 +       redsocks_log_write_plain(__FILE__, __LINE__, __func__, 1, &(client)->clientaddr, get_destaddr(client), prio, ## msg)
54  
55  static void redudp_pkt_from_socks(int fd, short what, void *_arg);
56  static void redudp_drop_client(redudp_client *client);
57  static void redudp_fini_instance(redudp_instance *instance);
58  static int redudp_fini();
59 +static int redudp_transparent(int fd);
60  
61  typedef struct redudp_expected_assoc_reply_t {
62         socks5_reply h;
63         socks5_addr_ipv4 ip;
64  } PACKED redudp_expected_assoc_reply;
65  
66 +struct bound_udp4_key {
67 +       struct in_addr sin_addr;
68 +       uint16_t       sin_port;
69 +};
70 +
71 +struct bound_udp4 {
72 +       struct bound_udp4_key key;
73 +       int ref;
74 +       int fd;
75 +};
76 +
77  /***********************************************************************
78   * Helpers
79   */
80 +// TODO: separate binding to privileged process (this operation requires uid-0)
81 +static void* root_bound_udp4 = NULL; // to avoid two binds to same IP:port
82 +
83 +static int bound_udp4_cmp(const void *a, const void *b)
84 +{
85 +       return memcmp(a, b, sizeof(struct bound_udp4_key));
86 +}
87 +
88 +static void bound_udp4_mkkey(struct bound_udp4_key *key, const struct sockaddr_in *addr)
89 +{
90 +       memset(key, 0, sizeof(*key));
91 +       key->sin_addr = addr->sin_addr;
92 +       key->sin_port = addr->sin_port;
93 +}
94 +
95 +static int bound_udp4_get(const struct sockaddr_in *addr)
96 +{
97 +       struct bound_udp4_key key;
98 +       struct bound_udp4 *node, **pnode;
99 +
100 +       bound_udp4_mkkey(&key, addr);
101 +       // I assume, that memory allocation for lookup is awful, so I use
102 +       // tfind/tsearch pair instead of tsearch/check-result.
103 +       pnode = tfind(&key, &root_bound_udp4, bound_udp4_cmp);
104 +       if (pnode) {
105 +               assert((*pnode)->ref > 0);
106 +               (*pnode)->ref++;
107 +               return (*pnode)->fd;
108 +       }
109 +
110 +       node = calloc(1, sizeof(*node));
111 +       if (!node) {
112 +               log_errno(LOG_ERR, "calloc");
113 +               goto fail;
114 +       }
115 +
116 +       node->key = key;
117 +       node->ref = 1;
118 +       node->fd = socket(AF_INET, SOCK_DGRAM, 0);
119 +       if (node->fd == -1) {
120 +               log_errno(LOG_ERR, "socket");
121 +               goto fail;
122 +       }
123 +
124 +       if (0 != redudp_transparent(node->fd))
125 +               goto fail;
126 +
127 +       if (0 != bind(node->fd, (struct sockaddr*)addr, sizeof(*addr))) {
128 +               log_errno(LOG_ERR, "bind");
129 +               goto fail;
130 +       }
131 +
132 +       pnode = tsearch(node, &root_bound_udp4, bound_udp4_cmp);
133 +       if (!pnode) {
134 +               log_errno(LOG_ERR, "tsearch(%p) == %p", node, pnode);
135 +               goto fail;
136 +       }
137 +       assert(node == *pnode);
138 +
139 +       return node->fd;
140 +
141 +fail:
142 +       if (node) {
143 +               if (node->fd != -1)
144 +                       redsocks_close(node->fd);
145 +               free(node);
146 +       }
147 +       return -1;
148 +}
149 +
150 +static void bound_udp4_put(const struct sockaddr_in *addr)
151 +{
152 +       struct bound_udp4_key key;
153 +       struct bound_udp4 **pnode, *node;
154 +       void *parent;
155 +
156 +       bound_udp4_mkkey(&key, addr);
157 +       pnode = tfind(&key, &root_bound_udp4, bound_udp4_cmp);
158 +       assert(pnode && (*pnode)->ref > 0);
159 +
160 +       node = *pnode;
161 +
162 +       node->ref--;
163 +       if (node->ref)
164 +               return;
165 +
166 +       parent = tdelete(node, &root_bound_udp4, bound_udp4_cmp);
167 +       assert(parent);
168 +
169 +       redsocks_close(node->fd); // expanding `pnode` to avoid use after free
170 +       free(node);
171 +}
172 +
173 +static int redudp_transparent(int fd)
174 +{
175 +       int on = 1;
176 +       int error = setsockopt(fd, SOL_IP, IP_TRANSPARENT, &on, sizeof(on));
177 +       if (error)
178 +               log_errno(LOG_ERR, "setsockopt(..., SOL_IP, IP_TRANSPARENT)");
179 +       return error;
180 +}
181 +
182 +static int do_tproxy(redudp_instance* instance)
183 +{
184 +       return instance->config.destaddr.sin_addr.s_addr == 0;
185 +}
186 +
187 +static struct sockaddr_in* get_destaddr(redudp_client *client)
188 +{
189 +       if (do_tproxy(client->instance))
190 +               return &client->destaddr;
191 +       else
192 +               return &client->instance->config.destaddr;
193 +}
194 +
195  static void redudp_fill_preamble(socks5_udp_preabmle *preamble, redudp_client *client)
196  {
197         preamble->reserved = 0;
198         preamble->frag_no = 0; /* fragmentation is not supported */
199         preamble->addrtype = socks5_addrtype_ipv4;
200 -       preamble->ip.addr = client->instance->config.destaddr.sin_addr.s_addr;
201 -       preamble->ip.port = client->instance->config.destaddr.sin_port;
202 +       preamble->ip.addr = get_destaddr(client)->sin_addr.s_addr;
203 +       preamble->ip.port = get_destaddr(client)->sin_port;
204  }
205  
206  static struct evbuffer* socks5_mkmethods_plain_wrapper(void *p)
207 @@ -104,6 +232,8 @@ static void redudp_drop_client(redudp_client *client)
208                         redudp_log_errno(client, LOG_ERR, "event_del");
209                 redsocks_close(fd);
210         }
211 +       if (client->sender_fd != -1)
212 +               bound_udp4_put(&client->destaddr);
213         list_for_each_entry_safe(q, tmp, &client->queue, list) {
214                 list_del(&q->list);
215                 free(q);
216 @@ -344,7 +474,8 @@ static void redudp_relay_connected(struct bufferevent *buffev, void *_arg)
217         redudp_client *client = _arg;
218         int do_password = socks5_is_valid_cred(client->instance->config.login, client->instance->config.password);
219         int error;
220 -       redudp_log_error(client, LOG_DEBUG, "<trace>");
221 +       char relayaddr_str[RED_INET_ADDRSTRLEN];
222 +       redudp_log_error(client, LOG_DEBUG, "via %s", red_inet_ntop(&client->instance->config.relayaddr, relayaddr_str, sizeof(relayaddr_str)));
223  
224         if (!red_is_socket_connected_ok(buffev)) {
225                 redudp_log_errno(client, LOG_NOTICE, "red_is_socket_connected_ok");
226 @@ -382,7 +513,7 @@ static void redudp_timeout(int fd, short what, void *_arg)
227         redudp_drop_client(client);
228  }
229  
230 -static void redudp_first_pkt_from_client(redudp_instance *self, struct sockaddr_in *clientaddr, char *buf, size_t pktlen)
231 +static void redudp_first_pkt_from_client(redudp_instance *self, struct sockaddr_in *clientaddr, struct sockaddr_in *destaddr, char *buf, size_t pktlen)
232  {
233         redudp_client *client = calloc(1, sizeof(*client));
234  
235 @@ -395,9 +526,13 @@ static void redudp_first_pkt_from_client(redudp_instance *self, struct sockaddr_
236         INIT_LIST_HEAD(&client->queue);
237         client->instance = self;
238         memcpy(&client->clientaddr, clientaddr, sizeof(*clientaddr));
239 +       if (destaddr)
240 +               memcpy(&client->destaddr, destaddr, sizeof(client->destaddr));
241         evtimer_set(&client->timeout, redudp_timeout, client);
242         // XXX: self->relay_ss->init(client);
243  
244 +       client->sender_fd = -1; // it's postponed until socks-server replies to avoid trivial DoS
245 +
246         client->relay = red_connect_relay(&client->instance->config.relayaddr,
247                                           redudp_relay_connected, redudp_relay_error, client);
248         if (!client->relay)
249 @@ -431,7 +566,7 @@ static void redudp_pkt_from_socks(int fd, short what, void *_arg)
250  
251         assert(fd == EVENT_FD(&client->udprelay));
252  
253 -       pktlen = red_recv_udp_pkt(fd, pkt.buf, sizeof(pkt.buf), &udprelayaddr);
254 +       pktlen = red_recv_udp_pkt(fd, pkt.buf, sizeof(pkt.buf), &udprelayaddr, NULL);
255         if (pktlen == -1)
256                 return;
257  
258 @@ -455,8 +590,8 @@ static void redudp_pkt_from_socks(int fd, short what, void *_arg)
259                 return;
260         }
261  
262 -       if (pkt.header.ip.port != client->instance->config.destaddr.sin_port ||
263 -           pkt.header.ip.addr != client->instance->config.destaddr.sin_addr.s_addr)
264 +       if (pkt.header.ip.port != get_destaddr(client)->sin_port ||
265 +           pkt.header.ip.addr != get_destaddr(client)->sin_addr.s_addr)
266         {
267                 char buf[RED_INET_ADDRSTRLEN];
268                 struct sockaddr_in pktaddr = {
269 @@ -472,8 +607,18 @@ static void redudp_pkt_from_socks(int fd, short what, void *_arg)
270         redsocks_time(&client->last_relay_event);
271         redudp_bump_timeout(client);
272  
273 +       if (do_tproxy(client->instance) && client->sender_fd == -1) {
274 +               client->sender_fd = bound_udp4_get(&client->destaddr);
275 +               if (client->sender_fd == -1) {
276 +                       redudp_log_error(client, LOG_WARNING, "bound_udp4_get failure");
277 +                       return;
278 +               }
279 +       }
280 +
281         fwdlen = pktlen - sizeof(pkt.header);
282 -       outgoing = sendto(EVENT_FD(&client->instance->listener),
283 +       outgoing = sendto(do_tproxy(client->instance)
284 +                             ? client->sender_fd
285 +                             : EVENT_FD(&client->instance->listener),
286                           pkt.buf + sizeof(pkt.header), fwdlen, 0,
287                           (struct sockaddr*)&client->clientaddr, sizeof(client->clientaddr));
288         if (outgoing != fwdlen) {
289 @@ -486,18 +631,21 @@ static void redudp_pkt_from_socks(int fd, short what, void *_arg)
290  static void redudp_pkt_from_client(int fd, short what, void *_arg)
291  {
292         redudp_instance *self = _arg;
293 -       struct sockaddr_in clientaddr;
294 +       struct sockaddr_in clientaddr, destaddr, *pdestaddr;
295         char buf[0xFFFF]; // UDP packet can't be larger then that
296         ssize_t pktlen;
297         redudp_client *tmp, *client = NULL;
298  
299 +       pdestaddr = do_tproxy(self) ? &destaddr : NULL;
300 +
301         assert(fd == EVENT_FD(&self->listener));
302 -       pktlen = red_recv_udp_pkt(fd, buf, sizeof(buf), &clientaddr);
303 +       pktlen = red_recv_udp_pkt(fd, buf, sizeof(buf), &clientaddr, pdestaddr);
304         if (pktlen == -1)
305                 return;
306  
307         // TODO: this lookup may be SLOOOOOW.
308         list_for_each_entry(tmp, &self->clients, list) {
309 +               // TODO: check destaddr
310                 if (0 == memcmp(&clientaddr, &tmp->clientaddr, sizeof(clientaddr))) {
311                         client = tmp;
312                         break;
313 @@ -515,7 +663,7 @@ static void redudp_pkt_from_client(int fd, short what, void *_arg)
314                 }
315         }
316         else {
317 -               redudp_first_pkt_from_client(self, &clientaddr, buf, pktlen);
318 +               redudp_first_pkt_from_client(self, &clientaddr, pdestaddr, buf, pktlen);
319         }
320  }
321  
322 @@ -554,7 +702,6 @@ static int redudp_onenter(parser_section *section)
323         instance->config.relayaddr.sin_family = AF_INET;
324         instance->config.relayaddr.sin_addr.s_addr = htonl(INADDR_LOOPBACK);
325         instance->config.destaddr.sin_family = AF_INET;
326 -       instance->config.destaddr.sin_addr.s_addr = htonl(INADDR_LOOPBACK);
327         instance->config.max_pktqueue = 5;
328         instance->config.udp_timeout = 30;
329         instance->config.udp_timeout_stream = 180;
330 @@ -614,6 +761,28 @@ static int redudp_init_instance(redudp_instance *instance)
331                 goto fail;
332         }
333  
334 +       if (do_tproxy(instance)) {
335 +               int on = 1;
336 +               char buf[RED_INET_ADDRSTRLEN];
337 +               // iptables TPROXY target does not send packets to non-transparent sockets
338 +               if (0 != redudp_transparent(fd))
339 +                       goto fail;
340 +
341 +               error = setsockopt(fd, SOL_IP, IP_RECVORIGDSTADDR, &on, sizeof(on));
342 +               if (error) {
343 +                       log_errno(LOG_ERR, "setsockopt(listener, SOL_IP, IP_RECVORIGDSTADDR)");
344 +                       goto fail;
345 +               }
346 +
347 +               log_error(LOG_DEBUG, "redudp @ %s: TPROXY", red_inet_ntop(&instance->config.bindaddr, buf, sizeof(buf)));
348 +       }
349 +       else {
350 +               char buf1[RED_INET_ADDRSTRLEN], buf2[RED_INET_ADDRSTRLEN];
351 +               log_error(LOG_DEBUG, "redudp @ %s: destaddr=%s",
352 +                       red_inet_ntop(&instance->config.bindaddr, buf1, sizeof(buf1)),
353 +                       red_inet_ntop(&instance->config.destaddr, buf2, sizeof(buf2)));
354 +       }
355 +
356         error = bind(fd, (struct sockaddr*)&instance->config.bindaddr, sizeof(instance->config.bindaddr));
357         if (error) {
358                 log_errno(LOG_ERR, "bind");
359 diff --git a/redudp.h b/redudp.h
360 index 308bd33..3f1d9d1 100644
361 --- a/redudp.h
362 +++ b/redudp.h
363 @@ -24,6 +24,8 @@ typedef struct redudp_client_t {
364         list_head           list;
365         redudp_instance    *instance;
366         struct sockaddr_in  clientaddr;
367 +       struct sockaddr_in  destaddr;
368 +       int                 sender_fd; // shared between several clients socket (bound to `destaddr`)
369         struct event        timeout;
370         struct bufferevent *relay;
371         struct event        udprelay;
372 diff --git a/utils.c b/utils.c
373 index 6e1f3af..afdeea8 100644
374 --- a/utils.c
375 +++ b/utils.c
376 @@ -26,17 +26,54 @@
377  #include "utils.h"
378  #include "redsocks.h" // for redsocks_close
379  
380 -int red_recv_udp_pkt(int fd, char *buf, size_t buflen, struct sockaddr_in *inaddr)
381 +int red_recv_udp_pkt(int fd, char *buf, size_t buflen, struct sockaddr_in *inaddr, struct sockaddr_in *toaddr)
382  {
383         socklen_t addrlen = sizeof(*inaddr);
384         ssize_t pktlen;
385 -
386 -       pktlen = recvfrom(fd, buf, buflen, 0, (struct sockaddr*)inaddr, &addrlen);
387 +       struct msghdr msg;
388 +       struct iovec io;
389 +       char control[1024];
390 +
391 +       memset(&msg, 0, sizeof(msg));
392 +       msg.msg_name = inaddr;
393 +       msg.msg_namelen = sizeof(*inaddr);
394 +       msg.msg_iov = &io;
395 +       msg.msg_iovlen = 1;
396 +       msg.msg_control = control;
397 +       msg.msg_controllen = sizeof(control);
398 +       io.iov_base = buf;
399 +       io.iov_len = buflen;
400 +
401 +       pktlen = recvmsg(fd, &msg, 0);
402         if (pktlen == -1) {
403                 log_errno(LOG_WARNING, "recvfrom");
404                 return -1;
405         }
406  
407 +       if (toaddr) {
408 +               memset(toaddr, 0, sizeof(*toaddr));
409 +               for (struct cmsghdr* cmsg = CMSG_FIRSTHDR(&msg); cmsg; cmsg = CMSG_NXTHDR(&msg, cmsg)) {
410 +                       if (
411 +                               cmsg->cmsg_level == SOL_IP &&
412 +                               cmsg->cmsg_type == IP_ORIGDSTADDR &&
413 +                               cmsg->cmsg_len >= CMSG_LEN(sizeof(*toaddr))
414 +                       ) {
415 +                               struct sockaddr_in* cmsgaddr = (struct sockaddr_in*)CMSG_DATA(cmsg);
416 +                               char buf[RED_INET_ADDRSTRLEN];
417 +                               log_error(LOG_DEBUG, "IP_ORIGDSTADDR: %s", red_inet_ntop(cmsgaddr, buf, sizeof(buf)));
418 +                               memcpy(toaddr, cmsgaddr, sizeof(*toaddr));
419 +                       }
420 +                       else {
421 +                               log_error(LOG_WARNING, "unexepcted cmsg (level,type) = (%d,%d)",
422 +                                       cmsg->cmsg_level, cmsg->cmsg_type);
423 +                       }
424 +               }
425 +               if (toaddr->sin_family != AF_INET) {
426 +                       log_error(LOG_WARNING, "(SOL_IP, IP_ORIGDSTADDR) not found");
427 +                       return -1;
428 +               }
429 +       }
430 +
431         if (addrlen != sizeof(*inaddr)) {
432                 log_error(LOG_WARNING, "unexpected address length %u instead of %zu", addrlen, sizeof(*inaddr));
433                 return -1;
434 diff --git a/utils.h b/utils.h
435 index d3af00f..c2277e9 100644
436 --- a/utils.h
437 +++ b/utils.h
438 @@ -44,7 +44,7 @@ char *redsocks_evbuffer_readline(struct evbuffer *buf);
439  struct bufferevent* red_connect_relay(struct sockaddr_in *addr, evbuffercb writecb, everrorcb errorcb, void *cbarg);
440  int red_socket_geterrno(struct bufferevent *buffev);
441  int red_is_socket_connected_ok(struct bufferevent *buffev);
442 -int red_recv_udp_pkt(int fd, char *buf, size_t buflen, struct sockaddr_in *inaddr);
443 +int red_recv_udp_pkt(int fd, char *buf, size_t buflen, struct sockaddr_in *fromaddr, struct sockaddr_in *toaddr);
444  
445  int fcntl_nonblock(int fd);
446  
447 -- 
448 1.9.1
449