/ libi2pd / Socks5.h
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