aboutsummaryrefslogblamecommitdiffstats
path: root/lib/kernel/test/inet_SUITE.erl
blob: 4d673e0814a23f416eac44b4dabd7bf49c1dc342 (plain) (tree)
1
2
3
4
5
6
7
8
9
10

                   


                                                        




                                                                      
  



                                                                         
  



                    
                                                    


                                        
                                                                                                     








                                                    
                                                  



                                                                            
 
                                                                       


                                                   

                                                



























                                                            





                         

                                     
 

                                    








                                                        

                    

                                                                  

                                                




















                                                                        
                                       



                                                        
                                     


                                                                           
                                           


















                                                                         
                                                          




                                                                  
                                  



                                                                  
                                           





                                                             
 


                                                               








                                                               
                                       


                                                           
                                     



                                                                           
                                           











                                                                                          
                                                           












































                                                                                        
                                                          

















                                                                       
                            



                                                      
                                           







                                                           
                                                                                    




                                                           
                                               



                                                         
                                           






                                                                      









                                                                      
                                                      
                                                                           






                                                                    
                                                    








                                                                     
                               







                                                                            
                                           















                                                                 









                                                                             
                                   
























                                                                                 
                                                             



                                                     
                                          






                                                         
                                                             



                                                       
                                               

















                                                       
 










                                                                 



































































































































































                                                                            














































































































































































                                                                                      
                                                









                                                               

                                               




































































































































                                                                               







                                                              



                                                          









                                                     

                                                               














                                                          


                                                       







                                                              



                                                                  






                                                                                                           









































































































                                                                              







                                                                        
%%
%% %CopyrightBegin%
%%
%% Copyright Ericsson AB 1997-2010. All Rights Reserved.
%%
%% The contents of this file are subject to the Erlang Public License,
%% Version 1.1, (the "License"); you may not use this file except in
%% compliance with the License. You should have received a copy of the
%% Erlang Public License along with this software. If not, it can be
%% retrieved online at http://www.erlang.org/.
%%
%% Software distributed under the License is distributed on an "AS IS"
%% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See
%% the License for the specific language governing rights and limitations
%% under the License.
%%
%% %CopyrightEnd%
%%
-module(inet_SUITE).

-include_lib("test_server/include/test_server.hrl").
-include_lib("kernel/include/inet.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, 
         gethostnative_soft_restart/0, gethostnative_soft_restart/1,
	 gethostnative_debug_level/0, gethostnative_debug_level/1,
	 getif/1,
	 getif_ifr_name_overflow/1,getservbyname_overflow/1, getifaddrs/1]).

-export([get_hosts/1, get_ipv6_hosts/1, parse_hosts/1, parse_address/1,
	 kill_gethost/0, parallell_gethost/0]).
-export([init_per_testcase/2, end_per_testcase/2]).

suite() -> [{suite_callbacks,[ts_install_scb]}].

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,
     gethostnative_debug_level, gethostnative_soft_restart,
     getif, getif_ifr_name_overflow, getservbyname_overflow,
     getifaddrs].

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; OS =:= vxworks ->
	    [{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(_Func, Config) ->
    Dog = test_server:timetrap(test_server:seconds(60)),
    [{watchdog,Dog}|Config].

end_per_testcase(_Func, Config) ->
    Dog = ?config(watchdog, Config),
    test_server:timetrap_cancel(Dog).

t_gethostbyaddr() ->
    required(v4).
t_gethostbyaddr(doc) -> "Test the inet:gethostbyaddr/1 function.";
t_gethostbyaddr(Config) when is_list(Config) ->
    ?line {Name,FullName,IPStr,IP,Aliases,_,_} =
	ct:get_config(test_host_ipv4_only),
    ?line {ok,HEnt} = inet:gethostbyaddr(IPStr),
    ?line {ok,HEnt} = inet:gethostbyaddr(IP),
    ?line {error,Error} = inet:gethostbyaddr(Name),
    ?line ok = io:format("Failure reason: ~p: ~s",
			 [error,inet:format_error(Error)]),
    ?line HEnt_ = HEnt#hostent{h_addrtype = inet,
			       h_length = 4,
			       h_addr_list = [IP]},
    ?line 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.
	    ?line check_elems([{HEnt#hostent.h_name,[Name,FullName]}]),
	    io:format("Buggy alias list: ~p", [HEnt#hostent.h_aliases]),
	    ok;
	_ ->
	    ?line check_elems([{HEnt#hostent.h_name,[Name,FullName]},
			       {HEnt#hostent.h_aliases,[[],Aliases]}])
    end,

    ?line {_DName, _DFullName, DIPStr, DIP, _, _, _} =
	ct:get_config(test_dummy_host),
    ?line {error,nxdomain} = inet:gethostbyaddr(DIPStr),
    ?line {error,nxdomain} = inet:gethostbyaddr(DIP),
    ok.

t_gethostbyaddr_v6() -> required(v6).
t_gethostbyaddr_v6(doc) -> "Test the inet:gethostbyaddr/1 inet6 function.";
t_gethostbyaddr_v6(Config) when is_list(Config) ->
    ?line {Name6, FullName6, IPStr6, IP6, Aliases6} =
	ct:get_config(test_host_ipv6_only),

    ?line 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} ->
	    ?line {ok,HEnt6} = inet:gethostbyaddr(IP6),
	    ?line {error,Error6} = inet:gethostbyaddr(Name6),
	    ?line ok = io:format("Failure reason: ~p: ~s",
				 [Error6, inet:format_error(Error6)]),
	    ?line HEnt6_ = HEnt6#hostent{h_addrtype = inet6,
					 h_length = 16,
					 h_addr_list = [IP6]},
	    ?line HEnt6_ = HEnt6,
	    ?line check_elems([{HEnt6#hostent.h_name,[Name6,FullName6]},
			       {HEnt6#hostent.h_aliases,[[],Aliases6]}]),

	    ?line {_DName6, _DFullName6, DIPStr6, DIP6, _} =
		      ct:get_config(test_dummy_ipv6_host),
	    ?line {error,nxdomain} = inet:gethostbyaddr(DIPStr6),
	    ?line {error,nxdomain} = inet:gethostbyaddr(DIP6),    
	    ok
    end.

t_gethostbyname() -> required(v4).
t_gethostbyname(doc) -> "Test the inet:gethostbyname/1 function.";
t_gethostbyname(suite) -> [];
t_gethostbyname(Config) when is_list(Config) ->
    ?line {Name,FullName,IPStr,IP,Aliases,IP_46_Str,_} =
	ct:get_config(test_host_ipv4_only),
    ?line {ok,_} = inet:gethostbyname(IPStr),
    ?line {ok,HEnt} = inet:gethostbyname(Name),
    ?line {ok,HEnt} = inet:gethostbyname(list_to_atom(Name)),
    ?line HEnt_ = HEnt#hostent{h_addrtype = inet,
			       h_length = 4,
			       h_addr_list = [IP]},

    ?line HEnt_ = HEnt,
    ?line check_elems([{HEnt#hostent.h_name,[Name,FullName]},
		       {HEnt#hostent.h_aliases,[[],Aliases]}]),
    ?line {ok,HEntF} = inet:gethostbyname(FullName),
    ?line HEntF_ = HEntF#hostent{h_name = FullName,
				 h_addrtype = inet,
				 h_length = 4,
				 h_addr_list = [IP]},
    ?line HEntF_ = HEntF,
    ?line check_elems([{HEnt#hostent.h_aliases,[[],Aliases]}]),

    ?line {DName, _DFullName, _DIPStr, _DIP, _, _, _} =
	ct:get_config(test_dummy_host),
    ?line {error,nxdomain} = inet:gethostbyname(DName),
    ?line {error,nxdomain} = inet:gethostbyname(IP_46_Str).

t_gethostbyname_v6() -> required(v6).
t_gethostbyname_v6(doc) -> "Test the inet:gethostbyname/1 inet6 function.";
t_gethostbyname_v6(suite) -> [];
t_gethostbyname_v6(Config) when is_list(Config) ->
    ?line {Name, _, _, _,Aliases,IP_46_Str,IP_46} =
	ct:get_config(test_host_ipv4_only),

    case {inet:gethostbyname(IP_46_Str, inet6),
	  inet:gethostbyname(Name, inet6)} of
	{{ok,HEnt46},{ok,_}} -> 
	    ?line HEnt46_ = HEnt46#hostent{h_name = IP_46_Str,
					   h_addrtype = inet6,
					   h_length = 16,
					   h_addr_list = [IP_46]},
	    ?line HEnt46_ = HEnt46,
	    ?line check_elems([{HEnt46#hostent.h_aliases,[[],Aliases]}]),		  

	    ?line {Name6, FullName6, IPStr6, IP6, Aliases6} =
		      	ct:get_config(test_host_ipv6_only),
	    ?line {ok,_} = inet:gethostbyname(IPStr6, inet6),
	    ?line {ok,HEnt6} = inet:gethostbyname(Name6, inet6),
	    ?line {ok,HEnt6} = inet:gethostbyname(list_to_atom(Name6), inet6),
	    ?line case HEnt6#hostent.h_addr_list of
		      [IP6] ->				% ipv6 ok
			  ?line HEnt6_ = HEnt6#hostent{h_addrtype = inet6,
						       h_length = 16,
						       h_addr_list = [IP6]},
			  ?line HEnt6_ = HEnt6,	    
			  ?line check_elems([{HEnt6#hostent.h_name,[Name6,FullName6]},
					     {HEnt6#hostent.h_aliases,[[],Aliases6]}]);
		      _ ->				% ipv4 compatible addr
			  ?line {ok,HEnt4} = inet:gethostbyname(Name6, inet),
			  ?line [IP4] = HEnt4#hostent.h_addr_list,
			  ?line {ok,IP46_2} = 
			      inet_parse:ipv6_address("::ffff:"++inet_parse:ntoa(IP4)),
			  ?line HEnt6_ = HEnt6#hostent{h_addrtype = inet6,
						       h_length = 16,
						       h_addr_list = [IP46_2]},
			  ?line HEnt6_ = HEnt6,
			  ?line check_elems([{HEnt6#hostent.h_name,[Name6,FullName6]}])
		  end,
	    
	    ?line {ok,HEntF6} = inet:gethostbyname(FullName6, inet6),
	    ?line case HEntF6#hostent.h_addr_list of
		      [IP6] ->				% ipv6 ok
			  ?line HEntF6_ = HEntF6#hostent{h_name = FullName6,
							 h_addrtype = inet6,
							 h_length = 16,
							 h_addr_list = [IP6]},
			  ?line HEntF6_ = HEntF6,
			  ?line check_elems([{HEntF6#hostent.h_aliases,[[],Aliases6]}]);
		      _ ->				% ipv4 compatible addr
			  ?line {ok,HEntF4} = inet:gethostbyname(FullName6, inet),
			  ?line [IPF4] = HEntF4#hostent.h_addr_list,
			  ?line {ok,IPF46_2} = 
			      inet_parse:ipv6_address("::ffff:"++inet_parse:ntoa(IPF4)),
			  ?line HEntF6_ = HEntF6#hostent{h_addrtype = inet6,
							 h_length = 16,
							 h_addr_list = [IPF46_2]},
			  ?line HEntF6_ = HEntF6,
			  ?line check_elems([{HEntF6#hostent.h_name,[Name6,FullName6]}])
		  end,
	    
	    ?line {DName6, _DFullName6, _DIPStr6, _DIP6, _} =
		      ct:get_config(test_dummy_ipv6_host),
	    ?line {error,nxdomain} = inet:gethostbyname(DName6, inet6),
	    ok;
	{_,_} ->
	    {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) ->
    ?t:fail({no_match,Val,Tests0}).


t_getaddr() -> required(v4).
t_getaddr(doc) -> "Test the inet:getaddr/2 function.";
t_getaddr(suite) -> [];
t_getaddr(Config) when is_list(Config) ->
    ?line {Name,FullName,IPStr,IP,_,IP_46_Str,IP46} =
	ct:get_config(test_host_ipv4_only),
    ?line {ok,IP} = inet:getaddr(list_to_atom(Name), inet),
    ?line {ok,IP} = inet:getaddr(Name, inet),
    ?line {ok,IP} = inet:getaddr(FullName, inet),
    ?line {ok,IP} = inet:getaddr(IP, inet),
    ?line {ok,IP} = inet:getaddr(IPStr, inet),
    ?line {error,nxdomain} = inet:getaddr(IP_46_Str, inet),
    ?line {error,eafnosupport} = inet:getaddr(IP46, inet),

    ?line {DName, DFullName, DIPStr, DIP, _, _, _} = ct:get_config(test_dummy_host),
    ?line {error,nxdomain} = inet:getaddr(DName, inet),
    ?line {error,nxdomain} = inet:getaddr(DFullName, inet),
    ?line {ok,DIP} = inet:getaddr(DIPStr, inet),
    ?line {ok,DIP} = inet:getaddr(DIP, inet).

t_getaddr_v6() -> required(v4) ++ required(v6).
t_getaddr_v6(doc) -> "Test the inet:getaddr/2 function.";
t_getaddr_v6(suite) -> [];
t_getaddr_v6(Config) when is_list(Config) ->
    ?line {Name,FullName,IPStr,_IP,_,IP_46_Str,IP46} =
	ct:get_config(test_host_ipv4_only),
    case {inet:getaddr(IP_46_Str, inet6),inet:getaddr(Name, inet6)} of
	{{ok,IP46},{ok,_}} -> 
	    %% Since we suceeded in parsing an IPv6 address string and
	    %% look up the name, this computer fully supports IPv6.
	    ?line {ok,IP46} = inet:getaddr(IP46, inet6),
	    ?line {ok,IP46} = inet:getaddr(Name, inet6),
	    ?line {ok,IP46} = inet:getaddr(FullName, inet6),
	    ?line {ok,IP46} = inet:getaddr(IPStr, inet6),
%% 	    ?line IP4toIP6 = inet:getaddr(IPStr, inet6),
%% 	    ?line case IP4toIP6 of
%% 		      {ok,IP46} ->
%% 			  ?line ok;
%% 		      {error,nxdomain} ->
%% 			  ?line false =
%% 			      lists:member(native,
%% 					   inet_db:res_option(lookup))
%% 		  end,
	    ?line {Name6, FullName6, IPStr6, IP6, _} =
				      	ct:get_config(test_host_ipv6_only),
	    ?line {ok,_} = inet:getaddr(list_to_atom(Name6), inet6),
	    ?line {ok,_} = inet:getaddr(Name6, inet6),
	    ?line {ok,_} = inet:getaddr(FullName6, inet6),
	    ?line {ok,IP6} = inet:getaddr(IP6, inet6),
	    ?line {ok,IP6} = inet:getaddr(IPStr6, inet6),

	    ?line {DName6, DFullName6, DIPStr6, DIP6, _} =
		ct:get_config(test_dummy_ipv6_host),
	    ?line {error,nxdomain} = inet:getaddr(DName6, inet6),
	    ?line {error,nxdomain} = inet:getaddr(DFullName6, inet6),
	    ?line {ok,DIP6} = inet:getaddr(DIPStr6, inet6),
	    ?line {ok,DIP6} = inet:getaddr(DIP6, inet6),
	    ok;
	{_,_} ->
 	    {skip, "IPv6 is not supported on this host"}
    end.

ipv4_to_ipv6() -> required(v4).
ipv4_to_ipv6(doc) -> "Test if IPv4 address is converted to IPv6 address.";
ipv4_to_ipv6(suite) -> [];
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.
    ?line {_Name,_FullName,IPStr,_IP,Aliases,IP_46_Str,IP_46} =
	ct:get_config(test_host_ipv4_only),
    ?line 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 ->
		?line ?t:fail({ipv4_to_ipv6_lookup_failed,Other})
	end,
    ?line case {IP4to6Res,inet:gethostbyname(IPStr, inet6)} of
	      {true,{ok,HEnt}} ->
		  ?line HEnt_ = HEnt#hostent{h_addrtype = inet6,
					     h_length = 16,
					     h_addr_list = [IP_46]},
		  ?line HEnt_ = HEnt,
		  ?line check_elems([{HEnt#hostent.h_name,[IP_46_Str,IPStr]},
				     {HEnt#hostent.h_aliases,[[],Aliases]}]);
	      {_,IP4to6Res} -> ok
	  end,
    ok.

host_and_addr() -> required(hosts).
host_and_addr(doc) -> ["Test looking up hosts and addresses. Use 'ypcat hosts' ",
		       "or the local eqivalent to find all hosts."];
host_and_addr(suite) -> [];
host_and_addr(Config) when is_list(Config) ->
    ?line Dog = test_server:timetrap(test_server:minutes(5)),

    ?line lists:foreach(fun try_host/1, get_hosts(Config)),
    ?line test_server:timetrap_cancel(Dog),
    ok.

try_host({Ip0, Host}) ->
    ?line {ok,Ip} = inet:getaddr(Ip0, inet),
    ?line {ok,{hostent, _, _, inet, _, Ips1}} = inet:gethostbyaddr(Ip),
    ?line {ok,{hostent, _, _, inet, _, _Ips2}} = inet:gethostbyname(Host),
    ?line 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(test_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) ->
    ?line DataDir = ?config(data_dir,Config),
    ?line HostFile = filename:join(DataDir, "hosts"),
    ?line inet_parse:hosts(HostFile),
    ?line HostFileErr1 = filename:join(DataDir, "hosts_err1"),
    ?line inet_parse:hosts(HostFileErr1),
    ?line Resolv = filename:join(DataDir,"resolv.conf"),
    ?line inet_parse:resolv(Resolv),
    ?line ResolvErr1 = filename:join(DataDir,"resolv.conf.err1"),
    ?line inet_parse:resolv(ResolvErr1).

parse_address(Config) when is_list(Config) ->
    V4Strict =
	[{{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"}],
    V6Strict =
	[{{0,0,0,0,0,0,0,0},"::"},
	 {{15,0,0,0,0,0,0,2},"f::2"},
	 {{15,16#f11,0,0,0,0,256,2},"f:f11::0100:2"},
	 {{0,0,0,0,0,0,0,16#17},"::17"},
	 {{16#700,0,0,0,0,0,0,0},"0700::"},
	 {{0,0,0,0,0,0,2,1},"::2: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},"::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::"},
	 {{16#c11,16#c22,16#5c33,16#c440,16#55c0,16#c66c,16#77,16#88},
	  "c11:0c22:5c33:c440:55c0:c66c:77:0088"},
	 {{16#c11,0,16#5c33,16#c440,16#55c0,16#c66c,16#77,16#88},
	  "c11::5c33:c440:55c0:c66c:77:0088"},
	 {{16#c11,16#c22,0,16#c440,16#55c0,16#c66c,16#77,16#88},
	  "c11:0c22::c440:55c0:c66c:77:0088"},
	 {{16#c11,16#c22,16#5c33,0,16#55c0,16#c66c,16#77,16#88},
	  "c11:0c22:5c33::55c0:c66c:77:0088"},
	 {{16#c11,16#c22,16#5c33,16#c440,0,16#c66c,16#77,16#88},
	  "c11:0c22:5c33:c440::c66c:77:0088"},
	 {{16#c11,16#c22,16#5c33,16#c440,16#55c0,0,16#77,16#88},
	  "c11:0c22:5c33:c440:55c0::77:0088"},
	 {{16#c11,16#c22,16#5c33,16#c440,16#55c0,16#c66c,0,16#88},
	  "c11:0c22:5c33:c440:55c0:c66c::0088"},
	 {{16#c11,0,0,16#c440,16#55c0,16#c66c,16#77,16#88},
	  "c11::c440:55c0:c66c:77:0088"},
	 {{16#c11,16#c22,0,0,16#55c0,16#c66c,16#77,16#88},
	  "c11:0c22::55c0:c66c:77:0088"},
	 {{16#c11,16#c22,16#5c33,0,0,16#c66c,16#77,16#88},
	  "c11:0c22:5c33::c66c:77:0088"},
	 {{16#c11,16#c22,16#5c33,16#c440,0,0,16#77,16#88},
	  "c11:0c22:5c33:c440::77:0088"},
	 {{16#c11,16#c22,16#5c33,16#c440,16#55c0,0,0,16#88},
	  "c11:0c22:5c33:c440:55c0::0088"},
	 {{16#c11,0,0,0,16#55c0,16#c66c,16#77,16#88},
	  "c11::55c0:c66c:77:0088"},
	 {{16#c11,16#c22,0,0,0,16#c66c,16#77,16#88},
	  "c11:0c22::c66c:77:0088"},
	 {{16#c11,16#c22,16#5c33,0,0,0,16#77,16#88},
	  "c11:0c22:5c33::77:0088"},
	 {{16#c11,16#c22,16#5c33,16#c440,0,0,0,16#88},
	  "c11:0c22:5c33:c440::0088"},
	 {{16#c11,0,0,0,0,16#c66c,16#77,16#88},
	  "c11::c66c:77:0088"},
	 {{16#c11,16#c22,0,0,0,0,16#77,16#88},
	  "c11:0c22::77:0088"},
	 {{16#c11,16#c22,16#5c33,0,0,0,0,16#88},
	  "c11:0c22:5c33::0088"},
	 {{16#c11,0,0,0,0,0,16#77,16#88},
	  "c11::77:0088"},
	 {{16#c11,16#c22,0,0,0,0,0,16#88},
	  "c11:0c22::0088"},
	 {{0,0,0,0,0,65535,258,65534},"::FFFF:1.2.255.254"},
	 {{16#ffff,16#ffff,16#ffff,16#ffff,16#ffff,16#ffff,16#ffff,16#ffff},
	  "ffff:ffff:ffff:ffff:ffff:ffff:ffff:ffff"}
	|[{{D2,0,0,0,0,P,(D1 bsl 8) bor D2,(D3 bsl 8) bor D4},
	   erlang:integer_to_list(D2, 16)++"::"++Q++S}
	  || {{D1,D2,D3,D4},S} <- V4Strict,
	     {P,Q} <- [{0,""},{16#17,"17:"},{16#ff0,"0ff0:"}]]],
    V4Sloppy =
	[{{10,1,16#98,16#76},"10.0x019876"},
	 {{8#12,1,8#130,8#321},"012.01.054321"},
	 {{255,255,255,255},"255.255.255.0377"},
	 {{255,255,255,255},"0Xff.000000000377.0x0000ff.255"},
	 {{255,255,255,255},"255.255.65535"},
	 {{255,255,255,255},"255.0xFF.0177777"},
	 {{255,255,255,255},"255.16777215"},
	 {{255,255,255,255},"00377.0XFFFFFF"},
	 {{255,255,255,255},"4294967295"},
	 {{255,255,255,255},"0xffffffff"},
	 {{255,255,255,255},"00000000000037777777777"},
	 {{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 =
	[{{0,0,0,0,0,65535,(D1 bsl 8) bor D2,(D3 bsl 8) bor D4},S}
	 || {{D1,D2,D3,D4},S} <- V4Strict++V4Sloppy],
    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",
	 "::17000",
	 "10000::",
	 "::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
      (ipv6_address,
       V6Strict++V6Sloppy++V6Err++V4Err),
    t_parse_address
      (ipv6strict_address,
       V6Strict++V6Err++V4Err++[S || {_,S} <- V6Sloppy]),
    t_parse_address
      (ipv4_address,
       V4Strict++V4Sloppy++V4Err++V6Err++[S || {_,S} <- V6Strict]),
    t_parse_address
      (ipv4strict_address,
       V4Strict++V4Err++V6Err++[S || {_,S} <- V4Sloppy++V6Strict]).

t_parse_address(Func, []) ->
    io:format("~p done.~n", [Func]),
    ok;
t_parse_address(Func, [{Addr,String}|L]) ->
    io:format("~p = ~p.~n", [Addr,String]),
    {ok,Addr} = inet_parse:Func(String),
    t_parse_address(Func, L);
t_parse_address(Func, [String|L]) ->
    io:format("~p.~n", [String]),
    {error,einval} = inet_parse:Func(String),
    t_parse_address(Func, L).



t_gethostnative(suite) ->[];
t_gethostnative(doc) ->[];
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 os:type() of
	vxworks ->
	    {skipped, "VxWorks has no native gethostbyname()"};
	_ ->
	    ?line case inet_gethost_native:gethostbyname(
			 "a23456789012345678901234") of
		      {error,notfound} ->
			  ?line ok;
		      {error,no_data} ->
			  ?line ok
		  end
    end.

gethostnative_parallell(suite) ->    
    [];
gethostnative_parallell(doc) ->
    ["Check that the emulator survives crashes in gethost_native"];
gethostnative_parallell(Config) when is_list(Config) ->
    ?line {ok,Hostname} = inet:gethostname(),
    ?line {ok,_} = inet:gethostbyname(Hostname),
    case whereis(inet_gethost_native) of
	Pid when is_pid(Pid) ->
	    ?line do_gethostnative_parallell();
	_ ->
	    ?line {skipped, "Not running native gethostbyname"}
    end.

do_gethostnative_parallell() ->
    ?line PA = filename:dirname(code:which(?MODULE)),
    ?line {ok,Node} = ?t:start_node(gethost_parallell, slave, 
				     [{args, "-pa " ++ PA}]),
    ?line ok = rpc:call(Node, ?MODULE, parallell_gethost, []),
    ?line receive after 10000 -> ok end,
    ?line pong = net_adm:ping(Node),
    ?line ?t: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.
	    
cname_loop(suite) ->
    [];
cname_loop(doc) ->
    ["Check that the resolver handles a CNAME loop"];
cname_loop(Config) when is_list(Config) ->
    %% getbyname (hostent_by_domain)
    ?line ok = inet_db:add_rr("mydomain.com", in, ?S_CNAME, ttl, "mydomain.com"),
    ?line {error,nxdomain} = inet_db:getbyname("mydomain.com", ?S_A),
    ?line 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]},
    ?line {error,nxdomain} = inet_db:res_hostent_by_domain("mydomain.com", ?S_A, Rec),
    ok.



%% 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).
gethostnative_soft_restart(suite) ->    
    [];
gethostnative_soft_restart(doc) ->
    ["Check that no name lookups fails during soft restart "
     "of inet_gethost_native"];
gethostnative_soft_restart(Config) when is_list(Config) ->
    ?line gethostnative_control(Config, 
				#gethostnative_control{
				  control_seq=[soft_restart]}).


gethostnative_debug_level() -> required(hosts).
gethostnative_debug_level(suite) ->    
    [];
gethostnative_debug_level(doc) ->
    ["Check that no name lookups fails during debug level change "
     "of inet_gethost_native"];
gethostnative_debug_level(Config) when is_list(Config) ->
    ?line gethostnative_control(Config, 
				#gethostnative_control{
				  control_seq=[{debug_level,1},
					       {debug_level,0}]}).

gethostnative_control(Config, Optrec) ->
    ?line case inet_db:res_option(lookup) of
	      [native] ->
		  case whereis(inet_gethost_native) of
		      Pid when is_pid(Pid) ->
			  ?line gethostnative_control_1(Config, Optrec);
		      _ ->
			  ?line {skipped, "Not running native gethostbyname"}
		  end;
	      _ ->
		  ?line {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}) ->
    ?line {ok, Hostname} = inet:gethostname(),
    ?line {ok, _} = inet:gethostbyname(Hostname),
    ?line 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.
    ?line TrapExit = process_flag(trap_exit, true),
    ?line gethostnative_control_2([undefined], Interval, Delay, Cnt, N, Hosts),
    ?line test_server:format(
	    "First intermission: now starting control sequence ~w\n",
	    [Seq]),
    ?line erlang:display(first_intermission),
    ?line gethostnative_control_2(Seq, Interval, Delay, Cnt, N, Hosts),
    ?line erlang:display(second_intermission),
    ?line test_server:format(
	    "Second intermission:  now stopping control sequence ~w\n",
	    [Seq]),
    ?line gethostnative_control_2([undefined], Interval, Delay, Cnt, N, Hosts),
    ?line true = process_flag(trap_exit, TrapExit),
    ?line ok.

gethostnative_control_2(Seq, Interval, Delay, Cnt, N, Hosts) ->
    ?line Tag = make_ref(),
    ?line Parent = self(),
    ?line Lookupers = 
	[spawn_link(
	   fun () -> 
		   random:seed(),
		   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} ->
	    ?line 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) ->
    ?line
	receive
	    {'EXIT',Pid,Reason} ->
		?line case Reason of
			  Tag -> % Done
			      ?line control_loop_1
				      (Op, Interval, Tag,
				       lists:delete(Pid, Lookupers));
			  _ ->
			      ?line io:format("Lookuper ~p died: ~p",
					      [Pid,Reason]),
			      ?line test_server:fail("Lookuper died")
		      end
	after Interval ->
		?line if Op =/= undefined -> 
			      ?line ok = inet_gethost_native:control(Op);
			 true ->
			      ?line ok
		      end,
		?line 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 ->
	    ?line io:format("Name lookup error for ~p for ~p: ~p",
			    [self(),H,Error]),
	    Parent ! {Tag,Error}
    end,
    receive 
    after random:uniform(Delay) -> 
	    lookup_loop(Hs, Delay, Tag, Parent, Cnt-1, Hosts) 
    end.



getif(suite) ->
    [];
getif(doc) ->
    ["Tests basic functionality of getiflist, getif, and ifget"];
getif(Config) when is_list(Config) ->
    ?line case os:type() of
	      {unix,Osname} ->
		  ?line do_getif(Osname);
	      {_,_} ->
		  {skip,"inet:getif/0 probably not supported"}
	  end.

do_getif(Osname) ->
    ?line {ok,Hostname} = inet:gethostname(),
    ?line {ok,Address} = inet:getaddr(Hostname, inet),
    ?line {ok,Loopback} = inet:getaddr("localhost", inet),
    ?line {ok,Interfaces} = inet:getiflist(),
    ?line HWAs =
	lists:sort(
	  lists:foldl(
	    fun (I, Acc) ->
		    case inet:ifget(I, [hwaddr]) of
			{ok,[{hwaddr,A}]} -> [A|Acc];
			{ok,[]} -> Acc
		    end
	    end, [], Interfaces)),
    ?line io:format("HWAs = ~p~n", [HWAs]),
    ?line (Osname =/= sunos)
	andalso ((length(HWAs) > 0) orelse (?t:fail(no_HWAs))),
    ?line Addresses = 
	lists:sort(
	  lists:foldl(
	    fun (I, Acc) ->
		    case inet:ifget(I, [addr]) of
			{ok,[{addr,A}]} -> [A|Acc];
			{ok,[]} -> Acc
		    end
	    end, [], Interfaces)),
    ?line {ok,Getif} = inet:getif(),
    ?line Addresses = lists:sort([A || {A,_,_} <- Getif]),
    ?line true = ip_member(Address, Addresses),
    ?line true = ip_member(Loopback, Addresses),
    ?line ok.

getif_ifr_name_overflow(doc) ->
    "Test long interface names do not overrun buffer";
getif_ifr_name_overflow(Config) when is_list(Config) ->
    ?line case os:type() of
	      {unix,Osname} ->
		  ?line do_getif_ifr_name_overflow(Osname);
	      {_,_} ->
		  {skip,"inet:ifget/2 probably not supported"}
	  end.

do_getif_ifr_name_overflow(_) ->
    %% emulator should not crash
    ?line {ok,[]} = inet:ifget(lists:duplicate(128, "x"), [addr]),
    ok.

getservbyname_overflow(doc) ->
    "Test long service names do not overrun buffer";
getservbyname_overflow(Config) when is_list(Config) ->
    %% emulator should not crash
    ?line {error,einval} = inet:getservbyname(list_to_atom(lists:flatten(lists:duplicate(128, "x"))), tcp),
    ok.

getifaddrs(doc) ->
    "Test inet:gifaddrs/0";
getifaddrs(Config) when is_list (Config) ->
    ?line {ok,IfAddrs} = inet:getifaddrs(),
    ?line ?t:format("IfAddrs = ~p.~n", [IfAddrs]),
    ?line
	case
	    {os:type(),
	     [If ||
		 {If,Opts} <- IfAddrs,
		 lists:keymember(hwaddr, 1, Opts)]} of
	    {{unix,sunos},[]} -> ok;
	    {OT,[]} ->
		?t:fail({should_have_hwaddr,OT});
	    _ -> ok
	end,
    ?line Addrs =
	[element(1, A) || A <- ifaddrs(IfAddrs)],
    ?line ?t:format("Addrs = ~p.~n", [Addrs]),
    ?line [check_addr(Addr) || Addr <- Addrs],
    ok.

check_addr(Addr)
  when tuple_size(Addr) =:= 8,
       element(1, Addr) band 16#FFC0 =:= 16#FE80 ->
    ?line ?t:format("Addr: ~p link local; SKIPPED!~n", [Addr]),
    ok;
check_addr(Addr) ->
    ?line ?t:format("Addr: ~p.~n", [Addr]),
    ?line Ping = "ping",
    ?line Pong = "pong",
    ?line {ok,L} = gen_tcp:listen(0, [{ip,Addr},{active,false}]),
    ?line {ok,P} = inet:port(L),
    ?line {ok,S1} = gen_tcp:connect(Addr, P, [{active,false}]),
    ?line {ok,S2} = gen_tcp:accept(L),
    ?line ok = gen_tcp:send(S2, Ping),
    ?line {ok,Ping} = gen_tcp:recv(S1, length(Ping)),
    ?line ok = gen_tcp:send(S1, Pong),
    ?line ok = gen_tcp:close(S1),
    ?line {ok,Pong} = gen_tcp:recv(S2, length(Pong)),
    ?line ok = gen_tcp:close(S2),
    ?line ok = gen_tcp:close(L),
    ok.

-record(ifopts, {name,flags,addrs=[],hwaddr}).

ifaddrs([]) -> [];
ifaddrs([{If,Opts}|IOs]) ->
    ?line #ifopts{flags=Flags} = Ifopts =
	check_ifopts(Opts, #ifopts{name=If}),
    ?line case Flags =/= undefined andalso lists:member(up, Flags) of
	      true  ->
		  Ifopts#ifopts.addrs;
	      false ->
		  []
	  end++ifaddrs(IOs).

check_ifopts([], #ifopts{name=If,flags=Flags,addrs=Raddrs}=Ifopts) ->
    Addrs = lists:reverse(Raddrs),
    R = Ifopts#ifopts{addrs=Addrs},
    ?t:format("~p.~n", [R]),
    %% See how we did...
    if  is_list(Flags) -> ok;
	true ->
	    ?t:fail({flags_undefined,If})
    end,
    case lists:member(broadcast, Flags) of
	true ->
	    [case A of
		 {_,_,_} -> A;
		 {T,_} when tuple_size(T) =:= 8 -> A;
		 _ ->
		     ?t:fail({broaddr_missing,If,A})
	     end || A <- Addrs];
	false ->
	    [case A of {_,_} -> A;
		 _ ->
		     ?t:fail({should_have_netmask,If,A})
	     end || A <- Addrs]
    end,
    R;
check_ifopts([{flags,Flags}|Opts], #ifopts{flags=undefined}=Ifopts) ->
    check_ifopts(Opts, Ifopts#ifopts{flags=Flags});
check_ifopts([{flags,Fs}|Opts], #ifopts{flags=Flags}=Ifopts) ->
    case Fs of
	Flags ->
	    check_ifopts(Opts, Ifopts#ifopts{});
	_ ->
	    ?t:fail({multiple_flags,Fs,Ifopts})
    end;
check_ifopts(
  [{addr,Addr},{netmask,Netmask},{broadaddr,Broadaddr}|Opts],
  #ifopts{addrs=Addrs}=Ifopts) ->
    check_ifopts(Opts, Ifopts#ifopts{addrs=[{Addr,Netmask,Broadaddr}|Addrs]});
check_ifopts(
  [{addr,Addr},{netmask,Netmask}|Opts],
  #ifopts{addrs=Addrs}=Ifopts) ->
    check_ifopts(Opts, Ifopts#ifopts{addrs=[{Addr,Netmask}|Addrs]});
check_ifopts([{addr,Addr}|Opts], #ifopts{addrs=Addrs}=Ifopts) ->
    check_ifopts(Opts, Ifopts#ifopts{addrs=[{Addr}|Addrs]});
check_ifopts([{hwaddr,Hwaddr}|Opts], #ifopts{hwaddr=undefined}=Ifopts)
  when is_list(Hwaddr) ->
    check_ifopts(Opts, Ifopts#ifopts{hwaddr=Hwaddr});
check_ifopts([{hwaddr,HwAddr}|_], #ifopts{}=Ifopts) ->
    ?t:fail({multiple_hwaddrs,HwAddr,Ifopts}).

%% 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.