src/detail/socks5_tunnel.cpp

100.0% Lines (2/2) 100.0% List of functions (1/1) 100.0% Branches (1/1)
socks5_tunnel.cpp
f(x) Functions (1)
Line Branch TLA Hits Source Code
1 //
2 // Copyright (c) 2026 Mohammad Nejati
3 //
4 // Distributed under the Boost Software License, Version 1.0. (See accompanying
5 // file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
6 //
7 // Official repository: https://github.com/cppalliance/burl
8 //
9
10 #include "socks5_tunnel.hpp"
11
12 #include <boost/burl/error.hpp>
13
14 #include "effective_port.hpp"
15
16 #include <boost/capy/buffers/make_buffer.hpp>
17 #include <boost/capy/read.hpp>
18 #include <boost/capy/write.hpp>
19 #include <boost/url/grammar/string_token.hpp>
20
21 #include <charconv>
22 #include <cstddef>
23 #include <cstdint>
24 #include <string>
25
26 namespace boost
27 {
28 namespace burl
29 {
30 namespace detail
31 {
32
33 capy::io_task<>
34
1/1
✓ Branch 1 taken 27 times.
27x open_socks5_tunnel(
35 capy::any_stream stream,
36 urls::url_view target,
37 urls::url_view proxy)
38 {
39 std::error_code ec;
40
41 // Greeting: offer username/password auth only when credentials are present.
42 if(proxy.has_userinfo())
43 {
44 std::uint8_t greeting[4] = { 0x05, 0x02, 0x00, 0x02 };
45 std::tie(ec, std::ignore) =
46 co_await capy::write(stream, capy::make_buffer(greeting));
47 if(ec)
48 co_return ec;
49 }
50 else
51 {
52 std::uint8_t greeting[3] = { 0x05, 0x01, 0x00 };
53 std::tie(ec, std::ignore) =
54 co_await capy::write(stream, capy::make_buffer(greeting));
55 if(ec)
56 co_return ec;
57 }
58
59 std::uint8_t greeting_resp[2];
60 std::tie(ec, std::ignore) =
61 co_await capy::read(stream, capy::make_buffer(greeting_resp));
62 if(ec)
63 co_return ec;
64
65 if(greeting_resp[0] != 0x05)
66 co_return { error::proxy_unsupported_version };
67
68 switch(greeting_resp[1])
69 {
70 case 0x00: // no authentication required
71 break;
72 case 0x02: // username/password (RFC 1929)
73 {
74 std::string auth_req;
75 auth_req.push_back(0x01); // sub-negotiation version
76
77 auto user = proxy.encoded_user();
78 auth_req.push_back(static_cast<char>(user.decoded_size()));
79 user.decode({}, urls::string_token::append_to(auth_req));
80
81 auto pass = proxy.encoded_password();
82 auth_req.push_back(static_cast<char>(pass.decoded_size()));
83 pass.decode({}, urls::string_token::append_to(auth_req));
84
85 std::tie(ec, std::ignore) =
86 co_await capy::write(stream, capy::make_buffer(auth_req));
87 if(ec)
88 co_return ec;
89
90 std::uint8_t auth_resp[2];
91 std::tie(ec, std::ignore) =
92 co_await capy::read(stream, capy::make_buffer(auth_resp));
93 if(ec)
94 co_return ec;
95
96 if(auth_resp[1] != 0x00)
97 co_return { error::proxy_auth_failed };
98 break;
99 }
100 default: // no acceptable method (0xFF) or anything unexpected
101 co_return { error::proxy_auth_failed };
102 }
103
104 // connection request: VER, CMD=connect, RSV
105 std::string conn_req = { 0x05, 0x01, 0x00 };
106
107 switch(target.host_type())
108 {
109 case urls::host_type::ipv4:
110 {
111 conn_req.push_back(0x01); // ATYP: IPv4 address
112 auto bytes = target.host_ipv4_address().to_bytes();
113 conn_req.append(
114 reinterpret_cast<const char*>(bytes.data()), bytes.size());
115 break;
116 }
117 case urls::host_type::ipv6:
118 {
119 conn_req.push_back(0x04); // ATYP: IPv6 address
120 auto bytes = target.host_ipv6_address().to_bytes();
121 conn_req.append(
122 reinterpret_cast<const char*>(bytes.data()), bytes.size());
123 break;
124 }
125 case urls::host_type::name:
126 {
127 auto host = target.host_address(); // decoded, without brackets
128 if(host.empty() || host.size() > 255)
129 co_return { error::proxy_connect_failed };
130 conn_req.push_back(0x03); // ATYP: domain name
131 conn_req.push_back(static_cast<char>(host.size()));
132 conn_req.append(host);
133 break;
134 }
135 default: // host_type::none or host_type::ipvfuture
136 co_return { error::proxy_connect_failed };
137 }
138
139 std::uint16_t port = 0;
140 auto port_str = effective_port(target);
141 std::from_chars(
142 port_str.data(), port_str.data() + port_str.size(), port);
143 conn_req.push_back(static_cast<char>((port >> 8) & 0xFF));
144 conn_req.push_back(static_cast<char>(port & 0xFF));
145
146 std::tie(ec, std::ignore) =
147 co_await capy::write(stream, capy::make_buffer(conn_req));
148 if(ec)
149 co_return ec;
150
151 // connection response
152 std::uint8_t reply_head[5];
153 std::tie(ec, std::ignore) =
154 co_await capy::read(stream, capy::make_buffer(reply_head));
155 if(ec)
156 co_return ec;
157
158 if(reply_head[1] != 0x00)
159 co_return { error::proxy_connect_failed };
160
161 std::size_t tail = 0;
162 switch(reply_head[3])
163 {
164 case 0x01:
165 tail = 4 + 2 - 1; // ipv4 + port
166 break;
167 case 0x03:
168 tail = reply_head[4] + 2u; // domain name + port
169 break;
170 case 0x04:
171 tail = 16 + 2 - 1; // ipv6 + port
172 break;
173 default:
174 co_return { error::proxy_connect_failed };
175 }
176
177 std::string reply_tail;
178 reply_tail.resize(tail);
179 std::tie(ec, std::ignore) =
180 co_await capy::read(stream, capy::make_buffer(reply_tail));
181 if(ec)
182 co_return ec;
183
184 co_return {};
185 54x }
186
187 } // namespace detail
188 } // namespace burl
189 } // namespace boost
190