socks5_session.hpp 15 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486
  1. /*
  2. * Copyright (c) 2017-2023 zhllxt
  3. *
  4. * author : zhllxt
  5. * email : 37792738@qq.com
  6. *
  7. * Distributed under the Boost Software License, Version 1.0. (See accompanying
  8. * file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
  9. */
  10. #ifndef __ASIO2_SOCKS5_SESSION_HPP__
  11. #define __ASIO2_SOCKS5_SESSION_HPP__
  12. #if defined(_MSC_VER) && (_MSC_VER >= 1200)
  13. #pragma once
  14. #endif // defined(_MSC_VER) && (_MSC_VER >= 1200)
  15. #include <variant>
  16. #include <asio2/base/detail/push_options.hpp>
  17. #include <asio2/tcp/tcp_session.hpp>
  18. #include <asio2/component/socks/socks5_server_cp.hpp>
  19. #include <asio2/proxy/socks5_transfer.hpp>
  20. namespace asio2::detail
  21. {
  22. ASIO2_CLASS_FORWARD_DECLARE_BASE;
  23. ASIO2_CLASS_FORWARD_DECLARE_TCP_BASE;
  24. ASIO2_CLASS_FORWARD_DECLARE_TCP_SERVER;
  25. ASIO2_CLASS_FORWARD_DECLARE_TCP_SESSION;
  26. template<class derived_t, class args_t = template_args_tcp_session>
  27. class socks5_session_impl_t
  28. : public tcp_session_impl_t<derived_t, args_t>
  29. {
  30. ASIO2_CLASS_FRIEND_DECLARE_BASE;
  31. ASIO2_CLASS_FRIEND_DECLARE_TCP_BASE;
  32. ASIO2_CLASS_FRIEND_DECLARE_TCP_SERVER;
  33. ASIO2_CLASS_FRIEND_DECLARE_TCP_SESSION;
  34. public:
  35. using super = tcp_session_impl_t <derived_t, args_t>;
  36. using self = socks5_session_impl_t<derived_t, args_t>;
  37. using args_type = args_t;
  38. using key_type = std::size_t;
  39. using buffer_type = typename args_t::buffer_t;
  40. protected:
  41. using super::send;
  42. using super::async_send;
  43. public:
  44. /**
  45. * @brief constructor
  46. */
  47. template<class... Args>
  48. explicit socks5_session_impl_t(Args&&... args)
  49. : super(std::forward<Args>(args)...)
  50. {
  51. }
  52. /**
  53. * @brief destructor
  54. */
  55. ~socks5_session_impl_t()
  56. {
  57. }
  58. public:
  59. /**
  60. * @brief destroy the content of all member variables, this is used for solve the memory leaks.
  61. * After this function is called, this class object cannot be used again.
  62. */
  63. inline void destroy()
  64. {
  65. derived_t& derive = this->derived();
  66. std::visit([](auto& ptr) mutable
  67. {
  68. ptr.reset();
  69. }, this->back_client_);
  70. derive.socks5_options_ = {};
  71. super::destroy();
  72. }
  73. /**
  74. * @brief get this object hash key,used for session map
  75. */
  76. inline key_type hash_key() const noexcept
  77. {
  78. return reinterpret_cast<key_type>(this);
  79. }
  80. /**
  81. * @brief Set the socks5 options.
  82. */
  83. inline derived_t& set_socks5_options(socks5::options socks5_options)
  84. {
  85. this->socks5_options_ = std::move(socks5_options);
  86. return this->derived();
  87. }
  88. /**
  89. * @brief Set the socks5 options.
  90. */
  91. inline socks5::options& get_socks5_options() noexcept
  92. {
  93. return this->socks5_options_;
  94. }
  95. /**
  96. * @brief Get the socks5 options.
  97. */
  98. inline const socks5::options& get_socks5_options() const noexcept
  99. {
  100. return this->socks5_options_;
  101. }
  102. /**
  103. * @brief Get the front client type. tcp or udp.
  104. */
  105. inline asio2::net_protocol get_front_client_type() const noexcept
  106. {
  107. switch (this->back_client_.index())
  108. {
  109. case 0: return asio2::net_protocol::tcp;
  110. case 1: return asio2::net_protocol::udp;
  111. default:return asio2::net_protocol::none;
  112. }
  113. }
  114. protected:
  115. template<class T>
  116. inline asio::steady_timer* _handle_socks5_command(
  117. std::shared_ptr<derived_t> session_ptr, T& s5_server_handshake_op)
  118. {
  119. ASIO2_ASSERT(session_ptr->running_in_this_thread());
  120. auto host = s5_server_handshake_op.host;
  121. auto port = s5_server_handshake_op.port;
  122. if (s5_server_handshake_op.cmd == socks5::command::connect)
  123. {
  124. auto back_client = std::make_shared<asio2::socks5_tcp_transfer>(this->io_);
  125. back_client->bind_connect([]() mutable
  126. {
  127. set_last_error(get_last_error());
  128. }).bind_disconnect([session_ptr]() mutable
  129. {
  130. // back client has disconnected, so we need disconnect the front client too.
  131. ASIO2_ASSERT(session_ptr->running_in_this_thread());
  132. session_ptr->stop();
  133. }).bind_recv([session_ptr](std::string_view data) mutable
  134. {
  135. // tcp: recvd data from the back client, forward the data to the front client.
  136. ASIO2_ASSERT(session_ptr->running_in_this_thread());
  137. session_ptr->async_send(data);
  138. });
  139. // async start the back client.
  140. if (!back_client->async_start(std::move(host), std::move(port)))
  141. return nullptr;
  142. // set timer expire, the socks5 async handshake will wait this timer by coroutine.
  143. back_client->connect_finish_timer_->expires_after(std::chrono::steady_clock::duration::max());
  144. this->back_client_ = back_client;
  145. return back_client->connect_finish_timer_.get();
  146. }
  147. else if (s5_server_handshake_op.cmd == socks5::command::udp_associate)
  148. {
  149. auto back_client = std::make_shared<asio2::socks5_udp_transfer>(this->io_);
  150. back_client->bind_start(
  151. [strbuf = s5_server_handshake_op.stream.get(), pback_client = back_client.get()]() mutable
  152. {
  153. set_last_error(get_last_error());
  154. // when code run to here, the socks5 async handshake is waiting the
  155. // connect finished timer.
  156. // when this callback returned, the connect finished timer will be canceled.
  157. // so timer waiter of the socks5 async handshake will be returned too.
  158. // we need reset the BND.PORT as the local binded port, beacuse the
  159. // BND.PORT will be sent to the front client, and the front client
  160. // will sent data to this BND.PORT
  161. std::uint16_t uport = pback_client->get_local_port();
  162. auto addr = pback_client->socket().local_endpoint(get_last_error()).address();
  163. char* p = const_cast<char*>(static_cast<const char*>(strbuf->data().data()));
  164. if /**/ (addr.is_v4())
  165. {
  166. p += 1 + 1 + 1 + 1 + 4;
  167. detail::write(p, uport);
  168. }
  169. else
  170. {
  171. p += 1 + 1 + 1 + 1 + 16;
  172. detail::write(p, uport);
  173. }
  174. }).bind_stop([session_ptr]() mutable
  175. {
  176. // back client has stoped, so we need disconnect the front client too.
  177. ASIO2_ASSERT(session_ptr->running_in_this_thread());
  178. session_ptr->stop();
  179. }).bind_recv([this, wptr = std::weak_ptr<asio2::socks5_udp_transfer>(back_client)]
  180. (asio::ip::udp::endpoint& endpoint, std::string_view data) mutable
  181. {
  182. // udp: recvd data, maybe from front client or back client,
  183. // we need to check whether from front or back by ip and port.
  184. ASIO2_ASSERT(this->running_in_this_thread());
  185. this->_forward_udp_data(wptr.lock(), endpoint, data);
  186. });
  187. this->udp_dst_port_ = std::uint16_t(std::strtoul(port.data(), nullptr, 10));
  188. // async start the back client
  189. if (!back_client->async_start(std::move(host), 0))
  190. return nullptr;
  191. // set timer expire, the socks5 async handshake will wait this timer by coroutine.
  192. back_client->connect_finish_timer_->expires_after(std::chrono::steady_clock::duration::max());
  193. this->back_client_ = back_client;
  194. return back_client->connect_finish_timer_.get();
  195. }
  196. else
  197. {
  198. return nullptr;
  199. }
  200. }
  201. // recvd data from udp
  202. void _forward_udp_data(
  203. std::shared_ptr<asio2::socks5_udp_transfer> udp_cast_ptr,
  204. asio::ip::udp::endpoint& endpoint, std::string_view data)
  205. {
  206. if (!udp_cast_ptr)
  207. return;
  208. ASIO2_ASSERT(udp_cast_ptr->running_in_this_thread());
  209. ASIO2_ASSERT(this->running_in_this_thread());
  210. // socks5 session is the front client.
  211. asio::ip::address front_addr = this->socket().lowest_layer().remote_endpoint().address();
  212. bool is_from_front = false;
  213. if (front_addr.is_loopback())
  214. is_from_front = (endpoint.address() == front_addr && endpoint.port() == this->udp_dst_port_);
  215. else
  216. is_from_front = (endpoint.address() == front_addr);
  217. // recvd data from the front client. forward it to the target endpoint.
  218. if (is_from_front)
  219. {
  220. // when socks5 server recvd data from the front client by udp, set the channel flag.
  221. this->udp_dst_channel_ = asio2::net_protocol::udp;
  222. auto [err, ep, domain, real_data] = asio2::socks5::parse_udp_packet(data, false);
  223. if (err == 0)
  224. {
  225. if (domain.empty())
  226. udp_cast_ptr->async_send(std::move(ep), real_data);
  227. else
  228. udp_cast_ptr->async_send(std::move(domain), ep.port(), real_data);
  229. }
  230. }
  231. // recvd data not from the front client. forward it to the front client.
  232. else
  233. {
  234. std::vector<std::uint8_t> edit_data;
  235. edit_data.reserve(data.size() + 4 + 4 + 2);
  236. edit_data.push_back(std::uint8_t(0x00)); // RSV
  237. edit_data.push_back(std::uint8_t(0x00)); // RSV
  238. edit_data.push_back(std::uint8_t(0x00)); // FRAG
  239. asio::ip::address end_addr = endpoint.address();
  240. if (end_addr.is_v4()) // IP V4 address: X'01'
  241. {
  242. edit_data.push_back(std::uint8_t(0x01));
  243. asio::ip::address_v4::bytes_type bytes = end_addr.to_v4().to_bytes();
  244. edit_data.insert(edit_data.cend(), bytes.begin(), bytes.end());
  245. }
  246. else if (end_addr.is_v6()) // IP V6 address: X'04'
  247. {
  248. edit_data.push_back(std::uint8_t(0x04));
  249. asio::ip::address_v6::bytes_type bytes = end_addr.to_v6().to_bytes();
  250. edit_data.insert(edit_data.cend(), bytes.begin(), bytes.end());
  251. }
  252. std::uint16_t uport = detail::host_to_network(std::uint16_t(endpoint.port()));
  253. std::uint8_t* pport = reinterpret_cast<std::uint8_t*>(std::addressof(uport));
  254. edit_data.push_back(pport[0]);
  255. edit_data.push_back(pport[1]);
  256. edit_data.insert(edit_data.cend(), data.begin(), data.end());
  257. if (this->udp_dst_channel_ == asio2::net_protocol::tcp)
  258. {
  259. std::uint16_t udatalen = detail::host_to_network(std::uint16_t(data.size()));
  260. std::uint8_t* pdatalen = reinterpret_cast<std::uint8_t*>(std::addressof(udatalen));
  261. edit_data[0] = pdatalen[0];
  262. edit_data[1] = pdatalen[1];
  263. this->derived().async_send(std::move(edit_data));
  264. }
  265. else if (this->udp_dst_channel_ == asio2::net_protocol::udp)
  266. {
  267. udp_cast_ptr->async_send(
  268. asio::ip::udp::endpoint(front_addr, this->udp_dst_port_), std::move(edit_data));
  269. }
  270. }
  271. }
  272. template<typename C, typename DeferEvent>
  273. inline void _handle_connect(
  274. const error_code& ec,
  275. std::shared_ptr<derived_t> this_ptr, std::shared_ptr<ecs_t<C>> ecs, DeferEvent chain)
  276. {
  277. detail::ignore_unused(ec);
  278. derived_t& derive = this->derived();
  279. ASIO2_ASSERT(!ec);
  280. ASIO2_ASSERT(derive.sessions_.io_->running_in_this_thread());
  281. asio::dispatch(derive.io_->context(), make_allocator(derive.wallocator(),
  282. [this, &derive, this_ptr = std::move(this_ptr), ecs = std::move(ecs), chain = std::move(chain)]
  283. () mutable
  284. {
  285. socks5_async_handshake(derive.socket(), derive.socks5_options_,
  286. [&derive, this_ptr](auto& s5_server_handshake_op) mutable
  287. {
  288. return derive._handle_socks5_command(std::move(this_ptr), s5_server_handshake_op);
  289. },
  290. [this, &derive, this_ptr, ecs = std::move(ecs), chain = std::move(chain)]
  291. (const error_code& ec) mutable
  292. {
  293. derive.sessions_.dispatch(
  294. [this, ec, this_ptr = std::move(this_ptr), ecs = std::move(ecs), chain = std::move(chain)]
  295. () mutable
  296. {
  297. set_last_error(ec);
  298. this->derived()._fire_socks5_handshake(this_ptr);
  299. super::_handle_connect(ec, std::move(this_ptr), std::move(ecs), std::move(chain));
  300. });
  301. });
  302. }));
  303. }
  304. template<typename DeferEvent>
  305. inline void _handle_stop(const error_code& ec, std::shared_ptr<derived_t> this_ptr, DeferEvent chain)
  306. {
  307. // front client has disconnected, so we need disconnect the back client too.
  308. std::visit([](auto& ptr) mutable
  309. {
  310. if (ptr)
  311. {
  312. ptr->stop();
  313. ptr.reset();
  314. }
  315. }, this->back_client_);
  316. super::_handle_stop(ec, std::move(this_ptr), std::move(chain));
  317. }
  318. template<typename C>
  319. inline void _fire_recv(
  320. std::shared_ptr<derived_t>& this_ptr, std::shared_ptr<ecs_t<C>>&, std::string_view data)
  321. {
  322. this->listener_.notify(event_type::recv, this_ptr, data);
  323. if (data.empty())
  324. return;
  325. // recvd data from the front client by tcp, we need check the front client is tcp or udp.
  326. std::visit(variant_overloaded
  327. {
  328. [](auto) {},
  329. // if it is tcp, just forward the data to back client.
  330. [data](std::shared_ptr<asio2::socks5_tcp_transfer>& back_client_ptr) mutable
  331. {
  332. if (back_client_ptr)
  333. back_client_ptr->async_send(data);
  334. },
  335. // if it is udp, it means that the packet is a extension protocol base of below:
  336. // +----+------+------+----------+----------+----------+
  337. // |RSV | FRAG | ATYP | DST.ADDR | DST.PORT | DATA |
  338. // +----+------+------+----------+----------+----------+
  339. // | 2 | 1 | 1 | Variable | 2 | Variable |
  340. // +----+------+------+----------+----------+----------+
  341. // the RSV field is the real data length of the field DATA.
  342. // so we need unpacket this data, and send the real data to the back client.
  343. [this, data](std::shared_ptr<asio2::socks5_udp_transfer>& back_client_ptr) mutable
  344. {
  345. // when socks5 server recvd data from the front client by tcp, set the channel flag.
  346. this->udp_dst_channel_ = asio2::net_protocol::tcp;
  347. if (back_client_ptr)
  348. {
  349. auto [err, ep, domain, real_data] = asio2::socks5::parse_udp_packet(data, true);
  350. if (err == 0)
  351. {
  352. if (domain.empty())
  353. back_client_ptr->async_send(std::move(ep), real_data);
  354. else
  355. back_client_ptr->async_send(std::move(domain), ep.port(), real_data);
  356. }
  357. }
  358. }
  359. }, this->back_client_);
  360. }
  361. inline void _fire_socks5_handshake(std::shared_ptr<derived_t>& this_ptr)
  362. {
  363. // the _fire_socks5_handshake must be executed in the thread 0.
  364. ASIO2_ASSERT(this->sessions_.io_->running_in_this_thread());
  365. this->listener_.notify(event_type::upgrade, this_ptr);
  366. }
  367. protected:
  368. socks5::options socks5_options_{};
  369. // if the command is UDP ASSOCIATE, we need save the port of the front udp client.
  370. std::uint16_t udp_dst_port_{};
  371. // if the front client is udp, and when socks5 server recvd data from the back client,
  372. // whether we use tcp channel or udp channcel to forward the data to the front client?
  373. asio2::net_protocol udp_dst_channel_ = asio2::net_protocol::udp;
  374. // create a new client, and connect to the target server.
  375. // if the command is CONNECT, we need create a tcp client.
  376. // if the command is UDP ASSOCIATE, we need create a udp cast.
  377. std::variant<
  378. std::shared_ptr<asio2::socks5_tcp_transfer>,
  379. std::shared_ptr<asio2::socks5_udp_transfer>> back_client_;
  380. };
  381. }
  382. namespace asio2
  383. {
  384. using socks5_session_args = detail::template_args_tcp_session;
  385. template<class derived_t, class args_t>
  386. using socks5_session_impl_t = detail::socks5_session_impl_t<derived_t, args_t>;
  387. /**
  388. * @brief socks5 tcp session
  389. */
  390. template<class derived_t>
  391. class socks5_session_t : public detail::socks5_session_impl_t<derived_t, detail::template_args_tcp_session>
  392. {
  393. public:
  394. using detail::socks5_session_impl_t<derived_t, detail::template_args_tcp_session>::socks5_session_impl_t;
  395. };
  396. /**
  397. * @brief socks5 tcp session
  398. */
  399. class socks5_session : public socks5_session_t<socks5_session>
  400. {
  401. public:
  402. using socks5_session_t<socks5_session>::socks5_session_t;
  403. };
  404. }
  405. #include <asio2/base/detail/pop_options.hpp>
  406. #endif // !__ASIO2_SOCKS5_SESSION_HPP__