From d8150feeaf08d8a186d3e11400e0360b102f1110 Mon Sep 17 00:00:00 2001 From: presidentua Date: Sun, 30 Jun 2013 17:53:19 +0300 Subject: [PATCH] add socks5 support --- .gitignore | 4 ++- README.md | 15 ++++++++++ src/ibrowse_http_client.erl | 8 +++++- src/ibrowser_socks5.erl | 57 +++++++++++++++++++++++++++++++++++++ 4 files changed, 82 insertions(+), 2 deletions(-) create mode 100644 src/ibrowser_socks5.erl diff --git a/.gitignore b/.gitignore index 6e8057b..4dc0202 100644 --- a/.gitignore +++ b/.gitignore @@ -5,4 +5,6 @@ ebin/ doc/*.html doc/*.css doc/*.png -doc/edoc-info \ No newline at end of file +doc/edoc-info +Emakefile +*.bat \ No newline at end of file diff --git a/README.md b/README.md index 2052ad7..0fcc243 100644 --- a/README.md +++ b/README.md @@ -27,6 +27,7 @@ ibrowse is a HTTP client written in erlang. * Asynchronous requests. Responses are streamed to a process * Basic authentication * Supports proxy authentication +* Supports socks5 * Can talk to secure webservers using SSL * *Any other features in the code not listed here :)* @@ -279,3 +280,17 @@ support this. Nor did www.google.com. But good old BBC supports this: {"Via","1.1 hatproxy01 (NetCache NetApp/5.6.2)"}], "TRACE / HTTP/1.1\r\nHost: www.bbc.co.uk\r\nConnection: keep-alive\r\nX-Forwarded-For: 172.24.28.29\r\nVia: 1.1 hatproxy01 (NetCache NetApp/5.6.2)\r\nCookie: BBC-UID=7452e...\r\n\r\n"} ``` + +A `GET` using a socks5: + +```erlang +ibrowse:send_req("http://google.com", [], get, [], + [{socks5_host, "127.0.0.1"}, + {socks5_port, 5335}]). + +ibrowse:send_req("http://google.com", [], get, [], + [{socks5_host, "127.0.0.1"}, + {socks5_port, 5335}, + {socks5_user, "user4321"}, + {socks5_pass, "pass7654"}]). +``` diff --git a/src/ibrowse_http_client.erl b/src/ibrowse_http_client.erl index b014e92..d6e4954 100644 --- a/src/ibrowse_http_client.erl +++ b/src/ibrowse_http_client.erl @@ -502,7 +502,13 @@ do_connect(Host, Port, Options, #state{is_ssl = true, Timeout) -> ssl:connect(Host, Port, get_sock_options(Host, Options, SSLOptions), Timeout); do_connect(Host, Port, Options, _State, Timeout) -> - gen_tcp:connect(Host, Port, get_sock_options(Host, Options, []), Timeout). + Socks5Host = get_value(socks5_host, Options, undefined), + case Socks5Host of + undefined -> + gen_tcp:connect(Host, Port, get_sock_options(Host, Options, []), Timeout); + _ -> + catch ibrowser_socks5:connect(Host, Port, Options) + end. get_sock_options(Host, Options, SSLOptions) -> Caller_socket_options = get_value(socket_options, Options, []), diff --git a/src/ibrowser_socks5.erl b/src/ibrowser_socks5.erl new file mode 100644 index 0000000..5325048 --- /dev/null +++ b/src/ibrowser_socks5.erl @@ -0,0 +1,57 @@ +-module(ibrowser_socks5). + +-include_lib("kernel/src/inet_dns.hrl"). + +-export([connect/3]). + +-define(TIMEOUT, 2000). + +-define(SOCKS5, 5). +-define(AUTH_METHOD_NO, 0). +-define(AUTH_METHOD_USERPASS, 2). +-define(ADDRESS_TYPE_IP4, 1). +-define(COMMAND_TYPE_TCPIP_STREAM, 1). +-define(RESERVER, 0). +-define(STATUS_GRANTED, 0). + +-define(DNS_IP, {8,8,8,8}). + +connect(Host, Port, Options) -> + Socks5Host = proplists:get_value(socks5_host, Options), + Socks5Port = proplists:get_value(socks5_port, Options), + + {ok, Socket} = gen_tcp:connect(Socks5Host, Socks5Port, [binary, {packet, 0}, {keepalive, true}, {active, false}]), + + case proplists:get_value(socks5_user, Options, undefined) of + undefined -> + ok = gen_tcp:send(Socket, <>), + {ok, <>} = gen_tcp:recv(Socket, 2, ?TIMEOUT); + _Else -> + Socks5User = list_to_binary(proplists:get_value(socks5_user, Options)), + Socks5Pass = list_to_binary(proplists:get_value(socks5_pass, Options)), + + ok = gen_tcp:send(Socket, <>), + {ok, <>} = gen_tcp:recv(Socket, 2, ?TIMEOUT), + + UserLength = byte_size(Socks5User), + + ok = gen_tcp:send(Socket, << 1, UserLength >>), + ok = gen_tcp:send(Socket, Socks5User), + PassLength = byte_size(Socks5Pass), + ok = gen_tcp:send(Socket, << PassLength >>), + ok = gen_tcp:send(Socket, Socks5Pass), + {ok, <<1, 0>>} = gen_tcp:recv(Socket, 2, ?TIMEOUT) + end, + + {IP1,IP2,IP3,IP4} = case inet_parse:address(Host) of + {ok, IP} -> + IP; + _Other -> + {ok, NsData} = inet_res:nslookup(Host, in, a, [{?DNS_IP, 53}]), + [Addr | _NewAnList] = [D || #dns_rr{data=D, type=a} <- NsData#dns_rec.anlist], + Addr + end, + + ok = gen_tcp:send(Socket, <>), + {ok, << ?SOCKS5, ?STATUS_GRANTED, ?RESERVER, ?ADDRESS_TYPE_IP4, IP1, IP2, IP3, IP4, Port:16 >>} = gen_tcp:recv(Socket, 10, ?TIMEOUT), + {ok, Socket}.