/* * Copyright (c) 2024, The PurpleI2P Project * * This file is part of Purple i2pd project and licensed under BSD3 * * See full license text in LICENSE file at top of project tree * */ #ifndef SOCKS5_H__ #define SOCKS5_H__ #include #include #include #include "I2PEndian.h" namespace i2p { namespace transport { // SOCKS5 constants const uint8_t SOCKS5_VER = 0x05; const uint8_t SOCKS5_CMD_CONNECT = 0x01; const uint8_t SOCKS5_CMD_UDP_ASSOCIATE = 0x03; const uint8_t SOCKS5_ATYP_IPV4 = 0x01; const uint8_t SOCKS5_ATYP_IPV6 = 0x04; const uint8_t SOCKS5_ATYP_NAME = 0x03; const size_t SOCKS5_UDP_IPV4_REQUEST_HEADER_SIZE = 10; const size_t SOCKS5_UDP_IPV6_REQUEST_HEADER_SIZE = 22; const uint8_t SOCKS5_REPLY_SUCCESS = 0x00; const uint8_t SOCKS5_REPLY_SERVER_FAILURE = 0x01; const uint8_t SOCKS5_REPLY_CONNECTION_NOT_ALLOWED = 0x02; const uint8_t SOCKS5_REPLY_NETWORK_UNREACHABLE = 0x03; const uint8_t SOCKS5_REPLY_HOST_UNREACHABLE = 0x04; const uint8_t SOCKS5_REPLY_CONNECTION_REFUSED = 0x05; const uint8_t SOCKS5_REPLY_TTL_EXPIRED = 0x06; const uint8_t SOCKS5_REPLY_COMMAND_NOT_SUPPORTED = 0x07; const uint8_t SOCKS5_REPLY_ADDRESS_TYPE_NOT_SUPPORTED = 0x08; // SOCKS5 handshake template void Socks5ReadReply (Socket& s, Handler handler) { auto readbuff = std::make_shared >(258); // max possible boost::asio::async_read(s, boost::asio::buffer(readbuff->data (), 5), boost::asio::transfer_all(), // read 4 bytes of header + first byte of address [readbuff, &s, handler](const boost::system::error_code& ec, std::size_t transferred) { if (!ec) { if ((*readbuff)[1] == SOCKS5_REPLY_SUCCESS) { size_t len = 0; switch ((*readbuff)[3]) // ATYP { case SOCKS5_ATYP_IPV4: len = 3; break; // address length 4 bytes case SOCKS5_ATYP_IPV6: len = 15; break; // address length 16 bytes case SOCKS5_ATYP_NAME: len += (*readbuff)[4]; break; // first byte of address is length default: ; } if (len) { len += 2; // port boost::asio::async_read(s, boost::asio::buffer(readbuff->data (), len), boost::asio::transfer_all(), [readbuff, handler](const boost::system::error_code& ec, std::size_t transferred) { if (!ec) handler (boost::system::error_code ()); // success else handler (boost::asio::error::make_error_code (boost::asio::error::connection_aborted)); }); } else handler (boost::asio::error::make_error_code (boost::asio::error::fault)); // unknown address type } else switch ((*readbuff)[1]) // REP { case SOCKS5_REPLY_SERVER_FAILURE: handler (boost::asio::error::make_error_code (boost::asio::error::access_denied )); break; case SOCKS5_REPLY_CONNECTION_NOT_ALLOWED: handler (boost::asio::error::make_error_code (boost::asio::error::no_permission)); break; case SOCKS5_REPLY_HOST_UNREACHABLE: handler (boost::asio::error::make_error_code (boost::asio::error::host_unreachable)); break; case SOCKS5_REPLY_NETWORK_UNREACHABLE: handler (boost::asio::error::make_error_code (boost::asio::error::network_unreachable)); break; case SOCKS5_REPLY_CONNECTION_REFUSED: handler (boost::asio::error::make_error_code (boost::asio::error::connection_refused)); break; case SOCKS5_REPLY_TTL_EXPIRED: handler (boost::asio::error::make_error_code (boost::asio::error::timed_out)); break; case SOCKS5_REPLY_COMMAND_NOT_SUPPORTED: handler (boost::asio::error::make_error_code (boost::asio::error::operation_not_supported)); break; case SOCKS5_REPLY_ADDRESS_TYPE_NOT_SUPPORTED: handler (boost::asio::error::make_error_code (boost::asio::error::no_protocol_option)); break; default: handler (boost::asio::error::make_error_code (boost::asio::error::connection_aborted)); } } else handler (ec); }); } template void Socks5Connect (Socket& s, Handler handler, std::shared_ptr > buff, uint16_t port) { if (buff && buff->size () >= 6) { (*buff)[0] = SOCKS5_VER; (*buff)[1] = SOCKS5_CMD_CONNECT; (*buff)[2] = 0x00; htobe16buf(buff->data () + buff->size () - 2, port); boost::asio::async_write(s, boost::asio::buffer(*buff), boost::asio::transfer_all(), [buff, &s, handler](const boost::system::error_code& ec, std::size_t transferred) { (void) transferred; if (!ec) Socks5ReadReply (s, handler); else handler (ec); }); } else handler (boost::asio::error::make_error_code (boost::asio::error::no_buffer_space)); } template void Socks5Connect (Socket& s, const boost::asio::ip::tcp::endpoint& ep, Handler handler) { std::shared_ptr > buff; if(ep.address ().is_v4 ()) { buff = std::make_shared >(10); (*buff)[3] = SOCKS5_ATYP_IPV4; auto addrbytes = ep.address ().to_v4().to_bytes(); memcpy(buff->data () + 4, addrbytes.data(), 4); } else if (ep.address ().is_v6 ()) { buff = std::make_shared >(22); (*buff)[3] = SOCKS5_ATYP_IPV6; auto addrbytes = ep.address ().to_v6().to_bytes(); memcpy(buff->data () + 4, addrbytes.data(), 16); } if (buff) Socks5Connect (s, handler, buff, ep.port ()); else handler (boost::asio::error::make_error_code (boost::asio::error::fault)); } template void Socks5Connect (Socket& s, const std::pair& ep, Handler handler) { auto& addr = ep.first; if (addr.length () <= 255) { auto buff = std::make_shared >(addr.length () + 7); (*buff)[3] = SOCKS5_ATYP_NAME; (*buff)[4] = addr.length (); memcpy (buff->data () + 5, addr.c_str (), addr.length ()); Socks5Connect (s, handler, buff, ep.second); } else handler (boost::asio::error::make_error_code (boost::asio::error::name_too_long)); } template void Socks5Handshake (Socket& s, Endpoint ep, Handler handler) { static const uint8_t methodSelection[3] = { SOCKS5_VER, 0x01, 0x00 }; // 1 method, no auth boost::asio::async_write(s, boost::asio::buffer(methodSelection, 3), boost::asio::transfer_all(), [&s, ep, handler] (const boost::system::error_code& ec, std::size_t transferred) { (void) transferred; if (!ec) { auto readbuff = std::make_shared >(2); boost::asio::async_read(s, boost::asio::buffer(*readbuff), boost::asio::transfer_all(), [&s, ep, handler, readbuff] (const boost::system::error_code& ec, std::size_t transferred) { if (!ec) { if (transferred == 2 && (*readbuff)[1] == 0x00) // no auth Socks5Connect (s, ep, handler); else handler (boost::asio::error::make_error_code (boost::asio::error::invalid_argument)); } else handler (ec); }); } else handler (ec); }); } } } #endif