You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

449 lines
15 KiB

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