%%
%% %CopyrightBegin%
%%
%% Copyright Ericsson AB 1997-2018. All Rights Reserved.
%%
%% 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.
%%
%% %CopyrightEnd%
%%
-module(inet_SUITE).
-include_lib("common_test/include/ct.hrl").
-include_lib("kernel/include/inet.hrl").
-include_lib("kernel/src/inet_res.hrl").
-include_lib("kernel/src/inet_dns.hrl").
-export([all/0, suite/0,groups/0,init_per_suite/1, end_per_suite/1,
init_per_group/2,end_per_group/2,
t_gethostbyaddr/0, t_gethostbyaddr/1,
t_getaddr/0, t_getaddr/1,
t_gethostbyname/0, t_gethostbyname/1,
t_gethostbyaddr_v6/0, t_gethostbyaddr_v6/1,
t_getaddr_v6/0, t_getaddr_v6/1,
t_gethostbyname_v6/0, t_gethostbyname_v6/1,
ipv4_to_ipv6/0, ipv4_to_ipv6/1,
host_and_addr/0, host_and_addr/1,
t_gethostnative/1,
gethostnative_parallell/1, cname_loop/1, missing_hosts_reload/1,
gethostnative_soft_restart/0, gethostnative_soft_restart/1,
gethostnative_debug_level/0, gethostnative_debug_level/1,
lookup_bad_search_option/1,
getif/1,
getif_ifr_name_overflow/1,getservbyname_overflow/1, getifaddrs/1,
parse_strict_address/1, ipv4_mapped_ipv6_address/1,
simple_netns/1, simple_netns_open/1,
simple_bind_to_device/1, simple_bind_to_device_open/1]).
-export([get_hosts/1, get_ipv6_hosts/1, parse_hosts/1, parse_address/1,
kill_gethost/0, parallell_gethost/0, test_netns/0]).
-export([init_per_testcase/2, end_per_testcase/2]).
suite() ->
[{ct_hooks,[ts_install_cth]},
{timetrap,{minutes,1}}].
all() ->
[t_gethostbyaddr, t_gethostbyname, t_getaddr,
t_gethostbyaddr_v6, t_gethostbyname_v6, t_getaddr_v6,
ipv4_to_ipv6, host_and_addr, {group, parse},
t_gethostnative, gethostnative_parallell, cname_loop, missing_hosts_reload,
gethostnative_debug_level, gethostnative_soft_restart,
lookup_bad_search_option,
getif, getif_ifr_name_overflow, getservbyname_overflow,
getifaddrs, parse_strict_address, simple_netns, simple_netns_open,
simple_bind_to_device, simple_bind_to_device_open].
groups() ->
[{parse, [], [parse_hosts, parse_address]}].
%% Required configuaration
required(v4) ->
[{require, test_host_ipv4_only},
{require, test_dummy_host}];
required(v6) ->
[{require, test_host_ipv6_only},
{require, test_dummy_ipv6_host}];
required(hosts) ->
case os:type() of
{OS, _} when OS =:= win32 ->
[{require, hardcoded_hosts},
{require, hardcoded_ipv6_hosts}];
_Else ->
[{require, test_hosts}]
end.
init_per_suite(Config) ->
Config.
end_per_suite(_Config) ->
ok.
init_per_group(_GroupName, Config) ->
Config.
end_per_group(_GroupName, Config) ->
Config.
init_per_testcase(lookup_bad_search_option, Config) ->
Db = inet_db,
Key = res_lookup,
%% The bad option can not enter through inet_db:set_lookup/1,
%% but through e.g .inetrc.
Prev = ets:lookup(Db, Key),
ets:delete(Db, Key),
ets:insert(Db, {Key,[lookup_bad_search_option]}),
io:format("Misconfigured resolver lookup order", []),
[{Key,Prev}|Config];
init_per_testcase(_Func, Config) ->
Config.
end_per_testcase(lookup_bad_search_option, Config) ->
Db = inet_db,
Key = res_lookup,
Prev = proplists:get_value(Key, Config),
ets:delete(Db, Key),
ets:insert(Db, Prev),
io:format("Restored resolver lookup order", []);
end_per_testcase(_Func, _Config) ->
ok.
t_gethostbyaddr() ->
required(v4).
%% Test the inet:gethostbyaddr/1 function.
t_gethostbyaddr(Config) when is_list(Config) ->
{Name,FullName,IPStr,{A,B,C,D}=IP,Aliases,_,_} = ct:get_config(test_host_ipv4_only),
Rname = integer_to_list(D) ++ "." ++
integer_to_list(C) ++ "." ++
integer_to_list(B) ++ "." ++
integer_to_list(A) ++ ".in-addr.arpa",
{ok,HEnt} = inet:gethostbyaddr(IPStr),
{ok,HEnt} = inet:gethostbyaddr(IP),
{error,Error} = inet:gethostbyaddr(Name),
ok = io:format("Failure reason: ~p: ~s", [error,inet:format_error(Error)]),
HEnt_ = HEnt#hostent{h_addrtype = inet,
h_length = 4,
h_addr_list = [IP]},
HEnt_ = HEnt,
case {os:type(),os:version()} of
{{unix,freebsd},{5,0,0}} ->
%% The alias list seems to be buggy in FreeBSD 5.0.0.
check_elems([{HEnt#hostent.h_name,[Name,FullName]}]),
io:format("Buggy alias list: ~p", [HEnt#hostent.h_aliases]),
ok;
_ ->
io:format("alias list: ~p", [HEnt#hostent.h_aliases]),
io:format(
"check alias list: ~p", [[Aliases,tl(Aliases),[Rname]]]),
io:format("name: ~p", [HEnt#hostent.h_name]),
io:format("check name: ~p", [[Name,FullName]]),
check_elems(
[{HEnt#hostent.h_name,[Name,FullName]},
{HEnt#hostent.h_aliases,[[],Aliases,tl(Aliases),[Rname]]}])
end,
{_DName, _DFullName, DIPStr, DIP, _, _, _} = ct:get_config(test_dummy_host),
{error,nxdomain} = inet:gethostbyaddr(DIPStr),
{error,nxdomain} = inet:gethostbyaddr(DIP),
ok.
t_gethostbyaddr_v6() -> required(v6).
%% Test the inet:gethostbyaddr/1 inet6 function.
t_gethostbyaddr_v6(Config) when is_list(Config) ->
{Name6, FullName6, IPStr6, IP6, Aliases6} =
ct:get_config(test_host_ipv6_only),
case inet:gethostbyaddr(IPStr6) of
%% Even if IPv6 is not supported, the native resolver may succeed
%% looking up the host. DNS lookup will probably fail.
{error,nxdomain} ->
{skip, "IPv6 test fails! IPv6 not supported on this host!?"};
{ok,HEnt6} ->
{ok,HEnt6} = inet:gethostbyaddr(IP6),
{error,Error6} = inet:gethostbyaddr(Name6),
ok = io:format("Failure reason: ~p: ~s",
[Error6, inet:format_error(Error6)]),
HEnt6_ = HEnt6#hostent{h_addrtype = inet6,
h_length = 16,
h_addr_list = [IP6]},
HEnt6_ = HEnt6,
check_elems(
[{HEnt6#hostent.h_name,[Name6,FullName6]},
{HEnt6#hostent.h_aliases,[[],Aliases6,tl(Aliases6)]}]),
{_DName6, _DFullName6, DIPStr6, DIP6, _} =
ct:get_config(test_dummy_ipv6_host),
{error,nxdomain} = inet:gethostbyaddr(DIPStr6),
{error,nxdomain} = inet:gethostbyaddr(DIP6),
ok
end.
t_gethostbyname() -> required(v4).
%% Test the inet:gethostbyname/1 function.
t_gethostbyname(Config) when is_list(Config) ->
{Name,FullName,IPStr,IP,Aliases,IP_46_Str,_} =
ct:get_config(test_host_ipv4_only),
{ok,_} = inet:gethostbyname(IPStr),
{ok,HEnt} = inet:gethostbyname(Name),
{ok,HEnt} = inet:gethostbyname(list_to_atom(Name)),
HEnt_ = HEnt#hostent{h_addrtype = inet,
h_length = 4,
h_addr_list = [IP]},
HEnt_ = HEnt,
check_elems([{HEnt#hostent.h_name,[Name,FullName]},
{HEnt#hostent.h_aliases,[[],Aliases,tl(Aliases)]}]),
{ok,HEntF} = inet:gethostbyname(FullName),
HEntF_ = HEntF#hostent{h_name = FullName,
h_addrtype = inet,
h_length = 4,
h_addr_list = [IP]},
HEntF_ = HEntF,
check_elems([{HEnt#hostent.h_aliases,[[],Aliases,tl(Aliases)]}]),
%%
FullNameU = toupper(FullName),
{ok,HEntU} = inet:gethostbyname(FullNameU),
FullNameU = toupper(HEntU#hostent.h_name),
#hostent{
h_addrtype = inet,
h_length = 4,
h_addr_list = [IP]} = HEntU,
check_elems(
[{[toupper(H) || H <- HEntU#hostent.h_aliases],
[[],[toupper(A) || A <- Aliases]]}]),
{DName, _DFullName, _DIPStr, _DIP, _, _, _} =
ct:get_config(test_dummy_host),
{error,nxdomain} = inet:gethostbyname(DName),
{error,nxdomain} = inet:gethostbyname(IP_46_Str),
ok.
t_gethostbyname_v6() -> required(v6).
%% Test the inet:gethostbyname/1 inet6 function.
t_gethostbyname_v6(Config) when is_list(Config) ->
{Name, FullName, IPStr, IP, Aliases} =
ct:get_config(test_host_ipv6_only),
case inet:gethostbyname(Name, inet6) of
{ok,HEnt} ->
{ok,_} = inet:gethostbyname(IPStr, inet6),
{ok,HEnt} = inet:gethostbyname(list_to_atom(Name), inet6),
case HEnt#hostent.h_addr_list of
[IP] -> % IPv6 address
#hostent{h_addrtype = inet6,
h_length = 16} = HEnt,
check_elems(
[{HEnt#hostent.h_name,[Name,FullName]},
{HEnt#hostent.h_aliases,[[],Aliases,tl(Aliases)]}]);
[IP46] -> % IPv4 compatible address
{ok,HEnt4} = inet:gethostbyname(Name, inet),
#hostent{h_addrtype = inet,
h_length = 4,
h_addr_list = [IP4]} = HEnt4,
{ok,IP46} =
inet_parse:ipv6_address(
"::ffff:" ++ inet:ntoa(IP4)),
check_elems(
[{HEnt#hostent.h_name,[Name,FullName]}])
end,
{ok,HEntF} = inet:gethostbyname(FullName, inet6),
case HEntF#hostent.h_addr_list of
[IP] -> % IPv6 address
#hostent{h_name = FullName,
h_addrtype = inet6,
h_length = 16} = HEntF,
check_elems(
[{HEnt#hostent.h_aliases,[[],Aliases,tl(Aliases)]}]);
[IP46F] -> % IPv4 compatible address
{ok,HEnt4F} = inet:gethostbyname(FullName, inet),
#hostent{h_addrtype = inet,
h_length = 4,
h_addr_list = [IP4F]} = HEnt4F,
{ok,IP46F} =
inet_parse:ipv6_address(
"::ffff:" ++ inet:ntoa(IP4F)),
check_elems(
[{HEntF#hostent.h_name,[Name,FullName]}])
end;
_ ->
{skip, "IPv6 is not supported on this host"}
end.
check_elems([{Val,Tests} | Elems]) ->
check_elem(Val, Tests, Tests),
check_elems(Elems);
check_elems([]) -> ok.
check_elem(Val, [Val|_], _) -> ok;
check_elem(Val, [_|Tests], Tests0) ->
check_elem(Val, Tests, Tests0);
check_elem(Val, [], Tests0) ->
ct:fail({no_match,Val,Tests0}).
t_getaddr() -> required(v4).
%% Test the inet:getaddr/2 function.
t_getaddr(Config) when is_list(Config) ->
{Name,FullName,IPStr,IP,_,IP_46_Str,IP46} =
ct:get_config(test_host_ipv4_only),
{ok,IP} = inet:getaddr(list_to_atom(Name), inet),
{ok,IP} = inet:getaddr(Name, inet),
{ok,IP} = inet:getaddr(FullName, inet),
{ok,IP} = inet:getaddr(IP, inet),
{ok,IP} = inet:getaddr(IPStr, inet),
{error,nxdomain} = inet:getaddr(IP_46_Str, inet),
{error,eafnosupport} = inet:getaddr(IP46, inet),
{DName, DFullName, DIPStr, DIP, _, _, _} = ct:get_config(test_dummy_host),
{error,nxdomain} = inet:getaddr(DName, inet),
{error,nxdomain} = inet:getaddr(DFullName, inet),
{ok,DIP} = inet:getaddr(DIPStr, inet),
{ok,DIP} = inet:getaddr(DIP, inet),
ok.
t_getaddr_v6() -> required(v4) ++ required(v6).
%% Test the inet:getaddr/2 function.
t_getaddr_v6(Config) when is_list(Config) ->
{Name,FullName,IPStr,IP,_} =
ct:get_config(test_host_ipv6_only),
case inet:getaddr(Name, inet6) of
{ok,Addr} ->
IP = Addr,
{ok,IP} = inet:getaddr(toupper(Name), inet6),
{ok,IP} = inet:getaddr(list_to_atom(Name), inet6),
{ok,IP} = inet:getaddr(list_to_atom(toupper(Name)), inet6),
{ok,IP} = inet:getaddr(FullName, inet6),
{ok,IP} = inet:getaddr(toupper(FullName), inet6),
{ok,IP} = inet:getaddr(IP, inet6),
{ok,IP} = inet:getaddr(IPStr, inet6),
%%
{DName,DFullName,DIPStr,DIP,_} =
ct:get_config(test_dummy_ipv6_host),
{error,nxdomain} = inet:getaddr(DName, inet6),
{error,nxdomain} = inet:getaddr(DFullName, inet6),
{ok,DIP} = inet:getaddr(DIPStr, inet6),
{ok,DIP} = inet:getaddr(DIP, inet6),
ok;
_ ->
{skip, "IPv6 is not supported on this host"}
end.
ipv4_to_ipv6() -> required(v4).
%% Test if IPv4 address is converted to IPv6 address.
ipv4_to_ipv6(Config) when is_list(Config) ->
%% Test what happens if an IPv4 address is looked up in an IPv6 context.
%% If the native resolver succeeds to look it up, an IPv4 compatible
%% address should be returned. If no IPv6 support on this host, an
%% error should beturned.
{_Name,_FullName,IPStr,_IP,Aliases,IP_46_Str,IP_46} =
ct:get_config(test_host_ipv4_only),
IP4to6Res =
case inet:getaddr(IPStr, inet6) of
{ok,IP_46} ->
io:format("IPv4->IPv6: success~n"),
true;
E = {error,nxdomain} ->
io:format("IPv4->IPv6: nxdomain~n"),
E;
E = {error,eafnosupport} ->
io:format("IPv6->IPv4: eafnosupport~n"),
E;
Other ->
ct:fail({ipv4_to_ipv6_lookup_failed,Other})
end,
case {IP4to6Res,inet:gethostbyname(IPStr, inet6)} of
{true,{ok,HEnt}} ->
HEnt_ = HEnt#hostent{h_addrtype = inet6,
h_length = 16,
h_addr_list = [IP_46]},
HEnt_ = HEnt,
check_elems([{HEnt#hostent.h_name,[IP_46_Str,IPStr]},
{HEnt#hostent.h_aliases,[[],Aliases,tl(Aliases)]}]);
{_,IP4to6Res} -> ok
end,
ok.
host_and_addr() ->
[{timetrap,{minutes,5}}|required(hosts)].
%% Test looking up hosts and addresses. Use 'ypcat hosts'
%% or the local eqivalent to find all hosts.
host_and_addr(Config) when is_list(Config) ->
lists:foreach(fun try_host/1, get_hosts(Config)),
ok.
try_host({Ip0, Host}) ->
{ok,Ip} = inet:getaddr(Ip0, inet),
{ok,{hostent, _, _, inet, _, Ips1}} = inet:gethostbyaddr(Ip),
{ok,{hostent, _, _, inet, _, _Ips2}} = inet:gethostbyname(Host),
true = lists:member(Ip, Ips1),
ok.
%% Get all hosts from the system using 'ypcat hosts' or the local
%% equvivalent.
get_hosts(_Config) ->
case os:type() of
{unix, _} ->
List = lists:map(fun(X) ->
atom_to_list(X)++" "
end, ct:get_config(test_hosts)),
Cmd = "ypmatch "++List++" hosts.byname",
HostFile = os:cmd(Cmd),
get_hosts(HostFile, [], [], []);
_ ->
ct:get_config(hardcoded_hosts)
end.
get_ipv6_hosts(_Config) ->
case os:type() of
{unix, _} ->
List = lists:map(fun(X) ->
atom_to_list(X)++" "
end, ct:get_config(ipv6_hosts)),
Cmd = "ypmatch "++List++" ipnodes.byname",
HostFile = os:cmd(Cmd),
get_hosts(HostFile, [], [], []);
_ ->
ct:get_config(hardcoded_ipv6_hosts)
end.
get_hosts([$\t|Rest], Cur, Ip, Result) when Ip /= [] ->
get_hosts(Rest, Cur, Ip, Result);
get_hosts([$\t|Rest], Cur, _Ip, Result) ->
get_hosts(Rest, [], lists:reverse(Cur), Result);
get_hosts([$\r|Rest], Cur, Ip, Result) ->
get_hosts(Rest, Cur, Ip, Result);
get_hosts([$\n|Rest], Cur, Ip, Result) ->
[First|_] = string:tokens(lists:reverse(Cur), " "),
Ips = string:tokens(Ip, ","),
Hosts = [{I, First} || I <- Ips],
get_hosts(Rest, [], [], Hosts++Result);
get_hosts([C|Rest], Cur, Ip, Result) ->
get_hosts(Rest, [C|Cur], Ip, Result);
get_hosts([], _, _, Result) ->
Result.
parse_hosts(Config) when is_list(Config) ->
DataDir = proplists:get_value(data_dir,Config),
HostFile = filename:join(DataDir, "hosts"),
inet_parse:hosts(HostFile),
HostFileErr1 = filename:join(DataDir, "hosts_err1"),
inet_parse:hosts(HostFileErr1),
Resolv = filename:join(DataDir,"resolv.conf"),
inet_parse:resolv(Resolv),
ResolvErr1 = filename:join(DataDir,"resolv.conf.err1"),
inet_parse:resolv(ResolvErr1).
parse_address(Config) when is_list(Config) ->
V4Reversable =
[{{0,0,0,0},"0.0.0.0"},
{{1,2,3,4},"1.2.3.4"},
{{253,252,251,250},"253.252.251.250"},
{{1,2,255,254},"1.2.255.254"}],
V6Reversable =
[{{0,0,0,0,0,0,0,0},"::"},
{{0,0,0,0,0,0,0,1},"::1"},
{{0,0,0,0,0,0,0,2},"::0.0.0.2"},
{{15,0,0,0,0,0,0,2},"f::2"},
{{15,16#f11,0,0,0,0,256,2},"f:f11::100:2"},
{{16#700,0,0,0,0,0,0,0},"700::"},
{{0,0,0,0,0,0,2,1},"::0.2.0.1"},
{{0,0,0,0,0,3,2,1},"::3:2:1"},
{{0,0,0,0,4,3,2,1},"::4:3:2:1"},
{{0,0,0,5,4,3,2,1},"::5:4:3:2:1"},
{{0,0,6,5,4,3,2,1},"::6:5:4:3:2:1"},
{{0,7,6,5,4,3,2,1},"0:7:6:5:4:3:2:1"},
{{7,0,0,0,0,0,0,0},"7::"},
{{7,6,0,0,0,0,0,0},"7:6::"},
{{7,6,5,0,0,0,0,0},"7:6:5::"},
{{7,6,5,4,0,0,0,0},"7:6:5:4::"},
{{7,6,5,4,3,0,0,0},"7:6:5:4:3::"},
{{7,6,5,4,3,2,0,0},"7:6:5:4:3:2::"},
{{7,6,5,4,3,2,1,0},"7:6:5:4:3:2:1:0"},
{{0,0,6,5,4,3,0,0},"::6:5:4:3:0:0"},
{{0,0,6,5,4,0,0,0},"0:0:6:5:4::"},
{{8,0,0,5,4,0,0,1},"8::5:4:0:0:1"},
{{8,0,0,5,0,0,0,1},"8:0:0:5::1"},
{{0,7,6,5,4,3,2,0},"0:7:6:5:4:3:2:0"},
{{0,0,6,5,4,3,0,0},"::6:5:4:3:0:0"},
{{0,0,0,5,4,0,0,0},"::5:4:0:0:0"},
{{0,0,0,0,4,0,0,0},"::4:0:0:0"},
{{0,0,0,5,0,0,0,0},"0:0:0:5::"},
{{16#c11,16#c22,16#5c33,16#c440,16#55c0,16#c66c,16#77,16#88},
"c11:c22:5c33:c440:55c0:c66c:77:88"},
{{0,16#c22,16#5c33,16#c440,16#55c0,16#c66c,16#77,16#88},
"0:c22:5c33:c440:55c0:c66c:77:88"},
{{16#c11,0,16#5c33,16#c440,16#55c0,16#c66c,16#77,16#88},
"c11:0:5c33:c440:55c0:c66c:77:88"},
{{16#c11,16#c22,0,16#c440,16#55c0,16#c66c,16#77,16#88},
"c11:c22:0:c440:55c0:c66c:77:88"},
{{16#c11,16#c22,16#5c33,0,16#55c0,16#c66c,16#77,16#88},
"c11:c22:5c33:0:55c0:c66c:77:88"},
{{16#c11,16#c22,16#5c33,16#c440,0,16#c66c,16#77,16#88},
"c11:c22:5c33:c440:0:c66c:77:88"},
{{16#c11,16#c22,16#5c33,16#c440,16#55c0,0,16#77,16#88},
"c11:c22:5c33:c440:55c0:0:77:88"},
{{16#c11,16#c22,16#5c33,16#c440,16#55c0,16#c66c,0,16#88},
"c11:c22:5c33:c440:55c0:c66c:0:88"},
{{16#c11,16#c22,16#5c33,16#c440,16#55c0,16#c66c,16#77,0},
"c11:c22:5c33:c440:55c0:c66c:77:0"},
{{0,0,16#5c33,16#c440,16#55c0,16#c66c,16#77,16#88},
"::5c33:c440:55c0:c66c:77:88"},
{{16#c11,0,0,16#c440,16#55c0,16#c66c,16#77,16#88},
"c11::c440:55c0:c66c:77:88"},
{{16#c11,16#c22,0,0,16#55c0,16#c66c,16#77,16#88},
"c11:c22::55c0:c66c:77:88"},
{{16#c11,16#c22,16#5c33,0,0,16#c66c,16#77,16#88},
"c11:c22:5c33::c66c:77:88"},
{{16#c11,16#c22,16#5c33,16#c440,0,0,16#77,16#88},
"c11:c22:5c33:c440::77:88"},
{{16#c11,16#c22,16#5c33,16#c440,16#55c0,0,0,16#88},
"c11:c22:5c33:c440:55c0::88"},
{{16#c11,16#c22,16#5c33,16#c440,16#55c0,16#c66c,0,0},
"c11:c22:5c33:c440:55c0:c66c::"},
{{0,0,0,16#c440,16#55c0,16#c66c,16#77,16#88},
"::c440:55c0:c66c:77:88"},
{{16#c11,0,0,0,16#55c0,16#c66c,16#77,16#88},
"c11::55c0:c66c:77:88"},
{{16#c11,16#c22,0,0,0,16#c66c,16#77,16#88},
"c11:c22::c66c:77:88"},
{{16#c11,16#c22,16#5c33,0,0,0,16#77,16#88},
"c11:c22:5c33::77:88"},
{{16#c11,16#c22,16#5c33,16#c440,0,0,0,16#88},
"c11:c22:5c33:c440::88"},
{{16#c11,16#c22,16#5c33,16#c440,16#55c0,0,0,0},
"c11:c22:5c33:c440:55c0::"},
{{0,0,0,0,16#55c0,16#c66c,16#77,16#88},
"::55c0:c66c:77:88"},
{{16#c11,0,0,0,0,16#c66c,16#77,16#88},
"c11::c66c:77:88"},
{{16#c11,16#c22,0,0,0,0,16#77,16#88},
"c11:c22::77:88"},
{{16#c11,16#c22,16#5c33,0,0,0,0,16#88},
"c11:c22:5c33::88"},
{{16#c11,16#c22,16#5c33,16#c440,0,0,0,0},
"c11:c22:5c33:c440::"},
{{0,0,0,0,0,16#c66c,16#77,16#88},
"::c66c:77:88"},
{{16#c11,0,0,0,0,0,16#77,16#88},
"c11::77:88"},
{{16#c11,16#c22,0,0,0,0,0,16#88},
"c11:c22::88"},
{{16#c11,16#c22,16#5c33,0,0,0,0,0},
"c11:c22:5c33::"},
{{0,0,0,0,0,65535,258,65534},"::ffff:1.2.255.254"},
{{16#fe80,12345,0,0,0,0,0,16#12},"fe80::12%012345"},
{{16#ffff,16#ffff,16#ffff,16#ffff,16#ffff,16#ffff,16#ffff,16#ffff},
"ffff:ffff:ffff:ffff:ffff:ffff:ffff:ffff"}
|[{list_to_tuple(P++[(D1 bsl 8) bor D2,(D3 bsl 8) bor D4]),
Q++S}
|| {{D1,D2,D3,D4},S} <-
tl(V4Reversable),
{P,Q} <-
[{[0,0,0,0,0,16#ffff],"::ffff:"},
{[0,0,0,0,0,0],"::"}]]],
V4Sloppy =
[{{10,1,16#98,16#76},"10.0x019876"},
{{8#12,1,8#130,8#321},"012.01.054321"},
{{252,253,254,255},"252.253.254.0377"},
{{252,253,254,255},"0Xfc.000000000375.0x0000fe.255"},
{{252,253,254,255},"252.253.65279"},
{{252,253,254,255},"252.0xFD.0177377"},
{{252,253,254,255},"252.16645887"},
{{252,253,254,255},"00374.0XFDFEFF"},
{{252,253,254,255},"4244504319"},
{{252,253,254,255},"0xfcfdfeff"},
{{252,253,254,255},"00000000000037477377377"},
{{16#12,16#34,16#56,16#78},"0x12345678"},
{{16#12,16#34,16#56,16#78},"0x12.0x345678"},
{{16#12,16#34,16#56,16#78},"0x12.0X34.0x5678"},
{{16#12,16#34,16#56,16#78},"0x12.0X34.0x56.0X78"},
{{0,0,0,0},"0"},
{{0,0,0,0},"00"},
{{0,0,0,0},"0.0"},
{{0,0,0,0},"00.00.00"},
{{0,0,0,0},"0.00.0.0"},
{{0,0,0,0},"0.0.000000000000.0"}],
V6Sloppy =
[{{16#a,16#b,16#c,16#0,16#0,16#d,16#e,16#f},"A:B:C::d:e:f"},
{{16#fe80,0,0,0,0,0,0,16#12},"fe80::12%XXXXXXX"}]
++
[{{P,0,0,0,0,D2,(D1 bsl 8) bor D2,(D3 bsl 8) bor D4},
Q++erlang:integer_to_list(D2, 16)++":"++S}
|| {{D1,D2,D3,D4},S} <- V4Reversable,
{P,Q} <-
[{16#2001,"2001::"},{16#177,"177::"},{16#ff0,"Ff0::"}]],
V4Err =
["0.256.0.1",
"1.2.3.4.5",
"256.255.65535",
"4294967296",
"0x100000000",
"040000000000",
"1.2.3.-4",
"1.2.-3.4",
"1.-2.3.4",
"-1.2.3.4",
"10.",
"172.16.",
"198.168.0.",
"127.0.0.1."],
V6Err =
[":::",
"f:::2",
"::-1",
"::g",
"f:f11::10100:2",
"f:f11::01100:2",
"::17000",
"::01700",
"10000::",
"01000::",
"::8:7:6:5:4:3:2:1",
"8:7:6:5:4:3:2:1::",
"8:7:6:5:4::3:2:1",
"::1.2.3.4.5",
"::1.2.3.04",
"::1.256.3.4",
"::-5.4.3.2",
"::5.-4.3.2",
"::5.4.-3.2",
"::5.4.3.-2",
"::FFFF:1.2.3.4.5",
"::10.",
"::FFFF:172.16.",
"fe80::198.168.0.",
"fec0::fFfF:127.0.0.1."],
t_parse_address
(parse_ipv6_address,
false,
V6Reversable++V6Sloppy++V6Err++V4Err),
t_parse_address
(parse_ipv6strict_address,
true,
V6Reversable++V6Err++V4Err),
t_parse_address
(parse_ipv4_address,
false,
V4Reversable++V4Sloppy++V4Err++V6Err++[S || {_,S} <- V6Reversable]),
t_parse_address
(parse_ipv4strict_address,
true,
V4Reversable++V4Err++V6Err++[S || {_,S} <- V4Sloppy++V6Reversable]).
t_parse_address(Func, _Reversable, []) ->
io:format("~p done.~n", [Func]),
ok;
t_parse_address(Func, Reversable, [{Addr,String}|L]) ->
io:format("~p = ~p.~n", [Addr,String]),
{ok,Addr} = inet:Func(String),
case Reversable of
true ->String = inet:ntoa(Addr);
false -> ok
end,
t_parse_address(Func, Reversable, L);
t_parse_address(Func, Reversable, [String|L]) ->
io:format("~p.~n", [String]),
{error,einval} = inet:Func(String),
t_parse_address(Func, Reversable, L).
parse_strict_address(Config) when is_list(Config) ->
{ok, {127,0,0,1}} =
inet:parse_strict_address("127.0.0.1"),
{ok, {3089,3106,23603,50240,21952,50796,119,136}} =
inet:parse_strict_address("c11:0c22:5c33:c440:55c0:c66c:77:0088"),
{ok, {3089,3106,23603,50240,0,0,119,136}} =
inet:parse_strict_address("c11:0c22:5c33:c440::077:0088").
ipv4_mapped_ipv6_address(Config) when is_list(Config) ->
{D1,D2,D3,D4} = IPv4Address =
{rand:uniform(256) - 1,
rand:uniform(256) - 1,
rand:uniform(256) - 1,
rand:uniform(256) - 1},
E7 = (D1 bsl 8) bor D2,
E8 = (D3 bsl 8) bor D4,
io:format("IPv4Address: ~p.~n", [IPv4Address]),
{0,0,0,0,0,65535,E7,E8} = inet:ipv4_mapped_ipv6_address(IPv4Address),
IPv6Address =
{rand:uniform(65536) - 1,
rand:uniform(65536) - 1,
rand:uniform(65536) - 1,
rand:uniform(65536) - 1,
rand:uniform(65536) - 1,
rand:uniform(65536) - 1, E7, E8},
IPv4Address = inet:ipv4_mapped_ipv6_address(IPv6Address),
ok.
t_gethostnative(Config) when is_list(Config) ->
%% this will result in 26 bytes sent which causes problem in Windows
%% if the port-program has not assured stdin to be read in BINARY mode
%% OTP-2555
case inet_gethost_native:gethostbyname(
"a23456789012345678901234") of
{error,notfound} ->
ok;
{error,no_data} ->
ok
end.
%% Check that the emulator survives crashes in gethost_native.
gethostnative_parallell(Config) when is_list(Config) ->
{ok,Hostname} = inet:gethostname(),
{ok,_} = inet:gethostbyname(Hostname),
case whereis(inet_gethost_native) of
Pid when is_pid(Pid) ->
do_gethostnative_parallell();
_ ->
{skipped, "Not running native gethostbyname"}
end.
do_gethostnative_parallell() ->
PA = filename:dirname(code:which(?MODULE)),
{ok,Node} = test_server:start_node(gethost_parallell, slave,
[{args, "-pa " ++ PA}]),
ok = rpc:call(Node, ?MODULE, parallell_gethost, []),
receive after 10000 -> ok end,
pong = net_adm:ping(Node),
test_server:stop_node(Node),
ok.
parallell_gethost() ->
{ok,Hostname} = inet:gethostname(),
process_flag(trap_exit,true),
parallell_gethost_loop(10, Hostname).
parallell_gethost_loop(0, _) -> ok;
parallell_gethost_loop(N, Hostname) ->
case whereis(inet_gethost_native) of
Pid when is_pid(Pid) ->
true = exit(Pid,kill);
_ ->
ok
end,
L = spawn_gethosters(Hostname, 10),
release_gethosters(L),
collect_gethosters(10),
parallell_gethost_loop(N-1, Hostname).
spawn_gethosters(_, 0) ->
[];
spawn_gethosters(Hostname, N) ->
Collector = self(),
[spawn(fun() ->
receive
go ->
case (catch inet:gethostbyname(Hostname)) of
{ok,_} ->
Collector ! ok;
Else ->
Collector ! {error,Else}
end
end
end) |
spawn_gethosters(Hostname, N-1)].
release_gethosters([]) ->
ok;
release_gethosters([H|T]) ->
H ! go,
release_gethosters(T).
collect_gethosters(0) ->
ok;
collect_gethosters(N) ->
receive
ok ->
collect_gethosters(N-1);
Else ->
{failed, {unexpected, Else}}
after 2000 ->
{failed, {missing, N}}
end.
kill_gethost() ->
kill_gethost(20).
kill_gethost(0) ->
ok;
kill_gethost(N) ->
put(kill_gethost_n,N),
Pid = wait_for_gethost(10),
true = exit(Pid,kill),
wait_for_dead_gethost(10),
kill_gethost(N-1).
wait_for_dead_gethost(0) ->
exit({not_dead,inet_gethost_native});
wait_for_dead_gethost(N) ->
case whereis(inet_gethost_native) of
Pid when is_pid(Pid) ->
receive after 1000 ->
ok
end,
wait_for_dead_gethost(N-1);
undefined ->
ok
end.
wait_for_gethost(0) ->
exit(gethost_not_found);
wait_for_gethost(N) ->
{ok,Hostname} = inet:gethostname(),
case (catch inet:gethostbyname(Hostname)) of
{ok,_} ->
ok;
Otherwise ->
%% This is what I call an exit tuple :)
exit({inet,gethostbyname, returned, Otherwise, 'when',
'N','=',N,'and','hostname','=',Hostname,'and',
kill_gethost_n,'=',get(kill_gethost_n)})
end,
case whereis(inet_gethost_native) of
Pid when is_pid(Pid) ->
Pid;
_ ->
receive
after 1000 ->
ok
end,
wait_for_gethost(N-1)
end.
%% Check that the resolver handles a CNAME loop.
cname_loop(Config) when is_list(Config) ->
%% getbyname (hostent_by_domain)
ok = inet_db:add_rr("mydomain.com", in, ?S_CNAME, ttl, "mydomain.com"),
{error,nxdomain} = inet_db:getbyname("mydomain.com", ?S_A),
ok = inet_db:del_rr("mydomain.com", in, ?S_CNAME, "mydomain.com"),
%% res_hostent_by_domain
RR = #dns_rr{domain = "mydomain.com",
class = in,
type = ?S_CNAME,
data = "mydomain.com"},
Rec = #dns_rec{anlist = [RR]},
{error,nxdomain} = inet_db:res_hostent_by_domain("mydomain.com", ?S_A, Rec),
ok.
%% Test that hosts file gets reloaded correctly in case when it
% was missing during initial startup
missing_hosts_reload(Config) when is_list(Config) ->
RootDir = proplists:get_value(priv_dir,Config),
HostsFile = filename:join(RootDir, atom_to_list(?MODULE) ++ ".hosts"),
InetRc = filename:join(RootDir, "inetrc"),
ok = file:write_file(InetRc, "{hosts_file, \"" ++ HostsFile ++ "\"}.\n"),
{error, enoent} = file:read_file_info(HostsFile),
% start a node
Pa = filename:dirname(code:which(?MODULE)),
{ok, TestNode} = test_server:start_node(?MODULE, slave,
[{args, "-pa " ++ Pa ++ " -kernel inetrc '\"" ++ InetRc ++ "\"'"}]),
% ensure it has our RC
Rc = rpc:call(TestNode, inet_db, get_rc, []),
{hosts_file, HostsFile} = lists:keyfind(hosts_file, 1, Rc),
% ensure it does not resolve
{error, nxdomain} = rpc:call(TestNode, inet_hosts, gethostbyname, ["somehost"]),
% write hosts file
ok = file:write_file(HostsFile, "1.2.3.4 somehost"),
% wait for cached timestamp to expire
timer:sleep(?RES_FILE_UPDATE_TM * 1000 + 100),
% ensure it DOES resolve
{ok,{hostent,"somehost",[],inet,4,[{1,2,3,4}]}} =
rpc:call(TestNode, inet_hosts, gethostbyname, ["somehost"]),
% cleanup
true = test_server:stop_node(TestNode).
%% These must be run in the whole suite since they need
%% the host list and require inet_gethost_native to be started.
%%
-record(gethostnative_control, {control_seq,
control_interval=100,
lookup_delay=10,
lookup_count=300,
lookup_processes=20}).
gethostnative_soft_restart() -> required(hosts).
%% Check that no name lookups fails during soft restart
%% of inet_gethost_native.
gethostnative_soft_restart(Config) when is_list(Config) ->
gethostnative_control(Config,
#gethostnative_control{
control_seq=[soft_restart]}).
gethostnative_debug_level() -> required(hosts).
%% Check that no name lookups fails during debug level change
%% of inet_gethost_native.
gethostnative_debug_level(Config) when is_list(Config) ->
gethostnative_control(Config,
#gethostnative_control{
control_seq=[{debug_level,1},
{debug_level,0}]}).
gethostnative_control(Config, Optrec) ->
case inet_db:res_option(lookup) of
[native] ->
case whereis(inet_gethost_native) of
Pid when is_pid(Pid) ->
gethostnative_control_1(Config, Optrec);
_ ->
{skipped, "Not running native gethostbyname"}
end;
_ ->
{skipped, "Native not only lookup metod"}
end.
gethostnative_control_1(Config,
#gethostnative_control{
control_seq=Seq,
control_interval=Interval,
lookup_delay=Delay,
lookup_count=Cnt,
lookup_processes=N}) ->
{ok, Hostname} = inet:gethostname(),
{ok, _} = inet:gethostbyname(Hostname),
Hosts =
[Hostname|[H || {_,H} <- get_hosts(Config)]
++[H++D || H <- ["www.","www1.","www2.",""],
D <- ["erlang.org","erlang.se"]]
++[H++"cslab.ericsson.net" || H <- ["morgoth.","hades.","styx."]]],
%% Spawn some processes to do parallel lookups while
%% I repeatedly do inet_gethost_native:control/1.
TrapExit = process_flag(trap_exit, true),
gethostnative_control_2([undefined], Interval, Delay, Cnt, N, Hosts),
io:format(
"First intermission: now starting control sequence ~w\n",
[Seq]),
erlang:display(first_intermission),
gethostnative_control_2(Seq, Interval, Delay, Cnt, N, Hosts),
erlang:display(second_intermission),
io:format(
"Second intermission: now stopping control sequence ~w\n",
[Seq]),
gethostnative_control_2([undefined], Interval, Delay, Cnt, N, Hosts),
true = process_flag(trap_exit, TrapExit),
ok.
gethostnative_control_2(Seq, Interval, Delay, Cnt, N, Hosts) ->
Tag = make_ref(),
Parent = self(),
Lookupers =
[spawn_link(
fun () ->
lookup_loop(Hosts, Delay, Tag, Parent, Cnt, Hosts)
end)
|| _ <- lists:seq(1, N)],
control_loop(Seq, Interval, Tag, Lookupers, Seq),
gethostnative_control_3(Tag, ok).
gethostnative_control_3(Tag, Reason) ->
receive
{Tag,Error} ->
gethostnative_control_3(Tag, Error)
after 0 ->
Reason
end.
control_loop([], _Interval, _Tag, [], _Seq) ->
ok;
control_loop([], Interval, Tag, Lookupers, Seq) ->
control_loop(Seq, Interval, Tag, Lookupers, Seq);
control_loop([Op|Ops], Interval, Tag, Lookupers, Seq) ->
control_loop(Ops, Interval, Tag,
control_loop_1(Op, Interval, Tag, Lookupers),
Seq).
control_loop_1(Op, Interval, Tag, Lookupers) ->
receive
{'EXIT',Pid,Reason} ->
case Reason of
Tag -> % Done
control_loop_1
(Op, Interval, Tag,
lists:delete(Pid, Lookupers));
_ ->
io:format("Lookuper ~p died: ~p",
[Pid,Reason]),
ct:fail("Lookuper died")
end
after Interval ->
if Op =/= undefined ->
ok = inet_gethost_native:control(Op);
true ->
ok
end,
Lookupers
end.
lookup_loop(_, _Delay, Tag, _Parent, 0, _Hosts) ->
exit(Tag);
lookup_loop([], Delay, Tag, Parent, Cnt, Hosts) ->
lookup_loop(Hosts, Delay, Tag, Parent, Cnt, Hosts);
lookup_loop([H|Hs], Delay, Tag, Parent, Cnt, Hosts) ->
case inet:gethostbyname(H) of
{ok,_Hent} -> ok;
{error,nxdomain} -> ok;
Error ->
io:format("Name lookup error for ~p for ~p: ~p",
[self(),H,Error]),
Parent ! {Tag,Error}
end,
receive
after rand:uniform(Delay) ->
lookup_loop(Hs, Delay, Tag, Parent, Cnt-1, Hosts)
end.
%% Test lookup with erroneously configured lookup option (OTP-12133).
lookup_bad_search_option(Config) when is_list(Config) ->
%% Manipulation of resolver config is done in init_per_testcase
%% and end_per_testcase to ensure cleanup.
{ok,Hostname} = inet:gethostname(),
{ok,_Hent} = inet:gethostbyname(Hostname), % Will hang loop for this bug
ok.
%% Tests basic functionality of getiflist, getif, and ifget.
getif(Config) when is_list(Config) ->
case os:type() of
{unix,Osname} ->
do_getif(Osname);
{_,_} ->
{skip,"inet:getif/0 probably not supported"}
end.
do_getif(Osname) ->
{ok,Hostname} = inet:gethostname(),
{ok,Address} = inet:getaddr(Hostname, inet),
{ok,Loopback} = inet:getaddr("localhost", inet),
{ok,Interfaces} = inet:getiflist(),
HWAs =
lists:sort(
lists:foldl(
fun (I, Acc) ->
case inet:ifget(I, [hwaddr]) of
{ok,[{hwaddr,A}]} -> [A|Acc];
{ok,[]} -> Acc
end
end, [], Interfaces)),
io:format("HWAs = ~p~n", [HWAs]),
(Osname =/= sunos)
andalso ((length(HWAs) > 0) orelse (ct:fail(no_HWAs))),
Addresses =
lists:sort(
lists:foldl(
fun (I, Acc) ->
case inet:ifget(I, [addr]) of
{ok,[{addr,A}]} -> [A|Acc];
{ok,[]} -> Acc
end
end, [], Interfaces)),
{ok,Getif} = inet:getif(),
Addresses = lists:sort([A || {A,_,_} <- Getif]),
true = ip_member(Address, Addresses),
true = ip_member(Loopback, Addresses),
ok.
%% Test long interface names do not overrun buffer.
getif_ifr_name_overflow(Config) when is_list(Config) ->
case os:type() of
{unix,Osname} ->
do_getif_ifr_name_overflow(Osname);
{_,_} ->
{skip,"inet:ifget/2 probably not supported"}
end.
do_getif_ifr_name_overflow(_) ->
%% emulator should not crash
{ok,[]} = inet:ifget(lists:duplicate(128, "x"), [addr]),
ok.
%% Test long service names do not overrun buffer.
getservbyname_overflow(Config) when is_list(Config) ->
%% emulator should not crash
{error,einval} = inet:getservbyname(list_to_atom(lists:flatten(lists:duplicate(128, "x"))), tcp),
ok.
%% Test inet:gifaddrs/0.
getifaddrs(Config) when is_list (Config) ->
{ok,IfAddrs} = inet:getifaddrs(),
io:format("IfAddrs = ~p.~n", [IfAddrs]),
case [If || {If,Opts} <- IfAddrs, lists:keymember(hwaddr, 1, Opts)] of
[] ->
case os:type() of
{unix,sunos} -> ok;
OT ->
ct:fail({should_have_hwaddr,OT})
end;
[_|_] -> ok
end,
Addrs = ifaddrs(IfAddrs),
io:format("Addrs = ~p.~n", [Addrs]),
[check_addr(Addr) || Addr <- Addrs],
ok.
check_addr(Addr)
when tuple_size(Addr) =:= 8,
element(1, Addr) band 16#FFC0 =:= 16#FE80 ->
io:format("Addr: ~p link local; SKIPPED!~n", [Addr]),
ok;
check_addr(Addr) ->
io:format("Addr: ~p.~n", [Addr]),
Ping = "ping",
Pong = "pong",
{ok,L} = gen_tcp:listen(0, [{ip,Addr},{active,false}]),
{ok,P} = inet:port(L),
{ok,S1} = gen_tcp:connect(Addr, P, [{active,false}]),
{ok,S2} = gen_tcp:accept(L),
ok = gen_tcp:send(S2, Ping),
{ok,Ping} = gen_tcp:recv(S1, length(Ping)),
ok = gen_tcp:send(S1, Pong),
ok = gen_tcp:close(S1),
{ok,Pong} = gen_tcp:recv(S2, length(Pong)),
ok = gen_tcp:close(S2),
ok = gen_tcp:close(L).
ifaddrs(IfOpts) ->
IfMap = collect_ifopts(IfOpts),
ChkFun =
fun Self({{_,Flags} = Key, Opts}, ok) ->
Broadcast = lists:member(broadcast, Flags),
P2P = lists:member(pointtopoint, Flags),
case Opts of
[{addr,_},{netmask,_},{broadaddr,_}|Os]
when Broadcast ->
Self({Key, Os}, ok);
[{addr,_},{netmask,_},{dstaddr,_}|Os]
when P2P ->
Self({Key, Os}, ok);
[{addr,_},{netmask,_}|Os] ->
Self({Key, Os}, ok);
[{hwaddr,_}|Os] ->
Self({Key, Os}, ok);
[] ->
ok
end
end,
fold_ifopts(ChkFun, ok, IfMap),
AddrsFun =
fun ({{_,Flags}, Opts}, Acc) ->
case
lists:member(running, Flags)
andalso (not lists:member(pointtopoint, Flags))
of
true ->
lists:reverse(
[Addr || {addr,Addr} <- Opts],
Acc);
false ->
Acc
end
end,
fold_ifopts(AddrsFun, [], IfMap).
collect_ifopts(IfOpts) ->
collect_ifopts(IfOpts, #{}).
%%
collect_ifopts(IfOpts, IfMap) ->
case IfOpts of
[{If,[{flags,Flags}|Opts]}|IfOs] ->
Key = {If,Flags},
case maps:is_key(Key, IfMap) of
true ->
ct:fail({unexpected_ifopts,IfOpts,IfMap});
false ->
collect_ifopts(IfOs, IfMap, Opts, Key, [])
end;
[] ->
IfMap;
_ ->
ct:fail({unexpected_ifopts,IfOpts,IfMap})
end.
%%
collect_ifopts(IfOpts, IfMap, Opts, Key, R) ->
case Opts of
[{flags,_}|_] ->
{If,_} = Key,
collect_ifopts(
[{If,Opts}|IfOpts], maps:put(Key, lists:reverse(R), IfMap));
[OptVal|Os] ->
collect_ifopts(IfOpts, IfMap, Os, Key, [OptVal|R]);
[] ->
collect_ifopts(IfOpts, maps:put(Key, lists:reverse(R), IfMap))
end.
fold_ifopts(Fun, Acc, IfMap) ->
fold_ifopts(Fun, Acc, IfMap, maps:keys(IfMap)).
%%
fold_ifopts(Fun, Acc, IfMap, Keys) ->
case Keys of
[Key|Ks] ->
Opts = maps:get(Key, IfMap),
fold_ifopts(Fun, Fun({Key,Opts}, Acc), IfMap, Ks);
[] ->
Acc
end.
%% Works just like lists:member/2, except that any {127,_,_,_} tuple
%% matches any other {127,_,_,_}. We do this to handle Linux systems
%% that use (for instance) 127.0.1.1 as the IP address for the hostname.
ip_member({127,_,_,_}, [{127,_,_,_}|_]) -> true;
ip_member(K, [K|_]) -> true;
ip_member(K, [_|T]) -> ip_member(K, T);
ip_member(_, []) -> false.
%% Case fold to upper case according to RFC 4343
%%
toupper([C|Cs]) when is_integer(C) ->
if $a =< C, C =< $z ->
[(C - $a + $A)|toupper(Cs)];
true ->
[C|toupper(Cs)]
end;
toupper([]) ->
[].
simple_netns(Config) when is_list(Config) ->
{ok,U} = gen_udp:open(0),
case inet:setopts(U, [{netns,""}]) of
ok ->
jog_netns_opt(U),
ok = gen_udp:close(U),
%%
{ok,L} = gen_tcp:listen(0, []),
jog_netns_opt(L),
ok = gen_tcp:close(L),
%%
case gen_sctp:open() of
{ok,S} ->
jog_netns_opt(S),
ok = gen_sctp:close(S);
{error,eprotonosupport} ->
ok
end;
{error,einval} ->
{skip,"setns() not supported"}
end.
jog_netns_opt(S) ->
%% This is just jogging the option mechanics
ok = inet:setopts(S, [{netns,""}]),
{ok,[{netns,""}]} = inet:getopts(S, [netns]),
ok = inet:setopts(S, [{netns,"/proc/self/ns/net"}]),
{ok,[{netns,"/proc/self/ns/net"}]} = inet:getopts(S, [netns]),
ok.
%% Smoke test netns support.
simple_netns_open(Config) when is_list(Config) ->
%% Note: {error,enoent} will be returned if the run-time executable
%% has support for netns, but /proc/self/ns/net is missing.
case gen_udp:open(0, [binary,{netns,"/"},inet]) of
{ok,U} ->
ok = gen_udp:close(U);
{error,E1} when E1 =:= einval; E1 =:= eperm; E1 =:= enoent ->
ok
end,
case gen_tcp:listen(0, [binary,{netns,"/"},inet]) of
{ok,T} ->
ok = gen_tcp:close(T);
{error,E2} when E2 =:= einval; E2 =:= eperm; E2 =:= enoent ->
ok
end,
try gen_sctp:open(0, [binary,{netns,"/"},inet]) of
{ok,S} ->
ok = gen_sctp:close(S);
{error,E3}
when E3 =:= einval; E3 =:= eperm;
E3 =:= enoent; E3 =:= eprotonosupport ->
ok
catch
error:badarg ->
%% Some older platforms does not allow netns for sctp
ok
end.
%% Manual test to be run outside test_server in an emulator
%% started by root, in a machine with setns() support...
test_netns() ->
DefaultIF = v1,
DefaultIP = {192,168,1,17},
Namespace = "test",
NamespaceIF = v2,
NamespaceIP = {192,168,1,18},
%%
DefaultIPString = inet_parse:ntoa(DefaultIP),
NamespaceIPString = inet_parse:ntoa(NamespaceIP),
cmd("ip netns add ~s",
[Namespace]),
cmd("ip link add name ~w type veth peer name ~w netns ~s",
[DefaultIF,NamespaceIF,Namespace]),
cmd("ip netns exec ~s ip addr add ~s/30 dev ~w",
[Namespace,NamespaceIPString,NamespaceIF]),
cmd("ip netns exec ~s ip link set ~w up",
[Namespace,NamespaceIF]),
cmd("ip addr add ~s/30 dev ~w",
[DefaultIPString,DefaultIF]),
cmd("ip link set ~w up",
[DefaultIF]),
try test_netns(
{DefaultIF,DefaultIP},
filename:join("/var/run/netns/", Namespace),
{NamespaceIF,NamespaceIP}) of
Result ->
io:put_chars(["#### Test done",io_lib:nl()]),
Result
after
cmd("ip link delete ~w type veth",
[DefaultIF]),
cmd("ip netns delete ~s",
[Namespace])
end.
test_netns({DefaultIF,DefaultIP}, Namespace, {NamespaceIF,NamespaceIP}) ->
{ok,ListenSocket} = gen_tcp:listen(0, [{active,false}]),
{ok,[{addr,DefaultIP}]} = inet:ifget(ListenSocket, DefaultIF, [addr]),
{ok,ListenPort} = inet:port(ListenSocket),
{ok,ConnectSocket} =
gen_tcp:connect(
DefaultIP, ListenPort, [{active,false},{netns,Namespace}], 3000),
{ok,[{addr,NamespaceIP}]} = inet:ifget(ConnectSocket, NamespaceIF, [addr]),
{ok,ConnectPort} = inet:port(ConnectSocket),
{ok,AcceptSocket} = gen_tcp:accept(ListenSocket, 0),
{ok,AcceptPort} = inet:port(AcceptSocket),
{ok,{NamespaceIP,ConnectPort}} = inet:peername(AcceptSocket),
{ok,{DefaultIP,AcceptPort}} = inet:peername(ConnectSocket),
ok = gen_tcp:send(ConnectSocket, "data"),
ok = gen_tcp:close(ConnectSocket),
{ok,"data"} = gen_tcp:recv(AcceptSocket, 4, 1000),
{error,closed} = gen_tcp:recv(AcceptSocket, 1, 1000),
ok = gen_tcp:close(AcceptSocket),
ok = gen_tcp:close(ListenSocket).
cmd(Cmd, Args) ->
cmd(io_lib:format(Cmd, Args)).
%%
cmd(CmdString) ->
io:put_chars(["# ",CmdString,io_lib:nl()]),
io:put_chars([os:cmd(CmdString++" ; echo ' =>' $?")]),
ok.
-define(CAP_NET_RAW, 13). %% from /usr/include/linux/capability.h
can_bind_to_device({unix, linux}, {Major, _, _})
when Major > 2 ->
Status = os:cmd("cat /proc/self/status | grep CapEff"),
[_, CapEffStr] = string:tokens(Status, [$\n, $\t]),
CapEff = list_to_integer(CapEffStr, 16),
if CapEff band (1 bsl ?CAP_NET_RAW) =/= 0 ->
ok;
true ->
{skip,"insufficient capabilities, CAP_NET_RAW not granted"}
end;
can_bind_to_device(_OS, _Version) ->
{skip,"socket option bind_to_device not supported on this OS or version"}.
simple_bind_to_device(Config) when is_list(Config) ->
case can_bind_to_device(os:type(), os:version()) of
ok ->
{ok,U} = gen_udp:open(0),
jog_bind_to_device_opt(U),
ok = gen_udp:close(U),
%%
{ok,L} = gen_tcp:listen(0, []),
jog_bind_to_device_opt(L),
ok = gen_tcp:close(L),
%%
case gen_sctp:open() of
{ok,S} ->
jog_bind_to_device_opt(S),
ok = gen_sctp:close(S);
{error,eprotonosupport} ->
ok
end;
Other ->
Other
end.
%% Smoke test bind_to_device support.
simple_bind_to_device_open(Config) when is_list(Config) ->
case can_bind_to_device(os:type(), os:version()) of
ok ->
{ok,U} = gen_udp:open(0, [binary,{bind_to_device,<<"lo">>},inet]),
ok = gen_udp:close(U),
{ok,T} = gen_tcp:listen(0, [binary,{bind_to_device,<<"lo">>},inet]),
ok = gen_tcp:close(T),
case gen_sctp:open(0, [binary,{bind_to_device,<<"lo">>},inet]) of
{ok,S} ->
ok = gen_sctp:close(S);
{error,eprotonosupport} ->
ok
end;
Other ->
Other
end.
jog_bind_to_device_opt(S) ->
%% This is just jogging the option mechanics
ok = inet:setopts(S, [{bind_to_device,<<>>}]),
{ok,[{bind_to_device,<<>>}]} = inet:getopts(S, [bind_to_device]),
ok = inet:setopts(S, [{bind_to_device,<<"lo">>}]),
{ok,[{bind_to_device,<<"lo">>}]} = inet:getopts(S, [bind_to_device]),
ok.