Socks5.h
1 /* 2 * Copyright (c) 2024, The PurpleI2P Project 3 * 4 * This file is part of Purple i2pd project and licensed under BSD3 5 * 6 * See full license text in LICENSE file at top of project tree 7 * 8 */ 9 10 #ifndef SOCKS5_H__ 11 #define SOCKS5_H__ 12 13 #include <string> 14 #include <memory> 15 #include <boost/asio.hpp> 16 #include "I2PEndian.h" 17 18 namespace i2p 19 { 20 namespace transport 21 { 22 // SOCKS5 constants 23 const uint8_t SOCKS5_VER = 0x05; 24 const uint8_t SOCKS5_CMD_CONNECT = 0x01; 25 const uint8_t SOCKS5_CMD_UDP_ASSOCIATE = 0x03; 26 const uint8_t SOCKS5_ATYP_IPV4 = 0x01; 27 const uint8_t SOCKS5_ATYP_IPV6 = 0x04; 28 const uint8_t SOCKS5_ATYP_NAME = 0x03; 29 const size_t SOCKS5_UDP_IPV4_REQUEST_HEADER_SIZE = 10; 30 const size_t SOCKS5_UDP_IPV6_REQUEST_HEADER_SIZE = 22; 31 32 const uint8_t SOCKS5_REPLY_SUCCESS = 0x00; 33 const uint8_t SOCKS5_REPLY_SERVER_FAILURE = 0x01; 34 const uint8_t SOCKS5_REPLY_CONNECTION_NOT_ALLOWED = 0x02; 35 const uint8_t SOCKS5_REPLY_NETWORK_UNREACHABLE = 0x03; 36 const uint8_t SOCKS5_REPLY_HOST_UNREACHABLE = 0x04; 37 const uint8_t SOCKS5_REPLY_CONNECTION_REFUSED = 0x05; 38 const uint8_t SOCKS5_REPLY_TTL_EXPIRED = 0x06; 39 const uint8_t SOCKS5_REPLY_COMMAND_NOT_SUPPORTED = 0x07; 40 const uint8_t SOCKS5_REPLY_ADDRESS_TYPE_NOT_SUPPORTED = 0x08; 41 42 // SOCKS5 handshake 43 template<typename Socket, typename Handler> 44 void Socks5ReadReply (Socket& s, Handler handler) 45 { 46 auto readbuff = std::make_shared<std::vector<int8_t> >(258); // max possible 47 boost::asio::async_read(s, boost::asio::buffer(readbuff->data (), 5), boost::asio::transfer_all(), // read 4 bytes of header + first byte of address 48 [readbuff, &s, handler](const boost::system::error_code& ec, std::size_t transferred) 49 { 50 if (!ec) 51 { 52 if ((*readbuff)[1] == SOCKS5_REPLY_SUCCESS) 53 { 54 size_t len = 0; 55 switch ((*readbuff)[3]) // ATYP 56 { 57 case SOCKS5_ATYP_IPV4: len = 3; break; // address length 4 bytes 58 case SOCKS5_ATYP_IPV6: len = 15; break; // address length 16 bytes 59 case SOCKS5_ATYP_NAME: len += (*readbuff)[4]; break; // first byte of address is length 60 default: ; 61 } 62 if (len) 63 { 64 len += 2; // port 65 boost::asio::async_read(s, boost::asio::buffer(readbuff->data (), len), boost::asio::transfer_all(), 66 [readbuff, handler](const boost::system::error_code& ec, std::size_t transferred) 67 { 68 if (!ec) 69 handler (boost::system::error_code ()); // success 70 else 71 handler (boost::asio::error::make_error_code (boost::asio::error::connection_aborted)); 72 }); 73 } 74 else 75 handler (boost::asio::error::make_error_code (boost::asio::error::fault)); // unknown address type 76 } 77 else 78 switch ((*readbuff)[1]) // REP 79 { 80 case SOCKS5_REPLY_SERVER_FAILURE: 81 handler (boost::asio::error::make_error_code (boost::asio::error::access_denied )); 82 break; 83 case SOCKS5_REPLY_CONNECTION_NOT_ALLOWED: 84 handler (boost::asio::error::make_error_code (boost::asio::error::no_permission)); 85 break; 86 case SOCKS5_REPLY_HOST_UNREACHABLE: 87 handler (boost::asio::error::make_error_code (boost::asio::error::host_unreachable)); 88 break; 89 case SOCKS5_REPLY_NETWORK_UNREACHABLE: 90 handler (boost::asio::error::make_error_code (boost::asio::error::network_unreachable)); 91 break; 92 case SOCKS5_REPLY_CONNECTION_REFUSED: 93 handler (boost::asio::error::make_error_code (boost::asio::error::connection_refused)); 94 break; 95 case SOCKS5_REPLY_TTL_EXPIRED: 96 handler (boost::asio::error::make_error_code (boost::asio::error::timed_out)); 97 break; 98 case SOCKS5_REPLY_COMMAND_NOT_SUPPORTED: 99 handler (boost::asio::error::make_error_code (boost::asio::error::operation_not_supported)); 100 break; 101 case SOCKS5_REPLY_ADDRESS_TYPE_NOT_SUPPORTED: 102 handler (boost::asio::error::make_error_code (boost::asio::error::no_protocol_option)); 103 break; 104 default: 105 handler (boost::asio::error::make_error_code (boost::asio::error::connection_aborted)); 106 } 107 } 108 else 109 handler (ec); 110 }); 111 } 112 113 template<typename Socket, typename Handler> 114 void Socks5Connect (Socket& s, Handler handler, std::shared_ptr<std::vector<uint8_t> > buff, uint16_t port) 115 { 116 if (buff && buff->size () >= 6) 117 { 118 (*buff)[0] = SOCKS5_VER; 119 (*buff)[1] = SOCKS5_CMD_CONNECT; 120 (*buff)[2] = 0x00; 121 htobe16buf(buff->data () + buff->size () - 2, port); 122 boost::asio::async_write(s, boost::asio::buffer(*buff), boost::asio::transfer_all(), 123 [buff, &s, handler](const boost::system::error_code& ec, std::size_t transferred) 124 { 125 (void) transferred; 126 if (!ec) 127 Socks5ReadReply (s, handler); 128 else 129 handler (ec); 130 }); 131 } 132 else 133 handler (boost::asio::error::make_error_code (boost::asio::error::no_buffer_space)); 134 } 135 136 template<typename Socket, typename Handler> 137 void Socks5Connect (Socket& s, const boost::asio::ip::tcp::endpoint& ep, Handler handler) 138 { 139 std::shared_ptr<std::vector<uint8_t> > buff; 140 if(ep.address ().is_v4 ()) 141 { 142 buff = std::make_shared<std::vector<uint8_t> >(10); 143 (*buff)[3] = SOCKS5_ATYP_IPV4; 144 auto addrbytes = ep.address ().to_v4().to_bytes(); 145 memcpy(buff->data () + 4, addrbytes.data(), 4); 146 } 147 else if (ep.address ().is_v6 ()) 148 { 149 buff = std::make_shared<std::vector<uint8_t> >(22); 150 (*buff)[3] = SOCKS5_ATYP_IPV6; 151 auto addrbytes = ep.address ().to_v6().to_bytes(); 152 memcpy(buff->data () + 4, addrbytes.data(), 16); 153 } 154 if (buff) 155 Socks5Connect (s, handler, buff, ep.port ()); 156 else 157 handler (boost::asio::error::make_error_code (boost::asio::error::fault)); 158 } 159 160 template<typename Socket, typename Handler> 161 void Socks5Connect (Socket& s, const std::pair<std::string, uint16_t>& ep, Handler handler) 162 { 163 auto& addr = ep.first; 164 if (addr.length () <= 255) 165 { 166 auto buff = std::make_shared<std::vector<uint8_t> >(addr.length () + 7); 167 (*buff)[3] = SOCKS5_ATYP_NAME; 168 (*buff)[4] = addr.length (); 169 memcpy (buff->data () + 5, addr.c_str (), addr.length ()); 170 Socks5Connect (s, handler, buff, ep.second); 171 } 172 else 173 handler (boost::asio::error::make_error_code (boost::asio::error::name_too_long)); 174 } 175 176 177 template<typename Socket, typename Endpoint, typename Handler> 178 void Socks5Handshake (Socket& s, Endpoint ep, Handler handler) 179 { 180 static const uint8_t methodSelection[3] = { SOCKS5_VER, 0x01, 0x00 }; // 1 method, no auth 181 boost::asio::async_write(s, boost::asio::buffer(methodSelection, 3), boost::asio::transfer_all(), 182 [&s, ep, handler] (const boost::system::error_code& ec, std::size_t transferred) 183 { 184 (void) transferred; 185 if (!ec) 186 { 187 auto readbuff = std::make_shared<std::vector<uint8_t> >(2); 188 boost::asio::async_read(s, boost::asio::buffer(*readbuff), boost::asio::transfer_all(), 189 [&s, ep, handler, readbuff] (const boost::system::error_code& ec, std::size_t transferred) 190 { 191 if (!ec) 192 { 193 if (transferred == 2 && (*readbuff)[1] == 0x00) // no auth 194 Socks5Connect (s, ep, handler); 195 else 196 handler (boost::asio::error::make_error_code (boost::asio::error::invalid_argument)); 197 } 198 else 199 handler (ec); 200 }); 201 } 202 else 203 handler (ec); 204 }); 205 } 206 207 } 208 } 209 210 #endif