diff --git a/scripts/no_heap_baseline.txt b/scripts/no_heap_baseline.txt index 8894764f..276ecdf0 100644 --- a/scripts/no_heap_baseline.txt +++ b/scripts/no_heap_baseline.txt @@ -9,16 +9,15 @@ src/crypto.cpp 4 src/ipset.cpp 3 src/jconf.cpp 16 src/json.cpp 2 -src/local.cpp 2 +src/local.cpp 1 src/manager.cpp 14 src/plugin.cpp 3 src/process.cpp 26 -src/redir.cpp 15 +src/redir.cpp 1 src/rule.cpp 2 src/server.cpp 4 src/ss_event.cpp 5 src/ssurl.cpp 3 src/stream.cpp 3 -src/tunnel.cpp 2 -src/udprelay.cpp 7 +src/tunnel.cpp 1 src/utils.cpp 10 diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 69ba0dd3..b9393a22 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -53,6 +53,7 @@ set(SS_RELAY_SOURCE relay/codec.cpp relay/socket.cpp relay/tcp.cpp + relay/udp.cpp relay/resolver.cpp crypto/aead.cpp crypto/aead2022.cpp @@ -74,7 +75,6 @@ endif () set(LIBSHADOWSOCKS_LIBEV_SOURCE ${SS_SHARED_SOURCES} - udprelay.cpp cache.cpp local.cpp resolv.cpp @@ -92,7 +92,6 @@ set(SS_LOCAL_SOURCE set(SS_TUNNEL_SOURCE ${SS_SHARED_SOURCES} - udprelay.cpp cache.cpp tunnel.cpp ${SS_RELAY_SOURCE} @@ -102,7 +101,6 @@ set(SS_TUNNEL_SOURCE set(SS_SERVER_SOURCE ${SS_SHARED_SOURCES} - udprelay.cpp cache.cpp resolv.cpp server.cpp @@ -119,7 +117,6 @@ set(SS_MANAGER_SOURCE set(SS_REDIR_SOURCE ${SS_SHARED_SOURCES} - udprelay.cpp cache.cpp redir.cpp ${SS_RELAY_SOURCE} diff --git a/src/local.cpp b/src/local.cpp index 2daf3db8..2147f23d 100644 --- a/src/local.cpp +++ b/src/local.cpp @@ -62,10 +62,10 @@ #include "crypto.h" #include "jconf.h" #include "common.h" -#include "udprelay.h" #include "ss_windows.h" #include "relay/tcp.hpp" +#include "relay/udp.hpp" #undef min #undef max @@ -104,7 +104,6 @@ ss_tstamp last = 0; char *stat_path = NULL; #endif -static crypto_t *crypto; static int acl = 0; static int mode = TCP_ONLY; @@ -270,28 +269,35 @@ stat_update_cb() #endif -// The TCP side of ss-local: the relay engine on the ss_event loop, plus a -// resolver for host-name ACL rules. +// The relays of ss-local on the ss_event loop: TCP (with a resolver for +// host-name ACL rules) and UDP, sharing one cipher configuration. namespace { -struct TcpSide { +struct Relays { ss::event::Loop loop; ss::relay::Method method; ss::relay::Resolver resolver; - ss::relay::TcpRelay relay; + ss::relay::TcpRelay tcp; + ss::relay::UdpRelay udp; bool resolving = false; - bool running = false; + bool tcp_on = false; + bool udp_on = false; - bool start(struct ss_loop *ss_loop, const char *cipher, const char *pass, const char *key, - ss::relay::TcpConfig &config, int listenfd) + bool init(struct ss_loop *ss_loop, const char *cipher, const char *pass, const char *key) { if (!loop.adopt(ss_native_loop(ss_loop))) { return false; } + LOGI("initializing ciphers... %s", cipher); if (!method.init(cipher, pass, key, ss::crypto::Role::client)) { LOGE("failed to initialize ciphers"); return false; } + return true; + } + + bool start_tcp(ss::relay::TcpConfig &config, int listenfd) + { if (config.acl) { config.acl_hooks.match_host = &acl_match_host; config.acl_hooks.mode = &get_acl_mode; @@ -301,27 +307,60 @@ struct TcpSide { } resolving = true; } - if (!relay.init(loop, method, config, resolving ? &resolver : NULL) || !relay.listen(listenfd)) { + if (!tcp.init(loop, method, config, resolving ? &resolver : NULL) || !tcp.listen(listenfd)) { return false; } - running = true; + tcp_on = true; return true; } + // Returns the UDP socket, or -1. + int start_udp(const char *host, const char *port, const struct sockaddr_storage *remote, int mtu, int timeout, + const char *iface) + { + ss::relay::UdpConfig config; + config.kind = ss::relay::Kind::local; + config.remote = *remote; + config.timeout_secs = timeout; + config.max_sessions = 256; + config.mtu = mtu; + config.iface = iface; +#ifdef __ANDROID__ + config.vpn = vpn != 0; + config.protect = protect_socket; + config.tx = &tx; + config.rx = ℞ + config.on_traffic = &stat_update_cb; +#endif + auto fd = ss::relay::open_udp_listener(host, port, false, reuse_port != 0); + if (!fd || !udp.init(loop, method, config, NULL)) { + return -1; + } + if (!udp.listen(*fd)) { + ss_socket_close(*fd); + return -1; + } + udp_on = true; + return (int)*fd; + } + void stop() { - if (running) { - relay.shutdown(); + if (tcp_on) { + tcp.shutdown(); + } + if (udp_on) { + udp.shutdown(); } static bool resolver_closed; resolver_closed = !resolving; if (resolving) { resolver.shutdown(ss::CloseCompletion([](void *, uint32_t) { resolver_closed = true; }, NULL, 0)); } - while ((running && !relay.closed()) || !resolver_closed) { + while ((tcp_on && !tcp.closed()) || (udp_on && !udp.closed()) || !resolver_closed) { loop.run_nowait(); } - running = resolving = false; + tcp_on = udp_on = resolving = false; } }; @@ -1138,9 +1177,8 @@ Same behavior as `-S`; see that option for details. Short alias: `-S`. #endif // Setup keys - LOGI("initializing ciphers... %s", method); - crypto = crypto_init(password, key, method); - if (crypto == NULL) + Relays relays; + if (!relays.init(SS_DEFAULT, method, password, key)) FATAL("failed to initialize ciphers"); // Setup proxy context @@ -1165,7 +1203,6 @@ Same behavior as `-S`; see that option for details. Short alias: `-S`. } ss::relay::TcpConfig tcp_config; fill_tcp_config(tcp_config, remote_storage, remote_count, timeout_secs, mptcp, iface); - TcpSide tcp; // Setup signal handler ss_signal_init(&sigint_watcher, signal_cb, SIGINT); @@ -1198,7 +1235,7 @@ Same behavior as `-S`; see that option for details. Short alias: `-S`. if (listen(listenfd, SOMAXCONN) == -1) { FATAL("listen() error"); } - if (!tcp.start(loop, method, password, key, tcp_config, listenfd)) { + if (!relays.start_tcp(tcp_config, listenfd)) { FATAL("failed to set up the TCP relay"); } } @@ -1206,16 +1243,14 @@ Same behavior as `-S`; see that option for details. Short alias: `-S`. // Setup UDP if (mode != TCP_ONLY) { LOGI("udprelay enabled"); - char *host = remote_addr[0].host; - char *port = remote_addr[0].port == NULL ? remote_port : remote_addr[0].port; - struct sockaddr_storage *storage = (struct sockaddr_storage *)ss_malloc(sizeof(struct sockaddr_storage)); - memset(storage, 0, sizeof(struct sockaddr_storage)); - if (get_sockaddr(host, port, storage, 1, ipv6first) == -1) { + char *host = remote_addr[0].host; + char *port = remote_addr[0].port == NULL ? remote_port : remote_addr[0].port; + struct sockaddr_storage udp_remote; + memset(&udp_remote, 0, sizeof(udp_remote)); + if (get_sockaddr(host, port, &udp_remote, 1, ipv6first) == -1) { FATAL("failed to resolve the provided hostname"); } - struct sockaddr *addr = (struct sockaddr *)storage; - udp_fd = init_udprelay(local_addr, local_port, addr, - get_sockaddr_len(addr), mtu, crypto, timeout_secs, iface); + udp_fd = relays.start_udp(local_addr, local_port, &udp_remote, mtu, timeout_secs, iface); if (udp_fd == -1) { FATAL("failed to initialize UDP relay"); } @@ -1250,11 +1285,7 @@ Same behavior as `-S`; see that option for details. Short alias: `-S`. stop_plugin(); } - tcp.stop(); - - if (mode != TCP_ONLY) { - free_udprelay(); - } + relays.stop(); ss_loop_destroy(loop); #ifdef __MINGW32__ @@ -1326,9 +1357,8 @@ _start_ss_local_server(profile_t profile, ss_local_callback callback, void *udat #endif // Setup keys - LOGI("initializing ciphers... %s", method); - crypto = crypto_init(password, NULL, method); - if (crypto == NULL) + Relays relays; + if (!relays.init(SS_DEFAULT, method, password, NULL)) FATAL("failed to init ciphers"); struct sockaddr_storage storage; @@ -1341,7 +1371,6 @@ _start_ss_local_server(profile_t profile, ss_local_callback callback, void *udat struct ss_loop *loop = SS_DEFAULT; ss::relay::TcpConfig tcp_config; fill_tcp_config(tcp_config, &storage, 1, timeout, mptcp, NULL); - TcpSide tcp; int listen_fd = -1; // stays -1 in UDP_ONLY mode but is still passed to the callback below if (ss_is_ipv6addr(local_addr)) @@ -1361,7 +1390,7 @@ _start_ss_local_server(profile_t profile, ss_local_callback callback, void *udat ERROR("listen()"); return -1; } - if (!tcp.start(loop, method, password, NULL, tcp_config, listenfd)) { + if (!relays.start_tcp(tcp_config, listenfd)) { LOGE("failed to set up the TCP relay"); return -1; } @@ -1371,9 +1400,7 @@ _start_ss_local_server(profile_t profile, ss_local_callback callback, void *udat // Setup UDP if (mode != TCP_ONLY) { LOGI("udprelay enabled"); - struct sockaddr *addr = (struct sockaddr *)(&storage); - udp_fd = init_udprelay(local_addr, local_port_str, addr, - get_sockaddr_len(addr), mtu, crypto, timeout, NULL); + udp_fd = relays.start_udp(local_addr, local_port_str, &storage, mtu, timeout, NULL); if (udp_fd == -1) { return -1; } @@ -1391,11 +1418,7 @@ _start_ss_local_server(profile_t profile, ss_local_callback callback, void *udat } // Clean up - tcp.stop(); - - if (mode != TCP_ONLY) { - free_udprelay(); - } + relays.stop(); ss_loop_destroy(loop); #ifdef __MINGW32__ diff --git a/src/redir.cpp b/src/redir.cpp index d6920cac..5e46529d 100644 --- a/src/redir.cpp +++ b/src/redir.cpp @@ -51,7 +51,16 @@ #include "netutils.h" #include "utils.h" #include "common.h" -#include "redir.h" +#include "ss_event.h" +#include "crypto.h" +#include "jconf.h" +#include "common.h" + +#include "relay/tcp.hpp" +#include "relay/udp.hpp" + +#undef min +#undef max #ifndef EAGAIN #define EAGAIN EWOULDBLOCK @@ -73,20 +82,6 @@ #define IPV6_TRANSPARENT 75 #endif -static void accept_cb(SS_P_ ss_io *w, int revents); -static void server_recv_cb(SS_P_ ss_io *w, int revents); -static void server_send_cb(SS_P_ ss_io *w, int revents); -static void remote_recv_cb(SS_P_ ss_io *w, int revents); -static void remote_send_cb(SS_P_ ss_io *w, int revents); - -static remote_t *new_remote(int fd, int timeout); -static server_t *new_server(int fd); - -static void free_remote(remote_t *remote); -static void close_and_free_remote(SS_P_ remote_t *remote); -static void free_server(server_t *server); -static void close_and_free_server(SS_P_ server_t *server); - int verbose = 0; int reuse_port = 0; int tcp_incoming_sndbuf = 0; @@ -94,7 +89,6 @@ int tcp_incoming_rcvbuf = 0; int tcp_outgoing_sndbuf = 0; int tcp_outgoing_rcvbuf = 0; -static crypto_t *crypto; static int ipv6first = 0; static int mode = TCP_ONLY; @@ -111,27 +105,6 @@ static struct ss_signal sigchld_watcher; static int tcp_tproxy = 0; /* use tproxy instead of redirect (for tcp) */ -static int -getdestaddr(int fd, struct sockaddr_storage *destaddr) -{ - socklen_t socklen = sizeof(*destaddr); - int error = 0; - - if (tcp_tproxy) { - error = getsockname(fd, (struct sockaddr *)destaddr, &socklen); - } else { - error = getsockopt(fd, SOL_IPV6, IP6T_SO_ORIGINAL_DST, destaddr, &socklen); - if (error) { // Didn't find a proper way to detect IP version. - error = getsockopt(fd, SOL_IP, SO_ORIGINAL_DST, destaddr, &socklen); - } - } - - if (error) { - return -1; - } - return 0; -} - int setnonblocking(int fd) { @@ -226,647 +199,6 @@ create_and_bind(const char *addr, const char *port) return listen_sock; } -static void -server_recv_cb(SS_P_ ss_io *w, int revents) -{ - server_ctx_t *server_recv_ctx = (server_ctx_t *)w; - server_t *server = server_recv_ctx->server; - remote_t *remote = server->remote; - - ss_timer_stop(SS_A_ & server->delayed_connect_watcher); - - ssize_t r = recv(server->fd, remote->buf->data + remote->buf->len, - SOCKET_BUF_SIZE - remote->buf->len, 0); - - if (r == 0) { - // connection closed - close_and_free_remote(SS_A_ remote); - close_and_free_server(SS_A_ server); - return; - } else if (r == -1) { - if (ss_socket_error() == EAGAIN || ss_socket_error() == EWOULDBLOCK) { - // no data - // continue to wait for recv - return; - } else { - ERROR("server recv"); - close_and_free_remote(SS_A_ remote); - close_and_free_server(SS_A_ server); - return; - } - } - - remote->buf->len += r; - - if (verbose) { - uint16_t port = 0; - char ipstr[INET6_ADDRSTRLEN]; - memset(&ipstr, 0, INET6_ADDRSTRLEN); - - if (AF_INET == server->destaddr.ss_family) { - struct sockaddr_in *sa = (struct sockaddr_in *)&(server->destaddr); - ss_inet_ntop(AF_INET, &(sa->sin_addr), ipstr, INET_ADDRSTRLEN); - port = ntohs(sa->sin_port); - } else { - struct sockaddr_in6 *sa = (struct sockaddr_in6 *)&(server->destaddr); - ss_inet_ntop(AF_INET6, &(sa->sin6_addr), ipstr, INET6_ADDRSTRLEN); - port = ntohs(sa->sin6_port); - } - - LOGI("redir to %s:%d, len=%zu, recv=%zd", ipstr, port, remote->buf->len, r); - } - - if (!remote->send_ctx->connected) { - ss_io_stop(SS_A_ & server_recv_ctx->io); - ss_io_start(SS_A_ & remote->send_ctx->io); - return; - } - - int err = crypto->encrypt(remote->buf, server->e_ctx, SOCKET_BUF_SIZE); - - if (err) { - LOGE("invalid password or cipher"); - close_and_free_remote(SS_A_ remote); - close_and_free_server(SS_A_ server); - return; - } - - int s = send(remote->fd, remote->buf->data, remote->buf->len, 0); - - if (s == -1) { - if (ss_socket_error() == EAGAIN || ss_socket_error() == EWOULDBLOCK) { - // no data, wait for send - remote->buf->idx = 0; - ss_io_stop(SS_A_ & server_recv_ctx->io); - ss_io_start(SS_A_ & remote->send_ctx->io); - return; - } else { - ERROR("send"); - close_and_free_remote(SS_A_ remote); - close_and_free_server(SS_A_ server); - return; - } - } else if (s < remote->buf->len) { - remote->buf->len -= s; - remote->buf->idx = s; - ss_io_stop(SS_A_ & server_recv_ctx->io); - ss_io_start(SS_A_ & remote->send_ctx->io); - return; - } else { - remote->buf->idx = 0; - remote->buf->len = 0; - } -} - -static void -server_send_cb(SS_P_ ss_io *w, int revents) -{ - server_ctx_t *server_send_ctx = (server_ctx_t *)w; - server_t *server = server_send_ctx->server; - remote_t *remote = server->remote; - if (server->buf->len == 0) { - // close and free - close_and_free_remote(SS_A_ remote); - close_and_free_server(SS_A_ server); - return; - } else { - // has data to send - ssize_t s = send(server->fd, server->buf->data + server->buf->idx, - server->buf->len, 0); - if (s == -1) { - if (ss_socket_error() != EAGAIN && ss_socket_error() != EWOULDBLOCK) { - ERROR("send"); - close_and_free_remote(SS_A_ remote); - close_and_free_server(SS_A_ server); - } - return; - } else if (s < server->buf->len) { - // partly sent, move memory, wait for the next time to send - server->buf->len -= s; - server->buf->idx += s; - return; - } else { - // all sent out, wait for reading - server->buf->len = 0; - server->buf->idx = 0; - ss_io_stop(SS_A_ & server_send_ctx->io); - ss_io_start(SS_A_ & remote->recv_ctx->io); - } - } -} - -static void -delayed_connect_cb(SS_P_ ss_timer *watcher, int revents) -{ - server_t *server = ss_container_of(watcher, server_t, - delayed_connect_watcher); - remote_t *remote = server->remote; - - int r = connect(remote->fd, remote->addr, - get_sockaddr_len(remote->addr)); - - remote->addr = NULL; - - if (r == -1 && ss_socket_error() != CONNECT_IN_PROGRESS) { - ERROR("connect"); - close_and_free_remote(SS_A_ remote); - close_and_free_server(SS_A_ server); - return; - } else { - // listen to remote connected event - ss_io_start(SS_A_ & remote->send_ctx->io); - ss_timer_start(SS_A_ & remote->send_ctx->watcher); - } -} - -static void -remote_timeout_cb(SS_P_ ss_timer *watcher, int revents) -{ - remote_ctx_t *remote_ctx - = ss_container_of(watcher, remote_ctx_t, watcher); - - remote_t *remote = remote_ctx->remote; - server_t *server = remote->server; - - ss_timer_stop(SS_A_ watcher); - - close_and_free_remote(SS_A_ remote); - close_and_free_server(SS_A_ server); -} - -static void -remote_recv_cb(SS_P_ ss_io *w, int revents) -{ - remote_ctx_t *remote_recv_ctx = (remote_ctx_t *)w; - remote_t *remote = remote_recv_ctx->remote; - server_t *server = remote->server; - - ssize_t r = recv(remote->fd, server->buf->data, SOCKET_BUF_SIZE, 0); - - if (r == 0) { - // connection closed - close_and_free_remote(SS_A_ remote); - close_and_free_server(SS_A_ server); - return; - } else if (r == -1) { - if (ss_socket_error() == EAGAIN || ss_socket_error() == EWOULDBLOCK) { - // no data - // continue to wait for recv - return; - } else { - ERROR("remote recv"); - close_and_free_remote(SS_A_ remote); - close_and_free_server(SS_A_ server); - return; - } - } - - server->buf->len = r; - - int err = crypto->decrypt(server->buf, server->d_ctx, SOCKET_BUF_SIZE); - if (err == CRYPTO_ERROR) { - LOGE("invalid password or cipher"); - close_and_free_remote(SS_A_ remote); - close_and_free_server(SS_A_ server); - return; - } else if (err == CRYPTO_NEED_MORE) { - return; // Wait for more - } - - int s = send(server->fd, server->buf->data, server->buf->len, 0); - - if (s == -1) { - if (ss_socket_error() == EAGAIN || ss_socket_error() == EWOULDBLOCK) { - // no data, wait for send - server->buf->idx = 0; - ss_io_stop(SS_A_ & remote_recv_ctx->io); - ss_io_start(SS_A_ & server->send_ctx->io); - } else { - ERROR("send"); - close_and_free_remote(SS_A_ remote); - close_and_free_server(SS_A_ server); - return; - } - } else if (s < server->buf->len) { - server->buf->len -= s; - server->buf->idx = s; - ss_io_stop(SS_A_ & remote_recv_ctx->io); - ss_io_start(SS_A_ & server->send_ctx->io); - } - - // Disable TCP_NODELAY after the first response are sent - if (!remote->recv_ctx->connected && !no_delay) { - int opt = 0; - ss_setsockopt(server->fd, SOL_TCP, TCP_NODELAY, &opt, sizeof(opt)); - ss_setsockopt(remote->fd, SOL_TCP, TCP_NODELAY, &opt, sizeof(opt)); - } - remote->recv_ctx->connected = 1; -} - -static void -remote_send_cb(SS_P_ ss_io *w, int revents) -{ - remote_ctx_t *remote_send_ctx = (remote_ctx_t *)w; - remote_t *remote = remote_send_ctx->remote; - server_t *server = remote->server; - - ss_timer_stop(SS_A_ & remote_send_ctx->watcher); - - if (!remote_send_ctx->connected) { - int r = 0; - if (remote->addr == NULL) { - struct sockaddr_storage addr; - memset(&addr, 0, sizeof(struct sockaddr_storage)); - socklen_t len = sizeof addr; - r = getpeername(remote->fd, (struct sockaddr *)&addr, &len); - } - if (r == 0) { - remote_send_ctx->connected = 1; - - ss_io_stop(SS_A_ & remote_send_ctx->io); - ss_io_stop(SS_A_ & server->recv_ctx->io); - ss_io_start(SS_A_ & remote->recv_ctx->io); - - // send destaddr - buffer_t ss_addr_to_send; - buffer_t *abuf = &ss_addr_to_send; - balloc(abuf, SOCKET_BUF_SIZE); - - if (AF_INET6 == server->destaddr.ss_family) { // IPv6 - abuf->data[abuf->len++] = 4; // Type 4 is IPv6 address - - size_t in6_addr_len = sizeof(struct in6_addr); - memcpy(abuf->data + abuf->len, - &(((struct sockaddr_in6 *)&(server->destaddr))->sin6_addr), - in6_addr_len); - abuf->len += in6_addr_len; - memcpy(abuf->data + abuf->len, - &(((struct sockaddr_in6 *)&(server->destaddr))->sin6_port), - 2); - } else { // IPv4 - abuf->data[abuf->len++] = 1; // Type 1 is IPv4 address - - size_t in_addr_len = sizeof(struct in_addr); - memcpy(abuf->data + abuf->len, - &((struct sockaddr_in *)&(server->destaddr))->sin_addr, in_addr_len); - abuf->len += in_addr_len; - memcpy(abuf->data + abuf->len, - &((struct sockaddr_in *)&(server->destaddr))->sin_port, 2); - } - - abuf->len += 2; - - int err = crypto->encrypt(abuf, server->e_ctx, SOCKET_BUF_SIZE); - if (err) { - LOGE("invalid password or cipher"); - bfree(abuf); - close_and_free_remote(SS_A_ remote); - close_and_free_server(SS_A_ server); - return; - } - - err = crypto->encrypt(remote->buf, server->e_ctx, SOCKET_BUF_SIZE); - if (err) { - LOGE("invalid password or cipher"); - bfree(abuf); - close_and_free_remote(SS_A_ remote); - close_and_free_server(SS_A_ server); - return; - } - - bprepend(remote->buf, abuf, SOCKET_BUF_SIZE); - bfree(abuf); - } else { - ERROR("getpeername"); - // not connected - close_and_free_remote(SS_A_ remote); - close_and_free_server(SS_A_ server); - return; - } - } - - if (remote->buf->len == 0) { - // close and free - close_and_free_remote(SS_A_ remote); - close_and_free_server(SS_A_ server); - return; - } else { - // has data to send - int s = -1; - - if (remote->addr != NULL) { -#if defined(TCP_FASTOPEN_CONNECT) - int optval = 1; - if (ss_setsockopt(remote->fd, IPPROTO_TCP, TCP_FASTOPEN_CONNECT, - (void *)&optval, sizeof(optval)) < 0) - FATAL("failed to set TCP_FASTOPEN_CONNECT"); - s = connect(remote->fd, remote->addr, get_sockaddr_len(remote->addr)); - if (s == 0) - s = send(remote->fd, remote->buf->data, remote->buf->len, 0); -#elif defined(MSG_FASTOPEN) - s = sendto(remote->fd, remote->buf->data + remote->buf->idx, - remote->buf->len, MSG_FASTOPEN, remote->addr, - get_sockaddr_len(remote->addr)); -#else - FATAL("tcp fast open is not supported on this platform"); -#endif - - remote->addr = NULL; - - if (s == -1) { - if (ss_socket_error() == CONNECT_IN_PROGRESS) { - ss_io_start(SS_A_ & remote_send_ctx->io); - ss_timer_start(SS_A_ & remote_send_ctx->watcher); - } else { - if (ss_socket_error() == EOPNOTSUPP || ss_socket_error() == EPROTONOSUPPORT || - ss_socket_error() == ENOPROTOOPT) { - fast_open = 0; - LOGE("fast open is not supported on this platform"); - } else { - ERROR("fast_open_connect"); - } - close_and_free_remote(SS_A_ remote); - close_and_free_server(SS_A_ server); - } - return; - } - } else { - s = send(remote->fd, remote->buf->data + remote->buf->idx, - remote->buf->len, 0); - } - - if (s == -1) { - if (ss_socket_error() != EAGAIN && ss_socket_error() != EWOULDBLOCK) { - ERROR("send"); - // close and free - close_and_free_remote(SS_A_ remote); - close_and_free_server(SS_A_ server); - } - return; - } else if (s < remote->buf->len) { - // partly sent, move memory, wait for the next time to send - remote->buf->len -= s; - remote->buf->idx += s; - ss_io_start(SS_A_ & remote_send_ctx->io); - return; - } else { - // all sent out, wait for reading - remote->buf->len = 0; - remote->buf->idx = 0; - ss_io_stop(SS_A_ & remote_send_ctx->io); - ss_io_start(SS_A_ & server->recv_ctx->io); - } - } -} - -static remote_t * -new_remote(int fd, int timeout) -{ - remote_t *remote = (remote_t *)ss_malloc(sizeof(remote_t)); - memset(remote, 0, sizeof(remote_t)); - - remote->recv_ctx = (struct remote_ctx *)ss_malloc(sizeof(remote_ctx_t)); - remote->send_ctx = (struct remote_ctx *)ss_malloc(sizeof(remote_ctx_t)); - remote->buf = (buffer_t *)ss_malloc(sizeof(buffer_t)); - balloc(remote->buf, SOCKET_BUF_SIZE); - memset(remote->recv_ctx, 0, sizeof(remote_ctx_t)); - memset(remote->send_ctx, 0, sizeof(remote_ctx_t)); - remote->fd = fd; - remote->recv_ctx->remote = remote; - remote->recv_ctx->connected = 0; - remote->send_ctx->remote = remote; - remote->send_ctx->connected = 0; - - ss_io_init(&remote->recv_ctx->io, remote_recv_cb, fd, SS_READ); - ss_io_init(&remote->send_ctx->io, remote_send_cb, fd, SS_WRITE); - ss_timer_init(&remote->send_ctx->watcher, remote_timeout_cb, - min(MAX_CONNECT_TIMEOUT, timeout), 0); - - return remote; -} - -static void -free_remote(remote_t *remote) -{ - if (remote->server != NULL) { - remote->server->remote = NULL; - } - if (remote->buf != NULL) { - bfree(remote->buf); - ss_free(remote->buf); - } - ss_free(remote->recv_ctx); - ss_free(remote->send_ctx); - ss_free(remote); -} - -static void -close_and_free_remote(SS_P_ remote_t *remote) -{ - if (remote != NULL) { - ss_timer_stop(SS_A_ & remote->send_ctx->watcher); - ss_io_stop(SS_A_ & remote->send_ctx->io); - ss_io_stop(SS_A_ & remote->recv_ctx->io); - ss_socket_close(remote->fd); - free_remote(remote); - } -} - -static server_t * -new_server(int fd) -{ - server_t *server = (server_t *)ss_malloc(sizeof(server_t)); - memset(server, 0, sizeof(server_t)); - - server->recv_ctx = (struct server_ctx *)ss_malloc(sizeof(server_ctx_t)); - server->send_ctx = (struct server_ctx *)ss_malloc(sizeof(server_ctx_t)); - server->buf = (buffer_t *)ss_malloc(sizeof(buffer_t)); - balloc(server->buf, SOCKET_BUF_SIZE); - memset(server->recv_ctx, 0, sizeof(server_ctx_t)); - memset(server->send_ctx, 0, sizeof(server_ctx_t)); - server->fd = fd; - server->recv_ctx->server = server; - server->recv_ctx->connected = 0; - server->send_ctx->server = server; - server->send_ctx->connected = 0; - - server->e_ctx = (cipher_ctx_t *)ss_malloc(sizeof(cipher_ctx_t)); - server->d_ctx = (cipher_ctx_t *)ss_malloc(sizeof(cipher_ctx_t)); - crypto->ctx_init(crypto->cipher, server->e_ctx, 1); - crypto->ctx_init(crypto->cipher, server->d_ctx, 0); - cipher_ctx_pair(server->e_ctx, server->d_ctx); - - ss_io_init(&server->recv_ctx->io, server_recv_cb, fd, SS_READ); - ss_io_init(&server->send_ctx->io, server_send_cb, fd, SS_WRITE); - - ss_timer_init(&server->delayed_connect_watcher, delayed_connect_cb, 0.05, - 0); - - return server; -} - -static void -free_server(server_t *server) -{ - if (server->remote != NULL) { - server->remote->server = NULL; - } - if (server->e_ctx != NULL) { - crypto->ctx_release(server->e_ctx); - ss_free(server->e_ctx); - } - if (server->d_ctx != NULL) { - crypto->ctx_release(server->d_ctx); - ss_free(server->d_ctx); - } - if (server->buf != NULL) { - bfree(server->buf); - ss_free(server->buf); - } - ss_free(server->recv_ctx); - ss_free(server->send_ctx); - ss_free(server); -} - -static void -close_and_free_server(SS_P_ server_t *server) -{ - if (server != NULL) { - ss_io_stop(SS_A_ & server->send_ctx->io); - ss_io_stop(SS_A_ & server->recv_ctx->io); - ss_timer_stop(SS_A_ & server->delayed_connect_watcher); - ss_socket_close(server->fd); - free_server(server); - } -} - -static void -accept_cb(SS_P_ ss_io *w, int revents) -{ - listen_ctx_t *listener = (listen_ctx_t *)w; - struct sockaddr_storage destaddr; - memset(&destaddr, 0, sizeof(struct sockaddr_storage)); - - int err; - - int serverfd = ss_accept(listener->fd, NULL, NULL); - if (serverfd == -1) { - ERROR("accept"); - return; - } - - err = getdestaddr(serverfd, &destaddr); - if (err) { - ERROR("getdestaddr"); - return; - } - - setnonblocking(serverfd); - int opt = 1; - ss_setsockopt(serverfd, SOL_TCP, TCP_NODELAY, &opt, sizeof(opt)); -#ifdef SO_NOSIGPIPE - ss_setsockopt(serverfd, SOL_SOCKET, SO_NOSIGPIPE, &opt, sizeof(opt)); -#endif - - int index = (int)randombytes_uniform((uint32_t)listener->remote_num); - struct sockaddr *remote_addr = listener->remote_addr[index]; - - int protocol = IPPROTO_TCP; - if (listener->mptcp < 0) { - protocol = IPPROTO_MPTCP; // Enable upstream MPTCP - } - int remotefd = ss_socket(remote_addr->sa_family, SOCK_STREAM, protocol); - if (remotefd == -1) { - ERROR("socket"); - return; - } - - // Set flags - ss_setsockopt(remotefd, SOL_TCP, TCP_NODELAY, &opt, sizeof(opt)); -#ifdef SO_NOSIGPIPE - ss_setsockopt(remotefd, SOL_SOCKET, SO_NOSIGPIPE, &opt, sizeof(opt)); -#endif - - // Enable TCP keepalive feature - int keepAlive = 1; - int keepIdle = 40; - int keepInterval = 20; - int keepCount = 5; - ss_setsockopt(remotefd, SOL_SOCKET, SO_KEEPALIVE, (void *)&keepAlive, sizeof(keepAlive)); - ss_setsockopt(remotefd, SOL_TCP, TCP_KEEPIDLE, (void *)&keepIdle, sizeof(keepIdle)); - ss_setsockopt(remotefd, SOL_TCP, TCP_KEEPINTVL, (void *)&keepInterval, sizeof(keepInterval)); - ss_setsockopt(remotefd, SOL_TCP, TCP_KEEPCNT, (void *)&keepCount, sizeof(keepCount)); - - // Set non blocking - setnonblocking(remotefd); - - if (listener->tos >= 0) { - int rc = ss_setsockopt(remotefd, IPPROTO_IP, IP_TOS, &listener->tos, sizeof(listener->tos)); - if (rc < 0 && ss_socket_error() != ENOPROTOOPT) { - LOGE("setting ipv4 dscp failed: %d", ss_socket_error()); - } -#ifdef IPV6_TCLASS - rc = ss_setsockopt(remotefd, IPPROTO_IPV6, IPV6_TCLASS, &listener->tos, sizeof(listener->tos)); - if (rc < 0 && ss_socket_error() != ENOPROTOOPT) { - LOGE("setting ipv6 dscp failed: %d", ss_socket_error()); - } -#endif - } - - // Enable out-of-tree MPTCP - if (listener->mptcp > 1) { - int err = ss_setsockopt(remotefd, SOL_TCP, listener->mptcp, &opt, sizeof(opt)); - if (err == -1) { - ERROR("failed to enable out-of-tree multipath TCP"); - } - } else if (listener->mptcp == 1) { - int i = 0; - while ((listener->mptcp = mptcp_enabled_values[i]) > 0) { - int err = ss_setsockopt(remotefd, SOL_TCP, listener->mptcp, &opt, sizeof(opt)); - if (err != -1) { - break; - } - i++; - } - if (listener->mptcp == 0) { - ERROR("failed to enable out-of-tree multipath TCP"); - } - } - - if (tcp_outgoing_sndbuf > 0) { - ss_setsockopt(remotefd, SOL_SOCKET, SO_SNDBUF, &tcp_outgoing_sndbuf, sizeof(int)); - } - - if (tcp_outgoing_rcvbuf > 0) { - ss_setsockopt(remotefd, SOL_SOCKET, SO_RCVBUF, &tcp_outgoing_rcvbuf, sizeof(int)); - } - - server_t *server = new_server(serverfd); - remote_t *remote = new_remote(remotefd, listener->timeout); - server->remote = remote; - remote->server = server; - server->destaddr = destaddr; - - if (fast_open) { - // save remote addr for fast open - remote->addr = remote_addr; - ss_timer_start(SS_A_ & server->delayed_connect_watcher); - } else { - int r = connect(remotefd, remote_addr, get_sockaddr_len(remote_addr)); - - if (r == -1 && ss_socket_error() != CONNECT_IN_PROGRESS) { - ERROR("connect"); - close_and_free_remote(SS_A_ remote); - close_and_free_server(SS_A_ server); - return; - } - // listen to remote connected event - ss_io_start(SS_A_ & remote->send_ctx->io); - ss_timer_start(SS_A_ & remote->send_ctx->watcher); - } - ss_io_start(SS_A_ & server->recv_ctx->io); -} - static void signal_cb(SS_P_ ss_signal *w, int revents) { @@ -1492,16 +824,14 @@ Same behavior as `-T`; see that option for details. Short alias: `-T`. // Setup keys LOGI("initializing ciphers... %s", method); - crypto = crypto_init(password, key, method); - if (crypto == NULL) + ss::relay::Method relay_method; + if (!relay_method.init(method, password, key, ss::crypto::Role::client)) FATAL("failed to initialize ciphers"); // Setup proxy context - struct listen_ctx listen_ctx; - memset(&listen_ctx, 0, sizeof(struct listen_ctx)); - listen_ctx.remote_num = remote_num; - listen_ctx.remote_addr = (struct sockaddr **)ss_malloc(sizeof(struct sockaddr *) * remote_num); - memset(listen_ctx.remote_addr, 0, sizeof(struct sockaddr *) * remote_num); + struct sockaddr_storage remote_storage[MAX_REMOTE_NUM]; + int remote_count = 0; + memset(remote_storage, 0, sizeof(remote_storage)); for (i = 0; i < remote_num; i++) { char *host = remote_addr[i].host; char *port = remote_addr[i].port == NULL ? remote_port : @@ -1510,25 +840,62 @@ Same behavior as `-T`; see that option for details. Short alias: `-T`. host = plugin_host; port = plugin_port; } - struct sockaddr_storage *storage = (struct sockaddr_storage *)ss_malloc(sizeof(struct sockaddr_storage)); - memset(storage, 0, sizeof(struct sockaddr_storage)); - if (get_sockaddr(host, port, storage, 1, ipv6first) == -1) { + if (get_sockaddr(host, port, &remote_storage[i], 1, ipv6first) == -1) { FATAL("failed to resolve the provided hostname"); } - listen_ctx.remote_addr[i] = (struct sockaddr *)storage; + ++remote_count; if (plugin != NULL) break; } - listen_ctx.timeout = timeout_secs; - listen_ctx.mptcp = mptcp; struct ss_loop *loop = SS_DEFAULT; + ss::event::Loop tcp_loop; + ss::relay::TcpRelay tcp_relay; + ss::relay::UdpRelay udp_relay; + bool tcp_running = false; + if (!tcp_loop.adopt(ss_native_loop(loop))) { + FATAL("failed to set up the event loop"); + } + if (mode != TCP_ONLY) { + ss::relay::UdpConfig udp_config; + udp_config.kind = ss::relay::Kind::redir; + udp_config.timeout_secs = timeout_secs; + udp_config.max_sessions = 256; + udp_config.mtu = mtu; + udp_config.reuse_port = reuse_port != 0; + char *host = remote_addr[0].host; + char *port = remote_addr[0].port == NULL ? remote_port : remote_addr[0].port; + if (get_sockaddr(host, port, &udp_config.remote, 1, ipv6first) == -1) { + FATAL("failed to resolve the provided hostname"); + } + if (!udp_relay.init(tcp_loop, relay_method, udp_config, NULL)) { + FATAL("failed to initialize UDP relay"); + } + } + if (mode != UDP_ONLY) { + ss::relay::TcpConfig tcp_config; + tcp_config.kind = ss::relay::Kind::redir; + tcp_config.remotes = { remote_storage, (size_t)remote_count }; + tcp_config.timeout_secs = timeout_secs; + tcp_config.max_connections = DEFAULT_MAX_CONNECTIONS; + tcp_config.fast_open = fast_open != 0; + tcp_config.no_delay = no_delay != 0; + tcp_config.tproxy = tcp_tproxy != 0; + tcp_config.upstream.sndbuf = tcp_outgoing_sndbuf; + tcp_config.upstream.rcvbuf = tcp_outgoing_rcvbuf; + tcp_config.upstream.mptcp = mptcp; + tcp_config.upstream.keepalive_tuned = true; + if (!tcp_relay.init(tcp_loop, relay_method, tcp_config, NULL)) { + FATAL("failed to set up the TCP relay"); + } + tcp_running = true; + } - listen_ctx_t *listen_ctx_current = &listen_ctx; + int tos = -1; do { - if (listen_ctx_current->tos) { - LOGI("listening at %s:%s (TOS 0x%x)", local_addr, local_port, listen_ctx_current->tos); + if (tos >= 0) { + LOGI("listening at %s:%s (TOS 0x%x)", local_addr, local_port, tos); } else { LOGI("listening at %s:%s", local_addr, local_port); } @@ -1543,28 +910,16 @@ Same behavior as `-T`; see that option for details. Short alias: `-T`. if (listen(listenfd, SOMAXCONN) == -1) { FATAL("listen() error"); } - setnonblocking(listenfd); - - listen_ctx_current->fd = listenfd; - - ss_io_init(&listen_ctx_current->io, accept_cb, listenfd, SS_READ); - ss_io_start(loop, &listen_ctx_current->io); + if (!tcp_relay.listen(listenfd, tos)) { + FATAL("failed to watch the listening socket"); + } } // Setup UDP if (mode != TCP_ONLY) { LOGI("UDP relay enabled"); - char *host = remote_addr[0].host; - char *port = remote_addr[0].port == NULL ? remote_port : remote_addr[0].port; - struct sockaddr_storage *storage = (struct sockaddr_storage *)ss_malloc(sizeof(struct sockaddr_storage)); - memset(storage, 0, sizeof(struct sockaddr_storage)); - if (get_sockaddr(host, port, storage, 1, ipv6first) == -1) { - FATAL("failed to resolve the provided hostname"); - } - struct sockaddr *addr = (struct sockaddr *)storage; - if (init_udprelay(local_addr, local_port, addr, - get_sockaddr_len(addr), mtu, crypto, - listen_ctx_current->timeout, NULL) == -1) { + auto udp_fd = ss::relay::open_udp_listener(local_addr, local_port, true, reuse_port != 0); + if (!udp_fd || !udp_relay.listen(*udp_fd)) { FATAL("failed to initialize UDP relay"); } } @@ -1575,10 +930,8 @@ Same behavior as `-T`; see that option for details. Short alias: `-T`. // Handle additionals TOS/DSCP listening ports if (dscp_num > 0) { - listen_ctx_current = (listen_ctx_t *)ss_malloc(sizeof(listen_ctx_t)); - listen_ctx_current = (listen_ctx_t *)memcpy(listen_ctx_current, &listen_ctx, sizeof(listen_ctx_t)); - local_port = dscp[dscp_num - 1].port; - listen_ctx_current->tos = dscp[dscp_num - 1].dscp << 2; + local_port = dscp[dscp_num - 1].port; + tos = dscp[dscp_num - 1].dscp << 2; } } while (dscp_num-- > 0); @@ -1597,9 +950,12 @@ Same behavior as `-T`; see that option for details. Short alias: `-T`. stop_plugin(); } - for (i = 0; i < remote_num; i++) - ss_free(listen_ctx.remote_addr[i]); - ss_free(listen_ctx.remote_addr); + tcp_relay.shutdown(); + udp_relay.shutdown(); + while (!tcp_relay.closed() || !udp_relay.closed()) { + tcp_loop.run_nowait(); + } + (void)tcp_running; return ret_val; } diff --git a/src/relay/codec.cpp b/src/relay/codec.cpp index 327c5b0a..04c640e6 100644 --- a/src/relay/codec.cpp +++ b/src/relay/codec.cpp @@ -5,6 +5,8 @@ #include #include "crypto.h" +#include "ppbloom.h" +#include "utils.h" // crypto.h defines min/max macros for the C sources. #undef min @@ -17,7 +19,19 @@ Status Method::init(const char *method, const char *password, const char *key, R if (method == nullptr) { return fail(Errc::config); } + if (sodium_init() == -1) { + return fail(Errc::crypto); + } role_ = role; + // The salt/IV replay filter shared by every connection and datagram. + if (!bloom_ready_) { + if (role == Role::server) { + ppbloom_init(BF_NUM_ENTRIES_FOR_SERVER, BF_ERROR_RATE_FOR_SERVER); + } else { + ppbloom_init(BF_NUM_ENTRIES_FOR_CLIENT, BF_ERROR_RATE_FOR_CLIENT); + } + bloom_ready_ = true; + } if (auto spec = crypto::find_aead2022(method)) { // The password field may carry the base64 PSK too. SS_TRY_ASSIGN(psk, crypto::Psk::from_base64(*spec, key != nullptr ? key : password)); @@ -36,6 +50,7 @@ Status Method::init(const char *method, const char *password, const char *key, R } #if SS_ENABLE_LEGACY if (auto spec = crypto::find_stream(method)) { + LOGI("Stream ciphers are insecure, therefore deprecated, and should be almost always avoided."); if (key != nullptr) { crypto::StreamKey stream{ *spec, {} }; if (crypto_parse_key(key, stream.bytes.data(), spec->key_len) != spec->key_len) { @@ -52,6 +67,13 @@ Status Method::init(const char *method, const char *password, const char *key, R return fail(Errc::config); } +Method::~Method() +{ + if (bloom_ready_) { + ppbloom_free(); + } +} + Status Encoder::init(Method &method, bool plain, const Decoder *peer, bool printable_salt) noexcept { if (plain) { diff --git a/src/relay/codec.hpp b/src/relay/codec.hpp index ffd3ced5..df202063 100644 --- a/src/relay/codec.hpp +++ b/src/relay/codec.hpp @@ -30,11 +30,18 @@ using crypto::Role; class Method { public: // The same inputs as crypto_init(): a method name and either a password - // or a base64 key (AEAD-2022 needs the key). Replay protection uses the - // process-wide ppbloom filter, which crypto_init() sets up. + // or a base64 key (AEAD-2022 needs the key). Also initialises libsodium + // and the process-wide ppbloom replay filter, sized for the role. Status init(const char *method, const char *password, const char *key, Role role) noexcept; + ~Method(); Role role() const noexcept { return role_; } + // The key, by family (exactly one is set). + const crypto::MasterKey *aead() const noexcept { return std::get_if(&key_); } + const crypto::Psk *psk() const noexcept { return std::get_if(&key_); } +#if SS_ENABLE_LEGACY + const crypto::StreamKey *stream() const noexcept { return std::get_if(&key_); } +#endif bool is_2022() const noexcept { return std::holds_alternative(key_); } // Room a first seal() needs on top of the plaintext. static constexpr std::size_t kMaxOverhead = 2048; @@ -55,7 +62,8 @@ private: using Key = std::variant; #endif Key key_{}; - Role role_ = Role::client; + Role role_ = Role::client; + bool bloom_ready_ = false; crypto::SaltPool salt_pool_; }; diff --git a/src/relay/tcp.cpp b/src/relay/tcp.cpp index 00ec72a9..a4386ebf 100644 --- a/src/relay/tcp.cpp +++ b/src/relay/tcp.cpp @@ -145,7 +145,7 @@ Status TcpRelay::init(event::Loop &loop, Method &method, const TcpConfig &config return {}; } -Status TcpRelay::listen(ss_socket_t fd) noexcept +Status TcpRelay::listen(ss_socket_t fd, int tos) noexcept { if (listener_count_ == kMaxListeners || loop_ == nullptr) { return fail(Errc::exhausted); @@ -153,6 +153,7 @@ Status TcpRelay::listen(ss_socket_t fd) noexcept SS_TRY(set_nonblocking(fd)); Listener &l = listeners_[listener_count_]; l.relay = this; + l.tos = tos; SS_TRY(l.poll.open(*loop_, l, fd)); ++listener_count_; return l.poll.want(event::readable); @@ -210,6 +211,7 @@ void TcpRelay::accept_from(Listener &listener) noexcept Connection &c = **conn; std::uint32_t at = pool_.handle_of(&c).index; owners_[at] = std::move(*conn); + c.tos_ = listener.tos; (void)c.start(fd); } } @@ -802,8 +804,13 @@ Flow Connection::start_upstream(const sockaddr *target, bool direct) noexcept (void)to_upstream_.commit(*w); } - bool protect = cfg.vpn && !loopback_v4(target); - auto fd = open_upstream(target->sa_family, cfg.upstream, protect); + bool protect = cfg.vpn && !loopback_v4(target); + SocketTuning tuning = cfg.upstream; + if (tos_ >= 0) { + tuning.tos = tos_; + } + auto fd = open_upstream(target->sa_family, tuning, protect); + cfg.upstream.mptcp = tuning.mptcp; // remember the option that worked if (!fd) { return close(); } diff --git a/src/relay/tcp.hpp b/src/relay/tcp.hpp index 6ad4e1e8..3f3b03ee 100644 --- a/src/relay/tcp.hpp +++ b/src/relay/tcp.hpp @@ -159,6 +159,7 @@ private: bool dns_ok_ = false; bool replied_ = false; // first upstream bytes reached the client void *hook_state_ = nullptr; + int tos_ = -1; // from the listener that accepted it sockaddr_storage dns_answer_{}; }; @@ -172,7 +173,8 @@ public: // `resolver` is needed by ss-server and by ss-local with an ACL. Status init(event::Loop &loop, Method &method, const TcpConfig &config, Resolver *resolver) noexcept; // Start accepting on a bound, listening socket; the relay owns it now. - Status listen(ss_socket_t fd) noexcept; + // Upstream connections of its clients get IP TOS `tos` when >= 0. + Status listen(ss_socket_t fd, int tos = -1) noexcept; // Close every connection and listener. Run the loop until closed(). void shutdown() noexcept; @@ -186,6 +188,7 @@ private: struct Listener { TcpRelay *relay = nullptr; + int tos = -1; event::Flow on_ready(int events) noexcept; event::Poll poll; }; diff --git a/src/relay/udp.cpp b/src/relay/udp.cpp new file mode 100644 index 00000000..8af2bc9b --- /dev/null +++ b/src/relay/udp.cpp @@ -0,0 +1,867 @@ +/* SPDX-License-Identifier: GPL-3.0-or-later */ +#include "relay/udp.hpp" + +#include +#include +#include + +#include + +#ifndef _WIN32 +#include +#include +#endif + +#include "jconf.h" +#include "utils.h" + +extern "C" int verbose; + +#ifndef SOL_IP +#define SOL_IP IPPROTO_IP +#endif +#ifndef SOL_IPV6 +#define SOL_IPV6 IPPROTO_IPV6 +#endif +#ifdef __linux__ +#ifndef IP_TRANSPARENT +#define IP_TRANSPARENT 19 +#endif +#ifndef IPV6_TRANSPARENT +#define IPV6_TRANSPARENT 75 +#endif +#ifndef IP_RECVORIGDSTADDR +#define IP_RECVORIGDSTADDR 20 +#endif +#ifndef IPV6_RECVORIGDSTADDR +#define IPV6_RECVORIGDSTADDR 74 +#endif +#endif + +namespace ss::relay { + +using event::Flow; + +namespace { + +constexpr int kQosTos = 46 << 2; // DSCP EF, as udprelay.cpp +constexpr int kReceiveBurst = 64; +constexpr std::uint64_t kTtl2022 = 600; // seconds an idle AEAD-2022 client session is kept +constexpr std::size_t kPacketHeader = 1 + 28 + 2 + 64; // as udprelay.h + +void set_qos(ss_socket_t fd) noexcept +{ + int tos = kQosTos; + if (ss_setsockopt(fd, IPPROTO_IP, IP_TOS, &tos, sizeof(tos)) < 0 && ss_socket_error() != ENOPROTOOPT) { + LOGE("setting ipv4 dscp failed: %d", ss_socket_error()); + } +#ifdef IPV6_TCLASS + if (ss_setsockopt(fd, IPPROTO_IPV6, IPV6_TCLASS, &tos, sizeof(tos)) < 0 && ss_socket_error() != ENOPROTOOPT) { + LOGE("setting ipv6 dscp failed: %d", ss_socket_error()); + } +#endif +} + +void no_fragment(ss_socket_t fd) noexcept +{ +#if defined(__linux__) && defined(IP_MTU_DISCOVER) + int val = IP_PMTUDISC_DO; + (void)ss_setsockopt(fd, IPPROTO_IP, IP_MTU_DISCOVER, &val, sizeof(val)); +#else + (void)fd; +#endif +} + +bool valid_socket(ss_socket_t fd) noexcept +{ +#ifdef _WIN32 + return fd != INVALID_SOCKET; +#else + return fd >= 0; +#endif +} + +std::uint64_t pack_ptr(void *p) noexcept +{ + return static_cast(reinterpret_cast(p)); +} + +void *unpack_ptr(std::uint64_t v) noexcept +{ + return reinterpret_cast(static_cast(v)); +} + +// ss-redir replies come from the original destination: a transparent +// socket bound to it, one per datagram as in the C relay. +void send_as(const sockaddr_storage &from, const sockaddr_storage &to, std::span data, + bool reuse_port) noexcept +{ +#ifdef __linux__ + sockaddr_storage src = from, dst = to; + auto map = [](sockaddr_storage &a) { + if (a.ss_family != AF_INET) { + return; + } + sockaddr_in v4; + std::memcpy(&v4, &a, sizeof(v4)); + sockaddr_in6 v6{}; + v6.sin6_family = AF_INET6; + v6.sin6_port = v4.sin_port; + v6.sin6_addr.s6_addr[10] = 0xff; + v6.sin6_addr.s6_addr[11] = 0xff; + std::memcpy(&v6.sin6_addr.s6_addr[12], &v4.sin_addr, 4); + a = {}; + std::memcpy(&a, &v6, sizeof(v6)); + }; + map(src); + map(dst); + ss_socket_t fd = ss_socket(AF_INET6, SOCK_DGRAM, 0); + if (!valid_socket(fd)) { + ERROR("[udp] remote_recv_socket"); + return; + } + int opt = 1; + if (ss_setsockopt(fd, SOL_IPV6, IPV6_TRANSPARENT, &opt, sizeof(opt)) + || ss_setsockopt(fd, SOL_SOCKET, SO_REUSEADDR, &opt, sizeof(opt))) { + ERROR("[udp] remote_recv_setsockopt"); + ss_socket_close(fd); + return; + } + if (reuse_port && set_reuseport(fd) != 0) { + ERROR("[udp] remote_recv port_reuse"); + } + set_qos(fd); + if (bind(fd, reinterpret_cast(&src), sizeof(sockaddr_in6)) != 0) { + ERROR("[udp] remote_recv_bind"); + ss_socket_close(fd); + return; + } + if (sendto(fd, data.data(), data.size(), 0, reinterpret_cast(&dst), sizeof(sockaddr_in6)) == -1 + && ss_socket_error() != EAGAIN && ss_socket_error() != EWOULDBLOCK) { + ERROR("[udp] remote_recv_sendto"); + } + ss_socket_close(fd); +#else + (void)from; + (void)to; + (void)data; + (void)reuse_port; +#endif +} + +} // namespace + +Result open_udp_listener(const char *host, const char *port, bool transparent, bool reuse_port) noexcept +{ + addrinfo hints{}; + hints.ai_family = AF_UNSPEC; + hints.ai_socktype = SOCK_DGRAM; + hints.ai_flags = AI_PASSIVE | AI_ADDRCONFIG; + hints.ai_protocol = IPPROTO_UDP; + addrinfo *result = nullptr; + int s = getaddrinfo(host, port, &hints, &result); + if (s != 0 || result == nullptr) { + LOGE("[udp] getaddrinfo: %s", gai_strerror(s)); + return fail(Errc::config); + } + // getaddrinfo(NULL, AI_PASSIVE) gives 0.0.0.0 then ::; the dual-stack + // :: covers both, so prefer it. + addrinfo *rp = result; + if (host == nullptr) { + for (addrinfo *p = result; p != nullptr; p = p->ai_next) { + if (p->ai_family == AF_INET6) { + rp = p; + break; + } + } + } + ss_socket_t fd = static_cast(-1); + for (; rp != nullptr; rp = rp->ai_next) { + fd = ss_socket(rp->ai_family, rp->ai_socktype, rp->ai_protocol); + if (!valid_socket(fd)) { + continue; + } + if (rp->ai_family == AF_INET6) { + int v6only = host != nullptr ? 1 : 0; + (void)ss_setsockopt(fd, IPPROTO_IPV6, IPV6_V6ONLY, &v6only, sizeof(v6only)); + } + int opt = 1; + (void)ss_setsockopt(fd, SOL_SOCKET, SO_REUSEADDR, &opt, sizeof(opt)); + set_nosigpipe(fd); + if (reuse_port && set_reuseport(static_cast(fd)) == 0) { + LOGI("udp port reuse enabled"); + } + set_qos(fd); +#ifdef __linux__ + if (transparent) { + int sol = rp->ai_family == AF_INET ? SOL_IP : SOL_IPV6; + int flag_t = rp->ai_family == AF_INET ? IP_TRANSPARENT : IPV6_TRANSPARENT; + int flag_r = rp->ai_family == AF_INET ? IP_RECVORIGDSTADDR : IPV6_RECVORIGDSTADDR; + if (ss_setsockopt(fd, sol, flag_t, &opt, sizeof(opt))) { + ERROR("[udp] setsockopt IP_TRANSPARENT"); + exit(EXIT_FAILURE); + } + if (ss_setsockopt(fd, sol, flag_r, &opt, sizeof(opt))) { + FATAL("[udp] setsockopt IP_RECVORIGDSTADDR"); + } + } +#else + (void)transparent; +#endif + if (bind(fd, rp->ai_addr, rp->ai_addrlen) == 0) { + break; + } + ERROR("[udp] bind"); + ss_socket_close(fd); + fd = static_cast(-1); + } + freeaddrinfo(result); + if (!valid_socket(fd)) { + return fail(Errc::io); + } + no_fragment(fd); + return fd; +} + +// ------------------------------------------------------------- sessions + +void Shared2022::destroy(Shared2022 *self) noexcept +{ + UdpRelay *relay = self->relay_; + relay->shared_pool_.adopt(self).reset(); +} + +void UdpSession::begin_close(CloseCompletion done) noexcept +{ + if (static_cast &>(*this).linked()) { + event::DeadlineList::remove(*this); + } + server2022_.reset(); + done_ = done; + closer_.arm(CloseCompletion( + [](void *self, std::uint32_t) { + auto *s = static_cast(self); + CloseCompletion d = s->done_; + d(); + }, + this, 0)); + socket_.close(closer_); + closer_.seal(); +} + +Flow UdpSession::on_readable(int) noexcept +{ + relay_->reply(*this); + return Flow::proceed; +} + +// ---------------------------------------------------------------- relay + +UdpRelay::~UdpRelay() +{ + sessions_.clear(); + shared_.clear(); +} + +Status UdpRelay::init(event::Loop &loop, Method &method, const UdpConfig &config, Resolver *resolver) noexcept +{ + if (loop_ != nullptr || config.max_sessions == 0) { + return fail(Errc::invalid); + } + if (config.mtu > 0 && config.mtu <= static_cast(kPacketHeader)) { + LOGE("MTU must be greater than %d", static_cast(kPacketHeader)); + return fail(Errc::config); + } + loop_ = &loop; + method_ = &method; + resolver_ = resolver; + config_ = config; + std::uint64_t seed = 0; + randombytes_buf(&seed, sizeof(seed)); + SS_TRY(pool_.reserve(config.max_sessions)); + SS_TRY(sessions_.reserve(config.max_sessions, seed)); + if (config.kind == Kind::server) { + if (method.psk() != nullptr) { + SS_TRY(shared_pool_.reserve(config.max_sessions_2022)); + SS_TRY(shared_.reserve(config.max_sessions_2022, seed ^ 0x9E3779B97F4A7C15ULL)); + } + SS_TRY(pending_.reserve(config.max_pending_dns)); + } + idle_.set_timeout(static_cast(std::max(config.timeout_secs, MIN_UDP_TIMEOUT)) * 1000); + SS_TRY(timer_.open(loop, *this)); + own_closed_ = false; + return {}; +} + +Status UdpRelay::listen(ss_socket_t fd) noexcept +{ + if (listener_count_ == kMaxListeners || loop_ == nullptr) { + return fail(Errc::exhausted); + } + SS_TRY(set_nonblocking(fd)); + Listener &l = listeners_[listener_count_]; + l.relay = this; + l.index = static_cast(listener_count_); + SS_TRY(l.poll.open(*loop_, l, fd)); + ++listener_count_; + return l.poll.want(event::readable); +} + +Flow UdpRelay::Listener::on_ready(int) noexcept +{ + relay->receive(*this); + return Flow::proceed; +} + +std::uint64_t UdpRelay::now_secs() const noexcept +{ + return loop_->now() / 1000; +} + +void UdpRelay::receive(Listener &listener) noexcept +{ + for (int i = 0; i < kReceiveBurst && !shutting_down_; ++i) { + sockaddr_storage from{}; + sockaddr_storage original{}; + bool have_original = false; + ssize_t r = -1; +#ifdef __linux__ + if (config_.kind == Kind::redir) { + char control[64] = {}; + iovec iov{ in_.data(), in_.size() }; + msghdr msg{}; + msg.msg_name = &from; + msg.msg_namelen = sizeof(from); + msg.msg_control = control; + msg.msg_controllen = sizeof(control); + msg.msg_iov = &iov; + msg.msg_iovlen = 1; + r = recvmsg(listener.poll.fd(), &msg, 0); + if (r >= 0) { + for (cmsghdr *c = CMSG_FIRSTHDR(&msg); c != nullptr; c = CMSG_NXTHDR(&msg, c)) { + if (c->cmsg_level == SOL_IP && c->cmsg_type == IP_RECVORIGDSTADDR) { + std::memcpy(&original, CMSG_DATA(c), sizeof(sockaddr_in)); + original.ss_family = AF_INET; + have_original = true; + } else if (c->cmsg_level == SOL_IPV6 && c->cmsg_type == IPV6_RECVORIGDSTADDR) { + std::memcpy(&original, CMSG_DATA(c), sizeof(sockaddr_in6)); + original.ss_family = AF_INET6; + have_original = true; + } + } + } + } else +#endif + { + socklen_t len = sizeof(from); + r = recvfrom(listener.poll.fd(), reinterpret_cast(in_.data()), in_.size(), 0, + reinterpret_cast(&from), &len); + } + if (r < 0) { + int err = ss_socket_error(); + if (err != EAGAIN && err != EWOULDBLOCK && err != EINTR) { + ERROR("[udp] server_recv_recvfrom"); + } + return; + } + if (verbose) { + LOGI("[udp] server receive a packet"); + } + std::span packet(in_.data(), static_cast(r)); + if (config_.kind == Kind::server) { + server_packet(listener, from, packet); + } else if (config_.kind == Kind::redir) { + if (!have_original) { + LOGE("[udp] unable to get dest addr"); + continue; + } + client_packet(listener, from, packet, &original); + } else { + client_packet(listener, from, packet, nullptr); + } + } +} + +Result UdpRelay::open_upstream_socket(int family) noexcept +{ + ss_socket_t fd = ss_socket(family, SOCK_DGRAM, 0); + if (!valid_socket(fd)) { + ERROR("[udp] cannot create socket"); + return fail(Errc::io); + } + const sockaddr_storage *source = family == AF_INET ? config_.bind_v4 : config_.bind_v6; + if (source != nullptr && source->ss_family == family) { + if (bind_to_addr(const_cast(source), static_cast(fd)) == -1) { + ERROR("bind_to_addr"); + ss_socket_close(fd); + return fail(Errc::io); + } + } else { + sockaddr_storage any{}; + any.ss_family = static_cast(family); + socklen_t len = family == AF_INET6 ? sizeof(sockaddr_in6) : sizeof(sockaddr_in); + if (bind(fd, reinterpret_cast(&any), len) != 0) { + ERROR("[udp] cannot bind socket"); + ss_socket_close(fd); + return fail(Errc::io); + } + } + no_fragment(fd); + (void)set_nonblocking(fd); +#ifdef SO_BROADCAST + if (config_.kind == Kind::server) { + int opt = 1; + (void)ss_setsockopt(fd, SOL_SOCKET, SO_BROADCAST, &opt, sizeof(opt)); + } +#endif + set_nosigpipe(fd); + set_qos(fd); +#ifdef SET_INTERFACE + if (config_.iface != nullptr && setinterface(static_cast(fd), config_.iface) == -1) { + ERROR("setinterface"); + } +#endif + if (config_.vpn && config_.protect != nullptr && config_.protect(static_cast(fd)) == -1) { + ERROR("protect_socket"); + ss_socket_close(fd); + return fail(Errc::io); + } + return fd; +} + +UdpSession *UdpRelay::session_for(std::uint8_t listener, const sockaddr_storage &client, int family) noexcept +{ + std::array raw{}; + std::memcpy(raw.data(), &family, sizeof(int)); + std::memcpy(raw.data() + sizeof(int), &client, sizeof(sockaddr_storage)); + auto key = Key::from(raw); + if (PoolPtr *found = sessions_.find(*key)) { + return found->get(); + } + if (verbose) { + LOGI("[%s] [udp] cache miss", config_.label); + } + if (sessions_.full()) { + (void)sessions_.pop_oldest(); // closes the least recently used session + } + auto fd = open_upstream_socket(family); + if (!fd) { + return nullptr; + } + auto session = pool_.acquire(*this, listener); + if (!session) { + ss_socket_close(*fd); + return nullptr; + } + UdpSession &s = **session; + if (!s.socket_.open(*loop_, s, *fd)) { + ss_socket_close(*fd); + return nullptr; + } + s.client_ = client; + s.key_ = raw; + s.key_len_ = static_cast(raw.size()); + (void)s.socket_.want(event::readable); + auto inserted = sessions_.emplace(*key, std::move(*session)); + if (!inserted) { + return nullptr; // the PoolPtr went out of scope: closed again + } + return &s; +} + +void UdpRelay::expire(UdpSession &session) noexcept +{ + if (verbose) { + LOGI("[udp] connection timeout"); + } + auto key = Key::from(session.key_); + (void)sessions_.take(*key); // the PoolPtr dies here and closes the session +} + +void UdpRelay::client_packet(Listener &listener, const sockaddr_storage &from, std::span packet, + const sockaddr_storage *original_dst) noexcept +{ + // Build [target address][payload] in out_. + std::size_t n = 0; + if (config_.kind == Kind::local) { +#ifdef __ANDROID__ + if (config_.tx != nullptr) { + *config_.tx += packet.size(); + } +#endif + if (packet.size() < 3) { + LOGE("[udp] invalid SOCKS5 UDP header"); + return; + } + if (packet[2] != 0) { + LOGE("[udp] drop a message since frag is not 0, but %d", packet[2]); + return; + } + SocksTarget target; + if (parse_socks_addr(packet.subspan(3), target) != ParseResult::complete) { + LOGE("[udp] invalid header"); + return; + } + n = packet.size() - 3; + std::memcpy(out_.data(), packet.data() + 3, n); + } else { + std::size_t header = 0; + if (config_.kind == Kind::tunnel) { + std::memcpy(out_.data(), config_.tunnel_target.data(), config_.tunnel_target_len); + header = config_.tunnel_target_len; + } else { + header = encode_socks_addr(reinterpret_cast(original_dst), out_.data()); + if (header == 0) { + LOGE("[udp] failed to parse tproxy addr"); + return; + } + } + std::memcpy(out_.data() + header, packet.data(), packet.size()); + n = header + packet.size(); + } + UdpSession *session = session_for(listener.index, from, config_.remote.ss_family); + if (session == nullptr) { + return; + } + idle_.touch(*session, loop_->now()); + schedule(); + + // Seal into in_ (the received packet is no longer needed). + std::span plain(out_.data(), n); + Result sealed = fail(Errc::config); + if (const crypto::MasterKey *master = method_->aead()) { + sealed = crypto::aead_udp_seal(*master, plain, in_, crypto::ReplayFilter::ppbloom()); + } else if (const crypto::Psk *psk = method_->psk()) { + if (n + crypto::udp2022_overhead(*psk, Role::client) <= in_.size()) { + sealed = crypto::udp2022_seal(*psk, Role::client, session->client2022_, plain, in_); + } + } +#if SS_ENABLE_LEGACY + else if (const crypto::StreamKey *stream = method_->stream()) { + sealed = crypto::stream_udp_seal(*stream, plain, in_, crypto::ReplayFilter::ppbloom()); + } +#endif + if (!sealed) { + return; // drop the packet silently + } + const sockaddr *remote = reinterpret_cast(&config_.remote); + if (sendto(session->socket_.fd(), reinterpret_cast(in_.data()), *sealed, 0, remote, + sockaddr_size(remote)) + == -1) { + ERROR("[udp] server_recv_sendto"); + } +} + +RefPtr UdpRelay::shared_for(const std::array &id) noexcept +{ + auto key = FixedKey::from(std::as_bytes(std::span(id))); + std::uint64_t now = now_secs(); + if (RefPtr *found = shared_.find(*key)) { + (*found)->last_used = now; + return *found; + } + // Forget client sessions idle for longer than the TTL (oldest first). + while (RefPtr *oldest = shared_.oldest()) { + if ((*oldest)->last_used + kTtl2022 > now) { + break; + } + (void)shared_.pop_oldest(); + } + if (shared_.full()) { + (void)shared_.pop_oldest(); // relay sessions still using it keep it alive + } + auto slot = shared_pool_.acquire(*this); + if (!slot) { + return nullptr; + } + Shared2022 *raw = slot->release(); // the references own it from here + raw->last_used = now; + RefPtr ref(raw); + if (!shared_.emplace(*key, ref)) { + return nullptr; + } + return ref; +} + +void UdpRelay::server_packet(Listener &listener, const sockaddr_storage &from, std::span packet) noexcept +{ + if (config_.tx != nullptr) { + *config_.tx += packet.size(); + } + Result opened = fail(Errc::config); + RefPtr shared; + if (const crypto::MasterKey *master = method_->aead()) { + opened = crypto::aead_udp_open(*master, packet, out_, crypto::ReplayFilter::ppbloom()); + } else if (const crypto::Psk *psk = method_->psk()) { + auto pkt = crypto::udp2022_decrypt(*psk, packet, out_); + if (pkt) { + shared = shared_for(pkt->session_id); + if (shared) { + opened = crypto::udp2022_accept(Role::server, shared->state, *pkt, out_, + static_cast(std::time(nullptr))); + } + } + } +#if SS_ENABLE_LEGACY + else if (const crypto::StreamKey *stream = method_->stream()) { + opened = crypto::stream_udp_open(*stream, packet, out_, crypto::ReplayFilter::ppbloom()); + } +#endif + if (!opened) { + char peer[INET6_ADDRSTRLEN] = {}; + const void *raw = from.ss_family == AF_INET + ? static_cast(&reinterpret_cast(&from)->sin_addr) + : static_cast(&reinterpret_cast(&from)->sin6_addr); + ss_inet_ntop(from.ss_family, raw, peer, sizeof(peer)); + LOGE("failed to handshake with %s: %s", peer, "suspicious UDP packet"); + return; + } + std::span plain(out_.data(), *opened); + SocksTarget target; + if (parse_socks_addr(plain, target) != ParseResult::complete) { + LOGE("[udp] error in parse header"); + return; + } + auto payload = plain.subspan(target.length); + if (target.type == 3) { + in_addr v4{}; + in6_addr v6{}; + if (inet_pton(AF_INET, target.host, &v4) == 1) { + auto *sa = reinterpret_cast(&target.ip); + sa->sin_family = AF_INET; + sa->sin_addr = v4; + sa->sin_port = htons(target.port); + } else if (inet_pton(AF_INET6, target.host, &v6) == 1) { + auto *sa = reinterpret_cast(&target.ip); + sa->sin6_family = AF_INET6; + sa->sin6_addr = v6; + sa->sin6_port = htons(target.port); + } else { + if (!validate_hostname(target.host, static_cast(std::strlen(target.host)))) { + LOGE("[udp] invalid host name"); + return; + } + // Hold the payload until the name resolves. + auto pending = pending_.acquire(); + if (!pending || resolver_ == nullptr) { + LOGE("[udp] too many pending DNS queries"); + return; + } + Pending &p = **pending; + p.listener = listener.index; + p.client = from; + p.session = std::move(shared); + p.len = payload.size(); + std::memcpy(p.payload.data(), payload.data(), payload.size()); + ResolveCallback cb; + cb.fn = &UdpRelay::resolved; + cb.ctx = this; + cb.token = pack_ptr(pending->get()); + Pending *raw = pending->release(); + if (!resolver_->resolve(target.host, htons(target.port), cb)) { + (void)pending_.adopt(raw); // dropped again + LOGE("[udp] unable to resolve %s", target.host); + } + return; + } + } + server_forward(listener.index, from, reinterpret_cast(&target.ip), std::move(shared), payload); +} + +void UdpRelay::resolved(const ResolveCallback &cb, const sockaddr *address) noexcept +{ + auto *self = static_cast(cb.ctx); + PoolPtr p = self->pending_.adopt(static_cast(unpack_ptr(cb.token))); + if (address == nullptr) { + LOGE("[udp] unable to resolve"); + return; + } + if (self->shutting_down_) { + return; + } + self->server_forward(p->listener, p->client, address, std::move(p->session), { p->payload.data(), p->len }); +} + +void UdpRelay::server_forward(std::uint8_t listener, const sockaddr_storage &client, const sockaddr *target, + RefPtr shared, std::span payload) noexcept +{ + UdpSession *session = session_for(listener, client, target->sa_family); + if (session == nullptr) { + return; + } + if (shared) { + session->server2022_ = std::move(shared); // replies use the latest validated session + } + idle_.touch(*session, loop_->now()); + schedule(); + if (sendto(session->socket_.fd(), reinterpret_cast(payload.data()), payload.size(), 0, target, + sockaddr_size(target)) + == -1) { + ERROR("[udp] sendto_remote"); + } +} + +void UdpRelay::reply(UdpSession &session) noexcept +{ + for (int i = 0; i < kReceiveBurst; ++i) { + sockaddr_storage from{}; + socklen_t len = sizeof(from); + ssize_t r = recvfrom(session.socket_.fd(), reinterpret_cast(in_.data()), in_.size(), 0, + reinterpret_cast(&from), &len); + if (r < 0) { + int err = ss_socket_error(); + if (err != EAGAIN && err != EWOULDBLOCK && err != EINTR) { + ERROR("[udp] remote_recv_recvfrom"); + } + return; + } + if (verbose) { + LOGI("[udp] remote receive a packet"); + } + std::span packet(in_.data(), static_cast(r)); + const Listener &listener = listeners_[session.listener_]; + std::span datagram; + sockaddr_storage reply_from{}; + + if (config_.kind == Kind::server) { + if (config_.rx != nullptr) { + *config_.rx += packet.size(); + } + // [source address][payload], sealed back to the client. + std::size_t header = encode_socks_addr(reinterpret_cast(&from), out_.data()); + if (header == 0) { + continue; + } + std::memcpy(out_.data() + header, packet.data(), packet.size()); + std::span plain(out_.data(), header + packet.size()); + Result sealed = fail(Errc::config); + if (const crypto::MasterKey *master = method_->aead()) { + sealed = crypto::aead_udp_seal(*master, plain, in_, crypto::ReplayFilter::ppbloom()); + } else if (const crypto::Psk *psk = method_->psk()) { + if (session.server2022_ && plain.size() + crypto::udp2022_overhead(*psk, Role::server) <= in_.size()) { + sealed = crypto::udp2022_seal(*psk, Role::server, session.server2022_->state, plain, in_); + } + } +#if SS_ENABLE_LEGACY + else if (const crypto::StreamKey *stream = method_->stream()) { + sealed = crypto::stream_udp_seal(*stream, plain, in_, crypto::ReplayFilter::ppbloom()); + } +#endif + if (!sealed) { + continue; + } + datagram = { in_.data(), *sealed }; + } else { + Result opened = fail(Errc::config); + if (const crypto::MasterKey *master = method_->aead()) { + opened = crypto::aead_udp_open(*master, packet, out_, crypto::ReplayFilter::ppbloom()); + } else if (const crypto::Psk *psk = method_->psk()) { + auto pkt = crypto::udp2022_decrypt(*psk, packet, out_); + if (pkt) { + opened = crypto::udp2022_accept(Role::client, session.client2022_, *pkt, out_, + static_cast(std::time(nullptr))); + } + } +#if SS_ENABLE_LEGACY + else if (const crypto::StreamKey *stream = method_->stream()) { + opened = crypto::stream_udp_open(*stream, packet, out_, crypto::ReplayFilter::ppbloom()); + } +#endif + if (!opened) { + LOGE("failed to handshake with the server: %s", "suspicious UDP packet"); + continue; + } + SocksTarget target; + if (parse_socks_addr({ out_.data(), *opened }, target) != ParseResult::complete) { + LOGE("[udp] error in parse header"); + continue; + } + if (config_.kind == Kind::local) { +#ifdef __ANDROID__ + if (config_.rx != nullptr) { + *config_.rx += *opened; + } + if (config_.on_traffic != nullptr) { + config_.on_traffic(); + } +#endif + // SOCKS5 UDP reply: RSV(2) FRAG(1) then address and payload. + std::memmove(out_.data() + 3, out_.data(), *opened); + out_[0] = out_[1] = out_[2] = 0; + datagram = { out_.data(), *opened + 3 }; + } else { + datagram = { out_.data() + target.length, *opened - target.length }; + if (config_.kind == Kind::redir) { + if (target.type == 3) { + LOGI("[udp] ss-redir does not support domain name"); + continue; + } + reply_from = target.ip; + } + } + } + + if (config_.kind == Kind::redir) { + send_as(reply_from, session.client_, datagram, config_.reuse_port); + } else if (sendto(listener.poll.fd(), reinterpret_cast(datagram.data()), datagram.size(), 0, + reinterpret_cast(&session.client_), + sockaddr_size(reinterpret_cast(&session.client_))) + == -1 + && ss_socket_error() != EAGAIN && ss_socket_error() != EWOULDBLOCK) { + ERROR("[udp] remote_recv_sendto"); + continue; + } + idle_.touch(session, loop_->now()); + schedule(); + } +} + +void UdpRelay::on_timer() noexcept +{ + timer_due_ = 0; + idle_.expire(loop_->now(), [this](UdpSession &s) { expire(s); }); + schedule(); +} + +void UdpRelay::schedule() noexcept +{ + auto next = idle_.next_due(); + if (!next) { + if (timer_due_ != 0) { + timer_.stop(); + timer_due_ = 0; + } + return; + } + // Touches only push deadlines later, so an armed timer can stay. + if (timer_due_ != 0 && timer_due_ <= *next) { + return; + } + std::uint64_t now = loop_->now(); + timer_due_ = *next; + timer_.start(*next > now ? *next - now : 1); +} + +void UdpRelay::shutdown() noexcept +{ + if (shutting_down_ || loop_ == nullptr) { + return; + } + shutting_down_ = true; + sessions_.clear(); // closes every session + shared_.clear(); + closer_.arm(CloseCompletion([](void *self, std::uint32_t) { static_cast(self)->own_closed_ = true; }, + this, 0)); + for (std::size_t i = 0; i < listener_count_; ++i) { + listeners_[i].poll.close(closer_); + } + timer_.close(closer_); + closer_.seal(); +} + +bool UdpRelay::closed() const noexcept +{ + return own_closed_ && pool_.live() == 0 && pool_.closing() == 0 && pending_.live() == 0; +} + +} // namespace ss::relay diff --git a/src/relay/udp.hpp b/src/relay/udp.hpp new file mode 100644 index 00000000..2baf9e53 --- /dev/null +++ b/src/relay/udp.hpp @@ -0,0 +1,188 @@ +/* SPDX-License-Identifier: GPL-3.0-or-later */ +#ifndef SS_RELAY_UDP_HPP +#define SS_RELAY_UDP_HPP + +// The UDP relay shared by the four programs, replacing udprelay.cpp and its +// per-module conditionals. +// +// Ownership +// * A Session (one per client address and target family, with its own +// upstream socket) is owned by the relay's LRU table through a PoolPtr; +// evicting or expiring an entry is what closes the session. +// * AEAD-2022 server sessions are shared: the table keyed by client +// session ID holds one reference and every relay session talking to +// that client holds another (RefPtr), so evicting a busy table entry +// never frees state still in use. +// * Datagrams waiting for DNS are pool slots handed to the resolver and +// taken back by its callback. +// * Packets are received into, and built in, two buffers owned by the +// relay; nothing is allocated per datagram. + +#include +#include +#include +#include + +#include "core/buffer.hpp" +#include "core/fixed_map.hpp" +#include "core/pool.hpp" +#include "core/ref.hpp" +#include "core/result.hpp" +#include "event/loop.hpp" +#include "relay/codec.hpp" +#include "relay/resolver.hpp" +#include "relay/socket.hpp" +#include "relay/tcp.hpp" // Kind + +namespace ss::relay { + +inline constexpr std::size_t kMaxDatagram = 65536; + +struct UdpConfig { + Kind kind = Kind::local; + sockaddr_storage remote{}; // client kinds: the ss-server + std::array tunnel_target{}; + std::size_t tunnel_target_len = 0; + int timeout_secs = 60; + std::uint32_t max_sessions = 512; + std::uint32_t max_sessions_2022 = 4096; // server: AEAD-2022 client sessions + std::uint32_t max_pending_dns = 64; + int mtu = 0; + const char *iface = nullptr; + const sockaddr_storage *bind_v4 = nullptr; + const sockaddr_storage *bind_v6 = nullptr; + bool vpn = false; + int (*protect)(int fd) = nullptr; + bool reuse_port = false; + const char *label = ""; + std::uint64_t *tx = nullptr; + std::uint64_t *rx = nullptr; + void (*on_traffic)() = nullptr; +}; + +// Bind the UDP socket clients send to (ss-redir: transparent, with the +// original destination reported). Returns the socket or an error. +Result open_udp_listener(const char *host, const char *port, bool transparent, bool reuse_port) noexcept; + +class UdpRelay; + +// Shared AEAD-2022 server session (see above). +class Shared2022 : public RefCounted { +public: + explicit Shared2022(UdpRelay &relay) noexcept : relay_(&relay) {} + static void destroy(Shared2022 *self) noexcept; + + crypto::UdpSession2022 state; + std::uint64_t last_used = 0; + +private: + UdpRelay *relay_; +}; + +class UdpSession : public event::Deadline { +public: + UdpSession(UdpRelay &relay, std::uint8_t listener) noexcept : relay_(&relay), listener_(listener) {} + UdpSession(const UdpSession &) = delete; + UdpSession &operator=(const UdpSession &) = delete; + + void begin_close(CloseCompletion done) noexcept; + event::Flow on_readable(int events) noexcept; + +private: + friend class UdpRelay; + + UdpRelay *relay_; + std::uint8_t listener_; + CloseCompletion done_; + event::Poll socket_; + event::CloseGroup closer_; + sockaddr_storage client_{}; // where replies go + std::array key_{}; + std::uint8_t key_len_ = 0; + crypto::UdpSession2022 client2022_; // client kinds + RefPtr server2022_; // server +}; + +class UdpRelay { +public: + UdpRelay() noexcept = default; + UdpRelay(const UdpRelay &) = delete; + UdpRelay &operator=(const UdpRelay &) = delete; + ~UdpRelay(); + + // `resolver` is needed by ss-server (targets given by name). + Status init(event::Loop &loop, Method &method, const UdpConfig &config, Resolver *resolver) noexcept; + Status listen(ss_socket_t fd) noexcept; + + void shutdown() noexcept; + bool closed() const noexcept; + + std::uint32_t sessions() const noexcept { return sessions_.size(); } + +private: + friend class UdpSession; + friend class Shared2022; + + using Key = FixedKey; + + struct Listener { + UdpRelay *relay = nullptr; + std::uint8_t index = 0; + event::Flow on_ready(int events) noexcept; + event::Poll poll; + }; + static constexpr std::size_t kMaxListeners = 16; + + struct Pending { + std::uint8_t listener = 0; + sockaddr_storage client{}; + RefPtr session; + std::size_t len = 0; + std::array payload; // target payload only + }; + + void receive(Listener &listener) noexcept; + void client_packet(Listener &listener, const sockaddr_storage &from, std::span packet, + const sockaddr_storage *original_dst) noexcept; + void server_packet(Listener &listener, const sockaddr_storage &from, std::span packet) noexcept; + void server_forward(std::uint8_t listener, const sockaddr_storage &client, const sockaddr *target, + RefPtr shared, std::span payload) noexcept; + void reply(UdpSession &session) noexcept; + UdpSession *session_for(std::uint8_t listener, const sockaddr_storage &client, int family) noexcept; + RefPtr shared_for(const std::array &id) noexcept; + void expire(UdpSession &session) noexcept; + Result open_upstream_socket(int family) noexcept; + static void resolved(const ResolveCallback &cb, const sockaddr *address) noexcept; + void on_timer() noexcept; + void schedule() noexcept; + std::uint64_t now_secs() const noexcept; + + event::Loop *loop_ = nullptr; + Method *method_ = nullptr; + Resolver *resolver_ = nullptr; + UdpConfig config_{}; + + std::array listeners_{}; + std::size_t listener_count_ = 0; + + ObjectPool pool_; + FixedLru > sessions_; + ObjectPool shared_pool_; + FixedLru, RefPtr > shared_; + ObjectPool pending_; + + event::DeadlineList idle_; + event::Timer timer_; + std::uint64_t timer_due_ = 0; + event::CloseGroup closer_; + bool shutting_down_ = false; + bool own_closed_ = true; + + // Received datagram and the one built from it. + std::array in_{}; + std::array out_{}; +}; + +} // namespace ss::relay + +#endif diff --git a/src/server.cpp b/src/server.cpp index 8f6401d3..1f637cba 100644 --- a/src/server.cpp +++ b/src/server.cpp @@ -74,7 +74,6 @@ enum datatypes { #include "crypto.h" #include "jconf.h" #include "common.h" -#include "udprelay.h" #include "ss_windows.h" #include "resolv.h" @@ -84,6 +83,7 @@ enum datatypes { #endif #include "relay/tcp.hpp" +#include "relay/udp.hpp" #undef min #undef max @@ -125,7 +125,6 @@ int is_bind_local_addr = 0; struct sockaddr_storage local_addr_v4; struct sockaddr_storage local_addr_v6; -static crypto_t *crypto; static int acl = 0; static int mode = TCP_ONLY; @@ -1450,9 +1449,11 @@ Same behavior as `-d`; see that option for details. Short alias: `-d`. // setup keys LOGI("initializing ciphers... %s", method); - crypto = crypto_init(password, key, method); - if (crypto == NULL) + ss::relay::Method relay_method; + if (!relay_method.init(method, password, key, ss::crypto::Role::server)) FATAL("failed to initialize ciphers"); + if (relay_method.is_2022() && !relay_method.reserve_salt_pool(65536, randombytes_random())) + FATAL("failed to set up the AEAD-2022 salt pool"); // initialize ev loop struct ss_loop *loop = SS_DEFAULT; @@ -1522,12 +1523,17 @@ Same behavior as `-d`; see that option for details. Short alias: `-d`. } } - // The TCP relay engine runs on the ss_event loop next to the C modules. + // The relays run on the ss_event loop next to the C modules, and share + // one resolver for targets given by name. ss::event::Loop tcp_loop; - ss::relay::Method tcp_method; ss::relay::Resolver tcp_resolver; ss::relay::TcpRelay tcp_relay; + ss::relay::UdpRelay udp_relay; bool tcp_running = false; + if (!tcp_loop.adopt(ss_native_loop(loop)) + || !tcp_resolver.init(tcp_loop, nameservers, ipv6first != 0, DEFAULT_MAX_CONNECTIONS + 64)) { + FATAL("failed to set up the resolver"); + } if (mode != UDP_ONLY) { ss::relay::TcpConfig tcp_config; tcp_config.kind = ss::relay::Kind::server; @@ -1554,15 +1560,7 @@ Same behavior as `-d`; see that option for details. Short alias: `-d`. tcp_config.downstream = &setTosFromConnmark; tcp_config.release = &releaseTracker; #endif - if (!tcp_loop.adopt(ss_native_loop(loop)) - || !tcp_method.init(method, password, key, ss::crypto::Role::server)) { - FATAL("failed to initialize ciphers"); - } - if (tcp_method.is_2022() && !tcp_method.reserve_salt_pool(65536, randombytes_random())) { - FATAL("failed to set up the AEAD-2022 salt pool"); - } - if (!tcp_resolver.init(tcp_loop, nameservers, ipv6first != 0, tcp_config.max_connections) - || !tcp_relay.init(tcp_loop, tcp_method, tcp_config, &tcp_resolver)) { + if (!tcp_relay.init(tcp_loop, relay_method, tcp_config, &tcp_resolver)) { FATAL("failed to set up the TCP relay"); } tcp_running = true; @@ -1612,6 +1610,22 @@ Same behavior as `-d`; see that option for details. Short alias: `-d`. } if (mode != TCP_ONLY) { + ss::relay::UdpConfig udp_config; + udp_config.kind = ss::relay::Kind::server; + udp_config.timeout_secs = timeout_secs; + udp_config.max_sessions = 512; + udp_config.mtu = mtu; + udp_config.iface = iface; + udp_config.label = remote_port != NULL ? remote_port : ""; + udp_config.tx = &tx; + udp_config.rx = ℞ + if (is_bind_local_addr) { + udp_config.bind_v4 = &local_addr_v4; + udp_config.bind_v6 = &local_addr_v6; + } + if (!udp_relay.init(tcp_loop, relay_method, udp_config, &tcp_resolver)) { + FATAL("failed to initialize UDP relay"); + } int num_listen_ctx = 0; for (int i = 0; i < server_num; i++) { const char *host = server_addr[i].host; @@ -1624,9 +1638,13 @@ Same behavior as `-d`; see that option for details. Short alias: `-d`. else LOGI("udp server listening at %s:%s", host ? host : "0.0.0.0", port); // Setup UDP - int err = init_udprelay(host, port, mtu, crypto, timeout_secs, iface); - if (err == -1) + auto udp_fd = ss::relay::open_udp_listener(host, port, false, reuse_port != 0); + if (!udp_fd) continue; + if (!udp_relay.listen(*udp_fd)) { + ss_socket_close(*udp_fd); + continue; + } num_listen_ctx++; } @@ -1680,19 +1698,17 @@ Same behavior as `-d`; see that option for details. Short alias: `-d`. resolv_shutdown(loop); - if (tcp_running) { + { static bool resolver_closed; resolver_closed = false; tcp_relay.shutdown(); + udp_relay.shutdown(); tcp_resolver.shutdown(ss::CloseCompletion([](void *, uint32_t) { resolver_closed = true; }, NULL, 0)); - while (!tcp_relay.closed() || !resolver_closed) { + while (!tcp_relay.closed() || !udp_relay.closed() || !resolver_closed) { tcp_loop.run_nowait(); } } - - if (mode != TCP_ONLY) { - free_udprelay(); - } + (void)tcp_running; ss_loop_destroy(loop); #ifdef __MINGW32__ diff --git a/src/tunnel.cpp b/src/tunnel.cpp index 9b0d249d..67a924de 100644 --- a/src/tunnel.cpp +++ b/src/tunnel.cpp @@ -56,10 +56,10 @@ #include "crypto.h" #include "jconf.h" #include "common.h" -#include "udprelay.h" #include "ss_windows.h" #include "relay/tcp.hpp" +#include "relay/udp.hpp" #undef min #undef max @@ -105,7 +105,6 @@ int tcp_incoming_rcvbuf = 0; int tcp_outgoing_sndbuf = 0; int tcp_outgoing_rcvbuf = 0; -static crypto_t *crypto; static int ipv6first = 0; static int mode = TCP_ONLY; @@ -933,8 +932,8 @@ Same behavior as `-V`; see that option for details. Short alias: `-V`. // Setup keys LOGI("initializing ciphers... %s", method); - crypto = crypto_init(password, key, method); - if (crypto == NULL) + ss::relay::Method relay_method; + if (!relay_method.init(method, password, key, ss::crypto::Role::client)) FATAL("failed to initialize ciphers"); // Setup proxy context @@ -987,17 +986,14 @@ Same behavior as `-V`; see that option for details. Short alias: `-V`. struct ss_loop *loop = SS_DEFAULT; ss::event::Loop tcp_loop; - ss::relay::Method tcp_method; ss::relay::TcpRelay tcp_relay; + ss::relay::UdpRelay udp_relay; if (!tcp_loop.adopt(ss_native_loop(loop))) { FATAL("failed to set up the event loop"); } if (mode != UDP_ONLY) { - if (!tcp_method.init(method, password, key, ss::crypto::Role::client)) { - FATAL("failed to initialize ciphers"); - } - if (!tcp_relay.init(tcp_loop, tcp_method, tcp_config, NULL)) { + if (!tcp_relay.init(tcp_loop, relay_method, tcp_config, NULL)) { FATAL("failed to set up the TCP relay"); } // Setup socket @@ -1017,16 +1013,25 @@ Same behavior as `-V`; see that option for details. Short alias: `-V`. // Setup UDP if (mode != TCP_ONLY) { LOGI("UDP relay enabled"); - char *host = remote_addr[0].host; - char *port = remote_addr[0].port == NULL ? remote_port : remote_addr[0].port; - struct sockaddr_storage *storage = (struct sockaddr_storage *)ss_malloc(sizeof(struct sockaddr_storage)); - memset(storage, 0, sizeof(struct sockaddr_storage)); - if (get_sockaddr(host, port, storage, 1, ipv6first) == -1) { + char *host = remote_addr[0].host; + char *port = remote_addr[0].port == NULL ? remote_port : remote_addr[0].port; + ss::relay::UdpConfig udp_config; + udp_config.kind = ss::relay::Kind::tunnel; + udp_config.tunnel_target = tcp_config.tunnel_target; + udp_config.tunnel_target_len = tcp_config.tunnel_target_len; + udp_config.timeout_secs = timeout_secs; + udp_config.max_sessions = 256; + udp_config.mtu = mtu; + udp_config.iface = iface; +#ifdef __ANDROID__ + udp_config.vpn = vpn != 0; + udp_config.protect = protect_socket; +#endif + if (get_sockaddr(host, port, &udp_config.remote, 1, ipv6first) == -1) { FATAL("failed to resolve the provided hostname"); } - struct sockaddr *addr = (struct sockaddr *)storage; - if (init_udprelay(local_addr, local_port, addr, get_sockaddr_len(addr), - tunnel_addr, mtu, crypto, timeout_secs, iface) == -1) { + auto udp_fd = ss::relay::open_udp_listener(local_addr, local_port, false, reuse_port != 0); + if (!udp_fd || !udp_relay.init(tcp_loop, relay_method, udp_config, NULL) || !udp_relay.listen(*udp_fd)) { FATAL("failed to initialize UDP relay"); } } @@ -1053,7 +1058,8 @@ Same behavior as `-V`; see that option for details. Short alias: `-V`. } tcp_relay.shutdown(); - while (!tcp_relay.closed()) { + udp_relay.shutdown(); + while (!tcp_relay.closed() || !udp_relay.closed()) { tcp_loop.run_nowait(); }