summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorToni Uhlig <matzeton@googlemail.com>2025-02-05 16:12:38 +0100
committerToni Uhlig <matzeton@googlemail.com>2025-02-14 14:50:09 +0100
commit1297bc7c8d116eee936bb2d081c898e8917c322f (patch)
treebb2572d1b8dc654dd738d47867cc86b2e93cd18a
initial commit
Signed-off-by: Toni Uhlig <matzeton@googlemail.com>
-rw-r--r--Makefile25
-rw-r--r--main.cpp18
-rw-r--r--socks5.cpp397
-rw-r--r--socks5.hpp133
4 files changed, 573 insertions, 0 deletions
diff --git a/Makefile b/Makefile
new file mode 100644
index 0000000..64facb5
--- /dev/null
+++ b/Makefile
@@ -0,0 +1,25 @@
+CXX = g++
+CXXFLAGS = -Wall -Wextra
+SERVER_HDRS = socks5.hpp
+SERVER_SRCS = socks5.cpp main.cpp
+
+ifneq ($(ENABLE_SANITIZER),)
+CXXFLAGS += -fsanitize=address -fsanitize=leak -fsanitize=undefined
+else
+ifneq ($(ENABLE_THREAD_SANITIZER),)
+CXXFLAGS += -fsanitize=thread -fsanitize=undefined
+endif
+endif
+ifneq ($(DEBUG),)
+CXXFLAGS += -g #-DBOOST_ASIO_ENABLE_HANDLER_TRACKING=1
+endif
+
+all: server
+
+server: $(SERVER_HDRS) $(SERVER_SRCS)
+ $(CXX) $(CXXFLAGS) $(SERVER_SRCS) -o $@
+
+clean:
+ rm -f server
+
+.PHONY: clean
diff --git a/main.cpp b/main.cpp
new file mode 100644
index 0000000..64a4888
--- /dev/null
+++ b/main.cpp
@@ -0,0 +1,18 @@
+#include "socks5.hpp"
+
+#include <boost/asio/io_context.hpp>
+#include <thread>
+
+int main() {
+ boost::asio::io_context ioc;
+ auto server = SOCKS5::ProxyServer(ioc, "127.0.0.1", 1080);
+ auto threads = std::vector<std::thread>();
+
+ server.start();
+ for (size_t i = 0; i < 4; ++i) {
+ threads.emplace_back([&ioc]() { ioc.run(); });
+ }
+ for (size_t i = 0; i < threads.size(); ++i) {
+ threads[i].join();
+ }
+}
diff --git a/socks5.cpp b/socks5.cpp
new file mode 100644
index 0000000..a00748c
--- /dev/null
+++ b/socks5.cpp
@@ -0,0 +1,397 @@
+#include "socks5.hpp"
+
+#include <boost/asio/completion_condition.hpp>
+#include <boost/asio/impl/write.hpp>
+#include <boost/asio/io_context.hpp>
+#include <boost/asio/ip/address.hpp>
+#include <boost/asio/ip/address_v4.hpp>
+#include <boost/asio/ip/address_v6.hpp>
+#include <boost/asio/placeholders.hpp>
+#include <boost/asio/read.hpp>
+#include <boost/asio/strand.hpp>
+#include <boost/bind/bind.hpp>
+#include <boost/system/detail/error_code.hpp>
+#include <cstddef>
+#include <cstdint>
+#include <memory>
+#include <string>
+
+using namespace SOCKS5;
+using boost::asio::io_context;
+using boost::asio::ip::tcp;
+
+ProxySessionBase::ProxySessionBase(std::uint32_t session_id,
+ tcp::socket &&client_socket,
+ std::size_t buffer_size)
+ : m_sessionId{session_id}, m_inBuf{buffer_size}, m_outBuf{buffer_size},
+ m_clientSocket{std::move(client_socket)} {}
+
+ProxySessionBase::ProxySessionBase(std::uint32_t session_id,
+ boost::asio::ip::tcp::socket &&client_socket,
+ StreamBuffer &&input_buffer,
+ StreamBuffer &&output_buffer)
+ : m_sessionId{session_id}, m_inBuf{std::move(input_buffer)},
+ m_outBuf{std::move(output_buffer)},
+ m_clientSocket{std::move(client_socket)} {}
+
+ProxySessionAuth::ProxySessionAuth(std::uint32_t session_id,
+ tcp::socket &&client_socket)
+ : ProxySessionBase(session_id, std::move(client_socket), 32),
+ m_resolver{m_clientSocket.get_executor()} {}
+
+void ProxySessionAuth::start() {
+ boost::asio::async_read(
+ m_clientSocket, +m_inBuf, boost::asio::transfer_exactly(2),
+ boost::bind(&ProxySessionAuth::recv_client_greeting, shared_from_this(),
+ boost::asio::placeholders::error,
+ boost::asio::placeholders::bytes_transferred));
+}
+
+void ProxySessionAuth::recv_client_greeting(const boost::system::error_code &ec,
+ std::size_t length) {
+ if (ec || length == 0)
+ return;
+ m_inBuf += length;
+
+ if (m_inBuf[0] != 0x05 || m_inBuf[1] > 0x09 || m_inBuf[1] == 0x00)
+ return;
+ if (m_inBuf.size() < std::size_t(2) + m_inBuf[1]) {
+ boost::asio::async_read(
+ m_clientSocket, +m_inBuf, boost::asio::transfer_exactly(m_inBuf[1]),
+ boost::bind(&ProxySessionAuth::recv_client_greeting, shared_from_this(),
+ boost::asio::placeholders::error,
+ boost::asio::placeholders::bytes_transferred));
+ return;
+ }
+
+ auto found_no_auth = false; // only No Authentication supported
+ for (auto i = std::size_t(2); i < std::size_t(2) + m_inBuf[1]; ++i) {
+ if (m_inBuf[i] == 0x00) {
+ found_no_auth = true;
+ break;
+ }
+ }
+
+ m_inBuf -= std::size_t(2) + m_inBuf[1];
+ send_server_greeting(found_no_auth);
+}
+
+void ProxySessionAuth::send_server_greeting(bool auth_supported) {
+ if (!auth_supported) {
+ m_outBuf += {0x05, 0xFF};
+ m_clientSocket.async_send(
+ -m_outBuf,
+ boost::bind(&ProxySessionAuth::handle_write, shared_from_this(),
+ boost::asio::placeholders::error,
+ boost::asio::placeholders::bytes_transferred));
+ return;
+ }
+
+ m_outBuf += {0x05, 0x00};
+ m_clientSocket.async_send(
+ -m_outBuf,
+ boost::bind(&ProxySessionAuth::handle_write, shared_from_this(),
+ boost::asio::placeholders::error,
+ boost::asio::placeholders::bytes_transferred));
+ process_connection_request();
+}
+
+void ProxySessionAuth::recv_connection_request(
+ const boost::system::error_code &ec, std::size_t length) {
+ if (ec || length == 0)
+ return;
+ m_inBuf += length;
+
+ process_connection_request();
+}
+
+void ProxySessionAuth::process_connection_request() {
+ if (m_inBuf.size() < 7) {
+ boost::asio::async_read(
+ m_clientSocket, +m_inBuf, boost::asio::transfer_exactly(7),
+ boost::bind(&ProxySessionAuth::recv_connection_request,
+ shared_from_this(), boost::asio::placeholders::error,
+ boost::asio::placeholders::bytes_transferred));
+ return;
+ }
+
+ if (m_inBuf[0] != 0x05 || m_inBuf[1] > 0x03 || m_inBuf[2] != 0x00)
+ return;
+
+ size_t expected_size = 0;
+ uint8_t address_size;
+ switch (m_inBuf[3]) {
+ // IPv4 Address
+ case 0x01:
+ address_size = 4;
+ break;
+ // DNS FQDN
+ case 0x03:
+ address_size = m_inBuf[4];
+ expected_size++;
+ break;
+ // IPv6 Address
+ case 0x04:
+ address_size = 16;
+ break;
+ default:
+ return;
+ }
+
+ expected_size += std::size_t(6) + address_size;
+ if (m_inBuf.size() < expected_size) {
+ boost::asio::async_read(
+ m_clientSocket, +m_inBuf,
+ boost::asio::transfer_exactly(expected_size - m_inBuf.size()),
+ boost::bind(&ProxySessionAuth::recv_connection_request,
+ shared_from_this(), boost::asio::placeholders::error,
+ boost::asio::placeholders::bytes_transferred));
+ return;
+ }
+
+ const uint8_t proxy_cmd = m_inBuf[1];
+ switch (m_inBuf[3]) {
+ case 0x01: {
+ auto ip4_bytes = ::ntohl(*reinterpret_cast<const uint32_t *>(m_inBuf(4)));
+ m_endpoint = tcp::endpoint(boost::asio::ip::make_address_v4(ip4_bytes),
+ ::ntohs(*reinterpret_cast<const uint16_t *>(
+ m_inBuf(4 + address_size))));
+ send_server_response(proxy_cmd, 0x00);
+ return;
+ }
+ case 0x03: {
+ auto host = std::string_view(reinterpret_cast<const char *>(m_inBuf(5)),
+ address_size);
+ auto port =
+ ::ntohs(*reinterpret_cast<const uint16_t *>(m_inBuf(5 + address_size)));
+ resolve_destination_host(proxy_cmd, host, port);
+ return;
+ }
+ case 0x04: {
+ auto ip6_array =
+ *reinterpret_cast<const std::array<std::uint8_t, 16> *>(m_inBuf(4));
+ m_endpoint = tcp::endpoint(boost::asio::ip::make_address_v6(ip6_array),
+ ::ntohs(*reinterpret_cast<const uint16_t *>(
+ m_inBuf(4 + address_size))));
+ send_server_response(proxy_cmd, 0x00);
+ return;
+ }
+ default:
+ return;
+ }
+}
+
+void ProxySessionAuth::send_server_response(std::uint8_t proxy_cmd,
+ std::uint8_t status_code) {
+ m_outBuf += {0x05, status_code, 0x00};
+ // TODO: Set DNS domain if available
+ if (m_endpoint.address().is_v4()) {
+ const uint32_t addr = ::htonl(m_endpoint.address().to_v4().to_uint());
+ m_outBuf += {0x01, static_cast<uint8_t>(addr & 0x000000FF),
+ static_cast<uint8_t>((addr & 0x0000FF00) >> 8),
+ static_cast<uint8_t>((addr & 0x00FF0000) >> 16),
+ static_cast<uint8_t>((addr & 0xFF000000) >> 24)};
+ } else {
+ m_outBuf += {0x04};
+ const auto addr = m_endpoint.address().to_v6().to_bytes();
+ for (const auto byte : addr)
+ m_outBuf += {byte};
+ }
+ const auto port = ::htons(m_endpoint.port());
+ m_outBuf += {static_cast<uint8_t>(port & 0x00FF),
+ static_cast<uint8_t>((port & 0xFF00) >> 8)};
+ m_clientSocket.async_send(
+ -m_outBuf,
+ boost::bind(&ProxySessionAuth::handle_response_write, shared_from_this(),
+ proxy_cmd, status_code, boost::asio::placeholders::error,
+ boost::asio::placeholders::bytes_transferred));
+}
+
+void ProxySessionAuth::resolve_destination_host(std::uint8_t proxy_cmd,
+ const std::string_view &host,
+ std::uint16_t port) {
+ m_resolver.async_resolve(host, std::to_string(port),
+ [this, self = shared_from_this(),
+ proxy_cmd](const boost::system::error_code &ec,
+ const tcp::resolver::iterator &it) {
+ if (ec) {
+ send_server_response(proxy_cmd, 0x04);
+ return;
+ }
+ /* TODO: Support iterating and connecting to
+ * multiple resolved hosts on failure. */
+ m_endpoint = *it;
+ send_server_response(proxy_cmd, 0x00);
+ });
+}
+
+void ProxySessionAuth::connect_to_destination(std::uint8_t proxy_cmd) {
+ switch (proxy_cmd) {
+ case 0x01: // TCP client connection
+ {
+ auto tcp_session = std::make_shared<ProxySessionTcp>(*this);
+ tcp_session->start(m_endpoint);
+ break;
+ }
+ case 0x02: // TCP port bind
+ {
+ return;
+ }
+ case 0x03: // UDP port bind
+ {
+ return;
+ }
+ default:
+ return;
+ }
+}
+
+void ProxySessionAuth::handle_write(const boost::system::error_code &ec,
+ std::size_t length) {
+ if (ec || length == 0)
+ m_clientSocket.cancel();
+ m_outBuf -= length;
+
+ if (m_outBuf.size() > 0)
+ m_clientSocket.async_send(
+ -m_outBuf,
+ boost::bind(&ProxySessionAuth::handle_write, shared_from_this(),
+ boost::asio::placeholders::error,
+ boost::asio::placeholders::bytes_transferred));
+}
+
+void ProxySessionAuth::handle_response_write(
+ std::uint8_t proxy_cmd, std::uint8_t status_code,
+ const boost::system::error_code &ec, std::size_t length) {
+ if (ec || length == 0)
+ m_clientSocket.cancel();
+ m_outBuf -= length;
+
+ if (m_outBuf.size() > 0) {
+ m_clientSocket.async_send(
+ -m_outBuf, boost::bind(&ProxySessionAuth::handle_response_write,
+ shared_from_this(), proxy_cmd, status_code,
+ boost::asio::placeholders::error,
+ boost::asio::placeholders::bytes_transferred));
+ return;
+ }
+
+ if (status_code == 0x00)
+ connect_to_destination(proxy_cmd);
+}
+
+ProxySessionTcp::ProxySessionTcp(ProxySessionBase &base,
+ std::size_t buffer_size)
+ : ProxySessionBase(base.m_sessionId, std::move(base.m_clientSocket),
+ buffer_size),
+ m_destinationSocket(m_clientSocket.get_executor()) {}
+
+void ProxySessionTcp::start(tcp::endpoint &destination) {
+ m_destinationSocket.async_connect(
+ destination,
+ [this, self = shared_from_this()](const boost::system::error_code &ec) {
+ if (ec) {
+ return;
+ }
+ recv_from_both();
+ });
+}
+
+void ProxySessionTcp::recv_from_both() {
+ BOOST_ASIO_HANDLER_LOCATION((__FILE__, __LINE__, __func__));
+
+ m_clientSocket.async_read_some(
+ +m_inBuf,
+ boost::bind(&ProxySessionTcp::recv_from_client, shared_from_this(),
+ boost::asio::placeholders::error,
+ boost::asio::placeholders::bytes_transferred));
+ m_destinationSocket.async_read_some(
+ +m_outBuf,
+ boost::bind(&ProxySessionTcp::recv_from_destination, shared_from_this(),
+ boost::asio::placeholders::error,
+ boost::asio::placeholders::bytes_transferred));
+}
+
+void ProxySessionTcp::recv_from_destination(const boost::system::error_code &ec,
+ std::size_t length) {
+ BOOST_ASIO_HANDLER_LOCATION((__FILE__, __LINE__, __func__));
+
+ if (ec)
+ return;
+
+ m_outBuf += length;
+ boost::asio::async_write(
+ m_clientSocket, -m_outBuf, boost::asio::transfer_all(),
+ boost::bind(&ProxySessionTcp::handle_client_write, shared_from_this(),
+ boost::asio::placeholders::error,
+ boost::asio::placeholders::bytes_transferred));
+}
+
+void ProxySessionTcp::recv_from_client(const boost::system::error_code &ec,
+ std::size_t length) {
+ BOOST_ASIO_HANDLER_LOCATION((__FILE__, __LINE__, __func__));
+
+ if (ec)
+ return;
+
+ m_inBuf += length;
+ boost::asio::async_write(
+ m_destinationSocket, -m_inBuf, boost::asio::transfer_exactly(length),
+ boost::bind(&ProxySessionTcp::handle_destination_write,
+ shared_from_this(), boost::asio::placeholders::error,
+ boost::asio::placeholders::bytes_transferred));
+}
+
+void ProxySessionTcp::handle_client_write(const boost::system::error_code &ec,
+ std::size_t length) {
+ BOOST_ASIO_HANDLER_LOCATION((__FILE__, __LINE__, __func__));
+
+ if (ec)
+ return;
+
+ m_outBuf -= length;
+ m_destinationSocket.async_read_some(
+ +m_outBuf,
+ boost::bind(&ProxySessionTcp::recv_from_destination, shared_from_this(),
+ boost::asio::placeholders::error,
+ boost::asio::placeholders::bytes_transferred));
+}
+
+void ProxySessionTcp::handle_destination_write(
+ const boost::system::error_code &ec, std::size_t length) {
+ BOOST_ASIO_HANDLER_LOCATION((__FILE__, __LINE__, __func__));
+
+ if (ec)
+ return;
+
+ m_inBuf -= length;
+ m_clientSocket.async_read_some(
+ +m_inBuf,
+ boost::bind(&ProxySessionTcp::recv_from_client, shared_from_this(),
+ boost::asio::placeholders::error,
+ boost::asio::placeholders::bytes_transferred));
+}
+
+ProxyServer::ProxyServer(io_context &ioc, const tcp::endpoint &local_endpoint)
+ : m_nextSessionId{1}, m_acceptor(ioc, local_endpoint) {}
+
+ProxyServer::ProxyServer(io_context &ioc, const std::string &listen_addr,
+ std::uint16_t listen_port)
+ : ProxyServer(ioc, tcp::endpoint(boost::asio::ip::make_address(listen_addr),
+ listen_port)) {}
+
+void ProxyServer::start() { async_accept(); }
+
+void ProxyServer::async_accept() {
+ m_acceptor.async_accept(
+ boost::asio::make_strand(m_acceptor.get_executor()),
+ [this](const boost::system::error_code &ec, tcp::socket client_socket) {
+ if (!ec) {
+ auto auth_session = std::make_shared<ProxySessionAuth>(
+ m_nextSessionId++, std::move(client_socket));
+ if (auth_session)
+ auth_session->start();
+ }
+ async_accept();
+ });
+}
diff --git a/socks5.hpp b/socks5.hpp
new file mode 100644
index 0000000..a8b73a9
--- /dev/null
+++ b/socks5.hpp
@@ -0,0 +1,133 @@
+#include <boost/asio.hpp>
+#include <boost/asio/buffer.hpp>
+#include <boost/asio/io_context.hpp>
+#include <boost/asio/ip/tcp.hpp>
+#include <boost/core/noncopyable.hpp>
+#include <boost/noncopyable.hpp>
+#include <boost/system/detail/error_code.hpp>
+#include <boost/system/error_code.hpp>
+#include <cstdint>
+#include <memory>
+#include <string_view>
+#include <vector>
+
+namespace SOCKS5 {
+class StreamBuffer : public boost::noncopyable {
+public:
+ explicit StreamBuffer(std::size_t size)
+ : m_bufferUsed{0}, m_bufferSize{size} {
+ m_buffer = new std::uint8_t[size];
+ }
+ StreamBuffer(StreamBuffer &&moveable) {
+ m_bufferUsed = moveable.m_bufferUsed;
+ m_bufferSize = moveable.m_bufferSize;
+ m_buffer = moveable.m_buffer;
+ moveable.m_buffer = nullptr;
+ }
+ ~StreamBuffer() { delete[] m_buffer; }
+ void operator+=(std::size_t commit_size) { m_bufferUsed += commit_size; }
+ void operator+=(const std::initializer_list<uint8_t> &to_add) {
+ std::copy(to_add.begin(), to_add.end(), &m_buffer[m_bufferUsed]);
+ m_bufferUsed += to_add.size();
+ }
+ void operator-=(std::size_t consume_size) { m_bufferUsed -= consume_size; }
+ auto operator+() {
+ return boost::asio::buffer(&m_buffer[m_bufferUsed],
+ m_bufferSize - m_bufferUsed);
+ }
+ auto operator-() { return boost::asio::buffer(&m_buffer[0], m_bufferUsed); }
+ auto operator[](std::size_t index) const { return m_buffer[index]; }
+ const auto *operator()(std::size_t index = 0) const {
+ return &m_buffer[index];
+ }
+ auto size() const { return m_bufferUsed; }
+ auto getHealth() const {
+ return (static_cast<float>(m_bufferUsed) /
+ static_cast<float>(m_bufferSize));
+ }
+
+private:
+ std::size_t m_bufferUsed;
+ std::size_t m_bufferSize;
+ std::uint8_t *m_buffer;
+};
+
+class ProxySessionBase : public boost::noncopyable {
+public:
+ ProxySessionBase(std::uint32_t session_id,
+ boost::asio::ip::tcp::socket &&client_socket,
+ std::size_t buffer_size = BUFSIZ);
+ ProxySessionBase(std::uint32_t session_id,
+ boost::asio::ip::tcp::socket &&client_socket,
+ StreamBuffer &&input_buffer, StreamBuffer &&output_buffer);
+
+ std::uint32_t m_sessionId;
+ StreamBuffer m_inBuf;
+ StreamBuffer m_outBuf;
+ boost::asio::ip::tcp::socket m_clientSocket;
+};
+
+class ProxySessionAuth : public ProxySessionBase,
+ public std::enable_shared_from_this<ProxySessionAuth> {
+public:
+ ProxySessionAuth(std::uint32_t session_id,
+ boost::asio::ip::tcp::socket &&client_socket);
+ void start();
+
+private:
+ void recv_client_greeting(const boost::system::error_code &ec,
+ std::size_t length);
+ void send_server_greeting(bool auth_supported);
+ void recv_connection_request(const boost::system::error_code &ec,
+ std::size_t length);
+ void process_connection_request();
+ void send_server_response(std::uint8_t proxy_cmd, std::uint8_t status_code);
+ void resolve_destination_host(std::uint8_t proxy_cmd,
+ const std::string_view &host,
+ std::uint16_t port);
+ void connect_to_destination(std::uint8_t proxy_cmd);
+ void handle_write(const boost::system::error_code &ec, std::size_t length);
+ void handle_response_write(std::uint8_t proxy_cmd, std::uint8_t status_code,
+ const boost::system::error_code &ec,
+ std::size_t length);
+
+ boost::asio::ip::tcp::resolver m_resolver;
+ boost::asio::ip::tcp::endpoint m_endpoint;
+};
+
+class ProxySessionTcp : public ProxySessionBase,
+ public std::enable_shared_from_this<ProxySessionTcp> {
+public:
+ explicit ProxySessionTcp(ProxySessionBase &base,
+ std::size_t buffer_size = 65535);
+ void start(boost::asio::ip::tcp::endpoint &destination);
+
+private:
+ void recv_from_both();
+ void recv_from_destination(const boost::system::error_code &ec,
+ std::size_t length);
+ void recv_from_client(const boost::system::error_code &ec,
+ std::size_t length);
+ void handle_client_write(const boost::system::error_code &ec,
+ std::size_t length);
+ void handle_destination_write(const boost::system::error_code &ec,
+ std::size_t length);
+
+ boost::asio::ip::tcp::socket m_destinationSocket;
+};
+
+class ProxyServer : public boost::noncopyable {
+public:
+ ProxyServer(boost::asio::io_context &ioc,
+ const boost::asio::ip::tcp::endpoint &local_endpoint);
+ ProxyServer(boost::asio::io_context &ioc, const std::string &listen_addr,
+ std::uint16_t listen_port);
+ void start();
+
+private:
+ void async_accept();
+
+ uint32_t m_nextSessionId;
+ boost::asio::ip::tcp::acceptor m_acceptor;
+};
+}; // namespace SOCKS5