mirror of
https://github.com/shadowsocks/shadowsocks-libev.git
synced 2026-10-28 10:44:43 +08:00
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:
1 parent
5dbcbcbb89
commit
5c62b2544e
38 files changed
+4390
-6452
No files matched your search
@@ -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
@@ -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})
|
||||
|
||||
@@ -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
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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
@@ -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
@@ -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
@@ -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)()>
|
||||
|
||||
@@ -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
File diff suppressed because it is too large.
Load diff
-111
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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(©, 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 *>(©) : 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
|
||||
@@ -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
|
||||
@@ -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(©, addr, len);
|
||||
if (copy.ss_family == AF_INET) {
|
||||
reinterpret_cast<sockaddr_in *>(©)->sin_len = sizeof(sockaddr_in);
|
||||
}
|
||||
sa_endpoints_t endpoints{};
|
||||
endpoints.sae_dstaddr = reinterpret_cast<sockaddr *>(©);
|
||||
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
|
||||
@@ -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
File diff suppressed because it is too large.
Load diff
@@ -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
|
||||
@@ -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
|
||||
@@ -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
@@ -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
File diff suppressed because it is too large.
Load diff
-125
@@ -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
@@ -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
@@ -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) {
|
||||
|
||||
@@ -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
File diff suppressed because it is too large.
Load diff
-104
@@ -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
@@ -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()
|
||||
|
||||
|
||||
@@ -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)")
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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())
|
||||
Reference in new issue
Block a user