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 // Greeting: offer username/password auth only when credentials are present.
40 if(proxy.has_userinfo())
41 {
42 std::uint8_t greeting[4] = { 0x05, 0x02, 0x00, 0x02 };
43 auto [ec, n] =
44 co_await capy::write(stream, capy::make_buffer(greeting));
45 if(ec)
46 co_return ec;
47 }
48 else
49 {
50 std::uint8_t greeting[3] = { 0x05, 0x01, 0x00 };
51 auto [ec, n] =
52 co_await capy::write(stream, capy::make_buffer(greeting));
53 if(ec)
54 co_return ec;
55 }
56
57 std::uint8_t greeting_resp[2];
58 if(auto [ec, n] =
59 co_await capy::read(stream, capy::make_buffer(greeting_resp));
60 ec)
61 co_return ec;
62
63 if(greeting_resp[0] != 0x05)
64 co_return { error::proxy_unsupported_version };
65
66 switch(greeting_resp[1])
67 {
68 case 0x00: // no authentication required
69 break;
70 case 0x02: // username/password (RFC 1929)
71 {
72 std::string auth_req;
73 auth_req.push_back(0x01); // sub-negotiation version
74
75 auto user = proxy.encoded_user();
76 auth_req.push_back(static_cast<char>(user.decoded_size()));
77 user.decode({}, urls::string_token::append_to(auth_req));
78
79 auto pass = proxy.encoded_password();
80 auth_req.push_back(static_cast<char>(pass.decoded_size()));
81 pass.decode({}, urls::string_token::append_to(auth_req));
82
83 if(auto [ec, n] =
84 co_await capy::write(stream, capy::make_buffer(auth_req));
85 ec)
86 co_return ec;
87
88 std::uint8_t auth_resp[2];
89 if(auto [ec, n] =
90 co_await capy::read(stream, capy::make_buffer(auth_resp));
91 ec)
92 co_return ec;
93
94 if(auth_resp[1] != 0x00)
95 co_return { error::proxy_auth_failed };
96 break;
97 }
98 default: // no acceptable method (0xFF) or anything unexpected
99 co_return { error::proxy_auth_failed };
100 }
101
102 // connection request: VER, CMD=connect, RSV
103 std::string conn_req = { 0x05, 0x01, 0x00 };
104
105 switch(target.host_type())
106 {
107 case urls::host_type::ipv4:
108 {
109 conn_req.push_back(0x01); // ATYP: IPv4 address
110 auto bytes = target.host_ipv4_address().to_bytes();
111 conn_req.append(
112 reinterpret_cast<const char*>(bytes.data()), bytes.size());
113 break;
114 }
115 case urls::host_type::ipv6:
116 {
117 conn_req.push_back(0x04); // ATYP: IPv6 address
118 auto bytes = target.host_ipv6_address().to_bytes();
119 conn_req.append(
120 reinterpret_cast<const char*>(bytes.data()), bytes.size());
121 break;
122 }
123 case urls::host_type::name:
124 {
125 auto host = target.host_address(); // decoded, without brackets
126 if(host.empty() || host.size() > 255)
127 co_return { error::proxy_connect_failed };
128 conn_req.push_back(0x03); // ATYP: domain name
129 conn_req.push_back(static_cast<char>(host.size()));
130 conn_req.append(host);
131 break;
132 }
133 default: // host_type::none or host_type::ipvfuture
134 co_return { error::proxy_connect_failed };
135 }
136
137 std::uint16_t port = 0;
138 auto port_str = effective_port(target);
139 std::from_chars(
140 port_str.data(), port_str.data() + port_str.size(), port);
141 conn_req.push_back(static_cast<char>((port >> 8) & 0xFF));
142 conn_req.push_back(static_cast<char>(port & 0xFF));
143
144 if(auto [ec, n] = co_await capy::write(stream, capy::make_buffer(conn_req));
145 ec)
146 co_return ec;
147
148 // connection response
149 std::uint8_t reply_head[5];
150 if(auto [ec, n] =
151 co_await capy::read(stream, capy::make_buffer(reply_head));
152 ec)
153 co_return ec;
154
155 if(reply_head[1] != 0x00)
156 co_return { error::proxy_connect_failed };
157
158 std::size_t tail = 0;
159 switch(reply_head[3])
160 {
161 case 0x01:
162 tail = 4 + 2 - 1; // ipv4 + port
163 break;
164 case 0x03:
165 tail = reply_head[4] + 2u; // domain name + port
166 break;
167 case 0x04:
168 tail = 16 + 2 - 1; // ipv6 + port
169 break;
170 default:
171 co_return { error::proxy_connect_failed };
172 }
173
174 std::string reply_tail;
175 reply_tail.resize(tail);
176 if(auto [ec, n] =
177 co_await capy::read(stream, capy::make_buffer(reply_tail));
178 ec)
179 co_return ec;
180
181 co_return {};
182 54x }
183
184 } // namespace detail
185 } // namespace burl
186 } // namespace boost
187