├── README.md └── src ├── .gitignore ├── Makefile ├── no-epoll.h ├── tcpfwd.c └── udpfwd.c /README.md: -------------------------------------------------------------------------------- 1 | portfwd 2 | ======= 3 | 4 | User-space TCP/UDP port forwarding services 5 | 6 | ## Summary 7 | This project contains two applications: tcpfwd, udpfwd, which are for TCP and UDP port forwarding literally. 8 | Written in pure C. 9 | 10 | ## Usage ## 11 | 12 | tcpfwd|udpfwd [-d] [-o] 13 | 14 | Options: 15 | -d run in background 16 | -o accept IPv6 connections only for IPv6 listener 17 | -p write PID to file 18 | 19 | ## Examples 20 | 21 | ##### Map local TCP port 1022 to 192.168.1.77:22 22 | 23 | tcpfwd 0.0.0.0:1022 192.168.1.77:22 # allow access from all hosts 24 | tcpfwd 127.0.0.1:1022 192.168.1.77:22 # only allow localhost 25 | tcpfwd [::]:1022 192.168.1.77:22 # allow access to port 1022 via both IPv4 and IPv6 26 | 27 | ##### Map local UDP port 53 to 8.8.8.8:53 28 | 29 | udpfwd 0.0.0.0:53 8.8.8.8:53 30 | udpfwd [::]:53 8.8.8.8:53 31 | 32 | ##### IPv4-IPv6 transforming 33 | 34 | udpfwd [::]:1701 localhost:1701 # add IPv6 support for a local L2TP service 35 | tcpfwd 0.0.0.0:80 [2001:db8:3::2]:80 # enable IPv4 access for an IPv6-only web service 36 | -------------------------------------------------------------------------------- /src/.gitignore: -------------------------------------------------------------------------------- 1 | *.o 2 | tcpfwd 3 | udpfwd 4 | -------------------------------------------------------------------------------- /src/Makefile: -------------------------------------------------------------------------------- 1 | CC ?= $(CROSS_COMPILE)gcc 2 | PREFIX ?= /usr/local 3 | # CFLAGS += -g 4 | 5 | ifeq ($(shell uname), Linux) 6 | #LDFLAGS += -levent 7 | else 8 | HEADERS += no-epoll.h 9 | endif 10 | 11 | all: tcpfwd udpfwd 12 | 13 | tcpfwd: tcpfwd.o 14 | $(CC) -o $@ $^ $(LDFLAGS) 15 | 16 | udpfwd: udpfwd.o 17 | $(CC) -o $@ $^ $(LDFLAGS) 18 | 19 | %.o: %.c $(HEADERS) 20 | $(CC) -c -Wall $(CFLAGS) -o $@ $< 21 | 22 | install: all 23 | mkdir -p $(PREFIX)/bin 24 | cp -vf tcpfwd udpfwd $(PREFIX)/bin/ 25 | 26 | clean: 27 | rm -f tcpfwd udpfwd *.o 28 | 29 | -------------------------------------------------------------------------------- /src/no-epoll.h: -------------------------------------------------------------------------------- 1 | #ifndef __NO_EPOLL_H 2 | #define __NO_EPOLL_H 3 | 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | 10 | /* NOTICE: To make sure being included once in a single program. */ 11 | int build_error_on_linking = 0; 12 | 13 | typedef union epoll_data { 14 | void *ptr; 15 | int fd; 16 | uint32_t u32; 17 | uint64_t u64; 18 | } epoll_data_t; 19 | 20 | struct epoll_event { 21 | uint32_t events; /* epoll events */ 22 | epoll_data_t data; /* user data variable */ 23 | }; 24 | 25 | 26 | #define EPOLLIN 0x001 27 | #define EPOLLOUT 0x004 28 | 29 | #define EPOLL_CTL_ADD 1 30 | #define EPOLL_CTL_DEL 2 31 | #define EPOLL_CTL_MOD 3 32 | 33 | struct pseudo_epoll_handle { 34 | int allocated; 35 | fd_set rset; 36 | fd_set wset; 37 | int max_fd; 38 | struct epoll_event events[FD_SETSIZE]; 39 | }; 40 | 41 | #define PSEUDO_EPOLL_LIST_SIZE 4 42 | static struct pseudo_epoll_handle pseudo_epolls[PSEUDO_EPOLL_LIST_SIZE]; 43 | 44 | static int epoll_create(int size) 45 | { 46 | int i; 47 | 48 | for (i = 0; i < PSEUDO_EPOLL_LIST_SIZE; i++) { 49 | struct pseudo_epoll_handle *eh = &pseudo_epolls[i]; 50 | if (!eh->allocated) { 51 | eh->allocated = 1; 52 | FD_ZERO(&eh->rset); 53 | FD_ZERO(&eh->wset); 54 | eh->max_fd = -1; 55 | return i; 56 | } 57 | } 58 | 59 | return -EINVAL; 60 | } 61 | 62 | static int epoll_ctl(int epfd, int op, int fd, struct epoll_event *event) 63 | { 64 | struct pseudo_epoll_handle *eh = &pseudo_epolls[epfd]; 65 | 66 | assert(fd < FD_SETSIZE); 67 | 68 | switch (op) { 69 | case EPOLL_CTL_ADD: 70 | case EPOLL_CTL_MOD: 71 | assert((event->events & ~(EPOLLIN | EPOLLOUT)) == 0); 72 | FD_CLR(fd, &eh->rset); 73 | FD_CLR(fd, &eh->wset); 74 | if ((event->events & EPOLLIN)) 75 | FD_SET(fd, &eh->rset); 76 | if ((event->events & EPOLLOUT)) 77 | FD_SET(fd, &eh->wset); 78 | if (event->events && fd > eh->max_fd) 79 | eh->max_fd = fd; 80 | eh->events[fd] = *event; 81 | break; 82 | case EPOLL_CTL_DEL: 83 | FD_CLR(fd, &eh->rset); 84 | FD_CLR(fd, &eh->wset); 85 | if (eh->max_fd == fd) { 86 | while (!FD_ISSET(eh->max_fd, &eh->rset) && ! FD_ISSET(eh->max_fd, &eh->wset)) 87 | eh->max_fd--; 88 | } 89 | break; 90 | default: 91 | fprintf(stderr, "*** Unsupported operation: %d\n", op); 92 | abort(); 93 | } 94 | 95 | return 0; 96 | } 97 | 98 | static int epoll_wait(int epfd, struct epoll_event *events, int maxevents, int timeout) 99 | { 100 | struct pseudo_epoll_handle *eh = &pseudo_epolls[epfd]; 101 | fd_set rset, wset; 102 | struct timeval timeo; 103 | int nr_events = 0, nfds, fd; 104 | 105 | /* Remove closed fds from the poll list. */ 106 | for (fd = 0; fd <= eh->max_fd; fd++) { 107 | if (fcntl(fd, F_GETFL) < 0 && errno == EBADF) { 108 | FD_CLR(fd, &eh->rset); 109 | FD_CLR(fd, &eh->wset); 110 | if (eh->max_fd == fd) { 111 | while (!FD_ISSET(eh->max_fd, &eh->rset) && ! FD_ISSET(eh->max_fd, &eh->wset)) 112 | eh->max_fd--; 113 | } 114 | } 115 | } 116 | 117 | rset = eh->rset; 118 | wset = eh->wset; 119 | 120 | if (timeout >= 0) { 121 | timeo.tv_sec = timeout / 1000; 122 | timeo.tv_usec = (timeout % 1000) * 1000; 123 | nfds = select(eh->max_fd + 1, &rset, &wset, NULL, &timeo); 124 | } else { 125 | nfds = select(eh->max_fd + 1, &rset, &wset, NULL, NULL); 126 | } 127 | 128 | if (nfds <= 0) 129 | return nfds; 130 | 131 | /* Copy all popped events to result. */ 132 | for (fd = 0; fd <= eh->max_fd; fd++) { 133 | uint32_t evs = 0; 134 | if (FD_ISSET(fd, &rset)) 135 | evs |= EPOLLIN; 136 | if (FD_ISSET(fd, &wset)) 137 | evs |= EPOLLOUT; 138 | if (evs) { 139 | events[nr_events] = eh->events[fd]; 140 | events[nr_events].events = evs; 141 | if (++nr_events >= maxevents) 142 | break; 143 | } 144 | } 145 | 146 | return nr_events; 147 | } 148 | 149 | #endif /* __NO_EPOLL_H */ 150 | -------------------------------------------------------------------------------- /src/tcpfwd.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | #include 11 | #include 12 | #include 13 | #include 14 | #include 15 | #include 16 | 17 | #ifdef __linux__ 18 | #include 19 | #include 20 | #else 21 | #define ERESTART 700 22 | #include "no-epoll.h" 23 | #endif 24 | 25 | typedef int bool; 26 | #define true 1 27 | #define false 0 28 | 29 | #define countof(arr) (sizeof(arr) / sizeof((arr)[0])) 30 | 31 | #define container_of(ptr, type, member) ({ \ 32 | const typeof(((type *)0)->member) * __mptr = (ptr); \ 33 | (type *)((char *)__mptr - offsetof(type, member)); }) 34 | 35 | struct sockaddr_inx { 36 | union { 37 | struct sockaddr sa; 38 | struct sockaddr_in in; 39 | struct sockaddr_in6 in6; 40 | }; 41 | }; 42 | 43 | #define port_of_sockaddr(s) ((s)->sa.sa_family == AF_INET6 ? \ 44 | (s)->in6.sin6_port : (s)->in.sin_port) 45 | #define addr_of_sockaddr(s) ((s)->sa.sa_family == AF_INET6 ? \ 46 | (void *)&(s)->in6.sin6_addr : (void *)&(s)->in.sin_addr) 47 | #define sizeof_sockaddr(s) ((s)->sa.sa_family == AF_INET6 ? \ 48 | sizeof((s)->in6) : sizeof((s)->in)) 49 | 50 | /* -=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=- */ 51 | 52 | static struct sockaddr_inx g_src_addr; 53 | static struct sockaddr_inx g_dst_addr; 54 | static bool g_base_addr_mode = false; 55 | static const char *g_pidfile; 56 | 57 | /* -=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=- */ 58 | 59 | static int do_daemonize(void) 60 | { 61 | int rc; 62 | 63 | if ((rc = fork()) < 0) { 64 | fprintf(stderr, "*** fork() error: %s.\n", strerror(errno)); 65 | return rc; 66 | } else if (rc > 0) { 67 | /* In parent process */ 68 | exit(0); 69 | } else { 70 | /* In child process */ 71 | int fd; 72 | setsid(); 73 | fd = open("/dev/null", O_RDONLY); 74 | dup2(fd, STDIN_FILENO); 75 | dup2(fd, STDOUT_FILENO); 76 | dup2(fd, STDERR_FILENO); 77 | if (fd > 2) 78 | close(fd); 79 | chdir("/tmp"); 80 | } 81 | return 0; 82 | } 83 | 84 | static void write_pidfile(const char *filepath) 85 | { 86 | FILE *fp; 87 | if (!(fp = fopen(filepath, "w"))) { 88 | fprintf(stderr, "*** fopen(%s): %s\n", filepath, strerror(errno)); 89 | exit(1); 90 | } 91 | fprintf(fp, "%d\n", (int)getpid()); 92 | fclose(fp); 93 | } 94 | 95 | static void set_nonblock(int sockfd) 96 | { 97 | fcntl(sockfd, F_SETFL, fcntl(sockfd, F_GETFD, 0) | O_NONBLOCK); 98 | } 99 | 100 | static int get_sockaddr_inx_pair(const char *pair, struct sockaddr_inx *sa) 101 | { 102 | struct addrinfo hints, *result; 103 | char host[51] = "", s_port[20] = ""; 104 | int port = 0, rc; 105 | 106 | if (sscanf(pair, "[%50[^]]]:%d", host, &port) == 2) { 107 | /* Quoted IP and port: [10.0.0.1]:10000 */ 108 | } else if (sscanf(pair, "%50[^:]:%d", host, &port) == 2) { 109 | /* Regular IP and port: 10.0.0.1:10000 */ 110 | } else { 111 | /** 112 | * A single port number, usually for local IPv4 listen address. 113 | * e.g., "10000" stands for "0.0.0.0:10000" 114 | */ 115 | const char *sp; 116 | for (sp = pair; *sp; sp++) { 117 | if (!(*sp >= '0' && *sp <= '9')) 118 | return -EINVAL; 119 | } 120 | sscanf(pair, "%d", &port); 121 | strcpy(host, "0.0.0.0"); 122 | } 123 | sprintf(s_port, "%d", port); 124 | if (port <= 0 || port > 65535) 125 | return -EINVAL; 126 | 127 | memset(&hints, 0, sizeof(struct addrinfo)); 128 | hints.ai_family = AF_UNSPEC; /* Allow IPv4 or IPv6 */ 129 | hints.ai_socktype = SOCK_STREAM; 130 | hints.ai_flags = AI_PASSIVE; /* For wildcard IP address */ 131 | hints.ai_protocol = 0; /* Any protocol */ 132 | hints.ai_canonname = NULL; 133 | hints.ai_addr = NULL; 134 | hints.ai_next = NULL; 135 | 136 | if ((rc = getaddrinfo(host, s_port, &hints, &result))) 137 | return -EAGAIN; 138 | 139 | /* Get the first resolution. */ 140 | memcpy(sa, result->ai_addr, result->ai_addrlen); 141 | 142 | freeaddrinfo(result); 143 | return 0; 144 | } 145 | 146 | /* -=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=- */ 147 | 148 | /* Status indicators of proxy sessions */ 149 | enum conn_state { 150 | S_INVALID, 151 | S_SERVER_CONNECTING, 152 | S_SERVER_CONNECTED, 153 | S_FORWARDING, 154 | S_CLOSING, 155 | }; 156 | 157 | enum ev_magic { 158 | EV_MAGIC_LISTENER = 0x1010, 159 | EV_MAGIC_CLIENT = 0x2020, 160 | EV_MAGIC_SERVER = 0x3030, 161 | }; 162 | 163 | struct buffer_info { 164 | char data[4096]; 165 | unsigned rpos; 166 | unsigned dlen; 167 | }; 168 | 169 | /** 170 | * Connection tracking information to indicate 171 | * a proxy session. 172 | */ 173 | struct proxy_conn { 174 | int cli_sock; 175 | int svr_sock; 176 | 177 | /** 178 | * The two fields are used when an epoll event occurs, 179 | * to know on which socket fd it is triggered client 180 | * or server. 181 | * ev.data.ptr = &ct.magic_client; 182 | */ 183 | int magic_client; 184 | int magic_server; 185 | unsigned short state; 186 | 187 | /* Remember the session addresses */ 188 | struct sockaddr_inx cli_addr; 189 | struct sockaddr_inx svr_addr; 190 | 191 | /* Buffers for both direction */ 192 | struct buffer_info request; 193 | struct buffer_info response; 194 | }; 195 | 196 | static inline struct proxy_conn *alloc_proxy_conn(void) 197 | { 198 | struct proxy_conn *conn; 199 | 200 | if (!(conn = malloc(sizeof(*conn)))) 201 | return NULL; 202 | memset(conn, 0x0, sizeof(*conn)); 203 | 204 | conn->cli_sock = -1; 205 | conn->svr_sock = -1; 206 | conn->magic_client = EV_MAGIC_CLIENT; 207 | conn->magic_server = EV_MAGIC_SERVER; 208 | conn->state = S_INVALID; 209 | 210 | return conn; 211 | } 212 | 213 | /** 214 | * Close both sockets of the connection and remove it 215 | * from the current ready list. 216 | */ 217 | static void release_proxy_conn(struct proxy_conn *conn, 218 | struct epoll_event *pending_evs, int pending_fds, int epfd) 219 | { 220 | int i; 221 | 222 | /** 223 | * Clear possible fd events that might belong to current 224 | * connection. The event must be cleared or an invalid 225 | * pointer might be accessed. 226 | */ 227 | for (i = 0; i < pending_fds; i++) { 228 | struct epoll_event *ev = &pending_evs[i]; 229 | if (ev->data.ptr == &conn->magic_client || 230 | ev->data.ptr == &conn->magic_server) { 231 | ev->data.ptr = NULL; 232 | break; 233 | } 234 | } 235 | 236 | if (epfd >= 0) { 237 | if (conn->cli_sock >= 0) { 238 | epoll_ctl(epfd, EPOLL_CTL_DEL, conn->cli_sock, NULL); 239 | close(conn->cli_sock); 240 | } 241 | if (conn->svr_sock >= 0) { 242 | epoll_ctl(epfd, EPOLL_CTL_DEL, conn->svr_sock, NULL); 243 | close(conn->svr_sock); 244 | } 245 | } 246 | 247 | free(conn); 248 | } 249 | 250 | static void init_new_conn_epoll_fds(struct proxy_conn *conn, int epfd) 251 | { 252 | struct epoll_event ev_cli, ev_svr; 253 | 254 | ev_cli.events = EPOLLIN; 255 | ev_cli.data.ptr = &conn->magic_client; 256 | 257 | ev_svr.events = EPOLLIN; 258 | ev_svr.data.ptr = &conn->magic_server; 259 | 260 | epoll_ctl(epfd, EPOLL_CTL_ADD, conn->cli_sock, &ev_cli); 261 | epoll_ctl(epfd, EPOLL_CTL_ADD, conn->svr_sock, &ev_svr); 262 | } 263 | 264 | /** 265 | * Add or activate the epoll fds according to the status of 266 | * 'conn'. Different conn->state and buffer status will 267 | * affect the polling behaviors. 268 | */ 269 | static void set_conn_epoll_fds(struct proxy_conn *conn, int epfd) 270 | { 271 | struct epoll_event ev_cli, ev_svr; 272 | 273 | ev_cli.events = 0; 274 | ev_cli.data.ptr = &conn->magic_client; 275 | 276 | ev_svr.events = 0; 277 | ev_svr.data.ptr = &conn->magic_server; 278 | 279 | switch(conn->state) { 280 | case S_FORWARDING: 281 | /* Connection established, data forwarding in progress. */ 282 | if (conn->request.dlen < sizeof(conn->request.data)) 283 | ev_cli.events |= EPOLLIN; 284 | if (conn->response.dlen < sizeof(conn->response.data)) 285 | ev_svr.events |= EPOLLIN; 286 | if (conn->request.rpos < conn->request.dlen) 287 | ev_svr.events |= EPOLLOUT; 288 | if (conn->response.rpos < conn->response.dlen) 289 | ev_cli.events |= EPOLLOUT; 290 | break; 291 | case S_SERVER_CONNECTING: 292 | /* Wait for the server connection to establish. */ 293 | if (conn->request.dlen < sizeof(conn->request.data)) 294 | ev_cli.events |= EPOLLIN; /* for detecting client close */ 295 | ev_svr.events |= EPOLLOUT; 296 | break; 297 | } 298 | 299 | /* Reset epoll status */ 300 | epoll_ctl(epfd, EPOLL_CTL_MOD, conn->cli_sock, &ev_cli); 301 | epoll_ctl(epfd, EPOLL_CTL_MOD, conn->svr_sock, &ev_svr); 302 | } 303 | 304 | static int handle_accept_new_connection(int sockfd, struct proxy_conn **conn_p) 305 | { 306 | int cli_sock, svr_sock; 307 | struct sockaddr_inx cli_addr; 308 | socklen_t cli_alen = sizeof(cli_addr); 309 | struct proxy_conn *conn = NULL; 310 | char s_addr1[50] = "", s_addr2[50] = ""; 311 | 312 | cli_sock = accept(sockfd, (struct sockaddr *)&cli_addr, &cli_alen); 313 | if (cli_sock < 0) { 314 | /* FIXME: error indicated, need to exit? */ 315 | syslog(LOG_ERR, "*** accept(): %s", strerror(errno)); 316 | goto err; 317 | } 318 | 319 | /* Client calls in, allocate session data for it. */ 320 | if (!(conn = alloc_proxy_conn())) { 321 | syslog(LOG_ERR, "*** alloc_proxy_conn(): %s", strerror(errno)); 322 | close(cli_sock); 323 | goto err; 324 | } 325 | conn->cli_sock = cli_sock; 326 | set_nonblock(conn->cli_sock); 327 | 328 | conn->cli_addr = cli_addr; 329 | 330 | /* Calculate address of the real server */ 331 | conn->svr_addr = g_dst_addr; 332 | #ifdef __linux__ 333 | if (g_base_addr_mode) { 334 | struct sockaddr_inx loc_addr, orig_dst; 335 | socklen_t loc_alen = sizeof(loc_addr), orig_alen = sizeof(orig_dst); 336 | int port_offset = 0; 337 | uint32_t *addr_pos = NULL; /* big-endian data */ 338 | 339 | memset(&loc_addr, 0x0, sizeof(loc_addr)); 340 | memset(&orig_dst, 0x0, sizeof(orig_dst)); 341 | if (getsockname(conn->cli_sock, (struct sockaddr *)&loc_addr, &loc_alen)) { 342 | syslog(LOG_ERR, "*** getsockname(): %s.", strerror(errno)); 343 | goto err; 344 | } 345 | if (getsockopt(conn->cli_sock, SOL_IP, SO_ORIGINAL_DST, &orig_dst, &orig_alen)) { 346 | syslog(LOG_ERR, "*** getsockopt(SO_ORIGINAL_DST): %s.", strerror(errno)); 347 | goto err; 348 | } 349 | 350 | if (conn->svr_addr.sa.sa_family == AF_INET) { 351 | addr_pos = (uint32_t *)&conn->svr_addr.in.sin_addr; 352 | } else { 353 | addr_pos = (uint32_t *)&conn->svr_addr.in6.sin6_addr.s6_addr32[3]; 354 | } 355 | port_offset = (int)(ntohs(port_of_sockaddr(&orig_dst)) - ntohs(port_of_sockaddr(&loc_addr))); 356 | 357 | *addr_pos = htonl(ntohl(*addr_pos) + port_offset); 358 | } 359 | #endif 360 | 361 | inet_ntop(conn->cli_addr.sa.sa_family, addr_of_sockaddr(&conn->cli_addr), 362 | s_addr1, sizeof(s_addr1)); 363 | inet_ntop(conn->svr_addr.sa.sa_family, addr_of_sockaddr(&conn->svr_addr), 364 | s_addr2, sizeof(s_addr2)); 365 | syslog(LOG_INFO, "New connection [%s]:%d -> [%s]:%d", 366 | s_addr1, ntohs(port_of_sockaddr(&conn->cli_addr)), 367 | s_addr2, ntohs(port_of_sockaddr(&conn->svr_addr))); 368 | 369 | /* Initiate the connection to server right now. */ 370 | if ((svr_sock = socket(g_dst_addr.sa.sa_family, SOCK_STREAM, 0)) < 0) { 371 | syslog(LOG_ERR, "*** socket(svr_sock): %s", strerror(errno)); 372 | goto err; 373 | } 374 | conn->svr_sock = svr_sock; 375 | set_nonblock(conn->svr_sock); 376 | 377 | if (connect(conn->svr_sock, (struct sockaddr *)&conn->svr_addr, 378 | sizeof_sockaddr(&conn->svr_addr)) == 0) { 379 | /* Connected, prepare for data forwarding. */ 380 | conn->state = S_SERVER_CONNECTED; 381 | *conn_p = conn; 382 | return 0; 383 | } else if (errno == EINPROGRESS) { 384 | /* OK, poll for the connection to complete or fail */ 385 | conn->state = S_SERVER_CONNECTING; 386 | *conn_p = conn; 387 | return -EAGAIN; 388 | } else { 389 | /* Error occurs, drop the session. */ 390 | syslog(LOG_WARNING, "Connection to [%s]:%d failed: %s", 391 | s_addr2, ntohs(port_of_sockaddr(&conn->svr_addr)), 392 | strerror(errno)); 393 | goto err; 394 | } 395 | 396 | err: 397 | /** 398 | * 'conn' has only been used among this function, 399 | * so don't need the caller to release anything 400 | */ 401 | if (conn) 402 | release_proxy_conn(conn, NULL, 0, -1); 403 | *conn_p = NULL; 404 | return 0; 405 | } 406 | 407 | static int handle_server_connecting(struct proxy_conn *conn, int efd) 408 | { 409 | char s_addr[50] = ""; 410 | 411 | if (efd == conn->svr_sock) { 412 | /* The connection has established or failed. */ 413 | int err = 0; 414 | socklen_t errlen = sizeof(err); 415 | 416 | if (getsockopt(conn->svr_sock, SOL_SOCKET, SO_ERROR, &err, &errlen) < 0 || err) { 417 | inet_ntop(conn->svr_addr.sa.sa_family, addr_of_sockaddr(&conn->svr_addr), 418 | s_addr, sizeof(s_addr)); 419 | syslog(LOG_WARNING, "Connection to [%s]:%d failed: %s", 420 | s_addr, ntohs(port_of_sockaddr(&conn->svr_addr)), 421 | strerror(err ? err : errno)); 422 | conn->state = S_CLOSING; 423 | return 0; 424 | } 425 | 426 | /* Connected, preparing for data forwarding. */ 427 | conn->state = S_SERVER_CONNECTED; 428 | return 0; 429 | } else { 430 | /* Received data early before server connection is OK */ 431 | struct buffer_info *rxb = &conn->request; 432 | int rc; 433 | 434 | if ((rc = recv(efd , rxb->data + rxb->dlen, 435 | sizeof(rxb->data) - rxb->dlen, 0)) <= 0) { 436 | inet_ntop(conn->cli_addr.sa.sa_family, addr_of_sockaddr(&conn->cli_addr), 437 | s_addr, sizeof(s_addr)); 438 | syslog(LOG_INFO, "Connection [%s]:%d closed during server handshake", 439 | s_addr, ntohs(port_of_sockaddr(&conn->cli_addr))); 440 | conn->state = S_CLOSING; 441 | return 0; 442 | } 443 | rxb->dlen += rc; 444 | 445 | return -EAGAIN; 446 | } 447 | } 448 | 449 | static int handle_server_connected(struct proxy_conn *conn, int efd) 450 | { 451 | conn->state = S_FORWARDING; 452 | return -EAGAIN; 453 | } 454 | 455 | static int handle_forwarding(struct proxy_conn *conn, int efd, int epfd, 456 | struct epoll_event *ev) 457 | { 458 | struct buffer_info *rxb, *txb; 459 | int noefd, rc; 460 | char s_addr[50] = ""; 461 | 462 | if (efd == conn->cli_sock) { 463 | rxb = &conn->request; 464 | txb = &conn->response; 465 | noefd = conn->svr_sock; 466 | } else { 467 | rxb = &conn->response; 468 | txb = &conn->request; 469 | noefd = conn->cli_sock; 470 | } 471 | 472 | if (ev->events & EPOLLIN) { 473 | if ((rc = recv(efd , rxb->data + rxb->dlen, 474 | sizeof(rxb->data) - rxb->dlen, 0)) <= 0) 475 | goto err; 476 | rxb->dlen += rc; 477 | /* Try if we can send it out */ 478 | if ((rc = send(noefd, rxb->data + rxb->rpos, rxb->dlen - rxb->rpos, 0)) > 0) { 479 | rxb->rpos += rc; 480 | /* Buffer consumed, empty it */ 481 | if (rxb->rpos >= rxb->dlen) 482 | rxb->rpos = rxb->dlen = 0; 483 | } 484 | } 485 | 486 | if (ev->events & EPOLLOUT) { 487 | if ((rc = send(efd, txb->data + txb->rpos, txb->dlen - txb->rpos, 0)) <= 0) 488 | goto err; 489 | txb->rpos += rc; 490 | /* Buffer consumed, empty it */ 491 | if (txb->rpos >= txb->dlen) 492 | txb->rpos = txb->dlen = 0; 493 | } 494 | 495 | /* I/O not ready, handle in next event. */ 496 | return -EAGAIN; 497 | 498 | err: 499 | inet_ntop(conn->cli_addr.sa.sa_family, addr_of_sockaddr(&conn->cli_addr), s_addr, sizeof(s_addr)); 500 | syslog(LOG_INFO, "Connection [%s]:%d closed", s_addr, ntohs(port_of_sockaddr(&conn->cli_addr))); 501 | conn->state = S_CLOSING; 502 | return 0; 503 | } 504 | 505 | /* -=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=- */ 506 | 507 | static void show_help(int argc, char *argv[]) 508 | { 509 | printf("Userspace TCP proxy.\n"); 510 | printf("Usage:\n"); 511 | printf(" %s [-d] [-o] [-b]\n", argv[0]); 512 | printf("Options:\n"); 513 | printf(" -d run in background\n"); 514 | printf(" -o accept IPv6 connections only for IPv6 listener\n"); 515 | printf(" -b base address to port mapping mode\n"); 516 | printf(" -p write PID to file\n"); 517 | } 518 | 519 | int main(int argc, char *argv[]) 520 | { 521 | int opt, b_true = 1, lsn_sock, epfd; 522 | bool is_daemon = false, is_v6only = false; 523 | struct epoll_event ev, events[100]; 524 | int ev_magic_listener = EV_MAGIC_LISTENER; 525 | char s_addr1[50] = "", s_addr2[50] = ""; 526 | 527 | while ((opt = getopt(argc, argv, "dhobp:")) != -1) { 528 | switch (opt) { 529 | case 'd': 530 | is_daemon = true; 531 | break; 532 | case 'h': 533 | show_help(argc, argv); 534 | exit(0); 535 | break; 536 | case 'o': 537 | is_v6only = true; 538 | break; 539 | case 'b': 540 | g_base_addr_mode = true; 541 | break; 542 | case 'p': 543 | g_pidfile = optarg; 544 | break; 545 | case '?': 546 | exit(1); 547 | } 548 | } 549 | 550 | if (optind > argc - 2) { 551 | show_help(argc, argv); 552 | exit(1); 553 | } 554 | 555 | /* Resolve source address */ 556 | if (get_sockaddr_inx_pair(argv[optind], &g_src_addr) < 0) { 557 | fprintf(stderr, "*** Invalid source address '%s'.\n", argv[optind]); 558 | exit(1); 559 | } 560 | optind++; 561 | 562 | /* Resolve destination addresse */ 563 | if (get_sockaddr_inx_pair(argv[optind], &g_dst_addr) < 0) { 564 | fprintf(stderr, "*** Invalid destination address '%s'.\n", argv[optind]); 565 | exit(1); 566 | } 567 | optind++; 568 | 569 | openlog("tcpfwd", LOG_PERROR|LOG_NDELAY, LOG_USER); 570 | 571 | lsn_sock = socket(g_src_addr.sa.sa_family, SOCK_STREAM, 0); 572 | if (lsn_sock < 0) { 573 | fprintf(stderr, "*** socket(): %s.\n", strerror(errno)); 574 | exit(1); 575 | } 576 | setsockopt(lsn_sock, SOL_SOCKET, SO_REUSEADDR, &b_true, sizeof(b_true)); 577 | if (g_src_addr.sa.sa_family == AF_INET6 && is_v6only) 578 | setsockopt(lsn_sock, IPPROTO_IPV6, IPV6_V6ONLY, &b_true, sizeof(b_true)); 579 | if (bind(lsn_sock, (struct sockaddr *)&g_src_addr, 580 | sizeof_sockaddr(&g_src_addr)) < 0) { 581 | fprintf(stderr, "*** bind(): %s.\n", strerror(errno)); 582 | exit(1); 583 | } 584 | if (listen(lsn_sock, 100) < 0) { 585 | fprintf(stderr, "*** listen(): %s.\n", strerror(errno)); 586 | exit(1); 587 | } 588 | set_nonblock(lsn_sock); 589 | 590 | inet_ntop(g_src_addr.sa.sa_family, addr_of_sockaddr(&g_src_addr), 591 | s_addr1, sizeof(s_addr1)); 592 | inet_ntop(g_dst_addr.sa.sa_family, addr_of_sockaddr(&g_dst_addr), 593 | s_addr2, sizeof(s_addr2)); 594 | syslog(LOG_INFO, "TCP proxy [%s]:%d -> [%s]:%d", 595 | s_addr1, ntohs(port_of_sockaddr(&g_src_addr)), 596 | s_addr2, ntohs(port_of_sockaddr(&g_dst_addr))); 597 | 598 | /* Create epoll table. */ 599 | if ((epfd = epoll_create(2048)) < 0) { 600 | syslog(LOG_ERR, "epoll_create(): %s", strerror(errno)); 601 | exit(1); 602 | } 603 | 604 | if (is_daemon) 605 | do_daemonize(); 606 | if (g_pidfile) 607 | write_pidfile(g_pidfile); 608 | 609 | signal(SIGPIPE, SIG_IGN); 610 | 611 | /* epoll loop */ 612 | ev.data.ptr = &ev_magic_listener; 613 | ev.events = EPOLLIN; 614 | epoll_ctl(epfd, EPOLL_CTL_ADD, lsn_sock, &ev); 615 | 616 | for (;;) { 617 | int nfds, i; 618 | 619 | nfds = epoll_wait(epfd, events, countof(events), 1000 * 2); 620 | if (nfds == 0) 621 | continue; 622 | if (nfds < 0) { 623 | if (errno == EINTR || errno == ERESTART) 624 | continue; 625 | syslog(LOG_ERR, "*** epoll_wait(): %s", strerror(errno)); 626 | exit(1); 627 | } 628 | 629 | for (i = 0; i < nfds; i++) { 630 | struct epoll_event *evp = &events[i]; 631 | int *evptr = (int *)evp->data.ptr, efd = -1; 632 | struct proxy_conn *conn; 633 | int io_state = 0; 634 | 635 | /* 'evptr = NULL' indicates the socket is closed. */ 636 | if (evptr == NULL) 637 | continue; 638 | 639 | if (*evptr == EV_MAGIC_LISTENER) { 640 | /* A new connection */ 641 | conn = NULL; 642 | io_state = handle_accept_new_connection(lsn_sock, &conn); 643 | if (!conn) 644 | continue; 645 | init_new_conn_epoll_fds(conn, epfd); 646 | } else if (*evptr == EV_MAGIC_CLIENT) { 647 | conn = container_of(evptr, struct proxy_conn, magic_client); 648 | efd = conn->cli_sock; 649 | } else if (*evptr == EV_MAGIC_SERVER) { 650 | conn = container_of(evptr, struct proxy_conn, magic_server); 651 | efd = conn->svr_sock; 652 | } else { 653 | assert(*evptr == EV_MAGIC_CLIENT || *evptr == EV_MAGIC_SERVER); 654 | } 655 | 656 | /** 657 | * NOTICE: 658 | * - io_state = 0: no pending I/O, state machine can move forward 659 | * - io_state = -EAGAIN: has pending I/O, should wait for further events 660 | * - conn->state = S_CLOSING: connection must be closed at once 661 | */ 662 | while (conn->state != S_CLOSING && io_state == 0) { 663 | switch (conn->state) { 664 | case S_FORWARDING: 665 | io_state = handle_forwarding(conn, efd, epfd, evp); 666 | break; 667 | case S_SERVER_CONNECTING: 668 | io_state = handle_server_connecting(conn, efd); 669 | break; 670 | case S_SERVER_CONNECTED: 671 | io_state = handle_server_connected(conn, efd); 672 | break; 673 | default: 674 | syslog(LOG_ERR, "*** Undefined state: %d", conn->state); 675 | conn->state = S_CLOSING; 676 | io_state = 0; 677 | } 678 | } 679 | 680 | if (conn->state == S_CLOSING) { 681 | release_proxy_conn(conn, events + i + 1, nfds - 1 - i, epfd); 682 | } else { 683 | set_conn_epoll_fds(conn, epfd); 684 | } 685 | } 686 | } 687 | 688 | return 0; 689 | } 690 | 691 | -------------------------------------------------------------------------------- /src/udpfwd.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | #include 11 | #include 12 | #include 13 | #include 14 | #include 15 | #include 16 | #include 17 | 18 | #ifdef __linux__ 19 | #include 20 | #else 21 | #define ERESTART 700 22 | #include "no-epoll.h" 23 | #endif 24 | 25 | typedef int bool; 26 | #define true 1 27 | #define false 0 28 | 29 | #define countof(arr) (sizeof(arr) / sizeof((arr)[0])) 30 | 31 | struct sockaddr_inx { 32 | union { 33 | struct sockaddr sa; 34 | struct sockaddr_in in; 35 | struct sockaddr_in6 in6; 36 | }; 37 | }; 38 | 39 | #define port_of_sockaddr(s) ((s)->sa.sa_family == AF_INET6 ? \ 40 | (s)->in6.sin6_port : (s)->in.sin_port) 41 | #define addr_of_sockaddr(s) ((s)->sa.sa_family == AF_INET6 ? \ 42 | (void *)&(s)->in6.sin6_addr : (void *)&(s)->in.sin_addr) 43 | #define sizeof_sockaddr(s) ((s)->sa.sa_family == AF_INET6 ? \ 44 | sizeof((s)->in6) : sizeof((s)->in)) 45 | 46 | /* -=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=- */ 47 | 48 | #include 49 | 50 | #define container_of(ptr, type, member) ({ \ 51 | const typeof(((type *)0)->member) * __mptr = (ptr); \ 52 | (type *)((char *)__mptr - offsetof(type, member)); }) 53 | 54 | #define LIST_POISON1 ((void *) 0x00100100) 55 | #define LIST_POISON2 ((void *) 0x00200200) 56 | 57 | struct list_head { 58 | struct list_head *next, *prev; 59 | }; 60 | 61 | #define LIST_HEAD_INIT(name) { &(name), &(name) } 62 | 63 | #define LIST_HEAD(name) \ 64 | struct list_head name = LIST_HEAD_INIT(name) 65 | 66 | static inline void INIT_LIST_HEAD(struct list_head *list) 67 | { 68 | list->next = list; 69 | list->prev = list; 70 | } 71 | 72 | static inline void __list_add(struct list_head *new, 73 | struct list_head *prev, 74 | struct list_head *next) 75 | { 76 | next->prev = new; 77 | new->next = next; 78 | new->prev = prev; 79 | prev->next = new; 80 | } 81 | 82 | static inline void list_add(struct list_head *new, struct list_head *head) 83 | { 84 | __list_add(new, head, head->next); 85 | } 86 | 87 | static inline void list_add_tail(struct list_head *new, struct list_head *head) 88 | { 89 | __list_add(new, head->prev, head); 90 | } 91 | 92 | static inline void __list_del(struct list_head * prev, struct list_head * next) 93 | { 94 | next->prev = prev; 95 | prev->next = next; 96 | } 97 | 98 | static inline void list_del(struct list_head *entry) 99 | { 100 | __list_del(entry->prev, entry->next); 101 | entry->next = LIST_POISON1; 102 | entry->prev = LIST_POISON2; 103 | } 104 | 105 | static inline int list_empty(const struct list_head *head) 106 | { 107 | return head->next == head; 108 | } 109 | 110 | #define list_entry(ptr, type, member) \ 111 | container_of(ptr, type, member) 112 | 113 | #define list_first_entry(ptr, type, member) \ 114 | list_entry((ptr)->next, type, member) 115 | 116 | #define list_next_entry(pos, member) \ 117 | list_entry((pos)->member.next, typeof(*(pos)), member) 118 | 119 | #define list_for_each_entry(pos, head, member) \ 120 | for (pos = list_entry((head)->next, typeof(*pos), member); \ 121 | /*prefetch(pos->member.next),*/ &pos->member != (head); \ 122 | pos = list_entry(pos->member.next, typeof(*pos), member)) 123 | 124 | #define list_for_each_entry_safe(pos, n, head, member) \ 125 | for (pos = list_first_entry(head, typeof(*pos), member), \ 126 | n = list_next_entry(pos, member); \ 127 | &pos->member != (head); \ 128 | pos = n, n = list_next_entry(n, member)) 129 | 130 | /* -=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=- */ 131 | 132 | static struct sockaddr_inx g_src_addr; 133 | static struct sockaddr_inx g_dst_addr; 134 | static const char *g_pidfile; 135 | 136 | #define CONN_TBL_HASH_SIZE (1 < 8) 137 | static struct list_head conn_tbl_hbase[CONN_TBL_HASH_SIZE]; 138 | static unsigned conn_tbl_len; 139 | static unsigned proxy_conn_timeo = 60; 140 | 141 | /* -=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=- */ 142 | 143 | static int do_daemonize(void) 144 | { 145 | int rc; 146 | 147 | if ((rc = fork()) < 0) { 148 | fprintf(stderr, "*** fork() error: %s.\n", strerror(errno)); 149 | return rc; 150 | } else if (rc > 0) { 151 | /* In parent process */ 152 | exit(0); 153 | } else { 154 | /* In child process */ 155 | int fd; 156 | setsid(); 157 | fd = open("/dev/null", O_RDONLY); 158 | dup2(fd, STDIN_FILENO); 159 | dup2(fd, STDOUT_FILENO); 160 | dup2(fd, STDERR_FILENO); 161 | if (fd > 2) 162 | close(fd); 163 | chdir("/tmp"); 164 | } 165 | return 0; 166 | } 167 | 168 | static void write_pidfile(const char *filepath) 169 | { 170 | FILE *fp; 171 | if (!(fp = fopen(filepath, "w"))) { 172 | fprintf(stderr, "*** fopen(%s): %s\n", filepath, strerror(errno)); 173 | exit(1); 174 | } 175 | fprintf(fp, "%d\n", (int)getpid()); 176 | fclose(fp); 177 | } 178 | 179 | static void set_nonblock(int sockfd) 180 | { 181 | fcntl(sockfd, F_SETFL, fcntl(sockfd, F_GETFD, 0) | O_NONBLOCK); 182 | } 183 | 184 | static int get_sockaddr_inx_pair(const char *pair, struct sockaddr_inx *sa) 185 | { 186 | struct addrinfo hints, *result; 187 | char host[51] = "", s_port[20] = ""; 188 | int port = 0, rc; 189 | 190 | if (sscanf(pair, "[%50[^]]]:%d", host, &port) == 2) { 191 | /* Quoted IP and port: [10.0.0.1]:10000 */ 192 | } else if (sscanf(pair, "%50[^:]:%d", host, &port) == 2) { 193 | /* Regular IP and port: 10.0.0.1:10000 */ 194 | } else { 195 | /** 196 | * A single port number, usually for local IPv4 listen address. 197 | * e.g., "10000" stands for "0.0.0.0:10000" 198 | */ 199 | const char *sp; 200 | for (sp = pair; *sp; sp++) { 201 | if (!(*sp >= '0' && *sp <= '9')) 202 | return -EINVAL; 203 | } 204 | sscanf(pair, "%d", &port); 205 | strcpy(host, "0.0.0.0"); 206 | } 207 | sprintf(s_port, "%d", port); 208 | if (port <= 0 || port > 65535) 209 | return -EINVAL; 210 | 211 | memset(&hints, 0, sizeof(struct addrinfo)); 212 | hints.ai_family = AF_UNSPEC; /* Allow IPv4 or IPv6 */ 213 | hints.ai_socktype = SOCK_DGRAM; 214 | hints.ai_flags = AI_PASSIVE; /* For wildcard IP address */ 215 | hints.ai_protocol = 0; /* Any protocol */ 216 | hints.ai_canonname = NULL; 217 | hints.ai_addr = NULL; 218 | hints.ai_next = NULL; 219 | 220 | if ((rc = getaddrinfo(host, s_port, &hints, &result))) 221 | return -EAGAIN; 222 | 223 | /* Get the first resolution. */ 224 | memcpy(sa, result->ai_addr, result->ai_addrlen); 225 | 226 | freeaddrinfo(result); 227 | return 0; 228 | } 229 | 230 | static bool is_sockaddr_inx_equal(struct sockaddr_inx *sa1, struct sockaddr_inx *sa2) 231 | { 232 | if (sa1->sa.sa_family != sa2->sa.sa_family) 233 | return false; 234 | 235 | if (sa1->sa.sa_family == AF_INET) { 236 | if (sa1->in.sin_addr.s_addr != sa2->in.sin_addr.s_addr) 237 | return false; 238 | if (sa1->in.sin_port != sa2->in.sin_port) 239 | return false; 240 | return true; 241 | } else if (sa1->sa.sa_family == AF_INET6) { 242 | if (memcmp(&sa1->in6.sin6_addr, &sa2->in6.sin6_addr, sizeof(sa2->in6.sin6_addr))) 243 | return false; 244 | if (sa1->in6.sin6_port != sa2->in6.sin6_port) 245 | return false; 246 | return true; 247 | } 248 | 249 | return true; 250 | } 251 | 252 | /* -=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=- */ 253 | 254 | /** 255 | * Connection tracking information to indicate 256 | * a proxy session. 257 | */ 258 | struct proxy_conn { 259 | struct list_head list; 260 | time_t last_active; 261 | struct sockaddr_inx cli_addr; /* <-- key */ 262 | int svr_sock; 263 | }; 264 | 265 | static unsigned int proxy_conn_hash(struct sockaddr_inx *sa) 266 | { 267 | unsigned int hash = 0; 268 | 269 | if (sa->sa.sa_family == AF_INET) { 270 | hash = ntohl(sa->in.sin_addr.s_addr) + ntohs(sa->in.sin_port); 271 | } else if (sa->sa.sa_family == AF_INET6) { 272 | int i; 273 | for (i = 0; i < 4; i++) 274 | hash += ((uint32_t *)&sa->in6.sin6_addr)[i]; 275 | hash += ntohs(sa->in6.sin6_port); 276 | } 277 | 278 | return hash; 279 | } 280 | 281 | static struct proxy_conn *proxy_conn_get_or_create( 282 | struct sockaddr_inx *cli_addr, int epfd) 283 | { 284 | struct list_head *chain = &conn_tbl_hbase[ 285 | proxy_conn_hash(cli_addr) & (CONN_TBL_HASH_SIZE - 1)]; 286 | struct proxy_conn *conn; 287 | int svr_sock = -1; 288 | struct epoll_event ev; 289 | char s_addr[50] = ""; 290 | 291 | list_for_each_entry (conn, chain, list) { 292 | if (is_sockaddr_inx_equal(cli_addr, &conn->cli_addr)) { 293 | conn->last_active = time(NULL); 294 | return conn; 295 | } 296 | } 297 | 298 | /* ------------------------------------------ */ 299 | /* Establish the server-side connection */ 300 | if ((svr_sock = socket(g_dst_addr.sa.sa_family, SOCK_DGRAM, 0)) < 0) { 301 | syslog(LOG_ERR, "*** socket(svr_sock): %s", strerror(errno)); 302 | goto err; 303 | } 304 | /* Connect to real server. */ 305 | if (connect(svr_sock, (struct sockaddr *)&g_dst_addr, 306 | sizeof_sockaddr(&g_dst_addr)) != 0) { 307 | /* Error occurs, drop the session. */ 308 | syslog(LOG_WARNING, "Connection failed: %s", strerror(errno)); 309 | goto err; 310 | } 311 | set_nonblock(svr_sock); 312 | 313 | /* Allocate session data for the connection */ 314 | if ((conn = malloc(sizeof(*conn))) == NULL) { 315 | syslog(LOG_ERR, "*** malloc(conn): %s", strerror(errno)); 316 | goto err; 317 | } 318 | memset(conn, 0x0, sizeof(*conn)); 319 | conn->svr_sock = svr_sock; 320 | conn->cli_addr = *cli_addr; 321 | 322 | ev.data.ptr = conn; 323 | ev.events = EPOLLIN; 324 | epoll_ctl(epfd, EPOLL_CTL_ADD, conn->svr_sock, &ev); 325 | /* ------------------------------------------ */ 326 | 327 | list_add_tail(&conn->list, chain); 328 | conn_tbl_len++; 329 | 330 | inet_ntop(cli_addr->sa.sa_family, addr_of_sockaddr(cli_addr), 331 | s_addr, sizeof(s_addr)); 332 | syslog(LOG_INFO, "New connection %s:%d [%u]", 333 | s_addr, ntohs(port_of_sockaddr(cli_addr)), conn_tbl_len); 334 | 335 | conn->last_active = time(NULL); 336 | return conn; 337 | 338 | err: 339 | if (svr_sock >= 0) 340 | close(svr_sock); 341 | return NULL; 342 | } 343 | 344 | /** 345 | * Close both sockets of the connection and remove it 346 | * from the current ready list. 347 | */ 348 | static void release_proxy_conn(struct proxy_conn *conn, int epfd) 349 | { 350 | list_del(&conn->list); 351 | conn_tbl_len--; 352 | epoll_ctl(epfd, EPOLL_CTL_DEL, conn->svr_sock, NULL); 353 | close(conn->svr_sock); 354 | free(conn); 355 | } 356 | 357 | static void proxy_conn_walk_continue(unsigned walk_max, int epfd) 358 | { 359 | static unsigned bucket_index = 0; 360 | unsigned __bucket_index = bucket_index; 361 | unsigned walk_count = 0; 362 | time_t current_ts = time(NULL); 363 | 364 | if (walk_max > conn_tbl_len) 365 | walk_max = conn_tbl_len; 366 | if (walk_max == 0) 367 | return; 368 | 369 | do { 370 | struct proxy_conn *conn, *__conn; 371 | list_for_each_entry_safe (conn, __conn, &conn_tbl_hbase[bucket_index], list) { 372 | if ((unsigned)(current_ts - conn->last_active) > proxy_conn_timeo) { 373 | struct sockaddr_inx addr = conn->cli_addr; 374 | char s_addr[50] = ""; 375 | release_proxy_conn(conn, epfd); 376 | inet_ntop(addr.sa.sa_family, addr_of_sockaddr(&addr), 377 | s_addr, sizeof(s_addr)); 378 | syslog(LOG_INFO, "Recycled %s:%d [%u]", 379 | s_addr, ntohs(port_of_sockaddr(&addr)), conn_tbl_len); 380 | } 381 | walk_count++; 382 | } 383 | bucket_index = (bucket_index + 1) & (CONN_TBL_HASH_SIZE - 1); 384 | } while (walk_count < walk_max && bucket_index != __bucket_index); 385 | } 386 | 387 | /* -=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=- */ 388 | 389 | static void show_help(int argc, char *argv[]) 390 | { 391 | printf("Userspace UDP proxy.\n"); 392 | printf("Usage:\n"); 393 | printf(" %s [-d] [-o]\n", argv[0]); 394 | printf("Options:\n"); 395 | printf(" -t proxy session timeout (default: %u)\n", proxy_conn_timeo); 396 | printf(" -d run in background\n"); 397 | printf(" -o accept IPv6 connections only for IPv6 listener\n"); 398 | printf(" -r set SO_REUSEADDR before binding local port\n"); 399 | printf(" -p write PID to file\n"); 400 | } 401 | 402 | int main(int argc, char *argv[]) 403 | { 404 | int opt, b_true = 1, lsn_sock, epfd, i; 405 | bool is_daemon = false, is_v6only = false, is_reuseaddr = false; 406 | struct epoll_event ev, events[100]; 407 | char buffer[1024 * 64], s_addr1[50] = "", s_addr2[50] = ""; 408 | time_t last_check; 409 | 410 | while ((opt = getopt(argc, argv, "t:dhorp:")) != -1) { 411 | switch (opt) { 412 | case 't': 413 | proxy_conn_timeo = strtoul(optarg, NULL, 10); 414 | break; 415 | case 'd': 416 | is_daemon = true; 417 | break; 418 | case 'h': 419 | show_help(argc, argv); 420 | exit(0); 421 | break; 422 | case 'o': 423 | is_v6only = true; 424 | break; 425 | case 'r': 426 | is_reuseaddr = true; 427 | break; 428 | case 'p': 429 | g_pidfile = optarg; 430 | break; 431 | case '?': 432 | exit(1); 433 | } 434 | } 435 | 436 | if (optind > argc - 2) { 437 | show_help(argc, argv); 438 | exit(1); 439 | } 440 | 441 | /* Resolve source address */ 442 | if (get_sockaddr_inx_pair(argv[optind], &g_src_addr) < 0) { 443 | fprintf(stderr, "*** Invalid source address '%s'.\n", argv[optind]); 444 | exit(1); 445 | } 446 | optind++; 447 | 448 | /* Resolve destination addresse */ 449 | if (get_sockaddr_inx_pair(argv[optind], &g_dst_addr) < 0) { 450 | fprintf(stderr, "*** Invalid destination address '%s'.\n", argv[optind]); 451 | exit(1); 452 | } 453 | optind++; 454 | 455 | openlog("udpfwd", LOG_PERROR|LOG_NDELAY, LOG_USER); 456 | 457 | lsn_sock = socket(g_src_addr.sa.sa_family, SOCK_DGRAM, 0); 458 | if (lsn_sock < 0) { 459 | fprintf(stderr, "*** socket(): %s.\n", strerror(errno)); 460 | exit(1); 461 | } 462 | if (is_reuseaddr) 463 | setsockopt(lsn_sock, SOL_SOCKET, SO_REUSEADDR, &b_true, sizeof(b_true)); 464 | if (g_src_addr.sa.sa_family == AF_INET6 && is_v6only) 465 | setsockopt(lsn_sock, IPPROTO_IPV6, IPV6_V6ONLY, &b_true, sizeof(b_true)); 466 | if (bind(lsn_sock, (struct sockaddr *)&g_src_addr, 467 | sizeof_sockaddr(&g_src_addr)) < 0) { 468 | fprintf(stderr, "*** bind(): %s.\n", strerror(errno)); 469 | exit(1); 470 | } 471 | set_nonblock(lsn_sock); 472 | 473 | inet_ntop(g_src_addr.sa.sa_family, addr_of_sockaddr(&g_src_addr), 474 | s_addr1, sizeof(s_addr1)); 475 | inet_ntop(g_dst_addr.sa.sa_family, addr_of_sockaddr(&g_dst_addr), 476 | s_addr2, sizeof(s_addr2)); 477 | syslog(LOG_INFO, "UDP proxy [%s]:%d -> [%s]:%d", 478 | s_addr1, ntohs(port_of_sockaddr(&g_src_addr)), 479 | s_addr2, ntohs(port_of_sockaddr(&g_dst_addr))); 480 | 481 | /* Create epoll table. */ 482 | if ((epfd = epoll_create(2048)) < 0) { 483 | syslog(LOG_ERR, "epoll_create(): %s", strerror(errno)); 484 | exit(1); 485 | } 486 | 487 | if (is_daemon) 488 | do_daemonize(); 489 | if (g_pidfile) 490 | write_pidfile(g_pidfile); 491 | 492 | signal(SIGPIPE, SIG_IGN); 493 | 494 | /* Initialize the connection table */ 495 | for (i = 0; i < CONN_TBL_HASH_SIZE; i++) 496 | INIT_LIST_HEAD(&conn_tbl_hbase[i]); 497 | conn_tbl_len = 0; 498 | 499 | last_check = time(NULL); 500 | 501 | /* epoll loop */ 502 | ev.data.ptr = NULL; 503 | ev.events = EPOLLIN; 504 | epoll_ctl(epfd, EPOLL_CTL_ADD, lsn_sock, &ev); 505 | 506 | for (;;) { 507 | int nfds; 508 | time_t current_ts = time(NULL); 509 | 510 | /* Timeout check and recycle */ 511 | if ((unsigned)(current_ts - last_check) >= 2) { 512 | proxy_conn_walk_continue(200, epfd); 513 | last_check = current_ts; 514 | } 515 | 516 | nfds = epoll_wait(epfd, events, countof(events), 1000 * 2); 517 | if (nfds == 0) 518 | continue; 519 | if (nfds < 0) { 520 | if (errno == EINTR || errno == ERESTART) 521 | continue; 522 | syslog(LOG_ERR, "*** epoll_wait(): %s", strerror(errno)); 523 | exit(1); 524 | } 525 | 526 | for (i = 0; i < nfds; i++) { 527 | struct epoll_event *evp = &events[i]; 528 | struct proxy_conn *conn; 529 | int r; 530 | 531 | if (evp->data.ptr == NULL) { 532 | /* Data from client */ 533 | struct sockaddr_inx cli_addr; 534 | socklen_t cli_alen = sizeof(cli_addr); 535 | if ((r = recvfrom(lsn_sock, buffer, sizeof(buffer), 0, 536 | (struct sockaddr *)&cli_addr, &cli_alen)) <= 0) 537 | continue; 538 | if (!(conn = proxy_conn_get_or_create(&cli_addr, epfd))) 539 | continue; 540 | (void)send(conn->svr_sock, buffer, r, 0); 541 | } else { 542 | /* Data from server */ 543 | conn = (struct proxy_conn *)evp->data.ptr; 544 | if ((r = recv(conn->svr_sock, buffer, sizeof(buffer), 0)) <= 0) { 545 | /* Close the session. */ 546 | release_proxy_conn(conn, epfd); 547 | continue; 548 | } 549 | (void)sendto(lsn_sock, buffer, r, 0, (struct sockaddr *)&conn->cli_addr, 550 | sizeof_sockaddr(&conn->cli_addr)); 551 | } 552 | } 553 | } 554 | 555 | return 0; 556 | } 557 | 558 | --------------------------------------------------------------------------------