summaryrefslogtreecommitdiff
path: root/socks5.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'socks5.cpp')
-rw-r--r--socks5.cpp397
1 files changed, 397 insertions, 0 deletions
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();
+ });
+}