Skip to content

Commit 21528bd

Browse files
committed
Burl: SOCKS5 proxy support
1 parent 322da07 commit 21528bd

File tree

1 file changed

+183
-48
lines changed

1 file changed

+183
-48
lines changed

example/client/burl/main.cpp

Lines changed: 183 additions & 48 deletions
Original file line numberDiff line numberDiff line change
@@ -720,6 +720,174 @@ class message
720720
}
721721
};
722722

723+
asio::awaitable<void>
724+
connect_socks5_proxy(
725+
asio::ip::tcp::socket& stream,
726+
urls::url_view url,
727+
urls::url_view proxy)
728+
{
729+
auto executor = co_await asio::this_coro::executor;
730+
auto resolver = asio::ip::tcp::resolver{ executor };
731+
auto rresults = co_await resolver.async_resolve(
732+
proxy.host(), effective_port(proxy));
733+
734+
// Connect to the proxy server
735+
co_await asio::async_connect(stream, rresults);
736+
737+
// Greeting request
738+
if(proxy.has_userinfo())
739+
{
740+
std::uint8_t greeting_req[4] = { 0x05, 0x02, 0x00, 0x02 };
741+
co_await asio::async_write(stream, asio::buffer(greeting_req));
742+
}
743+
else
744+
{
745+
std::uint8_t greeting_req[3] = { 0x05, 0x01, 0x00 };
746+
co_await asio::async_write(stream, asio::buffer(greeting_req));
747+
}
748+
749+
// Greeting response
750+
std::uint8_t greeting_resp[2];
751+
co_await asio::async_read(stream, asio::buffer(greeting_resp));
752+
753+
if(greeting_resp[0] != 0x05)
754+
throw std::runtime_error{ "SOCKS5 invalid version" };
755+
756+
switch(greeting_resp[1])
757+
{
758+
case 0x00: // No Authentication
759+
break;
760+
case 0x02: // Username/password
761+
{
762+
// Authentication request
763+
auto auth_req = std::string{ 0x01 };
764+
765+
auto user = proxy.encoded_user();
766+
auth_req.push_back(static_cast<std::uint8_t>(user.decoded_size()));
767+
user.decode({}, urls::string_token::append_to(auth_req));
768+
769+
auto pass = proxy.encoded_password();
770+
auth_req.push_back(static_cast<std::uint8_t>(pass.decoded_size()));
771+
pass.decode({}, urls::string_token::append_to(auth_req));
772+
773+
co_await asio::async_write(stream, asio::buffer(auth_req));
774+
775+
// Authentication response
776+
std::uint8_t greeting_resp[2];
777+
co_await asio::async_read(stream, asio::buffer(greeting_resp));
778+
779+
if(greeting_resp[1] != 0x00)
780+
throw std::runtime_error{
781+
"SOCKS5 authentication failed" };
782+
break;
783+
}
784+
default:
785+
throw std::runtime_error{
786+
"SOCKS5 no acceptable authentication method"
787+
};
788+
}
789+
790+
// Connection request
791+
auto conn_req = std::string{ 0x05, 0x01, 0x00, 0x03 };
792+
auto host = url.encoded_host();
793+
conn_req.push_back(static_cast<std::uint8_t>(host.decoded_size()));
794+
host.decode({}, urls::string_token::append_to(conn_req));
795+
796+
std::uint16_t port = std::stoi(effective_port(url));
797+
conn_req.push_back(static_cast<std::uint8_t>((port >> 8) & 0xFF));
798+
conn_req.push_back(static_cast<std::uint8_t>(port & 0xFF));
799+
800+
co_await asio::async_write(stream, asio::buffer(conn_req));
801+
802+
// Connection response
803+
std::uint8_t conn_resp_head[5];
804+
co_await asio::async_read(stream, asio::buffer(conn_resp_head));
805+
806+
if(conn_resp_head[1] != 0x00)
807+
throw std::runtime_error{
808+
"SOCKS5 connection request failed" };
809+
810+
std::string conn_resp_tail;
811+
conn_resp_tail.resize(
812+
[&]()
813+
{
814+
// subtract 1 because we have pre-read one byte
815+
switch(conn_resp_head[3])
816+
{
817+
case 0x01:
818+
return 4 + 2 - 1; // ipv4 + port
819+
case 0x03:
820+
return conn_resp_head[4] + 2 - 1; // domain name + port
821+
case 0x04:
822+
return 16 + 2 - 1; // ipv6 + port
823+
default:
824+
throw std::runtime_error{
825+
"SOCKS5 invalid address type" };
826+
}
827+
}());
828+
co_await asio::async_read(stream, asio::buffer(conn_resp_tail));
829+
}
830+
831+
asio::awaitable<void>
832+
connect_http_proxy(
833+
const po::variables_map& vm,
834+
http_proto::context& http_proto_ctx,
835+
asio::ip::tcp::socket& stream,
836+
urls::url_view url,
837+
urls::url_view proxy)
838+
{
839+
auto executor = co_await asio::this_coro::executor;
840+
auto resolver = asio::ip::tcp::resolver{ executor };
841+
auto rresults = co_await resolver.async_resolve(
842+
proxy.host(), effective_port(proxy));
843+
844+
// Connect to the proxy server
845+
co_await asio::async_connect(stream, rresults);
846+
847+
using http_proto::field;
848+
auto request = http_proto::request{};
849+
auto host_port = [&]()
850+
{
851+
auto rs = url.encoded_host().decode();
852+
rs.push_back(':');
853+
rs.append(effective_port(url));
854+
return rs;
855+
}();
856+
857+
request.set_method(http_proto::method::connect);
858+
request.set_target(host_port);
859+
request.set(field::host, host_port);
860+
request.set(field::proxy_connection, "keep-alive");
861+
862+
if(vm.count("user-agent"))
863+
{
864+
request.set(
865+
field::user_agent,
866+
vm.at("user-agent").as<std::string>());
867+
}
868+
else
869+
{
870+
request.set(field::user_agent, "Boost.Http.Io");
871+
}
872+
873+
// TODO
874+
// request.set(field::proxy_authorization, "");
875+
876+
auto serializer = http_proto::serializer{ http_proto_ctx };
877+
auto parser = http_proto::response_parser{ http_proto_ctx };
878+
879+
serializer.start(request);
880+
co_await http_io::async_write(stream, serializer);
881+
882+
parser.reset();
883+
parser.start();
884+
co_await http_io::async_read_header(stream, parser);
885+
886+
if(parser.get().status() != http_proto::status::ok)
887+
throw std::runtime_error{
888+
"Proxy server rejected the connection" };
889+
}
890+
723891
asio::awaitable<any_stream>
724892
connect(
725893
const po::variables_map& vm,
@@ -728,7 +896,6 @@ connect(
728896
urls::url_view url)
729897
{
730898
auto executor = co_await asio::this_coro::executor;
731-
auto resolver = asio::ip::tcp::resolver{ executor };
732899
auto stream = asio::ip::tcp::socket{ executor };
733900

734901
if(vm.count("proxy"))
@@ -738,63 +905,31 @@ connect(
738905
if(proxy_url.has_error())
739906
throw system_error{ proxy_url.error(), "Failed to parse proxy" };
740907

741-
if(proxy_url->scheme() != "http")
742-
throw std::runtime_error{ "only HTTP proxies are supported" };
743-
744-
// Connect to the HTTP proxy server
745-
auto rr = co_await resolver.async_resolve(
746-
proxy_url->host(), effective_port(proxy_url.value()));
747-
co_await asio::async_connect(stream, rr);
748-
908+
if(proxy_url->scheme() == "http")
749909
{
750-
using http_proto::field;
751-
auto request = http_proto::request{};
752-
auto host = std::string{ url.encoded_host() };
753-
754-
host.push_back(':');
755-
host.append(effective_port(url));
756-
757-
request.set_method(http_proto::method::connect);
758-
request.set_target(host);
759-
request.set(field::host, host);
760-
request.set(field::proxy_connection, "keep-alive");
761-
762-
if(vm.count("user-agent"))
763-
{
764-
request.set(
765-
field::user_agent,
766-
vm.at("user-agent").as<std::string>());
767-
}
768-
else
769-
{
770-
request.set(field::user_agent, "Boost.Http.Io");
771-
}
772-
773-
// TODO
774-
// request.set(field::proxy_authorization, "");
775-
776-
auto serializer = http_proto::serializer{ http_proto_ctx };
777-
serializer.start(request);
778-
co_await http_io::async_write(stream, serializer);
910+
co_await connect_http_proxy(
911+
vm, http_proto_ctx, stream, url, proxy_url.value());
779912
}
780-
913+
else if(proxy_url->scheme() == "socks5")
781914
{
782-
auto parser = http_proto::response_parser{ http_proto_ctx };
783-
parser.reset();
784-
parser.start();
785-
co_await http_io::async_read_header(stream, parser);
786-
if(parser.get().status() != http_proto::status::ok)
787-
throw std::runtime_error{
788-
"Proxy server rejected the connection" };
915+
co_await connect_socks5_proxy(
916+
stream, url, proxy_url.value());
917+
}
918+
else
919+
{
920+
throw std::runtime_error{
921+
"only HTTP and SOCKS5 proxies are supported" };
789922
}
790923
}
791924
else // no proxy
792925
{
793-
auto rr = co_await resolver.async_resolve(
926+
auto resolver = asio::ip::tcp::resolver{ executor };
927+
auto rresults = co_await resolver.async_resolve(
794928
url.host(), effective_port(url));
795-
co_await asio::async_connect(stream, rr);
929+
co_await asio::async_connect(stream, rresults);
796930
}
797931

932+
// TLS handshake
798933
if(url.scheme() == "https")
799934
{
800935
auto ssl_stream = ssl::stream<asio::ip::tcp::socket>{

0 commit comments

Comments
 (0)