|
|
@ -0,0 +1,108 @@ |
|
|
|
% Licensed under the Apache License, Version 2.0 (the "License"); you may not |
|
|
|
% use this file except in compliance with the License. You may obtain a copy of |
|
|
|
% the License at |
|
|
|
% |
|
|
|
% http://www.apache.org/licenses/LICENSE-2.0 |
|
|
|
% |
|
|
|
% Unless required by applicable law or agreed to in writing, software |
|
|
|
% distributed under the License is distributed on an "AS IS" BASIS, WITHOUT |
|
|
|
% WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the |
|
|
|
% License for the specific language governing permissions and limitations under |
|
|
|
% the License. |
|
|
|
|
|
|
|
-module(ibrowse_socks5). |
|
|
|
|
|
|
|
-define(VERSION, 5). |
|
|
|
-define(CONNECT, 1). |
|
|
|
|
|
|
|
-define(NO_AUTH, 0). |
|
|
|
-define(USERPASS, 2). |
|
|
|
-define(UNACCEPTABLE, 16#FF). |
|
|
|
-define(RESERVED, 0). |
|
|
|
|
|
|
|
-define(ATYP_IPV4, 1). |
|
|
|
-define(ATYP_DOMAINNAME, 3). |
|
|
|
-define(ATYP_IPV6, 4). |
|
|
|
|
|
|
|
-define(SUCCEEDED, 0). |
|
|
|
|
|
|
|
-export([connect/5]). |
|
|
|
|
|
|
|
-import(ibrowse_lib, [get_value/2, get_value/3]). |
|
|
|
|
|
|
|
connect(Host, Port, Options, SockOptions, Timeout) -> |
|
|
|
Socks5Host = get_value(socks5_host, Options), |
|
|
|
Socks5Port = get_value(socks5_port, Options), |
|
|
|
case gen_tcp:connect(Socks5Host, Socks5Port, SockOptions, Timeout) of |
|
|
|
{ok, Socket} -> |
|
|
|
case handshake(Socket, Options) of |
|
|
|
ok -> |
|
|
|
case connect(Host, Port, Socket) of |
|
|
|
ok -> |
|
|
|
{ok, Socket}; |
|
|
|
Else -> |
|
|
|
gen_tcp:close(Socket), |
|
|
|
Else |
|
|
|
end; |
|
|
|
Else -> |
|
|
|
gen_tcp:close(Socket), |
|
|
|
Else |
|
|
|
end; |
|
|
|
Else -> |
|
|
|
Else |
|
|
|
end. |
|
|
|
|
|
|
|
handshake(Socket, Options) when is_port(Socket) -> |
|
|
|
{Handshake, Success} = case get_value(socks5_user, Options, <<>>) of |
|
|
|
<<>> -> |
|
|
|
{<<?VERSION, 1, ?NO_AUTH>>, ?NO_AUTH}; |
|
|
|
User -> |
|
|
|
Password = get_value(socks5_password, Options, <<>>), |
|
|
|
{<<?VERSION, 1, ?USERPASS, (byte_size(User)), User, |
|
|
|
(byte_size(Password)), Password>>, ?USERPASS} |
|
|
|
end, |
|
|
|
ok = gen_tcp:send(Socket, Handshake), |
|
|
|
case gen_tcp:recv(Socket, 0) of |
|
|
|
{ok, <<?VERSION, Success>>} -> |
|
|
|
ok; |
|
|
|
{ok, <<?VERSION, ?UNACCEPTABLE>>} -> |
|
|
|
{error, unacceptable}; |
|
|
|
{error, Reason} -> |
|
|
|
{error, Reason} |
|
|
|
end. |
|
|
|
|
|
|
|
connect(Host, Port, Via) when is_list(Host) -> |
|
|
|
connect(list_to_binary(Host), Port, Via); |
|
|
|
connect(Host, Port, Via) when is_binary(Host), is_integer(Port), |
|
|
|
is_port(Via) -> |
|
|
|
{AddressType, Address} = case inet:parse_address(binary_to_list(Host)) of |
|
|
|
{ok, {IP1, IP2, IP3, IP4}} -> |
|
|
|
{?ATYP_IPV4, <<IP1,IP2,IP3,IP4>>}; |
|
|
|
{ok, {IP1, IP2, IP3, IP4, IP5, IP6, IP7, IP8}} -> |
|
|
|
{?ATYP_IPV6, <<IP1,IP2,IP3,IP4,IP5,IP6,IP7,IP8>>}; |
|
|
|
_ -> |
|
|
|
HostLength = byte_size(Host), |
|
|
|
{?ATYP_DOMAINNAME, <<HostLength,Host/binary>>} |
|
|
|
end, |
|
|
|
ok = gen_tcp:send(Via, |
|
|
|
<<?VERSION, ?CONNECT, ?RESERVED, |
|
|
|
AddressType, Address/binary, |
|
|
|
(Port):16>>), |
|
|
|
case gen_tcp:recv(Via, 0) of |
|
|
|
{ok, <<?VERSION, ?SUCCEEDED, ?RESERVED, _/binary>>} -> |
|
|
|
ok; |
|
|
|
{ok, <<?VERSION, Rep, ?RESERVED, _/binary>>} -> |
|
|
|
{error, rep(Rep)}; |
|
|
|
{error, Reason} -> |
|
|
|
{error, Reason} |
|
|
|
end. |
|
|
|
|
|
|
|
rep(0) -> succeeded; |
|
|
|
rep(1) -> server_fail; |
|
|
|
rep(2) -> disallowed_by_ruleset; |
|
|
|
rep(3) -> network_unreachable; |
|
|
|
rep(4) -> host_unreachable; |
|
|
|
rep(5) -> connection_refused; |
|
|
|
rep(6) -> ttl_expired; |
|
|
|
rep(7) -> command_not_supported; |
|
|
|
rep(8) -> address_type_not_supported. |