Erlang and IPv6

Tony Rogvall <>
Wed Sep 27 15:16:46 CEST 2000


 wrote:

> Hi,
>
> I'm working on making a server(Erlang) to support IPv6.  I have come to
> the conclusion that Erlang today don't support IPv6(not gen_tcp or
> gen_udp). I suppose I could use ports and use socket communication
> outside Erlang. Anyone who has a better suggestion?
>
> Nils Flemström, Sweden.

Erlang DO support IPv6 and has done so for several year. We even had the
FIRST web server running on the IPv6 test network :-)

The status is that the OTP group does not update the ipv6 modules since
there are no real request for it.
But here is the updated modules you  need for running R7 on top of IPv6.

To run distributed erlang on IPv6 you write:  erl -[s]name <n> -proto_dist
inet6_tcp

To use ipv6 for gen_tcp in a "normal" system you write
gen_tcp:connect(IP, Port, [{tcp_module,inet6_tcp} | Opts]).

Or you set the default tcp_module in your local .inetrc file (I do not
think this feature is documented)

All of this stuff of course requires a kernel that supports IPv6 ! (not
default on linux)

Please give me some feedback on progress ....

Regards

--
/Tony


-------------- next part --------------
%% ``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 via the world wide web 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.
%% 
%% The Initial Developer of the Original Code is Ericsson Utvecklings AB.
%% Portions created by Ericsson are Copyright 1999, Ericsson Utvecklings
%% AB. All Rights Reserved.''
%% 
%%     $Id$
%%
-module(inet6_tcp).

-export([connect/3, connect/4, listen/2, accept/1, accept/2, close/1]).
-export([send/2, recv/2, recv/3, unrecv/2]).
-export([controlling_process/2]).
-export([fdopen/2]).

-export([getserv/1, getaddr/1, getaddr/2, getaddrs/1, getaddrs/2]).

-include("inet_int.hrl").

%% inet_tcp port lookup
getserv(Port) when integer(Port) -> {ok, Port};
getserv(Name) when atom(Name)    -> inet:getservbyname(Name,tcp).

%% inet_tcp address lookup
getaddr(Address) -> inet:getaddr(Address, inet6).
getaddr(Address,Timer) -> inet:getaddr_tm(Address, inet6, Timer).

%% inet_tcp address lookup
getaddrs(Address) -> inet:getaddrs(Address, inet6).
getaddrs(Address,Timer) -> inet:getaddrs_tm(Address,inet6,Timer).

%%
%% Send data on a socket
%%
send(Socket, Packet) -> prim_inet:send(Socket, Packet).

%%
%% Receive data from a socket (inactive only)
%%
recv(Socket, Length) -> prim_inet:recv(Socket, Length).
recv(Socket, Length, Timeout) -> prim_inet:recv(Socket, Length, Timeout).

unrecv(Socket, Data) -> prim_inet:unrecv(Socket, Data).
%%
%% Close a socket (async)
%%
close(Socket) -> 
    inet:tcp_close(Socket).

%%
%% Set controlling process
%% FIXE ME: move messages to new owner!!!
%%
controlling_process(Socket, NewOwner) ->
    inet:tcp_controlling_process(Socket, NewOwner).

%%
%% Connect
%%
connect(Address, Port, Opts) ->
    do_connect(Address, Port, Opts, infinity).

connect(Address, Port, Opts, infinity) ->
    do_connect(Address, Port, Opts, infinity);
connect(Address, Port, Opts, Timeout) when integer(Timeout), Timeout >= 0 ->
    do_connect(Address, Port, Opts, Timeout).

do_connect(Addr = {A,B,C,D,E,F,G,H}, Port, Opts, Time) when 
  ?ip6(A,B,C,D,E,F,G,H), integer(Port) ->
    case inet:connect_options(Opts, inet6) of
	{error, Reason} -> exit(Reason);
	{ok, R} ->
	    Fd       = R#connect_opts.fd,
	    BAddr    = R#connect_opts.ifaddr,
	    BPort    = R#connect_opts.port,
	    SockOpts = R#connect_opts.opts,
	    case inet:open(Fd,BAddr,BPort,SockOpts,stream,inet6,?MODULE) of
		{ok, S} ->
		    case prim_inet:connect(S, Addr, Port, Time) of
			ok    -> {ok,S};
			Error ->  prim_inet:close(S), Error
		    end;
		Error -> Error
	    end
    end.

%% 
%% Listen
%%
listen(Port, Opts) when Port >= 0, Port =< 16#ffff ->
    case inet:listen_options([{port,Port} | Opts], inet6) of
	{error, Reason} -> exit(Reason);
	{ok, R} ->
	    Fd       = R#listen_opts.fd,
	    BAddr    = R#listen_opts.ifaddr,
	    BPort    = R#listen_opts.port,
	    SockOpts = R#listen_opts.opts,
	    case inet:open(Fd,BAddr,BPort,SockOpts,stream,inet6,?MODULE) of
		{ok, S} ->
		    case prim_inet:listen(S, R#listen_opts.backlog) of
			ok -> {ok, S};
			Error -> prim_inet:close(S), Error
		    end;
		Error -> Error
	    end
    end.

%%
%% Accept
%%
accept(L) -> 
    case prim_inet:accept(L) of
	{ok, S} ->
	    inet_db:register_socket(S, ?MODULE),
	    {ok,S};
	Error -> Error
    end.
	    
accept(L,Timeout) -> 
    case prim_inet:accept(L,Timeout) of
	{ok, S} ->
	    inet_db:register_socket(S, ?MODULE),
	    {ok,S};
	Error -> Error
    end.
    
%%
%% Create a port/socket from a file descriptor 
%%
fdopen(Fd, Opts) ->
    inet:fdopen(Fd, Opts, stream, inet6, ?MODULE).

-------------- next part --------------
%% ``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 via the world wide web 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.
%% 
%% The Initial Developer of the Original Code is Ericsson Utvecklings AB.
%% Portions created by Ericsson are Copyright 1999, Ericsson Utvecklings
%% AB. All Rights Reserved.''
%% 
%%     $Id$
%%
-module(inet6_tcp_dist).

%% Handles the connection setup phase with other Erlang nodes.

-export([listen/1, accept/1, accept_connection/5,
	 setup/5, close/1, select/1, is_node_name/1]).

%% internal exports

-export([accept_loop/2,do_accept/6,do_setup/6, getstat/1,tick/1]).

-import(error_logger,[error_msg/2]).

-include("net_address.hrl").



-define(to_port(Socket, Data),
	case inet6_tcp:send(Socket, Data) of
	    {error, closed} ->
		self() ! {tcp_closed, Socket},
	        {error, closed};
	    R ->
	        R
        end).


-include("dist.hrl").
-include("dist_util.hrl").
-record(tick, {read = 0,
	       write = 0,
	       tick = 0,
	       ticked = 0
	       }).

%% ------------------------------------------------------------
%%  Select this protocol based on node name
%%  select(Node) => Bool
%% ------------------------------------------------------------

select(Node) ->
    case split_node(atom_to_list(Node), $@, []) of
	[_, Host] ->
	    case inet:getaddr(Host,inet6) of
		{ok,_} -> true;
		_ -> false
	    end;
	_ -> false
    end.

%% ------------------------------------------------------------
%% Create the listen socket, i.e. the port that this erlang
%% node is accessible through.
%% ------------------------------------------------------------

listen(Name) ->
    case inet6_tcp:listen(0, [{active, false}, {packet,2}]) of
	{ok, Socket} ->
	    TcpAddress = get_tcp_address(Socket),
	    {_,Port} = TcpAddress#net_address.address,
	    {ok, Creation} = erl_epmd:register_node(Name, Port),
	    {ok, {Socket, TcpAddress, Creation}};
	Error ->
	    Error
    end.

%% ------------------------------------------------------------
%% Accepts new connection attempts from other Erlang nodes.
%% ------------------------------------------------------------

accept(Listen) ->
    spawn_link(?MODULE, accept_loop, [self(), Listen]).

accept_loop(Kernel, Listen) ->
    process_flag(priority, max),
    case inet6_tcp:accept(Listen) of
	{ok, Socket} ->
	    Kernel ! {accept,self(),Socket,inet,tcp},
	    controller(Kernel, Socket),
	    accept_loop(Kernel, Listen);
	Error ->
	    exit(Error)
    end.

controller(Kernel, Socket) ->
    receive
	{Kernel, controller, Pid} ->
	    flush_controller(Pid, Socket),
	    inet6_tcp:controlling_process(Socket, Pid),
	    flush_controller(Pid, Socket),
	    Pid ! {self(), controller};
	{Kernel, unsupported_protocol} ->
	    exit(unsupported_protocol)
    end.

flush_controller(Pid, Socket) ->
    receive
	{tcp, Socket, Data} ->
	    Pid ! {tcp, Socket, Data},
	    flush_controller(Pid, Socket);
	{tcp_closed, Socket} ->
	    Pid ! {tcp_closed, Socket},
	    flush_controller(Pid, Socket)
    after 0 ->
	    ok
    end.

%% ------------------------------------------------------------
%% Accepts a new connection attempt from another Erlang node.
%% Performs the handshake with the other side.
%% ------------------------------------------------------------

accept_connection(AcceptPid, Socket, MyNode, Allowed, SetupTime) ->
    spawn_link(?MODULE, do_accept,
	       [self(), AcceptPid, Socket, MyNode,
		Allowed, SetupTime]).

do_accept(Kernel, AcceptPid, Socket, MyNode, Allowed, SetupTime) ->
    process_flag(priority, max),
    receive
	{AcceptPid, controller} ->
	    Timer = dist_util:start_timer(SetupTime),
	    case check_ip(Socket) of
		true ->
		    HSData = #hs_data{
		      kernel_pid = Kernel,
		      this_node = MyNode,
		      socket = Socket,
		      timer = Timer,
		      this_flags = ?DFLAG_PUBLISHED bor
		      ?DFLAG_ATOM_CACHE bor
		      ?DFLAG_EXTENDED_REFERENCES bor
		      ?DFLAG_DIST_MONITOR bor
		      ?DFLAG_FUN_TAGS bor
		      ?DFLAG_DIST_MONITOR_NAME,
		      allowed = Allowed,
		      f_send = fun(S,D) -> inet6_tcp:send(S,D) end,
		      f_recv = fun(S,N,T) -> inet6_tcp:recv(S,N,T) 
			       end,
		      f_setopts_pre_nodeup = 
		      fun(S) ->
			      inet:setopts(S, 
					   [{active, false},
					    {packet, 4},
					    nodelay()])
		      end,
		      f_setopts_post_nodeup = 
		      fun(S) ->
			      inet:setopts(S, 
					   [{active, true},
					    {deliver, port},
					    {packet, 4},
					    nodelay()])
		      end,
		      f_getll = fun(S) ->
					inet:getll(S)
				end,
		      f_address = fun get_remote_id/2,
		      mf_tick = {?MODULE, tick},
		      mf_getstat = {?MODULE,getstat}
		     },
		    dist_util:handshake_other_started(HSData);
		{false,IP} ->
		    error_msg("** Connection attempt from "
			      "disallowed IP ~w ** ~n", [IP]),
		    ?shutdown(no_node)
	    end
    end.


%% we may not always want the nodelay behaviour
%% for performance reasons

nodelay() ->
    case application:get_env(kernel, dist_nodelay) of
	undefined ->
	    {nodelay, true};
	{ok, true} ->
	    {nodelay, true};
	{ok, false} ->
	    {nodelay, false};
	_ ->
	    {nodelay, true}
    end.
	    

%% ------------------------------------------------------------
%% Get remote information about a Socket.
%% ------------------------------------------------------------

get_remote_id(Socket, Node) ->
    {ok, Address} = inet:peername(Socket),
    [_, Host] = split_node(atom_to_list(Node), $@, []),
    #net_address {
		  address = Address,
		  host = Host,
		  protocol = tcp,
		  family = inet6 }.

%% ------------------------------------------------------------
%% Setup a new connection to another Erlang node.
%% Performs the handshake with the other side.
%% ------------------------------------------------------------

setup(Node, Type, MyNode, LongOrShortNames,SetupTime) ->
    spawn_link(?MODULE, do_setup, [self(),
				   Node,
				   Type,
				   MyNode,
				   LongOrShortNames,
				   SetupTime]).

do_setup(Kernel, Node, Type, MyNode, LongOrShortNames,SetupTime) ->
    process_flag(priority, max),
    ?trace("~p~n",[{inet_tcp_dist,self(),setup,Node}]),
    [Name, Address] = splitnode(Node, LongOrShortNames),
    case inet:getaddr(Address, inet6) of
	{ok, Ip} ->
	    Timer = dist_util:start_timer(SetupTime),
	    case erl_epmd:port_please(Name, Ip) of
		{port, TcpPort, Version} ->
		    ?trace("port_please(~p) -> version ~p~n", 
			   [Node,Version]),
		    dist_util:reset_timer(Timer),
		    case inet6_tcp:connect(Ip, TcpPort, 
					  [{active, false}, 
					   {packet,2}]) of
			{ok, Socket} ->
			    PubFlag = if Type == hidden -> 0;
					 true -> ?DFLAG_PUBLISHED
				      end,
			    HSData = #hs_data{
			      kernel_pid = Kernel,
			      other_node = Node,
			      this_node = MyNode,
			      socket = Socket,
			      timer = Timer,
			      this_flags = PubFlag bor
			      ?DFLAG_ATOM_CACHE bor
			      ?DFLAG_EXTENDED_REFERENCES bor
			      ?DFLAG_DIST_MONITOR bor
			      ?DFLAG_FUN_TAGS bor
			      ?DFLAG_DIST_MONITOR_NAME,
			      other_version = Version,
			      f_send = fun(S,D) -> 
					       inet6_tcp:send(S,D) 
				       end,
			      f_recv = fun(S,N,T) -> 
					       inet6_tcp:recv(S,N,T) 
				       end,
			      f_setopts_pre_nodeup = 
			      fun(S) ->
				      inet:setopts
					(S, 
					 [{active, false},
					  {packet, 4},
					  nodelay()])
			      end,
			      f_setopts_post_nodeup = 
			      fun(S) ->
				      inet:setopts
					(S, 
					 [{active, true},
					  {deliver, port},
					  {packet, 4},
					  nodelay()])
			      end,
			      f_getll = fun(S) ->
						inet:getll(S)
					end,
			      f_address = 
			      fun(_,_) ->
				      #net_address {
				   address = {Ip,TcpPort},
				   host = Address,
				   protocol = tcp,
				   family = inet}
			      end,
			      mf_tick = {?MODULE, tick},
			      mf_getstat = {?MODULE,getstat}
			     },
			    dist_util:handshake_we_started(HSData);
			_ ->
			    %% Other Node may have closed since 
			    %% port_please !
			    ?trace("other node (~p) "
				   "closed since port_please.~n", 
				   [Node]),
			    ?shutdown(Node)
		    end;
		_ ->
		    ?trace("port_please (~p) "
			   "failed.~n", [Node]),
		    ?shutdown(Node)
	    end;
	Other ->
	    ?trace("inet_getaddr(~p) "
		   "failed (~p).~n", [Node,Other]),
	    ?shutdown(Node)
    end.

%%
%% Close a socket.
%%
close(Socket) ->
    inet6_tcp:close(Socket).


%% If Node is illegal terminate the connection setup!!
splitnode(Node, LongOrShortNames) ->
    case split_node(atom_to_list(Node), $@, []) of
	[Name|Tail] when Tail /= [] ->
	    Host = lists:append(Tail),
	    case split_node(Host, $., []) of
		[_] when LongOrShortNames == longnames ->
		    error_msg("** System running to use "
			      "fully qualified "
			      "hostnames **~n"
			      "** Hostname ~s is illegal **~n",
			      [Host]),
		    ?shutdown(Node);
		L when length(L) > 1, LongOrShortNames == shortnames ->
		    error_msg("** System NOT running to use fully qualified "
			      "hostnames **~n"
			      "** Hostname ~s is illegal **~n",
			      [Host]),
		    ?shutdown(Node);
		_ ->
		    [Name, Host]
	    end;
	[_] ->
	    error_msg("** Nodename ~p illegal, no '@' character **~n",
		      [Node]),
	    ?shutdown(Node);
	_ ->
	    error_msg("** Nodename ~p illegal **~n", [Node]),
	    ?shutdown(Node)
    end.

split_node([Chr|T], Chr, Ack) -> [lists:reverse(Ack)|split_node(T, Chr, [])];
split_node([H|T], Chr, Ack)   -> split_node(T, Chr, [H|Ack]);
split_node([], _, Ack)        -> [lists:reverse(Ack)].

%% ------------------------------------------------------------
%% Fetch local information about a Socket.
%% ------------------------------------------------------------
get_tcp_address(Socket) ->
    {ok, Address} = inet:sockname(Socket),
    {ok, Host} = inet:gethostname(),
    #net_address {
		  address = Address,
		  host = Host,
		  protocol = tcp,
		  family = inet6
		 }.

%% ------------------------------------------------------------
%% Do only accept new connection attempts from nodes at our
%% own LAN, if the check_ip environment parameter is true.
%% ------------------------------------------------------------
check_ip(Socket) ->
    case application:get_env(check_ip) of
	{ok, true} ->
	    case get_ifs(Socket) of
		{ok, IFs, IP} ->
		    check_ip(IFs, IP);
		_ ->
		    ?shutdown(no_node)
	    end;
	_ ->
	    true
    end.

get_ifs(Socket) ->
    case inet:peername(Socket) of
	{ok, {IP, _}} ->
	    case inet:getif(Socket) of
		{ok, IFs} -> {ok, IFs, IP};
		Error     -> Error
	    end;
	Error ->
	    Error
    end.

check_ip([{OwnIP, _, Netmask}|IFs], PeerIP) ->
    case {mask(Netmask, PeerIP), mask(Netmask, OwnIP)} of
	{M, M} -> true;
	_      -> check_ip(IFs, PeerIP)
    end;
check_ip([], PeerIP) ->
    {false, PeerIP}.
    
mask({M1,M2,M3,M4,M5,M6,M7,M8}, {IP1,IP2,IP3,IP4,IP5,IP6,IP7,IP8}) ->
    {M1 band IP1,
     M2 band IP2,
     M3 band IP3,
     M4 band IP4,
     M5 band IP5,
     M6 band IP6,
     M7 band IP7,
     M8 band IP8 }.

is_node_name(Node) when atom(Node) ->
    case split_node(atom_to_list(Node), $@, []) of
	[_, Host] -> true;
	_ -> false
    end;
is_node_name(Node) ->
    false.
tick(Sock) ->
    ?to_port(Sock,[]).
getstat(Socket) ->
    case inet:getstat(Socket, [recv_cnt, send_cnt, send_pend]) of
	{ok, Stat} ->
	    split_stat(Stat,0,0,0);
	Error ->
	    Error
    end.

split_stat([{recv_cnt, R}|Stat], _, W, P) ->
    split_stat(Stat, R, W, P);
split_stat([{send_cnt, W}|Stat], R, _, P) ->
    split_stat(Stat, R, W, P);
split_stat([{send_pend, P}|Stat], R, W, _) ->
    split_stat(Stat, R, W, P);
split_stat([], R, W, P) ->
    {ok, R, W, P}.


-------------- next part --------------
%% ``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 via the world wide web 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.
%% 
%% The Initial Developer of the Original Code is Ericsson Utvecklings AB.
%% Portions created by Ericsson are Copyright 1999, Ericsson Utvecklings
%% AB. All Rights Reserved.''
%% 
%%     $Id$
%%
-module(inet6_udp).

-export([open/1, open/2, close/1]).
-export([send/2, send/4, recv/2, recv/3, connect/3]).
-export([controlling_process/2]).
-export([fdopen/2]).

-export([getserv/1, getaddr/1, getaddr/2]).

-include("inet_int.hrl").

%% inet_udp port lookup
getserv(Port) when integer(Port) -> {ok, Port};
getserv(Name) when atom(Name)    -> inet:getservbyname(Name,udp).

%% inet_udp address lookup
getaddr(Address) -> inet:getaddr(Address, inet6).
getaddr(Address,Timer) -> inet:getaddr(Address, inet6, Timer).

open(Port) -> open(Port, []).

open(Port, Opts) when Port >= 0, Port =< 16#ffff ->
    case inet:udp_options([{port,Port} | Opts], inet6) of
	{error, Reason} -> exit(Reason);
	{ok, R} ->
	    Fd       = R#udp_opts.fd,
	    BAddr    = R#udp_opts.ifaddr,
	    BPort    = R#udp_opts.port,
	    SockOpts = R#udp_opts.opts,
	    inet:open(Fd,BAddr,BPort,SockOpts,dgram,inet,?MODULE)
    end.

send(S, Addr = {A,B,C,D,E,F,G,H},P,Data) when 
  ?ip6(A,B,C,D,E,F,G,H),P>=0,P=<16#ffff ->
    prim_inet:sendto(S, Addr, P, Data).

send(S, Data) ->
    prim_inet:sendto(S, {0,0,0,0,0,0,0,0}, 0, Data).
    
connect(S, Addr = {A,B,C,D,E,F,G,H}, P) when 
  ?ip6(A,B,C,D,E,F,G,H), P>=0, P=<16#ffff->
    prim_inet:connect(S, Addr, P).

recv(S,Len) ->
    prim_inet:recvfrom(S, Len).

recv(S,Len,Time) ->
    prim_inet:recvfrom(S, Len, Time).

close(S) ->
    inet:udp_close(S).

%%
%% Set controlling process:
%% 1) First sync socket into a known state
%% 2) Move all messages onto the new owners message queue
%% 3) Commit the owner 
%% 4) Wait for ack of new Owner (since socket does some link and unlink)
%%

controlling_process(Socket, NewOwner) ->
    inet:udp_controlling_process(Socket, NewOwner).

%%
%% Create a port/socket from a file descriptor 
%%
fdopen(Fd, Opts) ->
    inet:fdopen(Fd, Opts, dgram, inet6, ?MODULE).
-------------- next part --------------
A non-text attachment was scrubbed...
Name: tony.vcf
Type: text/x-vcard
Size: 352 bytes
Desc: Card for Tony Rogvall
URL: <http://erlang.org/pipermail/erlang-questions/attachments/20000927/d72f7e7a/attachment.vcf>


More information about the erlang-questions mailing list