Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with HTTPS or Subversion.

Download ZIP

Loading…

Ipv6 support #34

Merged
merged 3 commits into from

2 participants

@fdmanana

Hi Chandru,

Haven't tested it yet, but I think this is a useful addition.

Have you already tried using ibrowse against an IPv6-only host? Sometimes the user may be clueless about whether the remote host is IPv6-only or not, so detecting that and adding the int6 parameter if necessary seems reasonable to do.

regards

@cmullaparthi
@fdmanana

One last finding, is that ibrowse_lib:parse_url/1 doesn't parse IPv6 address - once it finds ":" it assumes that what follows is a port number.
I haven't had the time yet to change it, but in the meanwhile I found some code that does the parsing of IPv6 addresses:

http://stuff.mit.edu/afs/athena/software/erlang_vR12B-3/lib/erlang/lib/kernel-2.12.3/src/inet_parse.erl

It's just a matter of integrating it :)

@fdmanana

Ah and here is an interesting discussion about URL parsing with IPv6 address literals and port numbers:

http://lists.w3.org/Archives/Public/www-talk/1996JulAug/0088.html

Will try it when I get the time, possibly not anytime soon.

@cmullaparthi
fdmanana added some commits
@fdmanana fdmanana ibrowse_lib:parse_url/1 now accepts IPv6 literals as hostnames
As specified in the following RFC:  http://www.ietf.org/rfc/rfc2732.txt
3367f00
@fdmanana fdmanana More reliable IPv6 detection
inet:gethostbyname/1 might fail for IPv6 literals, therefore check first
if host is an IPv6 address literal.
f5640f5
@fdmanana

Hi again Chandru,

I just added support for IPv6 literals in hostnames when using ibrowse_lib:parse_url/1

Commits

fdmanana@3367f00

and

fdmanana@f5640f5

Note that I added a new eunit test for ibrowse_lib:parse_url/1, somehow it feels more natural (and standard) than adding it to ibrowse_test.erl.

Let me know what you think.

cheers

@cmullaparthi cmullaparthi merged commit f5640f5 into from
@PKRoma PKRoma referenced this pull request from a commit in PKRoma/couchdb
@fdmanana fdmanana Add support for replication over IPv6 (part 1)
This change upgrades ibrowse to version 2.2.0. This version adds support
for IPv6 (cmullaparthi/ibrowse#34).
This is part of COUCHDB-665.



git-svn-id: https://svn.apache.org/repos/asf/couchdb/trunk@1091709 13f79535-47bb-0310-9956-ffa450edef68
1ca42d2
@asfgit asfgit referenced this pull request from a commit in apache/couchdb-ibrowse
@fdmanana fdmanana Add support for replication over IPv6 (part 1)
This change upgrades ibrowse to version 2.2.0. This version adds support
for IPv6 (cmullaparthi/ibrowse#34).
This is part of COUCHDB-665.



git-svn-id: https://svn.apache.org/repos/asf/couchdb/trunk@1091709 13f79535-47bb-0310-9956-ffa450edef68
ca1ed96
@ksquaredkey ksquaredkey referenced this pull request from a commit
Commit has since been removed from the repository and is no longer available.
@ksquaredkey ksquaredkey referenced this pull request from a commit
Commit has since been removed from the repository and is no longer available.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Commits on Mar 16, 2011
  1. @fdmanana
Commits on Apr 12, 2011
  1. @fdmanana
  2. @fdmanana

    More reliable IPv6 detection

    fdmanana authored
    inet:gethostbyname/1 might fail for IPv6 literals, therefore check first
    if host is an IPv6 address literal.
This page is out of date. Refresh to see the latest.
View
1  Makefile
@@ -11,6 +11,7 @@ install: all
cp -r ebin $(DESTDIR)/lib/ibrowse-$(IBROWSE_VSN)/
test: all
+ ./rebar eunit
(cd test; make)
erl -noshell -pa ebin -pa test -s ibrowse -s ibrowse_test unit_tests \
-s ibrowse_test verify_chunked_streaming \
View
1  rebar.config
@@ -1 +1,2 @@
{erl_opts, [debug_info, warn_unused_vars, nowarn_shadow_vars, warn_unused_import]}.
+{eunit_opts, [verbose]}.
View
11 src/ibrowse.hrl
@@ -1,7 +1,16 @@
-ifndef(IBROWSE_HRL).
-define(IBROWSE_HRL, "ibrowse.hrl").
--record(url, {abspath, host, port, username, password, path, protocol}).
+-record(url, {
+ abspath,
+ host,
+ port,
+ username,
+ password,
+ path,
+ protocol,
+ host_type % 'hostname', 'ipv4_address' or 'ipv6_address'
+}).
-record(lb_pid, {host_port, pid}).
View
30 src/ibrowse_http_client.erl
@@ -35,6 +35,7 @@
]).
-include("ibrowse.hrl").
+-include_lib("kernel/include/inet.hrl").
-record(state, {host, port, connect_timeout,
inactivity_timer_ref,
@@ -489,13 +490,19 @@ do_connect(Host, Port, Options, #state{is_ssl = true,
use_proxy = false,
ssl_options = SSLOptions},
Timeout) ->
- ssl:connect(Host, Port, get_sock_options(Options, SSLOptions), 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(Options, []), Timeout).
+ gen_tcp:connect(Host, Port, get_sock_options(Host, Options, []), Timeout).
-get_sock_options(Options, SSLOptions) ->
+get_sock_options(Host, Options, SSLOptions) ->
Caller_socket_options = get_value(socket_options, Options, []),
- Other_sock_options = filter_sock_options(SSLOptions ++ Caller_socket_options),
+ Ipv6Options = case is_ipv6_host(Host) of
+ true ->
+ [inet6];
+ false ->
+ []
+ end,
+ Other_sock_options = filter_sock_options(SSLOptions ++ Caller_socket_options ++ Ipv6Options),
case lists:keysearch(nodelay, 1, Other_sock_options) of
false ->
[{nodelay, true}, binary, {active, false} | Other_sock_options];
@@ -503,6 +510,21 @@ get_sock_options(Options, SSLOptions) ->
[binary, {active, false} | Other_sock_options]
end.
+is_ipv6_host(Host) ->
+ case inet_parse:address(Host) of
+ {ok, {_, _, _, _, _, _, _, _}} ->
+ true;
+ {ok, {_, _, _, _}} ->
+ false;
+ _ ->
+ case inet:gethostbyname(Host) of
+ {ok, #hostent{h_addrtype = inet6}} ->
+ true;
+ _ ->
+ false
+ end
+ end.
+
%% We don't want the caller to specify certain options
filter_sock_options(Opts) ->
lists:filter(fun({active, _}) ->
View
51 src/ibrowse_lib.erl
@@ -180,7 +180,19 @@ get_value(Tag, TVL) ->
V.
parse_url(Url) ->
- parse_url(Url, get_protocol, #url{abspath=Url}, []).
+ case parse_url(Url, get_protocol, #url{abspath=Url}, []) of
+ #url{host_type = undefined, host = Host} = UrlRec ->
+ case inet_parse:address(Host) of
+ {ok, {_, _, _, _, _, _, _, _}} ->
+ UrlRec#url{host_type = ipv6_address};
+ {ok, {_, _, _, _}} ->
+ UrlRec#url{host_type = ipv4_address};
+ _ ->
+ UrlRec#url{host_type = hostname}
+ end;
+ Else ->
+ Else
+ end.
parse_url([$:, $/, $/ | _], get_protocol, Url, []) ->
{invalid_uri_1, Url};
@@ -215,6 +227,21 @@ parse_url([$@ | T], get_username, Url, TmpAcc) ->
Url#url{username = lists:reverse(TmpAcc),
password = ""},
[]);
+parse_url([$[ | T], get_username, Url, []) ->
+ % IPv6 address literals are enclosed by square brackets:
+ % http://www.ietf.org/rfc/rfc2732.txt
+ parse_url(T, get_ipv6_address, Url#url{host_type = ipv6_address}, []);
+parse_url([$[ | T], get_username, _Url, TmpAcc) ->
+ {error, {invalid_username_or_host, lists:reverse(TmpAcc) ++ "[" ++ T}};
+parse_url([$[ | _], get_password, _Url, []) ->
+ {error, missing_password};
+parse_url([$[ | T], get_password, Url, TmpAcc) ->
+ % IPv6 address literals are enclosed by square brackets:
+ % http://www.ietf.org/rfc/rfc2732.txt
+ parse_url(T, get_ipv6_address,
+ Url#url{host_type = ipv6_address,
+ password = lists:reverse(TmpAcc)},
+ []);
parse_url([$@ | T], get_password, Url, TmpAcc) ->
parse_url(T, get_host,
Url#url{password = lists:reverse(TmpAcc)},
@@ -236,6 +263,28 @@ parse_url([H | T], get_password, Url, TmpAcc) when H == $/;
username = undefined,
password = undefined,
path = Path};
+parse_url([$] | T], get_ipv6_address, #url{protocol = Prot} = Url, TmpAcc) ->
+ Addr = lists:reverse(TmpAcc),
+ case inet_parse:address(Addr) of
+ {ok, {_, _, _, _, _, _, _, _}} ->
+ Url2 = Url#url{host = Addr, port = default_port(Prot)},
+ case T of
+ [$: | T2] ->
+ parse_url(T2, get_port, Url2, []);
+ [$/ | T2] ->
+ Url2#url{path = [$/ | T2]};
+ [$? | T2] ->
+ Url2#url{path = [$/, $? | T2]};
+ [] ->
+ Url2#url{path = "/"};
+ _ ->
+ {error, {invalid_host, "[" ++ Addr ++ "]" ++ T}}
+ end;
+ _ ->
+ {error, {invalid_ipv6_address, Addr}}
+ end;
+parse_url([$[ | T], get_host, #url{} = Url, []) ->
+ parse_url(T, get_ipv6_address, Url#url{host_type = ipv6_address}, []);
parse_url([$: | T], get_host, #url{} = Url, TmpAcc) ->
parse_url(T, get_port,
Url#url{host = lists:reverse(TmpAcc)},
View
135 test/ibrowse_lib_tests.erl
@@ -0,0 +1,135 @@
+%%% File : ibrowse_lib.erl
+%%% Authors : Chandrashekhar Mullaparthi <chandrashekhar.mullaparthi@t-mobile.co.uk>,
+%%% Filipe David Manana <fdmanana@apache.org>
+%%% Description : Tests for the module ibrowse_lib.erl
+%%% Created : 12 April 2011 by Filipe David Manana <fdmanana@apache.org>
+
+-module(ibrowse_lib_tests).
+-include_lib("eunit/include/eunit.hrl").
+-include("src/ibrowse.hrl").
+
+
+parse_urls_test_() ->
+ {timeout, 60, [fun parse_urls/0]}.
+
+
+parse_urls() ->
+ ?assertMatch(#url{
+ abspath = "http://localhost",
+ host = "localhost",
+ host_type = hostname,
+ port = 80,
+ path = "/",
+ username = undefined,
+ password = undefined,
+ protocol = http
+ },
+ ibrowse_lib:parse_url("http://localhost")),
+ ?assertMatch(#url{
+ abspath = "http://localhost:80/",
+ host = "localhost",
+ host_type = hostname,
+ port = 80,
+ path = "/",
+ username = undefined,
+ password = undefined,
+ protocol = http
+ },
+ ibrowse_lib:parse_url("http://localhost:80/")),
+ ?assertMatch(#url{
+ abspath = "http://127.0.0.1:8000/",
+ host = "127.0.0.1",
+ host_type = ipv4_address,
+ port = 8000,
+ path = "/",
+ username = undefined,
+ password = undefined,
+ protocol = http
+ },
+ ibrowse_lib:parse_url("http://127.0.0.1:8000/")),
+ ?assertMatch(#url{
+ abspath = "https://foo:bar@127.0.0.1:8000/test",
+ host = "127.0.0.1",
+ host_type = ipv4_address,
+ port = 8000,
+ path = "/test",
+ username = "foo",
+ password = "bar",
+ protocol = https
+ },
+ ibrowse_lib:parse_url("https://foo:bar@127.0.0.1:8000/test")),
+ ?assertMatch(#url{
+ abspath = "https://[::1]",
+ host = "::1",
+ host_type = ipv6_address,
+ port = 443,
+ path = "/",
+ username = undefined,
+ password = undefined,
+ protocol = https
+ },
+ ibrowse_lib:parse_url("https://[::1]")),
+ ?assertMatch(#url{
+ abspath = "http://[::1]:8080",
+ host = "::1",
+ host_type = ipv6_address,
+ port = 8080,
+ path = "/",
+ username = undefined,
+ password = undefined,
+ protocol = http
+ },
+ ibrowse_lib:parse_url("http://[::1]:8080")),
+ ?assertMatch(#url{
+ abspath = "http://[FEDC:BA98:7654:3210:FEDC:BA98:7654:3210]:8081/index.html",
+ host = "FEDC:BA98:7654:3210:FEDC:BA98:7654:3210",
+ host_type = ipv6_address,
+ port = 8081,
+ path = "/index.html",
+ username = undefined,
+ password = undefined,
+ protocol = http
+ },
+ ibrowse_lib:parse_url("http://[FEDC:BA98:7654:3210:FEDC:BA98:7654:3210]:8081/index.html")),
+ ?assertMatch(#url{
+ abspath = "http://[1080:0:0:0:8:800:200C:417A]/foo/bar",
+ host = "1080:0:0:0:8:800:200C:417A",
+ host_type = ipv6_address,
+ port = 80,
+ path = "/foo/bar",
+ username = undefined,
+ password = undefined,
+ protocol = http
+ },
+ ibrowse_lib:parse_url("http://[1080:0:0:0:8:800:200C:417A]/foo/bar")),
+ ?assertMatch(#url{
+ abspath = "http://[1080:0:0:0:8:800:200C:417A]:8080/foo/bar",
+ host = "1080:0:0:0:8:800:200C:417A",
+ host_type = ipv6_address,
+ port = 8080,
+ path = "/foo/bar",
+ username = undefined,
+ password = undefined,
+ protocol = http
+ },
+ ibrowse_lib:parse_url("http://[1080:0:0:0:8:800:200C:417A]:8080/foo/bar")),
+ ?assertMatch(#url{
+ abspath = "http://[::192.9.5.5]:6000/foo?q=bar",
+ host = "::192.9.5.5",
+ host_type = ipv6_address,
+ port = 6000,
+ path = "/foo?q=bar",
+ username = undefined,
+ password = undefined,
+ protocol = http
+ },
+ ibrowse_lib:parse_url("http://[::192.9.5.5]:6000/foo?q=bar")),
+ ?assertMatch({error, {invalid_ipv6_address, ":1080:0:0:0:8:800:200C:417A:"}},
+ ibrowse_lib:parse_url("http://[:1080:0:0:0:8:800:200C:417A:]:6000/foo?q=bar")),
+ ?assertMatch({error, {invalid_ipv6_address, "12::z"}},
+ ibrowse_lib:parse_url("http://[12::z]")),
+ ?assertMatch({error, {invalid_username_or_host, _}},
+ ibrowse_lib:parse_url("http://foo[1080:0:0:0:8:800:200C:417A]:6000")),
+ ?assertMatch({error, missing_password},
+ ibrowse_lib:parse_url("http://foo:[1080:0:0:0:8:800:200C:417A]:6000")),
+ ok.
Something went wrong with that request. Please try again.