C++20 port 11/14: one TCP and UDP relay engine for all programs (#3075)

* Rewrite the resolver in C++ on a fixed query pool

Queries live in pool slots with their first A and AAAA answer inline,
c-ares sockets are watched from a fixed table, and answers reach the
waiter through a callback plus opaque words instead of a back-pointer.
resolv.cpp keeps the C API as a shim. The event Loop can now adopt the
ss_event loop, and a Poll can stop watching a socket it does not own.

* Add the C++ TCP relay engine and run ss-tunnel on it

One engine (src/relay/tcp) for the four TCP relays: connections live in a
fixed pool owned through PoolPtr, handshake, cipher state and buffers are
inline, timeouts are intrusive deadline lists on one timer and DNS answers
come back through generation-checked handles. A full pool refuses new
connections and pauses accepting until a slot frees.

ss-tunnel is the first user; its hand-written relay callbacks are gone.
tests/tunnel_interop.py covers every method against ss-server and
shadowsocks-rust.

* Run ss-local and libshadowsocks-c on the C++ TCP relay engine

The SOCKS5 handshake, the ACL bypass decision (with its own resolver for
host-name rules) and the short wait for a first payload are now engine
code; local.cpp keeps only option parsing and startup. The library entry
points start the same engine.

* Run ss-server on the C++ TCP relay engine

The request header, outbound ACL, target resolution through the relay's
resolver, the anti-probing stop stage and the manager traffic counters
are engine code now; the connmark-to-TOS tracker becomes a pair of relay
hooks. server.cpp keeps option parsing, nftables and the stats timer.

* Fix the build with GCC and libstdc++

crypto.h's min/max macros broke <algorithm> in C++ sources that include
it first, netutils.h's linux/tcp.h clashed with the netinet/tcp.h libuv
includes, and GCC rejects memcpy into crypto_t. Apple clang accepted all
three.

* Run ss-redir and every UDP relay on the C++ engines

ss-redir's TCP side moves onto the relay engine (per-listener TOS for the
DSCP ports). src/relay/udp replaces udprelay.cpp: sessions keyed by
client address own their upstream socket and live in a fixed LRU,
AEAD-2022 server sessions are shared through RefPtr between the
session-ID table and the relays using them, and datagrams waiting for
DNS sit in pool slots. Packets are received into and built in
relay-owned buffers instead of a 64 KiB allocation per datagram. The
programs no longer call crypto_init(); Method sets up libsodium and the
replay filter.

* Let the allocation audit target an IP so DNS costs show separately

* Copy the literal ACL address without strncpy

GCC 13 at -O3 rejects the strncpy from the 256-byte host into the
INET6_ADDRSTRLEN buffer (-Wstringop-truncation) on glibc and UCRT64.

* Include <ctime> for std::time in the UDP relay (libstdc++)

* Build the UDP relay with Clang on musl

musl's CMSG_NXTHDR compares a size_t with a ptrdiff_t, which Clang's
-Wsign-compare rejects in C++. Call it from one helper that suppresses
the warning there.

* Keep clang-tidy clean in the relay engine
This commit is contained in:
Max Lv authored and GitHub committed 2026-10-15 10:05:43 +08:00
1 parent 5dbcbcbb89
commit 5c62b2544e
38 files changed
+4390 -6452

No files matched your search

+4 -6
View File
@@ -9,17 +9,15 @@ src/crypto.cpp 4
src/ipset.cpp 3
src/jconf.cpp 16
src/json.cpp 2
src/local.cpp 16
src/local.cpp 1
src/manager.cpp 14
src/plugin.cpp 3
src/process.cpp 26
src/redir.cpp 15
src/resolv.cpp 6
src/redir.cpp 1
src/rule.cpp 2
src/server.cpp 17
src/server.cpp 4
src/ss_event.cpp 5
src/ssurl.cpp 3
src/stream.cpp 3
src/tunnel.cpp 14
src/udprelay.cpp 7
src/tunnel.cpp 1
src/utils.cpp 10
+19 -5
View File
@@ -48,6 +48,20 @@ else()
set(SS_PLUGIN_SOURCE plugin_disabled.cpp)
endif()
# The C++ relay engine (src/relay) and the codecs it runs on.
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
)
if(SS_ENABLE_LEGACY)
list(APPEND SS_RELAY_SOURCE crypto/stream.cpp)
endif()
set(SS_SHARED_SOURCES
ppbloom.cpp
utils.cpp
@@ -61,11 +75,11 @@ endif ()
set(LIBSHADOWSOCKS_LIBEV_SOURCE
${SS_SHARED_SOURCES}
udprelay.cpp
cache.cpp
local.cpp
resolv.cpp
ssurl.cpp
${SS_RELAY_SOURCE}
${SS_CRYPTO_SOURCE}
${SS_PLUGIN_SOURCE}
${SS_ACL_SOURCE}
@@ -78,19 +92,19 @@ set(SS_LOCAL_SOURCE
set(SS_TUNNEL_SOURCE
${SS_SHARED_SOURCES}
udprelay.cpp
cache.cpp
tunnel.cpp
${SS_RELAY_SOURCE}
${SS_CRYPTO_SOURCE}
${SS_PLUGIN_SOURCE}
)
set(SS_SERVER_SOURCE
${SS_SHARED_SOURCES}
udprelay.cpp
cache.cpp
resolv.cpp
server.cpp
${SS_RELAY_SOURCE}
${SS_CRYPTO_SOURCE}
${SS_PLUGIN_SOURCE}
${SS_ACL_SOURCE}
@@ -103,9 +117,9 @@ set(SS_MANAGER_SOURCE
set(SS_REDIR_SOURCE
${SS_SHARED_SOURCES}
udprelay.cpp
cache.cpp
redir.cpp
${SS_RELAY_SOURCE}
${SS_CRYPTO_SOURCE}
${SS_PLUGIN_SOURCE}
)
@@ -124,7 +138,7 @@ if(ENABLE_NFTABLES)
endif()
# Modules rewritten in C++ compile with the stricter warning set.
ss_strict_warnings(cache.cpp)
ss_strict_warnings(cache.cpp resolv.cpp ${SS_RELAY_SOURCE})
function(ss_program name module sources)
add_executable(${name} ${sources})
+10
View File
@@ -161,6 +161,16 @@ public:
return PoolPtr<T>(object, PoolDeleter<T>{ this });
}
// Re-own an object whose PoolPtr was release()d, e.g. to hand a raw
// pointer to a C library callback and take it back afterwards.
PoolPtr<T> adopt(T *object) noexcept
{
if (meta_at(index_of(object))->state != State::live) {
std::abort();
}
return PoolPtr<T>(object, PoolDeleter<T>{ this });
}
// Handle for a live object owned by this pool.
Handle<T> handle_of(const T *object) const noexcept
{
+3 -3
View File
@@ -57,7 +57,7 @@ brealloc(buffer_t *ptr, size_t len, size_t capacity)
{
if (ptr == NULL)
return -1;
size_t real_capacity = max(len, capacity);
size_t real_capacity = len > capacity ? len : capacity;
if (ptr->capacity < real_capacity) {
ptr->data = (char *)ss_realloc(ptr->data, real_capacity);
ptr->capacity = real_capacity;
@@ -171,7 +171,7 @@ crypto_init(const char *password, const char *key, const char *method)
.ctx_init = &stream_ctx_init,
.ctx_release = &stream_ctx_release,
};
memcpy(crypto, &tmp, sizeof(crypto_t));
memcpy((void *)crypto, &tmp, sizeof(crypto_t)); /* const members: copy the bytes */
return crypto;
}
@@ -200,7 +200,7 @@ crypto_init(const char *password, const char *key, const char *method)
.udp_session_release = aead_is_2022(cipher)
? &aead_2022_udp_session_release : NULL,
};
memcpy(crypto, &tmp, sizeof(crypto_t));
memcpy((void *)crypto, &tmp, sizeof(crypto_t)); /* const members: copy the bytes */
return crypto;
}
}
+3
View File
@@ -69,8 +69,11 @@ typedef mbedtls_md_info_t digest_type_t;
#define CRYPTO_NEED_MORE -1
#define CRYPTO_OK 0
/* Not for C++, where they would break <algorithm>. */
#ifndef __cplusplus
#define min(a, b) (((a) < (b)) ? (a) : (b))
#define max(a, b) (((a) > (b)) ? (a) : (b))
#endif
#define SUBKEY_INFO "ss-subkey"
#define IV_INFO "ss-iv"
+2
View File
@@ -184,6 +184,8 @@ public:
// Bytes received but not yet decrypted.
std::size_t pending() const noexcept { return buffer_.size(); }
// Input bytes the next decrypt() takes in full.
std::size_t accept_limit() const noexcept { return buffer_.capacity() - buffer_.size(); }
bool salt_received() const noexcept { return engine_ready_; }
private:
+57 -39
View File
@@ -368,6 +368,8 @@ Status Aead2022TcpDecryptor::init(const Psk &psk, Role role, std::span<const std
stage_ = Stage::fixed;
failed_ = false;
want_ = 0;
plain_ = 0;
skip_ = 0;
nonce_.fill(0);
request_salt_.fill(0);
if (!request_salt.empty()) {
@@ -384,9 +386,6 @@ Result<Aead2022TcpDecryptor::Step> Aead2022TcpDecryptor::decrypt(std::span<const
if (failed_) {
return fail(Errc::closed);
}
if (out.size() < k2022MaxChunk) {
return fail(Errc::invalid);
}
Step step{ 0, 0 };
if (buffer_.writable().size() < in.size()) {
buffer_.compact();
@@ -399,14 +398,17 @@ Result<Aead2022TcpDecryptor::Step> Aead2022TcpDecryptor::decrypt(std::span<const
}
auto bytes = [this]() {
auto r = buffer_.readable();
return std::span<const std::uint8_t>(reinterpret_cast<const std::uint8_t *>(r.data()), r.size());
return std::span<std::uint8_t>(reinterpret_cast<std::uint8_t *>(r.data()), r.size());
};
auto broken = [this](Errc code) {
failed_ = true;
return fail(code);
};
auto open = [this](std::uint8_t *dst, std::span<const std::uint8_t> chunk) {
bool ok = static_cast<bool>(engine_.open(dst, chunk, nonce_.data()));
// Chunks are opened in place: the plaintext ends up at the front of the
// buffer, followed by the tag (and for a request header the padding),
// which is skipped once the plaintext has been handed out.
auto open = [this](std::span<std::uint8_t> chunk) {
bool ok = static_cast<bool>(engine_.open(chunk.data(), chunk, nonce_.data()));
if (ok) {
sodium_increment(nonce_.data(), psk_.spec.aead.nonce_len);
}
@@ -415,6 +417,23 @@ Result<Aead2022TcpDecryptor::Step> Aead2022TcpDecryptor::decrypt(std::span<const
const std::size_t salt_len = psk_.size();
for (;;) {
if (plain_ != 0) {
std::size_t n = std::min(plain_, out.size() - step.produced);
if (n == 0) {
return step;
}
std::memcpy(out.data() + step.produced, bytes().data(), n);
(void)buffer_.consume(n);
plain_ -= n;
step.produced += n;
if (plain_ != 0) {
return step;
}
}
if (skip_ != 0) {
(void)buffer_.consume(skip_);
skip_ = 0;
}
auto data = bytes();
switch (stage_) {
case Stage::fixed: {
@@ -431,8 +450,8 @@ Result<Aead2022TcpDecryptor::Step> Aead2022TcpDecryptor::decrypt(std::span<const
if (!st) {
return broken(st.error().code);
}
std::uint8_t fixed[kMaxFixed];
if (!open(fixed, data.subspan(salt_len, fixed_len + kTag))) {
std::uint8_t *fixed = data.data() + salt_len;
if (!open(data.subspan(salt_len, fixed_len + kTag))) {
return broken(Errc::crypto);
}
std::uint64_t now = unix_now();
@@ -460,46 +479,45 @@ Result<Aead2022TcpDecryptor::Step> Aead2022TcpDecryptor::decrypt(std::span<const
break;
}
case Stage::variable: {
if (data.size() < want_ + kTag || out.size() - step.produced < want_) {
if (data.size() < want_ + kTag) {
return step;
}
std::uint8_t *dst = out.data() + step.produced;
if (!open(dst, data.first(want_ + kTag))) {
if (!open(data.first(want_ + kTag))) {
return broken(Errc::crypto);
}
(void)buffer_.consume(want_ + kTag);
if (role_ == Role::client) {
step.produced += want_; // the first response payload
} else {
std::span<const std::uint8_t> hdr(dst, want_);
std::size_t addr_len = socks_addr_len(hdr);
if (addr_len == 0 || want_ < addr_len + kLenSize) {
return broken(Errc::protocol);
}
std::size_t pad = load16_be(dst + addr_len);
std::size_t off = addr_len + kLenSize + pad;
if (pad > k2022MaxPad || off > want_) {
return broken(Errc::protocol);
}
std::size_t payload = want_ - off;
if (payload == 0 && pad == 0) {
return broken(Errc::protocol); // SIP022: neither payload nor padding
}
std::memmove(dst + addr_len, dst + off, payload);
step.produced += addr_len + payload;
}
stage_ = Stage::length;
if (role_ == Role::client) {
plain_ = want_; // the first response payload
skip_ = kTag;
break;
}
std::uint8_t *hdr = data.data();
std::size_t addr_len = socks_addr_len(data.first(want_));
if (addr_len == 0 || want_ < addr_len + kLenSize) {
return broken(Errc::protocol);
}
std::size_t pad = load16_be(hdr + addr_len);
std::size_t off = addr_len + kLenSize + pad;
if (pad > k2022MaxPad || off > want_) {
return broken(Errc::protocol);
}
std::size_t payload = want_ - off;
if (payload == 0 && pad == 0) {
return broken(Errc::protocol); // SIP022: neither payload nor padding
}
std::memmove(hdr + addr_len, hdr + off, payload);
plain_ = addr_len + payload;
skip_ = want_ - plain_ + kTag;
break;
}
case Stage::length: {
if (data.size() < kLenSize + kTag) {
return step;
}
std::uint8_t len[kLenSize];
if (!open(len, data.first(kLenSize + kTag))) {
if (!open(data.first(kLenSize + kTag))) {
return broken(Errc::crypto);
}
want_ = load16_be(len);
want_ = load16_be(data.data());
if (want_ == 0) {
return broken(Errc::protocol);
}
@@ -508,14 +526,14 @@ Result<Aead2022TcpDecryptor::Step> Aead2022TcpDecryptor::decrypt(std::span<const
break;
}
case Stage::payload: {
if (data.size() < want_ + kTag || out.size() - step.produced < want_) {
if (data.size() < want_ + kTag) {
return step;
}
if (!open(out.data() + step.produced, data.first(want_ + kTag))) {
if (!open(data.first(want_ + kTag))) {
return broken(Errc::crypto);
}
(void)buffer_.consume(want_ + kTag);
step.produced += want_;
plain_ = want_;
skip_ = kTag;
stage_ = Stage::length;
break;
}
+10 -5
View File
@@ -122,11 +122,14 @@ public:
std::size_t produced;
};
// Like AeadTcpDecryptor::decrypt; `out` must hold at least k2022MaxChunk
// bytes. A server's first output is the SOCKS target address followed by
// the initial payload, with the padding removed.
// Take up to accept_limit() bytes of `in`, decrypt complete chunks in
// place and copy as much plaintext as fits into `out` (any size). Call
// again with an empty input to collect the rest. A server's first
// output is the SOCKS target address followed by the initial payload,
// with the padding removed.
Result<Step> decrypt(std::span<const std::uint8_t> in, std::span<std::uint8_t> out) noexcept;
std::size_t accept_limit() const noexcept { return buffer_.capacity() - buffer_.size(); }
bool header_received() const noexcept { return stage_ != Stage::fixed; }
std::span<const std::uint8_t> salt() const noexcept { return { salt_.data(), psk_.size() }; }
std::size_t pending() const noexcept { return buffer_.size(); }
@@ -143,8 +146,10 @@ private:
std::array<std::uint8_t, kMaxNonce> nonce_{};
// The largest unit is a full 64 KiB chunk plus its tag.
FixedBuffer<k2022MaxChunk + kTag + 64> buffer_;
std::size_t want_ = 0; // plaintext length of the next variable/payload chunk
Stage stage_ = Stage::fixed;
std::size_t want_ = 0; // plaintext length of the next variable/payload chunk
std::size_t plain_ = 0; // decrypted bytes at the front of buffer_ not yet handed out
std::size_t skip_ = 0; // bytes after them to drop (tag, padding) once they are
Stage stage_ = Stage::fixed;
bool failed_ = false;
};
+33 -15
View File
@@ -62,7 +62,7 @@ public:
Status init() noexcept
{
if (initialised_) {
if (initialised_ || native_ != &loop_) {
return fail(Errc::invalid);
}
int rc = uv_loop_init(&loop_);
@@ -73,19 +73,30 @@ public:
return {};
}
// Use a loop owned by someone else (the ss_event default loop while C
// modules still run on it). The loop is neither run nor closed here.
Status adopt(uv_loop_t *loop) noexcept
{
if (initialised_ || loop == nullptr) {
return fail(Errc::invalid);
}
native_ = loop;
return {};
}
// Run until stop() or until nothing is left to do.
void run() noexcept { uv_run(&loop_, UV_RUN_DEFAULT); }
void run() noexcept { uv_run(native_, UV_RUN_DEFAULT); }
// Poll once without blocking.
void run_nowait() noexcept { uv_run(&loop_, UV_RUN_NOWAIT); }
void run_nowait() noexcept { uv_run(native_, UV_RUN_NOWAIT); }
// Block for one round of events.
void run_once() noexcept { uv_run(&loop_, UV_RUN_ONCE); }
void stop() noexcept { uv_stop(&loop_); }
void run_once() noexcept { uv_run(native_, UV_RUN_ONCE); }
void stop() noexcept { uv_stop(native_); }
// Cached loop time in milliseconds.
std::uint64_t now() const noexcept { return uv_now(&loop_); }
void update_time() noexcept { uv_update_time(&loop_); }
std::uint64_t now() const noexcept { return uv_now(native_); }
void update_time() noexcept { uv_update_time(native_); }
uv_loop_t *native() noexcept { return &loop_; }
uv_loop_t *native() noexcept { return native_; }
// Let pending close callbacks run and release the loop. Fails, leaving
// the loop usable, while handles are still open.
@@ -104,7 +115,8 @@ public:
private:
uv_loop_t loop_{};
bool initialised_ = false;
uv_loop_t *native_ = &loop_;
bool initialised_ = false;
};
// Collects the asynchronous closes of one owner's handles and invokes a
@@ -241,13 +253,16 @@ public:
int interest() const noexcept { return interest_; }
ss_socket_t fd() const noexcept { return fd_; }
bool is_open() const noexcept { return live; }
bool is_closing() const noexcept { return group != nullptr; }
// Stop polling and close; the socket is closed once libuv lets go.
void close(CloseGroup &closer) noexcept
// Stop polling and close; the socket is closed once libuv lets go,
// unless someone else (c-ares) owns it.
void close(CloseGroup &closer, bool close_socket = true) noexcept
{
if (!live || group != nullptr) {
return;
}
close_socket_ = close_socket;
group = &closer;
closer.enter();
uv_close(as_handle(), &Poll::on_closed);
@@ -267,16 +282,19 @@ private:
static void on_closed(uv_handle_t *h) noexcept
{
auto *self = static_cast<Poll *>(h->data);
ss_socket_close(self->fd_);
if (self->close_socket_) {
ss_socket_close(self->fd_);
}
self->live = false;
CloseGroup *closer = self->group;
self->group = nullptr;
closer->leave(); // may destroy self
}
Owner *owner_ = nullptr;
ss_socket_t fd_ = 0;
int interest_ = 0;
Owner *owner_ = nullptr;
ss_socket_t fd_ = 0;
int interest_ = 0;
bool close_socket_ = true;
};
template <class Owner, void (Owner::*OnFire)()>
+2
View File
@@ -33,6 +33,8 @@ extern "C" {
#define MAX_CONNECT_TIMEOUT 10
#define MIN_TCP_IDLE_TIMEOUT (24 * 3600)
#define MIN_UDP_TIMEOUT 10
/* Connections a TCP relay serves at once; more are refused. */
#define DEFAULT_MAX_CONNECTIONS 1024
#define DSCP_EF 0x2E
#define DSCP_MIN 0x0
+145 -1233
View File
File diff suppressed because it is too large. Load diff
-111
View File
@@ -1,111 +0,0 @@
/*
* local.h - Define the client's buffers and callbacks
*
* Copyright (C) 2013 - 2019, Max Lv <max.c.lv@gmail.com>
*
* This file is part of the shadowsocks-libev.
*
* shadowsocks-libev is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 3 of the License, or
* (at your option) any later version.
*
* shadowsocks-libev is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with shadowsocks-libev; see the file COPYING. If not, see
* <http://www.gnu.org/licenses/>.
*/
#ifndef _LOCAL_H
#define _LOCAL_H
#include "core.h"
#include "ss_event.h"
#ifdef __MINGW32__
#include "ss_windows.h"
#endif
#include "crypto.h"
#include "jconf.h"
#include "common.h"
#ifdef __cplusplus
extern "C" {
#endif
typedef struct listen_ctx {
ss_io io;
char *iface;
int remote_num;
int timeout;
int fd;
int mptcp;
struct sockaddr **remote_addr;
} listen_ctx_t;
typedef struct server_ctx {
ss_io io;
int connected;
struct server *server;
} server_ctx_t;
typedef struct server {
int fd;
int stage;
struct local_dns_query *dns_query;
int dns_done, dns_success;
struct sockaddr_storage dns_address;
cipher_ctx_t *e_ctx;
cipher_ctx_t *d_ctx;
struct server_ctx *recv_ctx;
struct server_ctx *send_ctx;
struct listen_ctx *listener;
struct remote *remote;
buffer_t *buf;
buffer_t *abuf;
ss_timer delayed_connect_watcher;
struct ss_list_item entries;
} server_t;
typedef struct remote_ctx {
ss_io io;
ss_timer watcher;
int connected;
struct remote *remote;
} remote_ctx_t;
typedef struct remote {
int fd;
int direct;
int addr_len;
uint32_t counter;
#ifdef TCP_FASTOPEN_WINSOCK
OVERLAPPED olap;
int connect_ex_done;
#endif
buffer_t *buf;
struct remote_ctx *recv_ctx;
struct remote_ctx *send_ctx;
struct server *server;
struct sockaddr_storage addr;
} remote_t;
#ifdef __cplusplus
}
#endif
#endif // _LOCAL_H
+1 -1
View File
@@ -1042,7 +1042,7 @@ manager_recv_cb(SS_P_ ss_io *w, int revents)
}
size_t pos = strlen(buf);
strcpy(buf + max(pos - 1, 1), "\n]"); // Remove trailing ","
strcpy(buf + (pos > 2 ? pos - 1 : 1), "\n]"); // Remove trailing ","
pos = strlen(buf);
if (sendto(manager->fd, buf, pos, 0, (struct sockaddr *)&claddr, len)
!= pos) {
+5 -3
View File
@@ -31,10 +31,12 @@
#include <netinet/in.h>
#endif
#if defined(HAVE_LINUX_TCP_H)
#include <linux/tcp.h>
#elif defined(HAVE_NETINET_TCP_H)
/* netinet/tcp.h first: libuv includes it, and linux/tcp.h redefines its
* structures. */
#if defined(HAVE_NETINET_TCP_H)
#include <netinet/tcp.h>
#elif defined(HAVE_LINUX_TCP_H)
#include <linux/tcp.h>
#elif defined(HAVE_NETDB_H)
#include <netdb.h>
#endif
+74 -718
View File
@@ -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;
}
-86
View File
@@ -1,86 +0,0 @@
/* * redir.h - Define the redirector's buffers and callbacks
*
* Copyright (C) 2013 - 2019, Max Lv <max.c.lv@gmail.com>
*
* This file is part of the shadowsocks-libev.
*
* shadowsocks-libev is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 3 of the License, or
* (at your option) any later version.
*
* shadowsocks-libev is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with shadowsocks-libev; see the file COPYING. If not, see
* <http://www.gnu.org/licenses/>.
*/
#ifndef _REDIR_H
#define _REDIR_H
#include "ss_event.h"
#include "crypto.h"
#include "jconf.h"
#ifdef __cplusplus
extern "C" {
#endif
typedef struct listen_ctx {
ss_io io;
int remote_num;
int timeout;
int fd;
int mptcp;
int tos;
struct sockaddr **remote_addr;
} listen_ctx_t;
typedef struct server_ctx {
ss_io io;
int connected;
struct server *server;
} server_ctx_t;
typedef struct server {
int fd;
buffer_t *buf;
cipher_ctx_t *e_ctx;
cipher_ctx_t *d_ctx;
struct server_ctx *recv_ctx;
struct server_ctx *send_ctx;
struct remote *remote;
struct sockaddr_storage destaddr;
ss_timer delayed_connect_watcher;
} server_t;
typedef struct remote_ctx {
ss_io io;
ss_timer watcher;
int connected;
struct remote *remote;
} remote_ctx_t;
typedef struct remote {
int fd;
buffer_t *buf;
struct remote_ctx *recv_ctx;
struct remote_ctx *send_ctx;
struct server *server;
uint32_t counter;
struct sockaddr *addr;
} remote_t;
#ifdef __cplusplus
}
#endif
#endif // _REDIR_H
+210
View File
@@ -0,0 +1,210 @@
/* SPDX-License-Identifier: GPL-3.0-or-later */
#include "relay/codec.hpp"
#include <algorithm>
#include <cstring>
#include "crypto.h"
#include "ppbloom.h"
#include "utils.h"
// crypto.h defines min/max macros for the C sources.
#undef min
#undef max
namespace ss::relay {
Status Method::init(const char *method, const char *password, const char *key, Role role) noexcept
{
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));
key_ = psk;
return {};
}
if (auto spec = crypto::find_aead(method)) {
if (key != nullptr) {
SS_TRY_ASSIGN(master, crypto::MasterKey::from_base64(*spec, key));
key_ = master;
} else {
SS_TRY_ASSIGN(master, crypto::MasterKey::from_password(*spec, password));
key_ = master;
}
return {};
}
#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) {
return fail(Errc::config);
}
key_ = stream;
} else {
SS_TRY_ASSIGN(stream, crypto::StreamKey::from_password(*spec, password));
key_ = stream;
}
return {};
}
#endif
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) {
impl_.emplace<Plain>();
return {};
}
const crypto::ReplayFilter filter = crypto::ReplayFilter::ppbloom();
if (auto *master = std::get_if<crypto::MasterKey>(&method.key_)) {
return impl_.emplace<crypto::AeadTcpEncryptor>().init(*master, filter, printable_salt);
}
if (auto *psk = std::get_if<crypto::Psk>(&method.key_)) {
const crypto::Aead2022TcpDecryptor *request = peer != nullptr ? peer->as_2022() : nullptr;
return impl_.emplace<crypto::Aead2022TcpEncryptor>().init(*psk, method.role_, request);
}
#if SS_ENABLE_LEGACY
if (auto *stream = std::get_if<crypto::StreamKey>(&method.key_)) {
return impl_.emplace<crypto::StreamTcpEncryptor>().init(*stream, filter);
}
#endif
return fail(Errc::config);
}
// Explicit dispatch: std::visit can report bad_variant_access through the
// C++ runtime, which the programs do not link.
#if SS_ENABLE_LEGACY
#define SS_STREAM_CASE(E, expr) \
if (auto *e = std::get_if<crypto::Stream##E>(&impl_)) { \
return expr; \
}
#else
#define SS_STREAM_CASE(E, expr)
#endif
std::size_t Encoder::sealed_size(std::size_t plaintext_len) const noexcept
{
if (auto *e = std::get_if<crypto::AeadTcpEncryptor>(&impl_)) {
return e->sealed_size(plaintext_len);
}
if (auto *e = std::get_if<crypto::Aead2022TcpEncryptor>(&impl_)) {
return e->sealed_size(plaintext_len);
}
SS_STREAM_CASE(TcpEncryptor, e->sealed_size(plaintext_len))
return plaintext_len;
}
Result<std::size_t> Encoder::seal(std::span<const std::uint8_t> plaintext, std::span<std::uint8_t> out) noexcept
{
if (auto *e = std::get_if<crypto::AeadTcpEncryptor>(&impl_)) {
return e->seal(plaintext, out);
}
if (auto *e = std::get_if<crypto::Aead2022TcpEncryptor>(&impl_)) {
return e->seal(plaintext, out);
}
SS_STREAM_CASE(TcpEncryptor, e->seal(plaintext, out))
if (out.size() < plaintext.size()) {
return fail(Errc::invalid);
}
if (!plaintext.empty()) {
std::memcpy(out.data(), plaintext.data(), plaintext.size());
}
return plaintext.size();
}
std::span<const std::uint8_t> Encoder::salt_2022() const noexcept
{
if (auto *e = std::get_if<crypto::Aead2022TcpEncryptor>(&impl_)) {
return e->salt();
}
return {};
}
Status Decoder::init(Method &method, bool plain, const Encoder *peer) noexcept
{
if (plain) {
impl_.emplace<Plain>();
return {};
}
const crypto::ReplayFilter filter = crypto::ReplayFilter::ppbloom();
if (auto *master = std::get_if<crypto::MasterKey>(&method.key_)) {
return impl_.emplace<crypto::AeadTcpDecryptor>().init(*master, filter);
}
if (auto *psk = std::get_if<crypto::Psk>(&method.key_)) {
if (method.role_ == Role::client) {
if (peer == nullptr) {
return fail(Errc::invalid);
}
return impl_.emplace<crypto::Aead2022TcpDecryptor>().init(*psk, Role::client, peer->salt_2022());
}
return impl_.emplace<crypto::Aead2022TcpDecryptor>().init(*psk, Role::server, {}, &method.salt_pool_);
}
#if SS_ENABLE_LEGACY
if (auto *stream = std::get_if<crypto::StreamKey>(&method.key_)) {
return impl_.emplace<crypto::StreamTcpDecryptor>().init(*stream, filter);
}
#endif
return fail(Errc::config);
}
std::size_t Decoder::accept_limit() const noexcept
{
if (auto *d = std::get_if<crypto::AeadTcpDecryptor>(&impl_)) {
return d->accept_limit();
}
if (auto *d = std::get_if<crypto::Aead2022TcpDecryptor>(&impl_)) {
return d->accept_limit();
}
return SIZE_MAX; // plain and stream decode as much as there is room for
}
Result<Decoder::Step> Decoder::decrypt(std::span<const std::uint8_t> in, std::span<std::uint8_t> out) noexcept
{
auto convert = [](auto step) -> Result<Step> {
if (!step) {
return fail(step.error());
}
return Step{ step->consumed, step->produced };
};
if (auto *d = std::get_if<crypto::AeadTcpDecryptor>(&impl_)) {
return convert(d->decrypt(in, out));
}
if (auto *d = std::get_if<crypto::Aead2022TcpDecryptor>(&impl_)) {
return convert(d->decrypt(in, out));
}
SS_STREAM_CASE(TcpDecryptor, convert(e->decrypt(in, out)))
std::size_t n = std::min(in.size(), out.size());
if (n != 0) {
std::memcpy(out.data(), in.data(), n);
}
return Step{ n, n };
}
#undef SS_STREAM_CASE
} // namespace ss::relay
+126
View File
@@ -0,0 +1,126 @@
/* SPDX-License-Identifier: GPL-3.0-or-later */
#ifndef SS_RELAY_CODEC_HPP
#define SS_RELAY_CODEC_HPP
// The cipher a relay is configured with, and the per-connection encoder and
// decoder built from it. Every connection holds both by value; which kind is
// live is chosen per connection, because a connection that bypasses the
// proxy (ACL) is plain.
#include <cstddef>
#include <cstdint>
#include <span>
#include <variant>
#ifdef HAVE_CONFIG_H
#include "config.h"
#endif
#include "core/result.hpp"
#include "crypto/aead.hpp"
#include "crypto/aead2022.hpp"
#if SS_ENABLE_LEGACY
#include "crypto/stream.hpp"
#endif
namespace ss::relay {
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). 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<crypto::MasterKey>(&key_); }
const crypto::Psk *psk() const noexcept { return std::get_if<crypto::Psk>(&key_); }
#if SS_ENABLE_LEGACY
const crypto::StreamKey *stream() const noexcept { return std::get_if<crypto::StreamKey>(&key_); }
#endif
bool is_2022() const noexcept { return std::holds_alternative<crypto::Psk>(key_); }
// Room a first seal() needs on top of the plaintext.
static constexpr std::size_t kMaxOverhead = 2048;
// AEAD-2022 servers remember every request salt for a minute.
Status reserve_salt_pool(std::uint32_t capacity, std::uint64_t seed) noexcept
{
return salt_pool_.reserve(capacity, seed);
}
private:
friend class Encoder;
friend class Decoder;
#if SS_ENABLE_LEGACY
using Key = std::variant<crypto::MasterKey, crypto::Psk, crypto::StreamKey>;
#else
using Key = std::variant<crypto::MasterKey, crypto::Psk>;
#endif
Key key_{};
Role role_ = Role::client;
bool bloom_ready_ = false;
crypto::SaltPool salt_pool_;
};
class Decoder;
class Encoder {
public:
// `plain` skips encryption (a direct connection). An AEAD-2022 server
// encoder answers the request `peer` decoded.
Status init(Method &method, bool plain, const Decoder *peer, bool printable_salt = false) noexcept;
std::size_t sealed_size(std::size_t plaintext_len) const noexcept;
Result<std::size_t> seal(std::span<const std::uint8_t> plaintext, std::span<std::uint8_t> out) noexcept;
std::span<const std::uint8_t> salt_2022() const noexcept;
private:
struct Plain {};
#if SS_ENABLE_LEGACY
std::variant<Plain, crypto::AeadTcpEncryptor, crypto::Aead2022TcpEncryptor, crypto::StreamTcpEncryptor> impl_;
#else
std::variant<Plain, crypto::AeadTcpEncryptor, crypto::Aead2022TcpEncryptor> impl_;
#endif
};
class Decoder {
public:
struct Step {
std::size_t consumed;
std::size_t produced;
};
// An AEAD-2022 client decoder checks the response against `peer`'s salt.
Status init(Method &method, bool plain, const Encoder *peer) noexcept;
// Input bytes the next decrypt() consumes in full.
std::size_t accept_limit() const noexcept;
// Decrypt into `out`; a decoder may keep plaintext back when `out` is
// small, so call again with an empty input to drain it. `out` must hold
// at least kMinOutput bytes.
Result<Step> decrypt(std::span<const std::uint8_t> in, std::span<std::uint8_t> out) noexcept;
bool is_2022() const noexcept { return std::holds_alternative<crypto::Aead2022TcpDecryptor>(impl_); }
const crypto::Aead2022TcpDecryptor *as_2022() const noexcept
{
return std::get_if<crypto::Aead2022TcpDecryptor>(&impl_);
}
static constexpr std::size_t kMinOutput = crypto::kMaxChunk;
private:
struct Plain {};
#if SS_ENABLE_LEGACY
std::variant<Plain, crypto::AeadTcpDecryptor, crypto::Aead2022TcpDecryptor, crypto::StreamTcpDecryptor> impl_;
#else
std::variant<Plain, crypto::AeadTcpDecryptor, crypto::Aead2022TcpDecryptor> impl_;
#endif
};
} // namespace ss::relay
#endif
+214
View File
@@ -0,0 +1,214 @@
/* SPDX-License-Identifier: GPL-3.0-or-later */
#include "relay/resolver.hpp"
#include <cstring>
#include "utils.h"
extern "C" int verbose;
namespace ss::relay {
Resolver::~Resolver()
{
if (channel_ != nullptr) {
std::abort(); // shutdown() first
}
}
Status Resolver::init(event::Loop &loop, const char *nameservers, bool ipv6_first, std::uint32_t max_queries) noexcept
{
if (channel_ != nullptr) {
return fail(Errc::invalid);
}
SS_TRY(queries_.reserve(max_queries));
int status = ares_library_init(ARES_LIB_INIT_ALL);
if (status != ARES_SUCCESS) {
LOGE("c-ares error: %s", ares_strerror(status));
return fail(Errc::config);
}
ares_options options{};
options.sock_state_cb = &Resolver::sock_state;
options.sock_state_cb_data = this;
options.timeout = 3000;
options.tries = 2;
status = ares_init_options(&channel_, &options,
ARES_OPT_NOROTATE | ARES_OPT_TIMEOUTMS | ARES_OPT_TRIES | ARES_OPT_SOCK_STATE_CB);
if (status != ARES_SUCCESS) {
LOGE("c-ares error: %s", ares_strerror(status));
channel_ = nullptr;
return fail(Errc::config);
}
if (nameservers != nullptr && ares_set_servers_ports_csv(channel_, nameservers) != ARES_SUCCESS) {
LOGE("failed to set nameservers %s", nameservers);
ares_destroy(channel_);
channel_ = nullptr;
return fail(Errc::config);
}
loop_ = &loop;
ipv6_first_ = ipv6_first;
for (Watch &w : watches_) {
w.owner = this;
}
SS_TRY(timer_.open(loop, *this));
return {};
}
Status Resolver::resolve(const char *host, std::uint16_t port, const ResolveCallback &cb) noexcept
{
if (channel_ == nullptr) {
return fail(Errc::closed);
}
SS_TRY_ASSIGN(query, queries_.acquire());
query->owner = this;
query->cb = cb;
query->port = port;
// c-ares holds the query until both lookups have reported back.
Query *raw = query.release();
ares_gethostbyname(channel_, host, AF_INET, &Resolver::on_host, raw);
ares_gethostbyname(channel_, host, AF_INET6, &Resolver::on_host, raw);
rearm();
return {};
}
void Resolver::on_host(void *arg, int status, int, hostent *he) noexcept
{
auto *q = static_cast<Query *>(arg);
if (status == ARES_SUCCESS && he != nullptr && he->h_addr_list[0] != nullptr) {
if (he->h_addrtype == AF_INET && !q->has4 && he->h_length == sizeof(in_addr)) {
q->v4.sin_family = AF_INET;
q->v4.sin_port = q->port;
std::memcpy(&q->v4.sin_addr, he->h_addr_list[0], sizeof(in_addr));
q->has4 = true;
} else if (he->h_addrtype == AF_INET6 && !q->has6 && he->h_length == sizeof(in6_addr)) {
q->v6.sin6_family = AF_INET6;
q->v6.sin6_port = q->port;
std::memcpy(&q->v6.sin6_addr, he->h_addr_list[0], sizeof(in6_addr));
q->has6 = true;
}
} else if (verbose && status != ARES_EDESTRUCTION) {
LOGI("failed to lookup address: %s", ares_strerror(status));
}
q->owner->finish(q, status);
}
void Resolver::finish(Query *q, int status) noexcept
{
if (--q->pending != 0) {
return;
}
PoolPtr<Query> owned = queries_.adopt(q);
const sockaddr *best = nullptr;
if (status != ARES_EDESTRUCTION) {
if (ipv6_first_) {
best = q->has6 ? reinterpret_cast<const sockaddr *>(&q->v6)
: (q->has4 ? reinterpret_cast<const sockaddr *>(&q->v4) : nullptr);
} else {
best = q->has4 ? reinterpret_cast<const sockaddr *>(&q->v4)
: (q->has6 ? reinterpret_cast<const sockaddr *>(&q->v6) : nullptr);
}
}
ResolveCallback cb = q->cb;
sockaddr_storage copy{};
if (best != nullptr) {
std::memcpy(&copy, best, best->sa_family == AF_INET ? sizeof(sockaddr_in) : sizeof(sockaddr_in6));
}
owned.reset(); // the slot is free before the callback can start another query
cb.fn(cb, best != nullptr ? reinterpret_cast<const sockaddr *>(&copy) : nullptr);
}
event::Flow Resolver::Watch::on_ready(int events) noexcept
{
ares_socket_t rfd = (events & (event::readable | event::failed)) ? socket : ARES_SOCKET_BAD;
ares_socket_t wfd = (events & (event::writable | event::failed)) ? socket : ARES_SOCKET_BAD;
ares_process_fd(owner->channel_, rfd, wfd);
owner->rearm();
return event::Flow::proceed;
}
void Resolver::sock_state(void *data, ares_socket_t s, int readable, int writable) noexcept
{
auto *self = static_cast<Resolver *>(data);
Watch *slot = nullptr, *free_slot = nullptr;
for (Watch &w : self->watches_) {
if (w.poll.is_open() && !w.poll.is_closing() && w.socket == s) {
slot = &w;
break;
}
if (free_slot == nullptr && !w.poll.is_open()) {
free_slot = &w;
}
}
int events = (readable ? event::readable : 0) | (writable ? event::writable : 0);
if (events == 0) {
if (slot != nullptr) {
// c-ares closes the socket itself right after this.
slot->poll.close(self->closer_, false);
}
return;
}
if (slot == nullptr) {
if (free_slot == nullptr) {
LOGE("resolver: too many DNS sockets");
return;
}
slot = free_slot;
if (!slot->poll.open(*self->loop_, *slot, static_cast<ss_socket_t>(s))) {
LOGE("resolver: cannot watch DNS socket");
return;
}
slot->socket = s;
}
(void)slot->poll.want(events);
}
void Resolver::rearm() noexcept
{
if (channel_ == nullptr) {
return;
}
timeval tv{};
if (ares_timeout(channel_, nullptr, &tv) == nullptr) {
timer_.stop();
return;
}
std::uint64_t ms = static_cast<std::uint64_t>(tv.tv_sec) * 1000 + static_cast<std::uint64_t>(tv.tv_usec) / 1000;
timer_.start(ms == 0 ? 1 : ms);
}
void Resolver::on_timer() noexcept
{
ares_process_fd(channel_, ARES_SOCKET_BAD, ARES_SOCKET_BAD);
rearm();
}
void Resolver::shutdown(CloseCompletion done) noexcept
{
if (channel_ == nullptr) {
done();
return;
}
done_ = done;
closer_.arm(CloseCompletion(
[](void *self, std::uint32_t) { static_cast<Resolver *>(self)->on_closed_all(); }, this, 0));
// Cancelled queries report nullptr; destroying the channel then reports
// ARES_EDESTRUCTION for anything left and closes the sockets.
ares_cancel(channel_);
ares_channel channel = channel_;
channel_ = nullptr;
ares_destroy(channel);
ares_library_cleanup();
for (Watch &w : watches_) {
w.poll.close(closer_, false);
}
timer_.close(closer_);
closer_.seal();
}
void Resolver::on_closed_all() noexcept
{
CloseCompletion done = done_;
done();
}
} // namespace ss::relay
+100
View File
@@ -0,0 +1,100 @@
/* SPDX-License-Identifier: GPL-3.0-or-later */
#ifndef SS_RELAY_RESOLVER_HPP
#define SS_RELAY_RESOLVER_HPP
// Asynchronous DNS over c-ares with a fixed number of queries in flight.
//
// Replaces resolv.cpp's per-query state, per-answer sockaddr allocations
// and per-socket watcher nodes. A query lives in a pool slot; its answers are
// kept inline (the first A and the first AAAA record are all that is ever
// used); c-ares sockets are watched from a fixed table. A query never points
// at the object waiting for it: it carries a callback plus opaque words, and
// the relay resolves its own generation-checked handle when the answer comes
// back, so a connection that closed in the meantime is simply not found.
#include <array>
#include <cstdint>
#include <ares.h>
#include "core/pool.hpp"
#include "core/result.hpp"
#include "event/loop.hpp"
#include "netutils.h"
namespace ss::relay {
class Resolver;
// Called once per query with the chosen address, or nullptr when the name
// did not resolve (or the resolver is shutting down).
struct ResolveCallback {
void (*fn)(const ResolveCallback &self, const sockaddr *address) noexcept = nullptr;
void *ctx = nullptr;
void *aux = nullptr;
std::uint64_t token = 0;
};
class Resolver {
public:
static constexpr std::uint32_t kMaxSockets = 32;
Resolver() noexcept = default;
Resolver(const Resolver &) = delete;
Resolver &operator=(const Resolver &) = delete;
~Resolver();
// `nameservers` is a c-ares CSV list (host[:port],...) or nullptr for the
// system configuration.
Status init(event::Loop &loop, const char *nameservers, bool ipv6_first, std::uint32_t max_queries) noexcept;
// `port` is in network byte order and copied into the answer. Fails with
// Errc::exhausted when max_queries are already in flight; the callback
// is then never called.
Status resolve(const char *host, std::uint16_t port, const ResolveCallback &cb) noexcept;
std::uint32_t in_flight() const noexcept { return queries_.live(); }
bool ready() const noexcept { return channel_ != nullptr; }
// Cancel everything (callbacks get nullptr) and close the watchers; the
// closes finish on the next loop iterations, then `done` runs.
void shutdown(CloseCompletion done) noexcept;
private:
struct Query {
Resolver *owner = nullptr;
ResolveCallback cb;
std::uint16_t port = 0;
std::uint8_t pending = 2; // A and AAAA
bool has4 = false, has6 = false;
sockaddr_in v4{};
sockaddr_in6 v6{};
};
struct Watch {
Resolver *owner = nullptr;
ares_socket_t socket = ARES_SOCKET_BAD;
event::Flow on_ready(int events) noexcept;
event::Poll<Watch, &Watch::on_ready> poll;
};
static void sock_state(void *data, ares_socket_t s, int readable, int writable) noexcept;
static void on_host(void *arg, int status, int timeouts, hostent *he) noexcept;
void on_timer() noexcept;
void rearm() noexcept;
void finish(Query *q, int status) noexcept;
void on_closed_all() noexcept;
event::Loop *loop_ = nullptr;
ares_channel channel_ = nullptr;
bool ipv6_first_ = false;
ObjectPool<Query> queries_;
std::array<Watch, kMaxSockets> watches_{};
event::Timer<Resolver, &Resolver::on_timer> timer_;
event::CloseGroup closer_;
CloseCompletion done_;
};
} // namespace ss::relay
#endif
+347
View File
@@ -0,0 +1,347 @@
/* SPDX-License-Identifier: GPL-3.0-or-later */
#include "relay/socket.hpp"
#include <cstring>
#ifndef _WIN32
#include <netinet/tcp.h>
#endif
#include "utils.h"
#ifndef SOL_TCP
#define SOL_TCP IPPROTO_TCP
#endif
namespace ss::relay {
namespace {
bool would_block(int err) noexcept
{
return err == EAGAIN || err == EWOULDBLOCK;
}
} // namespace
Status set_nonblocking(ss_socket_t fd) noexcept
{
#ifdef _WIN32
if (setnonblocking(fd) != 0) {
return fail(Errc::io, ss_socket_error());
}
#else
int flags = fcntl(fd, F_GETFL, 0);
if (flags == -1) {
flags = 0;
}
if (fcntl(fd, F_SETFL, flags | O_NONBLOCK) == -1) {
return fail(Errc::io, errno);
}
#endif
return {};
}
void set_nodelay(ss_socket_t fd, bool on) noexcept
{
int opt = on ? 1 : 0;
(void)ss_setsockopt(fd, SOL_TCP, TCP_NODELAY, &opt, sizeof(opt));
}
void set_nosigpipe(ss_socket_t fd) noexcept
{
#ifdef SO_NOSIGPIPE
int opt = 1;
(void)ss_setsockopt(fd, SOL_SOCKET, SO_NOSIGPIPE, &opt, sizeof(opt));
#else
(void)fd;
#endif
}
socklen_t sockaddr_size(const sockaddr *addr) noexcept
{
if (addr->sa_family == AF_INET) {
return sizeof(sockaddr_in);
}
if (addr->sa_family == AF_INET6) {
return sizeof(sockaddr_in6);
}
return 0;
}
Result<ss_socket_t> open_upstream(int family, SocketTuning &t, bool protect) noexcept
{
int protocol = t.mptcp < 0 ? IPPROTO_MPTCP : IPPROTO_TCP;
ss_socket_t fd = ss_socket(family, SOCK_STREAM, protocol);
#ifdef _WIN32
if (fd == INVALID_SOCKET) {
#else
if (fd < 0) {
#endif
int err = ss_socket_error();
ERROR("socket");
return fail(Errc::io, err);
}
if (protect && t.protect != nullptr && t.protect(static_cast<int>(fd)) == -1) {
ERROR("protect_socket");
ss_socket_close(fd);
return fail(Errc::io);
}
int opt = 1;
set_nodelay(fd, true);
set_nosigpipe(fd);
if (t.keepalive || t.keepalive_tuned) {
(void)ss_setsockopt(fd, SOL_SOCKET, SO_KEEPALIVE, &opt, sizeof(opt));
}
#if defined(TCP_KEEPIDLE) && defined(TCP_KEEPINTVL) && defined(TCP_KEEPCNT)
if (t.keepalive_tuned) {
int idle = 40, interval = 20, count = 5;
(void)ss_setsockopt(fd, SOL_TCP, TCP_KEEPIDLE, &idle, sizeof(idle));
(void)ss_setsockopt(fd, SOL_TCP, TCP_KEEPINTVL, &interval, sizeof(interval));
(void)ss_setsockopt(fd, SOL_TCP, TCP_KEEPCNT, &count, sizeof(count));
}
#endif
if (t.tos >= 0) {
if (ss_setsockopt(fd, IPPROTO_IP, IP_TOS, &t.tos, sizeof(t.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, &t.tos, sizeof(t.tos)) < 0
&& ss_socket_error() != ENOPROTOOPT) {
LOGE("setting ipv6 dscp failed: %d", ss_socket_error());
}
#endif
}
if (t.mptcp > 1) {
if (ss_setsockopt(fd, SOL_TCP, t.mptcp, &opt, sizeof(opt)) == -1) {
ERROR("failed to enable out-of-tree multipath TCP");
}
} else if (t.mptcp == 1) {
int i = 0;
while ((t.mptcp = mptcp_enabled_values[i]) > 0) {
if (ss_setsockopt(fd, SOL_TCP, t.mptcp, &opt, sizeof(opt)) != -1) {
break;
}
++i;
}
if (t.mptcp == 0) {
ERROR("failed to enable out-of-tree multipath TCP");
}
}
if (t.sndbuf > 0) {
(void)ss_setsockopt(fd, SOL_SOCKET, SO_SNDBUF, &t.sndbuf, sizeof(int));
}
if (t.rcvbuf > 0) {
(void)ss_setsockopt(fd, SOL_SOCKET, SO_RCVBUF, &t.rcvbuf, sizeof(int));
}
if (!set_nonblocking(fd)) {
ERROR("setnonblocking");
}
const sockaddr_storage *source = family == AF_INET ? t.bind_v4 : t.bind_v6;
if (source != nullptr && source->ss_family == family
&& bind_to_addr(const_cast<sockaddr_storage *>(source), static_cast<int>(fd)) == -1) {
ERROR("bind_to_addr");
ss_socket_close(fd);
return fail(Errc::io);
}
#ifdef SET_INTERFACE
if (t.iface != nullptr && setinterface(static_cast<int>(fd), t.iface) == -1) {
ERROR("setinterface");
}
#endif
return fd;
}
Result<std::size_t> start_connect(ss_socket_t fd, const sockaddr *addr, std::span<const std::uint8_t> data,
bool fast_open) noexcept
{
socklen_t len = sockaddr_size(addr);
if (!fast_open || data.empty()) {
if (connect(fd, addr, len) == -1 && ss_socket_error() != CONNECT_IN_PROGRESS) {
return fail(Errc::io, ss_socket_error());
}
return std::size_t{ 0 };
}
ssize_t s = -1;
#if defined(MSG_FASTOPEN) && !defined(TCP_FASTOPEN_CONNECT)
s = sendto(fd, reinterpret_cast<const char *>(data.data()), data.size(), MSG_FASTOPEN, addr, len);
#elif defined(CONNECT_DATA_IDEMPOTENT)
sockaddr_storage copy{};
std::memcpy(&copy, addr, len);
if (copy.ss_family == AF_INET) {
reinterpret_cast<sockaddr_in *>(&copy)->sin_len = sizeof(sockaddr_in);
}
sa_endpoints_t endpoints{};
endpoints.sae_dstaddr = reinterpret_cast<sockaddr *>(&copy);
endpoints.sae_dstaddrlen = len;
s = connectx(fd, &endpoints, SAE_ASSOCID_ANY, CONNECT_RESUME_ON_READ_WRITE | CONNECT_DATA_IDEMPOTENT, nullptr,
0, nullptr, nullptr);
if (s == 0) {
s = send(fd, data.data(), data.size(), 0);
}
#elif defined(TCP_FASTOPEN_CONNECT)
int optval = 1;
if (ss_setsockopt(fd, IPPROTO_TCP, TCP_FASTOPEN_CONNECT, &optval, sizeof(optval)) < 0) {
return fail(Errc::invalid, ss_socket_error());
}
s = connect(fd, addr, len);
if (s == 0) {
s = send(fd, data.data(), data.size(), 0);
}
#else
return fail(Errc::invalid, ENOPROTOOPT); // not on this platform
#endif
if (s >= 0) {
return static_cast<std::size_t>(s);
}
int err = ss_socket_error();
if (err == CONNECT_IN_PROGRESS || would_block(err)) {
return std::size_t{ 0 };
}
if (err == EOPNOTSUPP || err == EPROTONOSUPPORT || err == ENOPROTOOPT) {
return fail(Errc::invalid, err);
}
return fail(Errc::io, err);
}
bool is_connected(ss_socket_t fd) noexcept
{
sockaddr_storage addr{};
socklen_t len = sizeof(addr);
return getpeername(fd, reinterpret_cast<sockaddr *>(&addr), &len) == 0;
}
Result<std::size_t> recv_some(ss_socket_t fd, std::span<std::uint8_t> buf) noexcept
{
ssize_t r = recv(fd, reinterpret_cast<char *>(buf.data()), buf.size(), 0);
if (r > 0) {
return static_cast<std::size_t>(r);
}
if (r == 0) {
return fail(Errc::closed);
}
int err = ss_socket_error();
if (would_block(err) || err == EINTR) {
return fail(Errc::again);
}
return fail(Errc::io, err);
}
Result<std::size_t> send_some(ss_socket_t fd, std::span<const std::uint8_t> buf) noexcept
{
ssize_t s = send(fd, reinterpret_cast<const char *>(buf.data()), buf.size(), 0);
if (s >= 0) {
return static_cast<std::size_t>(s);
}
int err = ss_socket_error();
if (would_block(err) || err == EINTR) {
return fail(Errc::again);
}
return fail(Errc::io, err);
}
std::size_t encode_socks_addr(const sockaddr *addr, std::uint8_t *out) noexcept
{
if (addr->sa_family == AF_INET) {
const auto *in = reinterpret_cast<const sockaddr_in *>(addr);
out[0] = 1;
std::memcpy(out + 1, &in->sin_addr, 4);
std::memcpy(out + 5, &in->sin_port, 2);
return 7;
}
if (addr->sa_family == AF_INET6) {
const auto *in = reinterpret_cast<const sockaddr_in6 *>(addr);
out[0] = 4;
std::memcpy(out + 1, &in->sin6_addr, 16);
std::memcpy(out + 17, &in->sin6_port, 2);
return 19;
}
return 0;
}
std::size_t encode_socks_host(const char *host, std::uint16_t port, std::uint8_t *out) noexcept
{
std::uint16_t be = htons(port);
in_addr v4{};
in6_addr v6{};
std::size_t at = 0;
if (inet_pton(AF_INET, host, &v4) == 1) {
out[at++] = 1;
std::memcpy(out + at, &v4, 4);
at += 4;
} else if (inet_pton(AF_INET6, host, &v6) == 1) {
out[at++] = 4;
std::memcpy(out + at, &v6, 16);
at += 16;
} else {
std::size_t n = std::strlen(host);
if (n == 0 || n > 255) {
return 0;
}
out[at++] = 3;
out[at++] = static_cast<std::uint8_t>(n);
std::memcpy(out + at, host, n); // NOLINT(bugprone-not-null-terminated-result): binary, length-prefixed
at += n;
}
std::memcpy(out + at, &be, 2);
return at + 2;
}
ParseResult parse_socks_addr(std::span<const std::uint8_t> in, SocksTarget &out) noexcept
{
if (in.empty()) {
return ParseResult::need_more;
}
out.type = in[0] & 0x0F;
out.ip = {};
std::size_t at = 1;
switch (out.type) {
case 1: {
if (in.size() < 1 + 4 + 2) {
return ParseResult::need_more;
}
auto *sa = reinterpret_cast<sockaddr_in *>(&out.ip);
sa->sin_family = AF_INET;
std::memcpy(&sa->sin_addr, in.data() + 1, 4);
std::memcpy(&sa->sin_port, in.data() + 5, 2);
ss_inet_ntop(AF_INET, &sa->sin_addr, out.host, sizeof(out.host));
at += 4;
break;
}
case 4: {
if (in.size() < 1 + 16 + 2) {
return ParseResult::need_more;
}
auto *sa = reinterpret_cast<sockaddr_in6 *>(&out.ip);
sa->sin6_family = AF_INET6;
std::memcpy(&sa->sin6_addr, in.data() + 1, 16);
std::memcpy(&sa->sin6_port, in.data() + 17, 2);
ss_inet_ntop(AF_INET6, &sa->sin6_addr, out.host, sizeof(out.host));
at += 16;
break;
}
case 3: {
if (in.size() < 2) {
return ParseResult::need_more;
}
std::size_t n = in[1];
if (n == 0) {
return ParseResult::invalid;
}
if (in.size() < 2 + n + 2) {
return ParseResult::need_more;
}
std::memcpy(out.host, in.data() + 2, n);
out.host[n] = '\0';
at += 1 + n;
break;
}
default:
return ParseResult::invalid;
}
out.port = static_cast<std::uint16_t>((in[at] << 8) | in[at + 1]);
out.length = at + 2;
return ParseResult::complete;
}
} // namespace ss::relay
+83
View File
@@ -0,0 +1,83 @@
/* SPDX-License-Identifier: GPL-3.0-or-later */
#ifndef SS_RELAY_SOCKET_HPP
#define SS_RELAY_SOCKET_HPP
// Socket plumbing shared by the relays: option tuning, non-blocking I/O
// with Result errors, TCP Fast Open and SOCKS5 address encoding.
#include <array>
#include <cstddef>
#include <cstdint>
#include <span>
#ifdef HAVE_CONFIG_H
#include "config.h"
#endif
#include "core/result.hpp"
#include "netutils.h"
#include "platform.h"
namespace ss::relay {
// Options applied to every outgoing (upstream) socket.
struct SocketTuning {
int sndbuf = 0;
int rcvbuf = 0;
// As the C relays: < 0 opens IPPROTO_MPTCP sockets, 1 probes the
// out-of-tree option (and remembers the one that worked), > 1 is that
// option.
int mptcp = 0;
const char *iface = nullptr;
bool keepalive = false;
bool keepalive_tuned = false; // ss-redir: idle 40 s, interval 20 s, 5 probes
int tos = -1;
// ss-server --bind-address: source addresses for each family.
const sockaddr_storage *bind_v4 = nullptr;
const sockaddr_storage *bind_v6 = nullptr;
int (*protect)(int fd) = nullptr; // Android VpnService
};
Status set_nonblocking(ss_socket_t fd) noexcept;
void set_nodelay(ss_socket_t fd, bool on) noexcept;
void set_nosigpipe(ss_socket_t fd) noexcept;
socklen_t sockaddr_size(const sockaddr *addr) noexcept;
// A non-blocking stream socket for `family`, tuned for upstream use.
Result<ss_socket_t> open_upstream(int family, SocketTuning &tuning, bool protect) noexcept;
// Start connecting. With `fast_open` the first bytes of `data` travel in the
// SYN; returns how many were accepted (0 when nothing was sent yet). Fails
// with Errc::invalid when the platform turned fast open down; the caller then
// falls back to a plain connect.
Result<std::size_t> start_connect(ss_socket_t fd, const sockaddr *addr, std::span<const std::uint8_t> data,
bool fast_open) noexcept;
// After the socket became writable: did the connect succeed?
bool is_connected(ss_socket_t fd) noexcept;
// recv/send with EAGAIN as Errc::again and an orderly EOF as Errc::closed.
Result<std::size_t> recv_some(ss_socket_t fd, std::span<std::uint8_t> buf) noexcept;
Result<std::size_t> send_some(ss_socket_t fd, std::span<const std::uint8_t> buf) noexcept;
// SOCKS5 address (type, address, port) for an IP endpoint; returns its length.
inline constexpr std::size_t kMaxSocksAddr = 1 + 1 + 255 + 2;
std::size_t encode_socks_addr(const sockaddr *addr, std::uint8_t *out) noexcept;
// For a host name or a literal IP plus a port in host byte order. Returns 0
// when the name does not fit.
std::size_t encode_socks_host(const char *host, std::uint16_t port, std::uint8_t *out) noexcept;
struct SocksTarget {
std::size_t length = 0; // bytes of the encoded address
std::uint8_t type = 0; // 1, 3 or 4
char host[256] = {}; // dotted/colon form for IPs, the name for domains
std::uint16_t port = 0; // host byte order
sockaddr_storage ip{}; // filled for IP addresses
};
enum class ParseResult : std::uint8_t { complete, need_more, invalid };
// Parse a SOCKS5 address at the head of `in` (the shadowsocks request
// header). The address type's upper bits are ignored, as ss-server does.
ParseResult parse_socks_addr(std::span<const std::uint8_t> in, SocksTarget &out) noexcept;
} // namespace ss::relay
#endif
+1174
View File
File diff suppressed because it is too large. Load diff
+240
View File
@@ -0,0 +1,240 @@
/* SPDX-License-Identifier: GPL-3.0-or-later */
#ifndef SS_RELAY_TCP_HPP
#define SS_RELAY_TCP_HPP
// The TCP relay shared by ss-local, ss-tunnel, ss-redir and ss-server,
// replacing the four hand-copied sets of accept/recv/send/close callbacks.
//
// Ownership
// * TcpRelay owns every Connection through a PoolPtr kept in a table
// indexed by pool slot; closing a connection is resetting that pointer.
// The pool then closes the connection's two sockets (begin_close) and
// recycles the slot once libuv has let go of them.
// * Nothing points at a Connection from outside: DNS answers come back
// with a generation-checked Handle, timeouts are intrusive list links
// the connection removes itself from when it closes.
// * Buffers, cipher state and the handshake state live inside the
// connection, so accepting a connection allocates nothing; when the pool
// is full the new connection is refused.
//
// Data moves in lock step, as in the C relays: one direction reads its
// source only while its buffer towards the other side is empty.
#include <array>
#include <cstddef>
#include <cstdint>
#include <span>
#include "core/buffer.hpp"
#include "core/pool.hpp"
#include "core/region.hpp"
#include "core/result.hpp"
#include "event/loop.hpp"
#include "relay/codec.hpp"
#include "relay/resolver.hpp"
#include "relay/socket.hpp"
namespace ss::relay {
enum class Kind : std::uint8_t { local, tunnel, redir, server };
// The ACL module (acl.cpp), for the binaries that link it.
struct AclHooks {
int (*match_host)(const char *host) = nullptr; // > 0 listed as bypass/block, < 0 as proxy, 0 neither
int (*mode)() = nullptr; // BLACK_LIST or WHITE_LIST
int (*outbound_blocked)(const char *host) = nullptr;
};
struct TcpConfig {
Kind kind = Kind::local;
// Client kinds: the ss-server endpoints (or the plugin's local port).
std::span<const sockaddr_storage> remotes;
// ss-tunnel: the encoded target address.
std::array<std::uint8_t, kMaxSocksAddr> tunnel_target{};
std::size_t tunnel_target_len = 0;
int timeout_secs = 60;
std::uint32_t max_connections = 1024;
bool fast_open = false;
bool no_delay = false;
bool printable_salt = false; // ss-local experiment
bool acl = false; // with `acl_hooks` set
AclHooks acl_hooks;
bool tproxy = false; // ss-redir: the target is the local address
bool vpn = false; // Android: protect sockets, no local DNS for ACL
const char *label = ""; // ss-server: the port, for logs
SocketTuning upstream;
std::uint64_t *tx = nullptr; // bytes received from clients
std::uint64_t *rx = nullptr; // bytes received from upstreams
void (*on_traffic)() = nullptr;
// ss-server: a client failed the handshake (logging, nftables).
void (*report_failure)(ss_socket_t client, const char *reason) = nullptr;
// ss-server --connmark-to-TOS: runs before each write to the client
// with per-connection state the hook owns, released when it closes.
void (*downstream)(void **state, ss_socket_t client, ss_socket_t upstream) = nullptr;
void (*release)(void *state) = nullptr;
};
class TcpRelay;
// Buffers towards each side hold one received chunk plus cipher overhead.
inline constexpr std::size_t kRelayBuffer = SOCKET_BUF_SIZE + Method::kMaxOverhead;
inline constexpr std::size_t kHandshakeBuffer = 1024;
class Connection : public event::Deadline {
public:
explicit Connection(TcpRelay &relay) noexcept : relay_(&relay) {}
Connection(const Connection &) = delete;
Connection &operator=(const Connection &) = delete;
void begin_close(CloseCompletion done) noexcept;
event::Flow on_client(int events) noexcept;
event::Flow on_upstream(int events) noexcept;
void on_deadline() noexcept;
void on_resolved(const sockaddr *address) noexcept;
private:
friend class TcpRelay;
enum class Stage : std::uint8_t {
socks_method, // local: waiting for the method selection
socks_request, // local: waiting for the CONNECT request
acl_resolve, // local: resolving a name for the ACL
udp_assoc, // local: UDP ASSOCIATE held open until the client leaves
delay, // client kinds: waiting briefly for the first payload
header, // server: waiting for the request header
resolving, // server: resolving the target
connecting,
stream,
stopped, // server: failed the handshake; drain until timeout
};
enum class Timed : std::uint8_t { none, delay, connect, idle };
event::Flow start(ss_socket_t fd) noexcept;
event::Flow close() noexcept;
// Client side handshakes.
event::Flow socks_input() noexcept;
event::Flow socks_request() noexcept;
event::Flow socks_route() noexcept;
event::Flow delay_input() noexcept;
event::Flow start_upstream(const sockaddr *target, bool direct) noexcept;
// Server side.
event::Flow server_header() noexcept;
event::Flow server_connect(const sockaddr *target) noexcept;
void server_reject(const char *reason) noexcept;
event::Flow connected() noexcept;
event::Flow pump_to_upstream() noexcept;
event::Flow pump_to_client() noexcept;
void update_interest() noexcept;
enum class Pull : std::uint8_t { data, nothing, again, eof, error };
Pull pull(ss_socket_t fd, bool decode, FixedBuffer<kRelayBuffer> &dst, std::uint64_t *counter) noexcept;
Pull flush(ss_socket_t fd, FixedBuffer<kRelayBuffer> &src) noexcept;
bool client_kind() const noexcept;
TcpRelay *relay_;
CloseCompletion done_;
event::Poll<Connection, &Connection::on_client> client_;
event::Poll<Connection, &Connection::on_upstream> upstream_;
event::CloseGroup closer_;
FixedBuffer<kRelayBuffer> to_upstream_;
FixedBuffer<kRelayBuffer> to_client_;
FixedBuffer<kHandshakeBuffer> handshake_;
Encoder enc_;
Decoder dec_;
std::array<std::uint8_t, kMaxSocksAddr> target_header_{};
std::size_t target_header_len_ = 0;
SocksTarget target_{};
sockaddr_storage upstream_addr_{};
Stage stage_ = Stage::socks_method;
Timed timed_ = Timed::none;
bool direct_ = false;
bool dns_done_ = false;
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_{};
};
class TcpRelay {
public:
TcpRelay() noexcept = default;
TcpRelay(const TcpRelay &) = delete;
TcpRelay &operator=(const TcpRelay &) = delete;
~TcpRelay();
// `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.
// 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;
bool closed() const noexcept;
std::uint32_t live() const noexcept { return pool_.live(); }
std::uint64_t refused() const noexcept { return refused_; }
private:
friend class Connection;
struct Listener {
TcpRelay *relay = nullptr;
int tos = -1;
event::Flow on_ready(int events) noexcept;
event::Poll<Listener, &Listener::on_ready> poll;
};
static constexpr std::size_t kMaxListeners = 16;
void accept_from(Listener &listener) noexcept;
void close(Connection &conn) noexcept;
void slot_freed() noexcept;
void set_deadline(Connection &conn, Connection::Timed which) noexcept;
void clear_deadline(Connection &conn) noexcept;
void on_timer() noexcept;
void schedule() noexcept;
Status resolve(Connection &conn, const char *host, std::uint16_t port_be) noexcept;
static void resolved(const ResolveCallback &cb, const sockaddr *address) noexcept;
const sockaddr_storage &pick_remote() noexcept;
void pause_accept() noexcept;
void resume_accept() noexcept;
event::Loop *loop_ = nullptr;
Method *method_ = nullptr;
Resolver *resolver_ = nullptr;
TcpConfig config_{};
ObjectPool<Connection> pool_;
Region owners_region_;
std::span<PoolPtr<Connection> > owners_;
std::array<Listener, kMaxListeners> listeners_{};
std::size_t listener_count_ = 0;
bool accept_paused_ = false;
std::uint64_t refused_ = 0;
std::uint64_t last_refusal_log_ = 0;
event::DeadlineList<Connection> delay_list_;
event::DeadlineList<Connection> connect_list_;
event::DeadlineList<Connection> idle_list_;
event::Timer<TcpRelay, &TcpRelay::on_timer> timer_;
std::uint64_t timer_due_ = 0; // 0 when the timer is idle
event::CloseGroup closer_;
bool shutting_down_ = false;
bool own_closed_ = true;
// One receive at a time, shared by every connection.
std::array<std::uint8_t, SOCKET_BUF_SIZE + kMaxSocksAddr + kHandshakeBuffer> scratch_{};
};
} // namespace ss::relay
#endif
+886
View File
@@ -0,0 +1,886 @@
/* SPDX-License-Identifier: GPL-3.0-or-later */
#include "relay/udp.hpp"
#include <algorithm>
#include <cstring>
#include <ctime>
#include <memory>
#include <sodium.h>
#ifndef _WIN32
#include <netdb.h>
#include <sys/uio.h>
#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 {
#ifdef __linux__
// musl's CMSG_NXTHDR compares a size_t with a ptrdiff_t.
#pragma GCC diagnostic push
#pragma GCC diagnostic ignored "-Wsign-compare"
cmsghdr *next_cmsg(msghdr *msg, cmsghdr *c) noexcept
{
return CMSG_NXTHDR(msg, c);
}
#pragma GCC diagnostic pop
#endif
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<std::uint64_t>(reinterpret_cast<std::uintptr_t>(p));
}
void *unpack_ptr(std::uint64_t v) noexcept
{
return reinterpret_cast<void *>(static_cast<std::uintptr_t>(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<const std::uint8_t> 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<sockaddr *>(&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<sockaddr *>(&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<ss_socket_t> 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<ss_socket_t>(-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<int>(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<ss_socket_t>(-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<ListNode<event::DeadlineTag> &>(*this).linked()) {
event::DeadlineList<UdpSession>::remove(*this);
}
server2022_.reset();
done_ = done;
closer_.arm(CloseCompletion(
[](void *self, std::uint32_t) {
auto *s = static_cast<UdpSession *>(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<int>(kPacketHeader)) {
LOGE("MTU must be greater than %d", static_cast<int>(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::uint64_t>(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<std::uint8_t>(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 = next_cmsg(&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<char *>(in_.data()), in_.size(), 0,
reinterpret_cast<sockaddr *>(&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<std::uint8_t> packet(in_.data(), static_cast<std::size_t>(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<ss_socket_t> 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<sockaddr_storage *>(source), static_cast<int>(fd)) == -1) {
ERROR("bind_to_addr");
ss_socket_close(fd);
return fail(Errc::io);
}
} else {
sockaddr_storage any{};
any.ss_family = static_cast<decltype(any.ss_family)>(family);
socklen_t len = family == AF_INET6 ? sizeof(sockaddr_in6) : sizeof(sockaddr_in);
if (bind(fd, reinterpret_cast<sockaddr *>(&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<int>(fd), config_.iface) == -1) {
ERROR("setinterface");
}
#endif
if (config_.vpn && config_.protect != nullptr && config_.protect(static_cast<int>(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<std::byte, sizeof(int) + sizeof(sockaddr_storage)> raw{};
std::memcpy(raw.data(), &family, sizeof(int));
std::memcpy(raw.data() + sizeof(int), &client, sizeof(sockaddr_storage));
auto key = Key::from(raw);
if (!key) {
return nullptr;
}
if (PoolPtr<UdpSession> *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<std::uint8_t>(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");
}
if (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<std::uint8_t> 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<const sockaddr *>(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<const std::uint8_t> plain(out_.data(), n);
Result<std::size_t> 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<const sockaddr *>(&config_.remote);
if (sendto(session->socket_.fd(), reinterpret_cast<const char *>(in_.data()), *sealed, 0, remote,
sockaddr_size(remote))
== -1) {
ERROR("[udp] server_recv_sendto");
}
}
RefPtr<Shared2022> UdpRelay::shared_for(const std::array<std::uint8_t, crypto::kSessionIdLen> &id) noexcept
{
auto key = FixedKey<crypto::kSessionIdLen>::from(std::as_bytes(std::span(id)));
if (!key) {
return nullptr;
}
std::uint64_t now = now_secs();
if (RefPtr<Shared2022> *found = shared_.find(*key)) {
(*found)->last_used = now;
return *found;
}
// Forget client sessions idle for longer than the TTL (oldest first).
while (RefPtr<Shared2022> *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<Shared2022> ref(raw);
if (!shared_.emplace(*key, ref)) {
return nullptr;
}
return ref;
}
void UdpRelay::server_packet(Listener &listener, const sockaddr_storage &from, std::span<std::uint8_t> packet) noexcept
{
if (config_.tx != nullptr) {
*config_.tx += packet.size();
}
Result<std::size_t> opened = fail(Errc::config);
RefPtr<Shared2022> 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::uint64_t>(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<const void *>(&reinterpret_cast<const sockaddr_in *>(&from)->sin_addr)
: static_cast<const void *>(&reinterpret_cast<const sockaddr_in6 *>(&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<const std::uint8_t> 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<sockaddr_in *>(&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<sockaddr_in6 *>(&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<int>(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<const sockaddr *>(&target.ip), std::move(shared), payload);
}
void UdpRelay::resolved(const ResolveCallback &cb, const sockaddr *address) noexcept
{
auto *self = static_cast<UdpRelay *>(cb.ctx);
PoolPtr<Pending> p = self->pending_.adopt(static_cast<Pending *>(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<Shared2022> shared, std::span<const std::uint8_t> 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<const char *>(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<char *>(in_.data()), in_.size(), 0,
reinterpret_cast<sockaddr *>(&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<std::uint8_t> packet(in_.data(), static_cast<std::size_t>(r));
const Listener &listener = listeners_[session.listener_];
std::span<const std::uint8_t> 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<const sockaddr *>(&from), out_.data());
if (header == 0) {
continue;
}
std::memcpy(out_.data() + header, packet.data(), packet.size());
std::span<const std::uint8_t> plain(out_.data(), header + packet.size());
Result<std::size_t> 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<std::size_t> 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::uint64_t>(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<const char *>(datagram.data()), datagram.size(), 0,
reinterpret_cast<const sockaddr *>(&session.client_),
sockaddr_size(reinterpret_cast<const sockaddr *>(&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<UdpRelay *>(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
+188
View File
@@ -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 <array>
#include <cstddef>
#include <cstdint>
#include <span>
#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<std::uint8_t, kMaxSocksAddr> 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<ss_socket_t> 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<Shared2022> {
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<UdpSession, &UdpSession::on_readable> socket_;
event::CloseGroup closer_;
sockaddr_storage client_{}; // where replies go
std::array<std::byte, sizeof(int) + sizeof(sockaddr_storage)> key_{};
std::uint8_t key_len_ = 0;
crypto::UdpSession2022 client2022_; // client kinds
RefPtr<Shared2022> 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<sizeof(int) + sizeof(sockaddr_storage)>;
struct Listener {
UdpRelay *relay = nullptr;
std::uint8_t index = 0;
event::Flow on_ready(int events) noexcept;
event::Poll<Listener, &Listener::on_ready> poll;
};
static constexpr std::size_t kMaxListeners = 16;
struct Pending {
std::uint8_t listener = 0;
sockaddr_storage client{};
RefPtr<Shared2022> session;
std::size_t len = 0;
std::array<std::uint8_t, kMaxDatagram> payload; // target payload only
};
void receive(Listener &listener) noexcept;
void client_packet(Listener &listener, const sockaddr_storage &from, std::span<std::uint8_t> packet,
const sockaddr_storage *original_dst) noexcept;
void server_packet(Listener &listener, const sockaddr_storage &from, std::span<std::uint8_t> packet) noexcept;
void server_forward(std::uint8_t listener, const sockaddr_storage &client, const sockaddr *target,
RefPtr<Shared2022> shared, std::span<const std::uint8_t> payload) noexcept;
void reply(UdpSession &session) noexcept;
UdpSession *session_for(std::uint8_t listener, const sockaddr_storage &client, int family) noexcept;
RefPtr<Shared2022> shared_for(const std::array<std::uint8_t, crypto::kSessionIdLen> &id) noexcept;
void expire(UdpSession &session) noexcept;
Result<ss_socket_t> 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<Listener, kMaxListeners> listeners_{};
std::size_t listener_count_ = 0;
ObjectPool<UdpSession> pool_;
FixedLru<Key, PoolPtr<UdpSession> > sessions_;
ObjectPool<Shared2022> shared_pool_;
FixedLru<FixedKey<crypto::kSessionIdLen>, RefPtr<Shared2022> > shared_;
ObjectPool<Pending> pending_;
event::DeadlineList<UdpSession> idle_;
event::Timer<UdpRelay, &UdpRelay::on_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<std::uint8_t, kMaxDatagram> in_{};
std::array<std::uint8_t, kMaxDatagram + 3 + kMaxSocksAddr + 256> out_{};
};
} // namespace ss::relay
#endif
+61 -419
View File
@@ -28,454 +28,96 @@
#include "config.h"
#endif
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <fcntl.h>
#ifndef __MINGW32__
#include <netdb.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <errno.h>
#include <unistd.h>
#else
#include "ss_windows.h" // Should be before <ares.h>
#endif
#include <ares.h>
// The C resolver API, now a thin shim over ss::relay::Resolver (see
// src/relay/resolver.hpp), kept for the modules that still call it.
#include <cstdint>
#include <cstring>
#include <memory>
#include <new>
#include "ss_event.h"
#include "core.h"
#include "relay/resolver.hpp"
#include "resolv.h"
#include "utils.h"
#include "netutils.h"
/*
* Implement DNS resolution interface using libc-ares
*/
namespace {
#define SS_TIMER_AFTER 1.0
constexpr std::uint32_t kMaxQueries = 4096;
struct resolv_io {
ss_io io;
struct resolv_io *next;
};
// Constructed in resolv_init and destroyed in resolv_shutdown, so there is
// no static destructor to run at exit.
alignas(ss::relay::Resolver) std::byte resolver_storage[sizeof(ss::relay::Resolver)];
alignas(ss::event::Loop) std::byte loop_storage[sizeof(ss::event::Loop)];
ss::relay::Resolver *resolver = nullptr;
ss::event::Loop *loop = nullptr;
struct resolv_ctx {
struct resolv_io *ios;
struct ss_timer timer;
ss_tstamp last_tick;
using client_cb_t = void (*)(struct sockaddr *, void *);
using free_cb_t = void (*)(void *);
ares_channel channel;
struct ares_options options;
};
struct resolv_query {
int requests[2];
size_t response_count;
struct sockaddr **responses;
void (*client_cb)(struct sockaddr *, void *);
void (*free_cb)(void *);
uint16_t port;
void *data;
int is_closed;
};
extern int verbose;
static struct resolv_ctx default_ctx;
static struct ss_loop *default_loop;
enum {
MODE_IPV4_FIRST = 0,
MODE_IPV6_FIRST = 1
};
static int resolv_mode = MODE_IPV4_FIRST;
static void resolv_sock_cb(struct ss_loop *, struct ss_io *, int);
static void resolv_timer_cb(struct ss_loop *, struct ss_timer *, int);
static void resolv_sock_state_cb(void *, ares_socket_t, int, int);
static void dns_query_v4_cb(void *, int, int, struct hostent *);
static void dns_query_v6_cb(void *, int, int, struct hostent *);
static void process_client_callback(struct resolv_query *);
static inline int all_requests_are_null(struct resolv_query *);
static struct sockaddr *choose_ipv4_first(struct resolv_query *);
static struct sockaddr *choose_ipv6_first(struct resolv_query *);
static struct sockaddr *choose_any(struct resolv_query *);
/*
* DNS UDP socket activity callback
*/
static void
resolv_sock_cb(SS_P_ ss_io *w, int revents)
void deliver(const ss::relay::ResolveCallback &cb, const sockaddr *address) noexcept
{
ares_socket_t rfd = ARES_SOCKET_BAD, wfd = ARES_SOCKET_BAD;
if (revents & SS_READ)
rfd = w->fd;
if (revents & SS_WRITE)
wfd = w->fd;
default_ctx.last_tick = ss_now(default_loop);
ares_process_fd(default_ctx.channel, rfd, wfd);
auto client = reinterpret_cast<client_cb_t>(cb.ctx);
auto release = reinterpret_cast<free_cb_t>(cb.aux);
void *data = reinterpret_cast<void *>(static_cast<std::uintptr_t>(cb.token));
client(const_cast<sockaddr *>(address), data);
if (release != nullptr) {
release(data);
} else {
ss_free(data);
}
}
int
resolv_init(struct ss_loop *loop, char *nameservers, int ipv6first)
} // namespace
extern "C" int
resolv_init(struct ss_loop *ss_loop, char *nameservers, int ipv6first)
{
int status;
if (ipv6first)
resolv_mode = MODE_IPV6_FIRST;
else
resolv_mode = MODE_IPV4_FIRST;
default_loop = loop;
if ((status = ares_library_init(ARES_LIB_INIT_ALL)) != ARES_SUCCESS) {
LOGE("c-ares error: %s", ares_strerror(status));
if (resolver != nullptr) {
return 0;
}
loop = std::construct_at(reinterpret_cast<ss::event::Loop *>(loop_storage));
if (!loop->adopt(ss_native_loop(ss_loop))) {
FATAL("failed to initialize c-ares");
}
memset(&default_ctx, 0, sizeof(struct resolv_ctx));
default_ctx.options.sock_state_cb_data = &default_ctx;
default_ctx.options.sock_state_cb = resolv_sock_state_cb;
default_ctx.options.timeout = 3000;
default_ctx.options.tries = 2;
status = ares_init_options(&default_ctx.channel, &default_ctx.options,
#if ARES_VERSION_MAJOR >= 1 && ARES_VERSION_MINOR >= 12
ARES_OPT_NOROTATE |
#endif
ARES_OPT_TIMEOUTMS | ARES_OPT_TRIES | ARES_OPT_SOCK_STATE_CB);
if (status != ARES_SUCCESS) {
resolver = std::construct_at(reinterpret_cast<ss::relay::Resolver *>(resolver_storage));
if (!resolver->init(*loop, nameservers, ipv6first != 0, kMaxQueries)) {
FATAL("failed to initialize c-ares");
}
if (nameservers != NULL) {
#if ARES_VERSION_MAJOR >= 1 && ARES_VERSION_MINOR >= 11
status = ares_set_servers_ports_csv(default_ctx.channel, nameservers);
#else
status = ares_set_servers_csv(default_ctx.channel, nameservers);
#endif
}
if (status != ARES_SUCCESS) {
FATAL("failed to set nameservers");
}
default_ctx.last_tick = ss_now(default_loop);
ss_timer_init(&default_ctx.timer, resolv_timer_cb, 0, 0);
resolv_timer_cb(default_loop, &default_ctx.timer, 0);
return 0;
}
void
resolv_shutdown(struct ss_loop *loop)
{
ss_timer_stop(default_loop, &default_ctx.timer);
while (default_ctx.ios) {
struct resolv_io *node = default_ctx.ios;
default_ctx.ios = node->next;
ss_io_stop(default_loop, &node->io);
ss_free(node);
}
ares_cancel(default_ctx.channel);
ares_destroy(default_ctx.channel);
ares_library_cleanup();
}
void
extern "C" void
resolv_start(const char *hostname, uint16_t port,
void (*client_cb)(struct sockaddr *, void *),
void (*free_cb)(void *), void *data)
{
/*
* Wrap c-ares's call back in our own
*/
struct resolv_query *query = (struct resolv_query *)ss_malloc(sizeof(struct resolv_query));
memset(query, 0, sizeof(struct resolv_query));
query->port = port;
query->client_cb = client_cb;
query->response_count = 0;
query->responses = NULL;
query->data = data;
query->free_cb = free_cb;
query->requests[0] = AF_INET;
query->requests[1] = AF_INET6;
ares_gethostbyname(default_ctx.channel, hostname, AF_INET, dns_query_v4_cb, query);
ares_gethostbyname(default_ctx.channel, hostname, AF_INET6, dns_query_v6_cb, query);
ss::relay::ResolveCallback cb;
cb.fn = &deliver;
cb.ctx = reinterpret_cast<void *>(client_cb);
cb.aux = reinterpret_cast<void *>(free_cb);
cb.token = static_cast<std::uint64_t>(reinterpret_cast<std::uintptr_t>(data));
if (resolver == nullptr || !resolver->resolve(hostname, port, cb)) {
LOGE("resolver: too many queries in flight");
deliver(cb, nullptr);
}
}
/*
* Wrapper for client callback we provide to c-ares
*/
static void
dns_query_v4_cb(void *arg, int status, int timeouts, struct hostent *he)
extern "C" void
resolv_shutdown(struct ss_loop *)
{
int i, n;
struct resolv_query *query = (struct resolv_query *)arg;
if (status == ARES_EDESTRUCTION) {
if (resolver == nullptr) {
return;
}
if (!he || status != ARES_SUCCESS) {
if (verbose) {
LOGI("failed to lookup v4 address %s", ares_strerror(status));
}
goto CLEANUP;
}
if (verbose) {
LOGI("found address name v4 address %s", he->h_name);
}
n = 0;
while (he->h_addr_list[n])
n++;
if (n > 0) {
struct sockaddr **new_responses = (struct sockaddr **)ss_realloc(query->responses,
(query->response_count + n)
* sizeof(struct sockaddr *));
if (new_responses == NULL) {
LOGE("failed to allocate memory for additional DNS responses");
} else {
query->responses = new_responses;
for (i = 0; i < n; i++) {
struct sockaddr_in *sa = (struct sockaddr_in *)ss_malloc(sizeof(struct sockaddr_in));
memset(sa, 0, sizeof(struct sockaddr_in));
sa->sin_family = AF_INET;
sa->sin_port = query->port;
memcpy(&sa->sin_addr, he->h_addr_list[i], he->h_length);
query->responses[query->response_count] = (struct sockaddr *)sa;
if (query->responses[query->response_count] == NULL) {
LOGE("failed to allocate memory for DNS query result address");
} else {
query->response_count++;
}
}
}
}
CLEANUP:
query->requests[0] = 0; /* mark A query as being completed */
/* Once all requests have completed, call client callback */
if (all_requests_are_null(query)) {
return process_client_callback(query);
static bool closed;
closed = false;
resolver->shutdown(ss::CloseCompletion([](void *, std::uint32_t) { closed = true; }, nullptr, 0));
while (!closed) { // NOLINT(bugprone-infinite-loop): the completion callback sets it
loop->run_nowait();
}
}
static void
dns_query_v6_cb(void *arg, int status, int timeouts, struct hostent *he)
{
int i, n;
struct resolv_query *query = (struct resolv_query *)arg;
if (status == ARES_EDESTRUCTION) {
return;
}
if (!he || status != ARES_SUCCESS) {
if (verbose) {
LOGI("failed to lookup v6 address %s", ares_strerror(status));
}
goto CLEANUP;
}
if (verbose) {
LOGI("found address name v6 address %s", he->h_name);
}
n = 0;
while (he->h_addr_list[n])
n++;
if (n > 0) {
struct sockaddr **new_responses = (struct sockaddr **)ss_realloc(query->responses,
(query->response_count + n)
* sizeof(struct sockaddr *));
if (new_responses == NULL) {
LOGE("failed to allocate memory for additional DNS responses");
} else {
query->responses = new_responses;
for (i = 0; i < n; i++) {
struct sockaddr_in6 *sa = (struct sockaddr_in6 *)ss_malloc(sizeof(struct sockaddr_in6));
memset(sa, 0, sizeof(struct sockaddr_in6));
sa->sin6_family = AF_INET6;
sa->sin6_port = query->port;
memcpy(&sa->sin6_addr, he->h_addr_list[i], he->h_length);
query->responses[query->response_count] = (struct sockaddr *)sa;
if (query->responses[query->response_count] == NULL) {
LOGE("failed to allocate memory for DNS query result address");
} else {
query->response_count++;
}
}
}
}
CLEANUP:
query->requests[1] = 0; /* mark A query as being completed */
/* Once all requests have completed, call client callback */
if (all_requests_are_null(query)) {
return process_client_callback(query);
}
}
/*
* Called once all requests have been completed
*/
static void
process_client_callback(struct resolv_query *query)
{
struct sockaddr *best_address = NULL;
if (resolv_mode == MODE_IPV4_FIRST) {
best_address = choose_ipv4_first(query);
} else if (resolv_mode == MODE_IPV6_FIRST) {
best_address = choose_ipv6_first(query);
} else {
best_address = choose_any(query);
}
query->client_cb(best_address, query->data);
for (int i = 0; i < query->response_count; i++)
ss_free(query->responses[i]);
ss_free(query->responses);
if (query->free_cb != NULL)
query->free_cb(query->data);
else
ss_free(query->data);
ss_free(query);
}
static struct sockaddr *
choose_ipv4_first(struct resolv_query *query)
{
for (int i = 0; i < query->response_count; i++)
if (query->responses[i]->sa_family == AF_INET) {
return query->responses[i];
}
return choose_any(query);
}
static struct sockaddr *
choose_ipv6_first(struct resolv_query *query)
{
for (int i = 0; i < query->response_count; i++)
if (query->responses[i]->sa_family == AF_INET6) {
return query->responses[i];
}
return choose_any(query);
}
static struct sockaddr *
choose_any(struct resolv_query *query)
{
if (query->response_count >= 1) {
return query->responses[0];
}
return NULL;
}
static inline int
all_requests_are_null(struct resolv_query *query)
{
int result = 1;
for (int i = 0; i < sizeof(query->requests) / sizeof(query->requests[0]);
i++)
result = result && query->requests[i] == 0;
return result;
}
/*
* Timer callback
*/
static void
resolv_timer_cb(struct ss_loop *loop, struct ss_timer *w, int revents)
{
struct resolv_ctx *ctx = &default_ctx;
ss_tstamp now = ss_now(default_loop);
ss_tstamp after = ctx->last_tick - now + SS_TIMER_AFTER;
if (after < 0.0) {
ctx->last_tick = now;
ares_process_fd(ctx->channel, ARES_SOCKET_BAD, ARES_SOCKET_BAD);
ss_timer_set(w, SS_TIMER_AFTER, 0.0);
} else {
ss_timer_set(w, after, 0.0);
}
ss_timer_start(loop, w);
}
/*
* Handle c-ares events
*/
static void
resolv_sock_state_cb(void *data, ares_socket_t s, int read, int write)
{
struct resolv_ctx *ctx = (struct resolv_ctx *)data;
int events = (read ? SS_READ : 0) | (write ? SS_WRITE : 0);
struct resolv_io **entry = &ctx->ios;
while (*entry && (ares_socket_t)(unsigned int)(*entry)->io.fd != s)
entry = &(*entry)->next;
struct resolv_io *node = *entry;
if (node) ss_io_stop(default_loop, &node->io);
if (!events) {
if (node) {
*entry = node->next;
ss_free(node);
}
return;
}
if (!node) {
node = (struct resolv_io *)ss_malloc(sizeof(*node));
node->next = NULL;
*entry = node;
ss_io_init(&node->io, resolv_sock_cb, (int)s, events);
} else ss_io_set(&node->io, (int)s, events);
ss_io_start(default_loop, &node->io);
std::destroy_at(resolver);
std::destroy_at(loop);
resolver = nullptr;
loop = nullptr;
}
+179 -1124
View File
File diff suppressed because it is too large. Load diff
-125
View File
@@ -1,125 +0,0 @@
/*
* server.h - Define shadowsocks server's buffers and callbacks
*
* Copyright (C) 2013 - 2019, Max Lv <max.c.lv@gmail.com>
*
* This file is part of the shadowsocks-libev.
*
* shadowsocks-libev is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 3 of the License, or
* (at your option) any later version.
*
* shadowsocks-libev is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with shadowsocks-libev; see the file COPYING. If not, see
* <http://www.gnu.org/licenses/>.
*/
#ifndef _SERVER_H
#define _SERVER_H
#include <time.h>
#include "core.h"
#include "ss_event.h"
#ifdef __MINGW32__
#include "ss_windows.h"
#endif
#include "crypto.h"
#include "jconf.h"
#include "netutils.h"
#include "common.h"
#ifdef __cplusplus
extern "C" {
#endif
typedef struct listen_ctx {
ss_io io;
int fd;
int timeout;
char *iface;
struct ss_loop *loop;
} listen_ctx_t;
typedef struct server_ctx {
ss_io io;
ss_timer watcher;
int connected;
struct server *server;
} server_ctx_t;
#ifdef USE_NFCONNTRACK_TOS
#include <libnetfilter_conntrack/libnetfilter_conntrack.h>
#include <libnetfilter_conntrack/libnetfilter_conntrack_tcp.h>
struct dscptracker {
struct nf_conntrack *ct;
long unsigned int mark;
unsigned int dscp;
unsigned int packet_count;
};
#endif
struct query;
typedef struct server {
int fd;
int stage;
int frag;
buffer_t *buf;
cipher_ctx_t *e_ctx;
cipher_ctx_t *d_ctx;
struct server_ctx *recv_ctx;
struct server_ctx *send_ctx;
struct listen_ctx *listen_ctx;
struct remote *remote;
struct query *query;
struct ss_list_item entries;
#ifdef USE_NFCONNTRACK_TOS
struct dscptracker *tracker;
#endif
} server_t;
typedef struct query {
server_t *server;
char hostname[MAX_HOSTNAME_LEN];
} query_t;
typedef struct remote_ctx {
ss_io io;
int connected;
struct remote *remote;
} remote_ctx_t;
typedef struct remote {
int fd;
#ifdef TCP_FASTOPEN_WINSOCK
OVERLAPPED olap;
int connect_ex_done;
#endif
buffer_t *buf;
struct remote_ctx *recv_ctx;
struct remote_ctx *send_ctx;
struct server *server;
} remote_t;
#ifdef __cplusplus
}
#endif
#endif // _SERVER_H
+3 -2
View File
@@ -504,8 +504,9 @@ stream_decrypt(buffer_t *ciphertext, cipher_ctx_t *cipher_ctx, size_t capacity)
balloc(cipher_ctx->chunk, cipher->nonce_len);
}
size_t left_len = min(cipher->nonce_len - cipher_ctx->chunk->len,
ciphertext->len);
size_t left_len = cipher->nonce_len - cipher_ctx->chunk->len;
if (left_len > ciphertext->len)
left_len = ciphertext->len;
if (left_len > 0) {
memcpy(cipher_ctx->chunk->data + cipher_ctx->chunk->len, ciphertext->data, left_len);
+77 -722
View File
@@ -52,9 +52,18 @@
#include "netutils.h"
#include "utils.h"
#include "plugin.h"
#include "tunnel.h"
#include "ss_event.h"
#include "crypto.h"
#include "jconf.h"
#include "common.h"
#include "ss_windows.h"
#include "relay/tcp.hpp"
#include "relay/udp.hpp"
#undef min
#undef max
#ifndef EAGAIN
#define EAGAIN EWOULDBLOCK
#endif
@@ -63,20 +72,6 @@
#define EWOULDBLOCK EAGAIN
#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);
static int
validate_tunnel_addr(const ss_addr_t *addr)
{
@@ -110,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;
@@ -134,19 +128,6 @@ static struct plugin_watcher_t {
} plugin_watcher;
#endif
#ifndef __MINGW32__
static int
setnonblocking(int fd)
{
int flags;
if (-1 == (flags = fcntl(fd, F_GETFL, 0))) {
flags = 0;
}
return fcntl(fd, F_SETFL, flags | O_NONBLOCK);
}
#endif
int
create_and_bind(const char *addr, const char *port)
{
@@ -214,667 +195,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;
if (remote == NULL) {
close_and_free_server(SS_A_ server);
return;
}
ssize_t r = recv(server->fd, remote->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("server recv");
close_and_free_remote(SS_A_ remote);
close_and_free_server(SS_A_ server);
return;
}
}
remote->buf->len = r;
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;
}
}
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);
if (remote != NULL) {
ss_io_start(SS_A_ & remote->recv_ctx->io);
} else {
close_and_free_remote(SS_A_ remote);
close_and_free_server(SS_A_ server);
return;
}
}
}
}
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;
if (verbose) {
LOGI("TCP connection timeout");
}
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;
socklen_t len = sizeof(struct sockaddr_storage);
r = getpeername(remote->fd, (struct sockaddr *)&addr, &len);
}
if (r == 0) {
remote_send_ctx->connected = 1;
if (remote->buf->len != 0) {
LOGE("unexpected pending tunnel data");
close_and_free_remote(SS_A_ remote);
close_and_free_server(SS_A_ server);
return;
}
buffer_t *abuf = remote->buf;
ss_addr_t *sa = &server->destaddr;
struct ss_ip ip;
if (ss_ip_init(&ip, sa->host) != -1) {
if (ip.version == 4) {
// send as IPv4
struct in_addr host;
memset(&host, 0, sizeof(struct in_addr));
int host_len = sizeof(struct in_addr);
if (inet_pton(AF_INET, sa->host, &host) == -1) {
FATAL("IP parser error");
}
abuf->data[abuf->len++] = 1;
memcpy(abuf->data + abuf->len, &host, host_len);
abuf->len += host_len;
} else if (ip.version == 6) {
// send as IPv6
struct in6_addr host;
memset(&host, 0, sizeof(struct in6_addr));
int host_len = sizeof(struct in6_addr);
if (inet_pton(AF_INET6, sa->host, &host) == -1) {
FATAL("IP parser error");
}
abuf->data[abuf->len++] = 4;
memcpy(abuf->data + abuf->len, &host, host_len);
abuf->len += host_len;
} else {
FATAL("IP parser error");
}
} else {
// send as domain
int host_len = strlen(sa->host);
if (host_len > UINT8_MAX) {
LOGE("tunnel host name is too long");
close_and_free_remote(SS_A_ remote);
close_and_free_server(SS_A_ server);
return;
}
abuf->data[abuf->len++] = 3;
abuf->data[abuf->len++] = host_len;
memcpy(abuf->data + abuf->len, sa->host, host_len);
abuf->len += host_len;
}
uint16_t port_num;
if (ss_parse_uint16_port(sa->port, &port_num) == -1) {
LOGE("invalid tunnel port");
close_and_free_remote(SS_A_ remote);
close_and_free_server(SS_A_ server);
return;
}
uint16_t port = htons(port_num);
memcpy(abuf->data + abuf->len, &port, 2);
abuf->len += 2;
int err = crypto->encrypt(abuf, 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;
}
ss_io_start(SS_A_ & remote->recv_ctx->io);
} 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
ssize_t s = -1;
if (remote->addr != NULL) {
#if defined(TCP_FASTOPEN_WINSOCK)
DWORD s = -1;
DWORD err = 0;
do {
int optval = 1;
// Set fast open option
if (ss_setsockopt(remote->fd, IPPROTO_TCP, TCP_FASTOPEN,
&optval, sizeof(optval)) != 0) {
ERROR("setsockopt");
break;
}
// Load ConnectEx function
LPFN_CONNECTEX ConnectEx = winsock_getconnectex();
if (ConnectEx == NULL) {
LOGE("Cannot load ConnectEx() function");
err = WSAENOPROTOOPT;
break;
}
// ConnectEx requires a bound socket
if (winsock_dummybind(remote->fd,
(struct sockaddr *)&(remote->addr)) != 0) {
ERROR("bind");
break;
}
// Call ConnectEx to send data
memset(&remote->olap, 0, sizeof(remote->olap));
remote->connect_ex_done = 0;
if (ConnectEx(remote->fd, (const struct sockaddr *)&(remote->addr),
get_sockaddr_len(remote->addr), remote->buf->data, remote->buf->len,
&s, &remote->olap)) {
remote->connect_ex_done = 1;
break;
}
// XXX: ConnectEx pending, check later in remote_send
if (WSAGetLastError() == ERROR_IO_PENDING) {
err = CONNECT_IN_PROGRESS;
break;
}
ERROR("ConnectEx");
} while (0);
// Set error number
if (err) {
SetLastError(err);
}
#elif defined(CONNECT_DATA_IDEMPOTENT)
((struct sockaddr_in *)&(remote->addr))->sin_len = sizeof(struct sockaddr_in);
sa_endpoints_t endpoints;
memset((char *)&endpoints, 0, sizeof(endpoints));
endpoints.sae_dstaddr = (struct sockaddr *)&(remote->addr);
endpoints.sae_dstaddrlen = get_sockaddr_len(remote->addr);
s = connectx(remote->fd, &endpoints, SAE_ASSOCID_ANY,
CONNECT_RESUME_ON_READ_WRITE | CONNECT_DATA_IDEMPOTENT,
NULL, 0, NULL, NULL);
#elif 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 {
fast_open = 0;
if (ss_socket_error() == EOPNOTSUPP || ss_socket_error() == EPROTONOSUPPORT ||
ss_socket_error() == ENOPROTOOPT) {
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;
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);
return server;
}
static void
free_server(server_t *server)
{
if (server->remote != NULL) {
// NOLINTNEXTLINE(clang-analyzer-unix.Malloc): back-pointers are cleared symmetrically on free
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_socket_close(server->fd);
free_server(server);
}
}
static void
accept_cb(SS_P_ ss_io *w, int revents)
{
struct listen_ctx *listener = (struct listen_ctx *)w;
int serverfd = ss_accept(listener->fd, NULL, NULL);
if (serverfd == -1) {
ERROR("accept");
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;
}
#ifdef __ANDROID__
if (vpn) {
int not_protect = 0;
if (remote_addr->sa_family == AF_INET) {
struct sockaddr_in *s = (struct sockaddr_in *)remote_addr;
if (s->sin_addr.s_addr == inet_addr("127.0.0.1"))
not_protect = 1;
}
if (!not_protect) {
if (protect_socket(remotefd) == -1) {
ERROR("protect_socket");
ss_socket_close(remotefd);
return;
}
}
}
#endif
int keepAlive = 1;
ss_setsockopt(remotefd, SOL_SOCKET, SO_KEEPALIVE, (void *)&keepAlive, sizeof(keepAlive));
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 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));
}
// Setup
setnonblocking(remotefd);
#ifdef SET_INTERFACE
if (listener->iface) {
if (setinterface(remotefd, listener->iface) == -1)
ERROR("setinterface");
}
#endif
server_t *server = new_server(serverfd);
remote_t *remote = new_remote(remotefd, listener->timeout);
server->destaddr = listener->tunnel_addr;
server->remote = remote;
remote->server = server;
if (fast_open) {
remote->addr = remote_addr;
} 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);
}
static void
signal_cb(SS_P_ ss_signal *w, int revents)
{
@@ -1612,17 +932,14 @@ 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
struct listen_ctx listen_ctx;
memset(&listen_ctx, 0, sizeof(struct listen_ctx));
listen_ctx.tunnel_addr = tunnel_addr;
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);
int remote_count = 0;
struct sockaddr_storage remote_storage[MAX_REMOTE_NUM];
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 : remote_addr[i].port;
@@ -1630,25 +947,55 @@ Same behavior as `-V`; see that option for details. Short alias: `-V`.
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.iface = iface;
listen_ctx.mptcp = mptcp;
ss::relay::TcpConfig tcp_config;
tcp_config.kind = ss::relay::Kind::tunnel;
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.upstream.sndbuf = tcp_outgoing_sndbuf;
tcp_config.upstream.rcvbuf = tcp_outgoing_rcvbuf;
tcp_config.upstream.mptcp = mptcp;
tcp_config.upstream.iface = iface;
tcp_config.upstream.keepalive = true;
#ifdef __ANDROID__
tcp_config.vpn = vpn != 0;
tcp_config.upstream.protect = protect_socket;
#endif
{
uint16_t tunnel_port = 0;
ss_parse_uint16_port(tunnel_addr.port, &tunnel_port);
tcp_config.tunnel_target_len = ss::relay::encode_socks_host(tunnel_addr.host, tunnel_port,
tcp_config.tunnel_target.data());
if (tcp_config.tunnel_target_len == 0) {
FATAL("tunnel host name is too long");
}
}
LOGI("listening at %s:%s", local_addr, local_port);
struct ss_loop *loop = SS_DEFAULT;
ss::event::Loop tcp_loop;
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_relay.init(tcp_loop, relay_method, tcp_config, NULL)) {
FATAL("failed to set up the TCP relay");
}
// Setup socket
int listenfd;
listenfd = create_and_bind(local_addr, local_port);
@@ -1658,27 +1005,33 @@ Same behavior as `-V`; see that option for details. Short alias: `-V`.
if (listen(listenfd, SOMAXCONN) == -1) {
FATAL("listen() error");
}
setnonblocking(listenfd);
listen_ctx.fd = listenfd;
ss_io_init(&listen_ctx.io, accept_cb, listenfd, SS_READ);
ss_io_start(loop, &listen_ctx.io);
if (!tcp_relay.listen(listenfd)) {
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) {
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, listen_ctx.timeout, 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");
}
}
@@ -1704,9 +1057,11 @@ Same behavior as `-V`; see that option for details. Short alias: `-V`.
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();
}
#ifdef __MINGW32__
if (plugin_watcher.valid) {
-95
View File
@@ -1,95 +0,0 @@
/*
* tunnel.h - Define tunnel's buffers and callbacks
*
* Copyright (C) 2013 - 2019, Max Lv <max.c.lv@gmail.com>
*
* This file is part of the shadowsocks-libev.
*
* shadowsocks-libev is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 3 of the License, or
* (at your option) any later version.
*
* shadowsocks-libev is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with shadowsocks-libev; see the file COPYING. If not, see
* <http://www.gnu.org/licenses/>.
*/
#ifndef _TUNNEL_H
#define _TUNNEL_H
#include "ss_event.h"
#ifdef __MINGW32__
#include "ss_windows.h"
#endif
#include "crypto.h"
#include "jconf.h"
#include "common.h"
#ifdef __cplusplus
extern "C" {
#endif
typedef struct listen_ctx {
ss_io io;
ss_addr_t tunnel_addr;
char *iface;
int remote_num;
int timeout;
int fd;
int mptcp;
struct sockaddr **remote_addr;
} listen_ctx_t;
typedef struct server_ctx {
ss_io io;
int connected;
struct server *server;
} server_ctx_t;
typedef struct server {
int fd;
buffer_t *buf;
cipher_ctx_t *e_ctx;
cipher_ctx_t *d_ctx;
struct server_ctx *recv_ctx;
struct server_ctx *send_ctx;
struct remote *remote;
ss_addr_t destaddr;
} server_t;
typedef struct remote_ctx {
ss_io io;
ss_timer watcher;
int connected;
struct remote *remote;
} remote_ctx_t;
typedef struct remote {
int fd;
#ifdef TCP_FASTOPEN_WINSOCK
OVERLAPPED olap;
int connect_ex_done;
#endif
buffer_t *buf;
struct remote_ctx *recv_ctx;
struct remote_ctx *send_ctx;
struct server *server;
struct sockaddr *addr;
uint32_t counter;
} remote_t;
#ifdef __cplusplus
}
#endif
#endif // _TUNNEL_H
-1626
View File
File diff suppressed because it is too large. Load diff
-104
View File
@@ -1,104 +0,0 @@
/*
* udprelay.h - Define UDP relay's buffers and callbacks
*
* Copyright (C) 2013 - 2019, Max Lv <max.c.lv@gmail.com>
*
* This file is part of the shadowsocks-libev.
*
* shadowsocks-libev is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 3 of the License, or
* (at your option) any later version.
*
* shadowsocks-libev is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with shadowsocks-libev; see the file COPYING. If not, see
* <http://www.gnu.org/licenses/>.
*/
#ifndef _UDPRELAY_H
#define _UDPRELAY_H
#include <time.h>
#include "ss_event.h"
#include "crypto.h"
#include "jconf.h"
#ifdef MODULE_REMOTE
#include "resolv.h"
#endif
#include "cache.h"
#include "common.h"
#ifdef __cplusplus
extern "C" {
#endif
#define MAX_UDP_PACKET_SIZE (65507)
#define PACKET_HEADER_SIZE (1 + 28 + 2 + 64)
#define DEFAULT_PACKET_SIZE 1397 // 1492 - PACKET_HEADER_SIZE = 1397, the default MTU for UDP relay
#define MAX_ADDR_HEADER_SIZE (1 + 256 + 2) // 1-byte atyp + 256-byte hostname + 2-byte port
typedef struct server_ctx {
ss_io io;
int fd;
crypto_t *crypto;
int timeout;
const char *iface;
struct cache *conn_cache;
#ifdef MODULE_LOCAL
const struct sockaddr *remote_addr;
int remote_addr_len;
#ifdef MODULE_TUNNEL
ss_addr_t tunnel_addr;
#endif
#endif
#ifdef MODULE_REMOTE
struct ss_loop *loop;
#endif
} server_ctx_t;
#ifdef MODULE_REMOTE
typedef struct query_ctx {
struct sockaddr_storage src_addr;
buffer_t *buf;
int addr_header_len;
char addr_header[MAX_ADDR_HEADER_SIZE];
struct server_ctx *server_ctx;
struct remote_ctx *remote_ctx;
void *udp_session; /* Owned until transferred to the resolved relay. */
} query_ctx_t;
#endif
typedef struct remote_ctx {
ss_io io;
ss_timer watcher;
int af;
int fd;
struct sockaddr_storage src_addr;
#ifdef MODULE_REMOTE
struct sockaddr_storage dst_addr;
#endif
/*
* AEAD-2022 UDP session state for this conversation, owned by the
* crypto layer and released with crypto->udp_session_release.
* NULL for legacy ciphers.
*/
void *udp_session;
struct server_ctx *server_ctx;
} remote_ctx_t;
#ifdef __cplusplus
}
#endif
#endif // _UDPRELAY_H
+11 -2
View File
@@ -263,7 +263,7 @@ ss_add_test(test_process
set_tests_properties(test_process PROPERTIES TIMEOUT 20)
ss_add_test(test_resolv
"test_resolv.c;${PROJECT_SOURCE_DIR}/src/resolv.cpp;${PROJECT_SOURCE_DIR}/src/utils.cpp"
"test_resolv.c;${PROJECT_SOURCE_DIR}/src/resolv.cpp;${PROJECT_SOURCE_DIR}/src/relay/resolver.cpp;${PROJECT_SOURCE_DIR}/src/utils.cpp"
"ss_ev;ss_cares;ss_sodium")
set_tests_properties(test_resolv PROPERTIES TIMEOUT 20)
@@ -278,7 +278,7 @@ ss_add_test(test_acl_literal
ss_add_test(test_event "test_event.c" "ss_ev")
set_tests_properties(test_event PROPERTIES TIMEOUT 30)
ss_add_test(test_resolv_async
"test_resolv_async.c;${PROJECT_SOURCE_DIR}/src/resolv.cpp;${PROJECT_SOURCE_DIR}/src/utils.cpp"
"test_resolv_async.c;${PROJECT_SOURCE_DIR}/src/resolv.cpp;${PROJECT_SOURCE_DIR}/src/relay/resolver.cpp;${PROJECT_SOURCE_DIR}/src/utils.cpp"
"ss_ev;ss_cares;ss_sodium")
set_tests_properties(test_resolv_async PROPERTIES TIMEOUT 30)
@@ -354,6 +354,15 @@ if(SS_BUILD_EXECUTABLES AND NOT CMAKE_CROSSCOMPILING)
--self --printable-salt --bin ${CMAKE_RUNTIME_OUTPUT_DIRECTORY})
set_tests_properties(test_printable_salt_interop PROPERTIES
LABELS "integration" TIMEOUT 90)
# ss-tunnel through the C relay engine against ss-server (and
# shadowsocks-rust when installed), every method.
set(SS_TUNNEL_INTEROP_ARGS --bin ${CMAKE_RUNTIME_OUTPUT_DIRECTORY})
if(SS_ENABLE_LEGACY)
list(APPEND SS_TUNNEL_INTEROP_ARGS --legacy)
endif()
add_test(NAME test_tunnel_interop
COMMAND ${Python3_EXECUTABLE} ${CMAKE_CURRENT_SOURCE_DIR}/tunnel_interop.py ${SS_TUNNEL_INTEROP_ARGS})
set_tests_properties(test_tunnel_interop PROPERTIES LABELS "integration" TIMEOUT 300)
endif()
endif()
+17 -6
View File
@@ -27,7 +27,7 @@ import threading
import time
sys.path.insert(0, str(Path(__file__).resolve().parent.parent))
from interop import TCPHandler, TCPOrigin, free_port, peers, tcp_case # noqa: E402
from interop import TCPHandler, TCPOrigin, free_port, peers, receive, socks # noqa: E402
def read_snapshot(path, previous_lines, timeout=10):
@@ -50,9 +50,18 @@ def snapshot_all(processes, outputs, counts):
return results
def relay(local_port, origin_port, connections, concurrency, size):
def tcp_case(local_port, target, origin_port, size):
sock, _ = socks(local_port, 1, (target, origin_port))
payload = os.urandom(size)
with sock, concurrent.futures.ThreadPoolExecutor(max_workers=1) as pool:
upload = pool.submit(sock.sendall, payload)
assert receive(sock, len(payload)) == payload, "TCP payload mismatch"
upload.result(timeout=10)
def relay(local_port, target, origin_port, connections, concurrency, size):
with concurrent.futures.ThreadPoolExecutor(max_workers=concurrency) as pool:
futures = [pool.submit(tcp_case, local_port, origin_port, size) for _ in range(connections)]
futures = [pool.submit(tcp_case, local_port, target, origin_port, size) for _ in range(connections)]
for future in futures:
future.result(timeout=60)
@@ -67,6 +76,8 @@ def main():
parser.add_argument("--concurrency", type=int, default=10)
parser.add_argument("--size", type=int, default=16384, help="bytes echoed per connection")
parser.add_argument("--max-per-connection", type=float, help="fail if any peer exceeds this")
parser.add_argument("--target", default="localhost",
help="destination host sent to the server; a name makes the server resolve it")
parser.add_argument("--json", action="store_true", help="print the result as JSON")
args = parser.parse_args()
@@ -101,10 +112,10 @@ def main():
peers([commands[1]], [local_port], environments[1]) as local:
processes = server + local
counts = [0, 0]
relay(local_port, origin.server_address[1], args.warmup, args.concurrency, args.size)
relay(local_port, args.target, origin.server_address[1], args.warmup, args.concurrency, args.size)
time.sleep(.5)
before = snapshot_all(processes, outputs, counts)
relay(local_port, origin.server_address[1], args.connections, args.concurrency, args.size)
relay(local_port, args.target, origin.server_address[1], args.connections, args.concurrency, args.size)
time.sleep(.5)
after = snapshot_all(processes, outputs, counts)
@@ -125,7 +136,7 @@ def main():
if args.json:
print(json.dumps(report, indent=2))
else:
print(f"method {args.method}, {args.connections} connections of {args.size} bytes")
print(f"method {args.method}, {args.connections} connections of {args.size} bytes to {args.target}")
for name, peer in report["peers"].items():
print(f" {name:9} {peer['allocations_per_connection']:8.2f} allocations/connection "
f"({peer['allocations']} total, {peer['bytes']} bytes)")
+4 -2
View File
@@ -92,12 +92,14 @@ const std::uint8_t kAddr[] = { 0x03, 11, 'e', 'x', 'a', 'm', 'p', 'l', 'e', '.',
std::vector<std::uint8_t> cpp_decrypt_all(sc::Aead2022TcpDecryptor &dec, const std::vector<std::uint8_t> &wire)
{
std::vector<std::uint8_t> got;
std::vector<std::uint8_t> out(sc::k2022MaxChunk);
std::vector<std::uint8_t> out(70000);
for (std::size_t off = 0; off < wire.size();) {
std::size_t n = std::min<std::size_t>(1 + randombytes_uniform(9000), wire.size() - off);
std::span<const std::uint8_t> in(wire.data() + off, n);
for (;;) {
auto step = dec.decrypt(in, out);
// Output windows of any size, down to one byte.
std::size_t room = randombytes_uniform(4) == 0 ? 1 + randombytes_uniform(16) : 1 + randombytes_uniform(70000);
auto step = dec.decrypt(in, std::span<std::uint8_t>(out).first(room));
SS_CHECK(step);
got.insert(got.end(), out.begin(), out.begin() + step->produced);
in = in.subspan(step->consumed);
+102
View File
@@ -0,0 +1,102 @@
#!/usr/bin/env python3
"""ss-tunnel against ss-server (and shadowsocks-rust when present): concurrent TCP
transfers and a UDP round trip through a fixed tunnel target, for every method."""
import argparse
import base64
import concurrent.futures
import os
from pathlib import Path
import shutil
import socket
import subprocess
import threading
from interop import TCPHandler, TCPOrigin, UDPHandler, UDPOrigin, free_port, peers, receive
METHODS = ["2022-blake3-aes-128-gcm", "2022-blake3-aes-256-gcm", "2022-blake3-chacha20-poly1305",
"aes-128-gcm", "aes-256-gcm", "chacha20-ietf-poly1305"]
LEGACY = ["aes-256-cfb", "aes-128-ctr", "camellia-256-cfb", "chacha20-ietf", "salsa20"]
def tcp_case(port, size):
payload = os.urandom(size)
with socket.create_connection(("127.0.0.1", port), timeout=15) as sock, \
concurrent.futures.ThreadPoolExecutor(max_workers=1) as pool:
upload = pool.submit(sock.sendall, payload)
assert receive(sock, size) == payload, "TCP payload mismatch"
upload.result(timeout=15)
def udp_case(port):
with socket.socket(socket.AF_INET, socket.SOCK_DGRAM) as sock:
sock.settimeout(.5)
for size in (1, 1200):
payload = os.urandom(size)
# The UDP-only tunnel has no TCP port to wait on: retry until up.
for attempt in range(20):
sock.sendto(payload, ("127.0.0.1", port))
try:
reply, _ = sock.recvfrom(65535)
break
except TimeoutError:
if attempt == 19:
raise
assert reply == payload, "UDP payload mismatch"
def main():
parser = argparse.ArgumentParser(description=__doc__)
parser.add_argument("--bin", required=True, type=Path)
parser.add_argument("--legacy", action="store_true", help="also run the stream ciphers")
parser.add_argument("--fast-open", action="store_true")
args = parser.parse_args()
bins = args.bin.resolve()
server, tunnel = str(bins / "ss-server"), str(bins / "ss-tunnel")
rust_server = shutil.which("ssserver")
methods = METHODS + (LEGACY if args.legacy else [])
failures = 0
with TCPOrigin(("127.0.0.1", 0), TCPHandler) as tcp, UDPOrigin(("127.0.0.1", 0), UDPHandler) as udp:
for origin in (tcp, udp):
threading.Thread(target=origin.serve_forever, daemon=True).start()
try:
for method in methods:
stream = method in LEGACY
for peer in ("C server",) + (("Rust server",) if rust_server and not stream else ()):
key = base64.b64encode(os.urandom(16 if "aes-128" in method else 32)).decode()
secret = ["-k", "password"] if stream else ["-k", key]
sport, tport, uport = free_port(), free_port(), free_port()
if peer == "C server":
srv = [server, "-s", "127.0.0.1", "-p", str(sport), "-m", method, "-u"] + secret
else:
srv = [rust_server, "-s", f"127.0.0.1:{sport}", "-m", method, "-U"] + secret
extra = ["--fast-open"] if args.fast_open else []
tcp_tunnel = [tunnel, "-s", "127.0.0.1", "-p", str(sport), "-l", str(tport), "-m", method,
"-L", f"127.0.0.1:{tcp.server_address[1]}"] + secret + extra
udp_tunnel = [tunnel, "-s", "127.0.0.1", "-p", str(sport), "-l", str(uport), "-m", method,
"-U", "-L", f"127.0.0.1:{udp.server_address[1]}"] + secret
label = f"{method}: tunnel -> {peer}"
try:
with peers([srv, tcp_tunnel], [sport, tport]):
with concurrent.futures.ThreadPoolExecutor(max_workers=4) as pool:
cases = [pool.submit(tcp_case, tport, size) for size in (1, 4096, 65536, 1048576)]
for case in cases:
case.result(timeout=60)
udp_proc = subprocess.Popen(udp_tunnel, stdout=subprocess.DEVNULL,
stderr=subprocess.DEVNULL)
try:
udp_case(uport)
finally:
udp_proc.terminate()
udp_proc.wait(timeout=5)
print("PASS " + label, flush=True)
except (OSError, AssertionError, TimeoutError) as error:
failures += 1
print(f"FAIL {label}: {error!r}", flush=True)
finally:
tcp.shutdown()
udp.shutdown()
return int(failures != 0)
if __name__ == "__main__":
raise SystemExit(main())