%% %% %CopyrightBegin% %% %% Copyright Ericsson AB 1997-2009. 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). -include("inet.hrl"). -include("inet_int.hrl"). -include("inet_sctp.hrl"). %% socket -export([peername/1, sockname/1, port/1, send/2, setopts/2, getopts/2, getif/1, getif/0, getiflist/0, getiflist/1, ifget/3, ifget/2, ifset/3, ifset/2, getstat/1, getstat/2, ip/1, stats/0, options/0, pushf/3, popf/1, close/1, gethostname/0, gethostname/1]). -export([connect_options/2, listen_options/2, udp_options/2, sctp_options/2]). -export([i/0, i/1, i/2]). -export([getll/1, getfd/1, open/7, fdopen/5]). -export([tcp_controlling_process/2, udp_controlling_process/2, tcp_close/1, udp_close/1]). %% used by socks5 -export([setsockname/2, setpeername/2]). %% resolve -export([gethostbyname/1, gethostbyname/2, gethostbyname/3, gethostbyname_tm/3]). -export([gethostbyaddr/1, gethostbyaddr/2, gethostbyaddr_tm/2]). -export([getservbyname/2, getservbyport/2]). -export([getaddrs/2, getaddrs/3, getaddrs_tm/3, getaddr/2, getaddr/3, getaddr_tm/3]). -export([translate_ip/2]). -export([get_rc/0]). %% format error -export([format_error/1]). %% timer interface -export([start_timer/1, timeout/1, timeout/2, stop_timer/1]). %% imports -import(lists, [append/1, duplicate/2, filter/2, foldl/3]). %% Record Signature -define(RS(Record), {Record, record_info(size, Record)}). %% Record Signature Check (guard) -define(RSC(Record, RS), element(1, Record) =:= element(1, RS), tuple_size(Record) =:= element(2, RS)). %%% --------------------------------- %%% Contract type definitions -type socket() :: port(). -type posix() :: atom(). -type socket_setopt() :: {'raw', non_neg_integer(), non_neg_integer(), binary()} | %% TCP/UDP options {'reuseaddr', boolean()} | {'keepalive', boolean()} | {'dontroute', boolean()} | {'linger', {boolean(), non_neg_integer()}} | {'broadcast', boolean()} | {'sndbuf', non_neg_integer()} | {'recbuf', non_neg_integer()} | {'priority', non_neg_integer()} | {'tos', non_neg_integer()} | {'nodelay', boolean()} | {'multicast_ttl', non_neg_integer()} | {'multicast_loop', boolean()} | {'multicast_if', ip_address()} | {'add_membership', {ip_address(), ip_address()}} | {'drop_membership', {ip_address(), ip_address()}} | {'header', non_neg_integer()} | {'buffer', non_neg_integer()} | {'active', boolean() | 'once'} | {'packet', 0 | 1 | 2 | 4 | 'raw' | 'sunrm' | 'asn1' | 'cdr' | 'fcgi' | 'line' | 'tpkt' | 'http' | 'httph' | 'http_bin' | 'httph_bin' } | {'mode', list() | binary()} | {'port', 'port', 'term'} | {'exit_on_close', boolean()} | {'low_watermark', non_neg_integer()} | {'high_watermark', non_neg_integer()} | {'bit8', 'clear' | 'set' | 'on' | 'off'} | {'send_timeout', non_neg_integer() | 'infinity'} | {'send_timeout_close', boolean()} | {'delay_send', boolean()} | {'packet_size', non_neg_integer()} | {'read_packets', non_neg_integer()} | %% SCTP options {'sctp_rtoinfo', #sctp_rtoinfo{}} | {'sctp_associnfo', #sctp_assocparams{}} | {'sctp_initmsg', #sctp_initmsg{}} | {'sctp_nodelay', boolean()} | {'sctp_autoclose', non_neg_integer()} | {'sctp_disable_fragments', boolean()} | {'sctp_i_want_mapped_v4_addr', boolean()} | {'sctp_maxseg', non_neg_integer()} | {'sctp_primary_addr', #sctp_prim{}} | {'sctp_set_peer_primary_addr', #sctp_setpeerprim{}} | {'sctp_adaptation_layer', #sctp_setadaptation{}} | {'sctp_peer_addr_params', #sctp_paddrparams{}} | {'sctp_default_send_param', #sctp_sndrcvinfo{}} | {'sctp_events', #sctp_event_subscribe{}} | {'sctp_delayed_ack_time', #sctp_assoc_value{}}. -type socket_getopt() :: {'raw', non_neg_integer(), non_neg_integer(), binary()|non_neg_integer()} | %% TCP/UDP options 'reuseaddr' | 'keepalive' | 'dontroute' | 'linger' | 'broadcast' | 'sndbuf' | 'recbuf' | 'priority' | 'tos' | 'nodelay' | 'multicast_ttl' | 'multicast_loop' | 'multicast_if' | 'add_membership' | 'drop_membership' | 'header' | 'buffer' | 'active' | 'packet' | 'mode' | 'port' | 'exit_on_close' | 'low_watermark' | 'high_watermark' | 'bit8' | 'send_timeout' | 'send_timeout_close' | 'delay_send' | 'packet_size' | 'read_packets' | %% SCTP options {'sctp_status', #sctp_status{}} | 'sctp_get_peer_addr_info' | {'sctp_get_peer_addr_info', #sctp_status{}} | 'sctp_rtoinfo' | {'sctp_rtoinfo', #sctp_rtoinfo{}} | 'sctp_associnfo' | {'sctp_associnfo', #sctp_assocparams{}} | 'sctp_initmsg' | {'sctp_initmsg', #sctp_initmsg{}} | 'sctp_nodelay' | 'sctp_autoclose' | 'sctp_disable_fragments' | 'sctp_i_want_mapped_v4_addr' | 'sctp_maxseg' | {'sctp_primary_addr', #sctp_prim{}} | {'sctp_set_peer_primary_addr', #sctp_setpeerprim{}} | 'sctp_adaptation_layer' | {'sctp_adaptation_layer', #sctp_setadaptation{}} | {'sctp_peer_addr_params', #sctp_paddrparams{}} | 'sctp_default_send_param' | {'sctp_default_send_param', #sctp_sndrcvinfo{}} | 'sctp_events' | {'sctp_events', #sctp_event_subscribe{}} | 'sctp_delayed_ack_time' | {'sctp_delayed_ack_time', #sctp_assoc_value{}}. -type ether_address() :: [0..255]. -type if_setopt() :: {'addr', ip_address()} | {'broadaddr', ip_address()} | {'dstaddr', ip_address()} | {'mtu', non_neg_integer()} | {'netmask', ip_address()} | {'flags', ['up' | 'down' | 'broadcast' | 'no_broadcast' | 'pointtopoint' | 'no_pointtopoint' | 'running' | 'multicast']} | {'hwaddr', ether_address()}. -type if_getopt() :: 'addr' | 'broadaddr' | 'dstaddr' | 'mtu' | 'netmask' | 'flags' |'hwaddr'. -type family_option() :: 'inet' | 'inet6'. -type protocol_option() :: 'tcp' | 'udp' | 'sctp'. -type stat_option() :: 'recv_cnt' | 'recv_max' | 'recv_avg' | 'recv_oct' | 'recv_dvi' | 'send_cnt' | 'send_max' | 'send_avg' | 'send_oct' | 'send_pend'. %%% --------------------------------- -spec get_rc() -> [{any(),any()}]. get_rc() -> inet_db:get_rc(). -spec close(Socket :: socket()) -> 'ok'. close(Socket) -> prim_inet:close(Socket), receive {Closed, Socket} when Closed =:= tcp_closed; Closed =:= udp_closed -> ok after 0 -> ok end. -spec peername(Socket :: socket()) -> {'ok', {ip_address(), non_neg_integer()}} | {'error', posix()}. peername(Socket) -> prim_inet:peername(Socket). -spec setpeername(Socket :: socket(), Address :: {ip_address(), ip_port()}) -> 'ok' | {'error', any()}. setpeername(Socket, {IP,Port}) -> prim_inet:setpeername(Socket, {IP,Port}); setpeername(Socket, undefined) -> prim_inet:setpeername(Socket, undefined). -spec sockname(Socket :: socket()) -> {'ok', {ip_address(), non_neg_integer()}} | {'error', posix()}. sockname(Socket) -> prim_inet:sockname(Socket). -spec setsockname(Socket :: socket(), Address :: {ip_address(), ip_port()}) -> 'ok' | {'error', any()}. setsockname(Socket, {IP,Port}) -> prim_inet:setsockname(Socket, {IP,Port}); setsockname(Socket, undefined) -> prim_inet:setsockname(Socket, undefined). -spec port(Socket :: socket()) -> {'ok', ip_port()} | {'error', any()}. port(Socket) -> case prim_inet:sockname(Socket) of {ok, {_,Port}} -> {ok, Port}; Error -> Error end. -spec send(Socket :: socket(), Packet :: iolist()) -> % iolist()? 'ok' | {'error', posix()}. send(Socket, Packet) -> prim_inet:send(Socket, Packet). -spec setopts(Socket :: socket(), Opts :: [socket_setopt()]) -> 'ok' | {'error', posix()}. setopts(Socket, Opts) -> prim_inet:setopts(Socket, Opts). -spec getopts(Socket :: socket(), Opts :: [socket_getopt()]) -> {'ok', [socket_setopt()]} | {'error', posix()}. getopts(Socket, Opts) -> prim_inet:getopts(Socket, Opts). -spec getiflist(Socket :: socket()) -> {'ok', [string()]} | {'error', posix()}. getiflist(Socket) -> prim_inet:getiflist(Socket). -spec getiflist() -> {'ok', [string()]} | {'error', posix()}. getiflist() -> withsocket(fun(S) -> prim_inet:getiflist(S) end). -spec ifget(Socket :: socket(), Name :: string() | atom(), Opts :: [if_getopt()]) -> {'ok', [if_setopt()]} | {'error', posix()}. ifget(Socket, Name, Opts) -> prim_inet:ifget(Socket, Name, Opts). -spec ifget(Name :: string() | atom(), Opts :: [if_getopt()]) -> {'ok', [if_setopt()]} | {'error', posix()}. ifget(Name, Opts) -> withsocket(fun(S) -> prim_inet:ifget(S, Name, Opts) end). -spec ifset(Socket :: socket(), Name :: string() | atom(), Opts :: [if_setopt()]) -> 'ok' | {'error', posix()}. ifset(Socket, Name, Opts) -> prim_inet:ifset(Socket, Name, Opts). -spec ifset(Name :: string() | atom(), Opts :: [if_setopt()]) -> 'ok' | {'error', posix()}. ifset(Name, Opts) -> withsocket(fun(S) -> prim_inet:ifset(S, Name, Opts) end). -spec getif() -> {'ok', [{ip_address(), ip_address() | 'undefined', ip_address()}]} | {'error', posix()}. getif() -> withsocket(fun(S) -> getif(S) end). %% backwards compatible getif -spec getif(Socket :: socket()) -> {'ok', [{ip_address(), ip_address() | 'undefined', ip_address()}]} | {'error', posix()}. getif(Socket) -> case prim_inet:getiflist(Socket) of {ok, IfList} -> {ok, lists:foldl( fun(Name,Acc) -> case prim_inet:ifget(Socket,Name, [addr,broadaddr,netmask]) of {ok,[{addr,A},{broadaddr,B},{netmask,M}]} -> [{A,B,M}|Acc]; %% Some interfaces does not have a b-addr {ok,[{addr,A},{netmask,M}]} -> [{A,undefined,M}|Acc]; _ -> Acc end end, [], IfList)}; Error -> Error end. withsocket(Fun) -> case inet_udp:open(0,[]) of {ok,Socket} -> Res = Fun(Socket), inet_udp:close(Socket), Res; Error -> Error end. pushf(_Socket, Fun, _State) when is_function(Fun) -> {error, einval}. popf(_Socket) -> {error, einval}. %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% % the hostname is not cached any more because this % could cause troubles on at least windows with plug-and-play % and network-cards inserted and removed in conjunction with % use of the DHCP-protocol % should never fail -spec gethostname() -> {'ok', string()}. gethostname() -> case inet_udp:open(0,[]) of {ok,U} -> {ok,Res} = gethostname(U), inet_udp:close(U), {Res2,_} = lists:splitwith(fun($.)->false;(_)->true end,Res), {ok, Res2}; _ -> {ok, "nohost.nodomain"} end. -spec gethostname(Socket :: socket()) -> {'ok', string()} | {'error', posix()}. gethostname(Socket) -> prim_inet:gethostname(Socket). -spec getstat(Socket :: socket()) -> {'ok', [{stat_option(), integer()}]} | {'error', posix()}. getstat(Socket) -> prim_inet:getstat(Socket, stats()). -spec getstat(Socket :: socket(), Statoptions :: [stat_option()]) -> {'ok', [{stat_option(), integer()}]} | {'error', posix()}. getstat(Socket,What) -> prim_inet:getstat(Socket, What). -spec gethostbyname(Name :: string() | atom()) -> {'ok', #hostent{}} | {'error', posix()}. gethostbyname(Name) -> gethostbyname_tm(Name, inet, false). -spec gethostbyname(Name :: string() | atom(), Family :: family_option()) -> {'ok', #hostent{}} | {'error', posix()}. gethostbyname(Name,Family) -> gethostbyname_tm(Name, Family, false). -spec gethostbyname(Name :: string() | atom(), Family :: family_option(), Timeout :: non_neg_integer() | 'infinity') -> {'ok', #hostent{}} | {'error', posix()}. gethostbyname(Name,Family,Timeout) -> Timer = start_timer(Timeout), Res = gethostbyname_tm(Name,Family,Timer), stop_timer(Timer), Res. gethostbyname_tm(Name,Family,Timer) -> gethostbyname_tm(Name,Family,Timer,inet_db:res_option(lookup)). -spec gethostbyaddr(Address :: string() | ip_address()) -> {'ok', #hostent{}} | {'error', posix()}. gethostbyaddr(Address) -> gethostbyaddr_tm(Address, false). -spec gethostbyaddr(Address :: string() | ip_address(), Timeout :: non_neg_integer() | 'infinity') -> {'ok', #hostent{}} | {'error', posix()}. gethostbyaddr(Address,Timeout) -> Timer = start_timer(Timeout), Res = gethostbyaddr_tm(Address, Timer), stop_timer(Timer), Res. gethostbyaddr_tm(Address,Timer) -> gethostbyaddr_tm(Address, Timer, inet_db:res_option(lookup)). -spec ip(Ip :: ip_address() | string() | atom()) -> {'ok', ip_address()} | {'error', posix()}. ip({A,B,C,D}) when ?ip(A,B,C,D) -> {ok, {A,B,C,D}}; ip(Name) -> case gethostbyname(Name) of {ok, Ent} -> {ok, hd(Ent#hostent.h_addr_list)}; Error -> Error end. %% This function returns the erlang port used (with inet_drv) -spec getll(Socket :: socket()) -> {'ok', socket()}. getll(Socket) when is_port(Socket) -> {ok, Socket}. %% %% Return the internal file descriptor number %% -spec getfd(Socket :: socket()) -> {'ok', non_neg_integer()} | {'error', posix()}. getfd(Socket) -> prim_inet:getfd(Socket). %% %% Lookup an ip address %% -spec getaddr(Host :: ip_address() | string() | atom(), Family :: family_option()) -> {'ok', ip_address()} | {'error', posix()}. getaddr(Address, Family) -> getaddr(Address, Family, infinity). -spec getaddr(Host :: ip_address() | string() | atom(), Family :: family_option(), Timeout :: non_neg_integer() | 'infinity') -> {'ok', ip_address()} | {'error', posix()}. getaddr(Address, Family, Timeout) -> Timer = start_timer(Timeout), Res = getaddr_tm(Address, Family, Timer), stop_timer(Timer), Res. getaddr_tm(Address, Family, Timer) -> case getaddrs_tm(Address, Family, Timer) of {ok, [IP|_]} -> {ok, IP}; Error -> Error end. -spec getaddrs(Host :: ip_address() | string() | atom(), Family :: family_option()) -> {'ok', [ip_address()]} | {'error', posix()}. getaddrs(Address, Family) -> getaddrs(Address, Family, infinity). -spec getaddrs(Host :: ip_address() | string() | atom(), Family :: family_option(), Timeout :: non_neg_integer() | 'infinity') -> {'ok', [ip_address()]} | {'error', posix()}. getaddrs(Address, Family, Timeout) -> Timer = start_timer(Timeout), Res = getaddrs_tm(Address, Family, Timer), stop_timer(Timer), Res. -spec getservbyport(Port :: ip_port(), Protocol :: atom() | string()) -> {'ok', string()} | {'error', posix()}. getservbyport(Port, Proto) -> case inet_udp:open(0, []) of {ok,U} -> Res = prim_inet:getservbyport(U, Port, Proto), inet_udp:close(U), Res; Error -> Error end. -spec getservbyname(Name :: atom() | string(), Protocol :: atom() | string()) -> {'ok', ip_port()} | {'error', posix()}. getservbyname(Name, Protocol) when is_atom(Name) -> case inet_udp:open(0, []) of {ok,U} -> Res = prim_inet:getservbyname(U, Name, Protocol), inet_udp:close(U), Res; Error -> Error end. %% Return a list of available options options() -> [ tos, priority, reuseaddr, keepalive, dontroute, linger, broadcast, sndbuf, recbuf, nodelay, buffer, header, active, packet, deliver, mode, multicast_if, multicast_ttl, multicast_loop, exit_on_close, high_watermark, low_watermark, bit8, send_timeout, send_timeout_close ]. %% Return a list of statistics options -spec stats() -> [stat_option(),...]. stats() -> [recv_oct, recv_cnt, recv_max, recv_avg, recv_dvi, send_oct, send_cnt, send_max, send_avg, send_pend]. %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% %% Available options for tcp:connect %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% connect_options() -> [tos, priority, reuseaddr, keepalive, linger, sndbuf, recbuf, nodelay, header, active, packet, packet_size, buffer, mode, deliver, exit_on_close, high_watermark, low_watermark, bit8, send_timeout, send_timeout_close, delay_send,raw]. connect_options(Opts, Family) -> BaseOpts = case application:get_env(kernel, inet_default_connect_options) of {ok,List} when is_list(List) -> NList = [{active, true} | lists:keydelete(active,1,List)], #connect_opts{ opts = NList}; {ok,{active,_Bool}} -> #connect_opts{ opts = [{active,true}]}; {ok,Option} -> #connect_opts{ opts = [{active,true}, Option]}; _ -> #connect_opts{ opts = [{active,true}]} end, case con_opt(Opts, BaseOpts, connect_options()) of {ok, R} -> {ok, R#connect_opts { ifaddr = translate_ip(R#connect_opts.ifaddr, Family) }}; Error -> Error end. con_opt([{raw,A,B,C}|Opts],R,As) -> con_opt([{raw,{A,B,C}}|Opts],R,As); con_opt([Opt | Opts], R, As) -> case Opt of {ip,IP} -> con_opt(Opts, R#connect_opts { ifaddr = IP }, As); {ifaddr,IP} -> con_opt(Opts, R#connect_opts { ifaddr = IP }, As); {port,P} -> con_opt(Opts, R#connect_opts { port = P }, As); {fd,Fd} -> con_opt(Opts, R#connect_opts { fd = Fd }, As); binary -> con_add(mode, binary, R, Opts, As); list -> con_add(mode, list, R, Opts, As); {tcp_module,_} -> con_opt(Opts, R, As); inet -> con_opt(Opts, R, As); inet6 -> con_opt(Opts, R, As); {Name,Val} when is_atom(Name) -> con_add(Name, Val, R, Opts, As); _ -> {error, badarg} end; con_opt([], R, _) -> {ok, R}. con_add(Name, Val, R, Opts, AllOpts) -> case add_opt(Name, Val, R#connect_opts.opts, AllOpts) of {ok, SOpts} -> con_opt(Opts, R#connect_opts { opts = SOpts }, AllOpts); Error -> Error end. %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% %% Available options for tcp:listen %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% listen_options() -> [tos, priority, reuseaddr, keepalive, linger, sndbuf, recbuf, nodelay, header, active, packet, buffer, mode, deliver, backlog, exit_on_close, high_watermark, low_watermark, bit8, send_timeout, send_timeout_close, delay_send, packet_size,raw]. listen_options(Opts, Family) -> BaseOpts = case application:get_env(kernel, inet_default_listen_options) of {ok,List} when is_list(List) -> NList = [{active, true} | lists:keydelete(active,1,List)], #listen_opts{ opts = NList}; {ok,{active,_Bool}} -> #listen_opts{ opts = [{active,true}]}; {ok,Option} -> #listen_opts{ opts = [{active,true}, Option]}; _ -> #listen_opts{ opts = [{active,true}]} end, case list_opt(Opts, BaseOpts, listen_options()) of {ok, R} -> {ok, R#listen_opts { ifaddr = translate_ip(R#listen_opts.ifaddr, Family) }}; Error -> Error end. list_opt([{raw,A,B,C}|Opts], R, As) -> list_opt([{raw,{A,B,C}}|Opts], R, As); list_opt([Opt | Opts], R, As) -> case Opt of {ip,IP} -> list_opt(Opts, R#listen_opts { ifaddr = IP }, As); {ifaddr,IP} -> list_opt(Opts, R#listen_opts { ifaddr = IP }, As); {port,P} -> list_opt(Opts, R#listen_opts { port = P }, As); {fd,Fd} -> list_opt(Opts, R#listen_opts { fd = Fd }, As); {backlog,BL} -> list_opt(Opts, R#listen_opts { backlog = BL }, As); binary -> list_add(mode, binary, R, Opts, As); list -> list_add(mode, list, R, Opts, As); {tcp_module,_} -> list_opt(Opts, R, As); inet -> list_opt(Opts, R, As); inet6 -> list_opt(Opts, R, As); {Name,Val} when is_atom(Name) -> list_add(Name, Val, R, Opts, As); _ -> {error, badarg} end; list_opt([], R, _SockOpts) -> {ok, R}. list_add(Name, Val, R, Opts, As) -> case add_opt(Name, Val, R#listen_opts.opts, As) of {ok, SOpts} -> list_opt(Opts, R#listen_opts { opts = SOpts }, As); Error -> Error end. %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% %% Available options for udp:open %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% udp_options() -> [tos, priority, reuseaddr, sndbuf, recbuf, header, active, buffer, mode, deliver, broadcast, dontroute, multicast_if, multicast_ttl, multicast_loop, add_membership, drop_membership, read_packets,raw]. udp_options(Opts, Family) -> case udp_opt(Opts, #udp_opts { }, udp_options()) of {ok, R} -> {ok, R#udp_opts { ifaddr = translate_ip(R#udp_opts.ifaddr, Family) }}; Error -> Error end. udp_opt([{raw,A,B,C}|Opts], R, As) -> udp_opt([{raw,{A,B,C}}|Opts], R, As); udp_opt([Opt | Opts], R, As) -> case Opt of {ip,IP} -> udp_opt(Opts, R#udp_opts { ifaddr = IP }, As); {ifaddr,IP} -> udp_opt(Opts, R#udp_opts { ifaddr = IP }, As); {port,P} -> udp_opt(Opts, R#udp_opts { port = P }, As); {fd,Fd} -> udp_opt(Opts, R#udp_opts { fd = Fd }, As); binary -> udp_add(mode, binary, R, Opts, As); list -> udp_add(mode, list, R, Opts, As); {udp_module,_} -> udp_opt(Opts, R, As); inet -> udp_opt(Opts, R, As); inet6 -> udp_opt(Opts, R, As); {Name,Val} when is_atom(Name) -> udp_add(Name, Val, R, Opts, As); _ -> {error, badarg} end; udp_opt([], R, _SockOpts) -> {ok, R}. udp_add(Name, Val, R, Opts, As) -> case add_opt(Name, Val, R#udp_opts.opts, As) of {ok, SOpts} -> udp_opt(Opts, R#udp_opts { opts = SOpts }, As); Error -> Error end. %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% %% Available options for sctp:open %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% % Currently supported options include: % (*) {mode, list|binary} or just list|binary % (*) {active, true|false|once} % (*) {sctp_module, inet_sctp|inet6_sctp} or just inet|inet6 % (*) options set via setsockopt. % The full list is below in sctp_options/0 . % All other options are currently NOT supported. In particular: % (*) multicast on SCTP is not (yet) supported, as it may be incompatible % with automatic associations; % (*) passing of open FDs ("fdopen") is not supported. sctp_options() -> [ % The following are generic inet options supported for SCTP sockets: mode, active, buffer, tos, priority, dontroute, reuseaddr, linger, sndbuf, recbuf, % Other options are SCTP-specific (though they may be similar to their % TCP and UDP counter-parts): sctp_rtoinfo, sctp_associnfo, sctp_initmsg, sctp_autoclose, sctp_nodelay, sctp_disable_fragments, sctp_i_want_mapped_v4_addr, sctp_maxseg, sctp_primary_addr, sctp_set_peer_primary_addr, sctp_adaptation_layer, sctp_peer_addr_params, sctp_default_send_param, sctp_events, sctp_delayed_ack_time, sctp_status, sctp_get_peer_addr_info ]. sctp_options(Opts, Mod) -> case sctp_opt(Opts, Mod, #sctp_opts{}, sctp_options()) of {ok,#sctp_opts{ifaddr=undefined}=SO} -> {ok,SO#sctp_opts{ifaddr=Mod:translate_ip(?SCTP_DEF_IFADDR)}}; {ok,_}=OK -> OK; Error -> Error end. sctp_opt([Opt|Opts], Mod, R, As) -> case Opt of {ip,IP} -> sctp_opt_ifaddr(Opts, Mod, R, As, IP); {ifaddr,IP} -> sctp_opt_ifaddr(Opts, Mod, R, As, IP); {port,Port} -> case Mod:getserv(Port) of {ok,P} -> sctp_opt(Opts, Mod, R#sctp_opts{port=P}, As); Error -> Error end; binary -> sctp_opt (Opts, Mod, R, As, mode, binary); list -> sctp_opt (Opts, Mod, R, As, mode, list); {sctp_module,_} -> sctp_opt (Opts, Mod, R, As); % Done with inet -> sctp_opt (Opts, Mod, R, As); % Done with inet6 -> sctp_opt (Opts, Mod, R, As); % Done with {Name,Val} -> sctp_opt (Opts, Mod, R, As, Name, Val); _ -> {error,badarg} end; sctp_opt([], _Mod, R, _SockOpts) -> {ok, R}. sctp_opt(Opts, Mod, R, As, Name, Val) -> case add_opt(Name, Val, R#sctp_opts.opts, As) of {ok,SocketOpts} -> sctp_opt(Opts, Mod, R#sctp_opts{opts=SocketOpts}, As); Error -> Error end. sctp_opt_ifaddr(Opts, Mod, #sctp_opts{ifaddr=IfAddr}=R, As, Addr) -> IP = Mod:translate_ip(Addr), sctp_opt(Opts, Mod, R#sctp_opts{ ifaddr=case IfAddr of undefined -> IP; _ when is_list(IfAddr) -> [IP|IfAddr]; _ -> [IP,IfAddr] end}, As). %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% %% Util to check and insert option in option list %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% add_opt(Name, Val, Opts, As) -> case lists:member(Name, As) of true -> case prim_inet:is_sockopt_val(Name, Val) of true -> Opts1 = lists:keydelete(Name, 1, Opts), {ok, [{Name,Val} | Opts1]}; false -> {error,badarg} end; false -> {error,badarg} end. translate_ip(any, inet) -> {0,0,0,0}; translate_ip(loopback, inet) -> {127,0,0,1}; translate_ip(any, inet6) -> {0,0,0,0,0,0,0,0}; translate_ip(loopback, inet6) -> {0,0,0,0,0,0,0,1}; translate_ip(IP, _) -> IP. getaddrs_tm({A,B,C,D} = IP, Fam, _) -> %% Only "syntactic" validation and check of family. if ?ip(A,B,C,D) -> if Fam =:= inet -> {ok,[IP]}; true -> {error,eafnosupport} end; true -> {error,einval} end; getaddrs_tm({A,B,C,D,E,F,G,H} = IP, Fam, _) -> %% Only "syntactic" validation; we assume that the address was %% "semantically" validated when it was converted to a tuple. if ?ip6(A,B,C,D,E,F,G,H) -> if Fam =:= inet6 -> {ok,[IP]}; true -> {error,eafnosupport} end; true -> {error,einval} end; getaddrs_tm(Address, Family, Timer) when is_atom(Address) -> getaddrs_tm(atom_to_list(Address), Family, Timer); getaddrs_tm(Address, Family, Timer) -> case inet_parse:visible_string(Address) of false -> {error,einval}; true -> %% Address is a host name or a valid IP address, %% either way check it with the resolver. case gethostbyname_tm(Address, Family, Timer) of {ok,Ent} -> {ok,Ent#hostent.h_addr_list}; Error -> Error end end. %% %% gethostbyname with option search %% gethostbyname_tm(Name, Type, Timer, [dns | Opts]) -> Res = inet_res:gethostbyname_tm(Name, Type, Timer), case Res of {ok,_} -> Res; {error,timeout} -> Res; {error,formerr} -> {error,einval}; {error,_} -> gethostbyname_tm(Name,Type,Timer,Opts) end; gethostbyname_tm(Name, Type, Timer, [file | Opts]) -> case inet_hosts:gethostbyname(Name, Type) of {error,formerr} -> {error,einval}; {error,_} -> gethostbyname_tm(Name,Type,Timer,Opts); Result -> Result end; gethostbyname_tm(Name, Type, Timer, [yp | Opts]) -> gethostbyname_tm_native(Name, Type, Timer, Opts); gethostbyname_tm(Name, Type, Timer, [nis | Opts]) -> gethostbyname_tm_native(Name, Type, Timer, Opts); gethostbyname_tm(Name, Type, Timer, [nisplus | Opts]) -> gethostbyname_tm_native(Name, Type, Timer, Opts); gethostbyname_tm(Name, Type, Timer, [wins | Opts]) -> gethostbyname_tm_native(Name, Type, Timer, Opts); gethostbyname_tm(Name, Type, Timer, [native | Opts]) -> gethostbyname_tm_native(Name, Type, Timer, Opts); gethostbyname_tm(_, _, _, [no_default|_]) -> %% If the native resolver has failed, we should not bother %% to try to be smarter and parse the IP address here. {error,nxdomain}; gethostbyname_tm(Name, Type, Timer, [_ | Opts]) -> gethostbyname_tm(Name, Type, Timer, Opts); %% Last resort - parse the hostname as address gethostbyname_tm(Name, inet, _Timer, []) -> case inet_parse:ipv4_address(Name) of {ok,IP4} -> {ok,make_hostent(Name, [IP4], [], inet)}; _ -> gethostbyname_self(Name) end; gethostbyname_tm(Name, inet6, _Timer, []) -> case inet_parse:ipv6_address(Name) of {ok,IP6} -> {ok,make_hostent(Name, [IP6], [], inet6)}; _ -> %% Even if Name is a valid IPv4 address, we can't %% assume it's correct to return it on a IPv6 %% format ( {0,0,0,0,0,16#ffff,?u16(A,B),?u16(C,D)} ). %% This host might not support IPv6. gethostbyname_self(Name) end. gethostbyname_tm_native(Name, Type, Timer, Opts) -> %% Fixme: add (global) timeout to gethost_native case inet_gethost_native:gethostbyname(Name, Type) of {error,formerr} -> {error,einval}; {error,timeout} -> {error,timeout}; {error,_} -> gethostbyname_tm(Name, Type, Timer, Opts++[no_default]); Result -> Result end. %% Make sure we always can look up our own hostname. gethostbyname_self(Name) -> Type = case inet_db:res_option(inet6) of true -> inet6; false -> inet end, case inet_db:gethostname() of Name -> {ok,make_hostent(Name, [translate_ip(loopback, Type)], [], Type)}; Self -> case inet_db:res_option(domain) of "" -> {error,nxdomain}; Domain -> case lists:append([Self,".",Domain]) of Name -> {ok,make_hostent(Name, [translate_ip(loopback, Type)], [], Type)}; _ -> {error,nxdomain} end end end. make_hostent(Name, Addrs, Aliases, Type) -> #hostent{h_name = Name, h_aliases = Aliases, h_addrtype = Type, h_length = case Type of inet -> 4; inet6 -> 16 end, h_addr_list = Addrs}. %% %% gethostbyaddr with option search %% gethostbyaddr_tm(Addr, Timer, [dns | Opts]) -> Res = inet_res:gethostbyaddr_tm(Addr,Timer), case Res of {ok,_} -> Res; {error,timeout} -> Res; {error,formerr} -> {error, einval}; {error,_} -> gethostbyaddr_tm(Addr,Timer,Opts) end; gethostbyaddr_tm(Addr, Timer, [file | Opts]) -> case inet_hosts:gethostbyaddr(Addr) of {error,formerr} -> {error, einval}; {error,_} -> gethostbyaddr_tm(Addr,Timer,Opts); Result -> Result end; gethostbyaddr_tm(Addr, Timer, [yp | Opts]) -> gethostbyaddr_tm_native(Addr, Timer, Opts); gethostbyaddr_tm(Addr, Timer, [nis | Opts]) -> gethostbyaddr_tm_native(Addr, Timer, Opts); gethostbyaddr_tm(Addr, Timer, [nisplus | Opts]) -> gethostbyaddr_tm_native(Addr, Timer, Opts); gethostbyaddr_tm(Addr, Timer, [wins | Opts]) -> gethostbyaddr_tm_native(Addr, Timer, Opts); gethostbyaddr_tm(Addr, Timer, [native | Opts]) -> gethostbyaddr_tm_native(Addr, Timer, Opts); gethostbyaddr_tm(Addr, Timer, [_ | Opts]) -> gethostbyaddr_tm(Addr, Timer, Opts); gethostbyaddr_tm({127,0,0,1}=IP, _Timer, []) -> gethostbyaddr_self(IP, inet); gethostbyaddr_tm({0,0,0,0,0,0,0,1}=IP, _Timer, []) -> gethostbyaddr_self(IP, inet6); gethostbyaddr_tm(_Addr, _Timer, []) -> {error, nxdomain}. gethostbyaddr_self(IP, Type) -> Name = inet_db:gethostname(), case inet_db:res_option(domain) of "" -> {ok,make_hostent(Name, [IP], [], Type)}; Domain -> {ok,make_hostent(Name++"."++Domain, [IP], [Name], Type)} end. gethostbyaddr_tm_native(Addr, Timer, Opts) -> %% Fixme: user timer for timeoutvalue case inet_gethost_native:gethostbyaddr(Addr) of {error,formerr} -> {error, einval}; {error,_} -> gethostbyaddr_tm(Addr,Timer,Opts); Result -> Result end. -spec open(Fd :: integer(), Addr :: ip_address(), Port :: ip_port(), Opts :: [socket_setopt()], Protocol :: protocol_option(), Family :: 'inet' | 'inet6', Module :: atom()) -> {'ok', socket()} | {'error', posix()}. open(Fd, Addr, Port, Opts, Protocol, Family, Module) when Fd < 0 -> case prim_inet:open(Protocol, Family) of {ok,S} -> case prim_inet:setopts(S, Opts) of ok -> case if is_list(Addr) -> prim_inet:bind(S, add, [case A of {_,_} -> A; _ -> {A,Port} end || A <- Addr]); true -> prim_inet:bind(S, Addr, Port) end of {ok, _} -> inet_db:register_socket(S, Module), {ok,S}; Error -> prim_inet:close(S), Error end; Error -> prim_inet:close(S), Error end; Error -> Error end; open(Fd, _Addr, _Port, Opts, Protocol, Family, Module) -> fdopen(Fd, Opts, Protocol, Family, Module). -spec fdopen(Fd :: non_neg_integer(), Opts :: [socket_setopt()], Protocol :: protocol_option(), Family :: family_option(), Module :: atom()) -> {'ok', socket()} | {'error', posix()}. fdopen(Fd, Opts, Protocol, Family, Module) -> case prim_inet:fdopen(Protocol, Fd, Family) of {ok, S} -> case prim_inet:setopts(S, Opts) of ok -> inet_db:register_socket(S, Module), {ok, S}; Error -> prim_inet:close(S), Error end; Error -> Error end. %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% %% socket stat %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% i() -> i(tcp), i(udp). i(Proto) -> i(Proto, [port, module, recv, sent, owner, local_address, foreign_address, state]). i(tcp, Fs) -> ii(tcp_sockets(), Fs, tcp); i(udp, Fs) -> ii(udp_sockets(), Fs, udp). ii(Ss, Fs, Proto) -> LLs = [h_line(Fs) | info_lines(Ss, Fs, Proto)], Maxs = foldl( fun(Line,Max0) -> smax(Max0,Line) end, duplicate(length(Fs),0),LLs), Fmt = append(["~-" ++ integer_to_list(N) ++ "s " || N <- Maxs]) ++ "\n", lists:foreach(fun(Line) -> io:format(Fmt, Line) end, LLs). smax([Max|Ms], [Str|Strs]) -> N = length(Str), [if N > Max -> N; true -> Max end | smax(Ms, Strs)]; smax([], []) -> []. info_lines(Ss, Fs, Proto) -> [i_line(S, Fs,Proto) || S <- Ss]. i_line(S, Fs, Proto) -> [info(S, F, Proto) || F <- Fs]. h_line(Fs) -> [h_field(atom_to_list(F)) || F <- Fs]. h_field([C|Cs]) -> [upper(C) | hh_field(Cs)]. hh_field([$_,C|Cs]) -> [$\s,upper(C) | hh_field(Cs)]; hh_field([C|Cs]) -> [C|hh_field(Cs)]; hh_field([]) -> []. upper(C) when C >= $a, C =< $z -> (C-$a) + $A; upper(C) -> C. info(S, F, Proto) -> case F of owner -> case erlang:port_info(S, connected) of {connected, Owner} -> pid_to_list(Owner); _ -> " " end; port -> case erlang:port_info(S,id) of {id, Id} -> integer_to_list(Id); undefined -> " " end; sent -> case prim_inet:getstat(S, [send_oct]) of {ok,[{send_oct,N}]} -> integer_to_list(N); _ -> " " end; recv -> case prim_inet:getstat(S, [recv_oct]) of {ok,[{recv_oct,N}]} -> integer_to_list(N); _ -> " " end; local_address -> fmt_addr(prim_inet:sockname(S), Proto); foreign_address -> fmt_addr(prim_inet:peername(S), Proto); state -> case prim_inet:getstatus(S) of {ok,Status} -> fmt_status(Status); _ -> " " end; packet -> case prim_inet:getopt(S, packet) of {ok,Type} when is_atom(Type) -> atom_to_list(Type); {ok,Type} when is_integer(Type) -> integer_to_list(Type); _ -> " " end; type -> case prim_inet:gettype(S) of {ok,{_,stream}} -> "STREAM"; {ok,{_,dgram}} -> "DGRAM"; _ -> " " end; fd -> case prim_inet:getfd(S) of {ok, Fd} -> integer_to_list(Fd); _ -> " " end; module -> case inet_db:lookup_socket(S) of {ok,Mod} -> atom_to_list(Mod); _ -> "prim_inet" end end. %% Possible flags: (sorted) %% [accepting,bound,busy,connected,connecting,listen,listening,open] %% fmt_status(Flags) -> case lists:sort(Flags) of [accepting | _] -> "ACCEPTING"; [bound,busy,connected|_] -> "CONNECTED*"; [bound,connected|_] -> "CONNECTED"; [bound,listen,listening | _] -> "LISTENING"; [bound,listen | _] -> "LISTEN"; [bound,connecting | _] -> "CONNECTING"; [bound,open] -> "BOUND"; [open] -> "IDLE"; [] -> "CLOSED"; _ -> "????" end. fmt_addr({error,enotconn}, _) -> "*:*"; fmt_addr({error,_}, _) -> " "; fmt_addr({ok,Addr}, Proto) -> case Addr of %%Dialyzer {0,0} -> "*:*"; {{0,0,0,0},Port} -> "*:" ++ fmt_port(Port, Proto); {{0,0,0,0,0,0,0,0},Port} -> "*:" ++ fmt_port(Port, Proto); {{127,0,0,1},Port} -> "localhost:" ++ fmt_port(Port, Proto); {{0,0,0,0,0,0,0,1},Port} -> "localhost:" ++ fmt_port(Port, Proto); {IP,Port} -> inet_parse:ntoa(IP) ++ ":" ++ fmt_port(Port, Proto) end. fmt_port(N, Proto) -> case inet:getservbyport(N, Proto) of {ok, Name} -> Name; _ -> integer_to_list(N) end. %% Return a list of all tcp sockets tcp_sockets() -> port_list("tcp_inet"). udp_sockets() -> port_list("udp_inet"). %% Return all ports having the name 'Name' port_list(Name) -> filter( fun(Port) -> case erlang:port_info(Port, name) of {name, Name} -> true; _ -> false end end, erlang:ports()). %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% %% utils %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% -spec format_error(posix()) -> string(). format_error(exbadport) -> "invalid port state"; format_error(exbadseq) -> "bad command sequence"; format_error(Tag) -> erl_posix_msg:message(Tag). %% Close a TCP socket. tcp_close(S) when is_port(S) -> %% if exit_on_close is set we must force a close even if remotely closed!!! prim_inet:close(S), receive {tcp_closed, S} -> ok after 0 -> ok end. %% Close a UDP socket. udp_close(S) when is_port(S) -> receive {udp_closed, S} -> ok after 0 -> prim_inet:close(S), receive {udp_closed, S} -> ok after 0 -> ok end end. %% Set controlling process for TCP socket. tcp_controlling_process(S, NewOwner) when is_port(S), is_pid(NewOwner) -> case erlang:port_info(S, connected) of {connected, Pid} when Pid =/= self() -> {error, not_owner}; undefined -> {error, einval}; _ -> case prim_inet:getopt(S, active) of {ok, A0} -> prim_inet:setopt(S, active, false), case tcp_sync_input(S, NewOwner, false) of true -> %% socket already closed, ok; false -> try erlang:port_connect(S, NewOwner) of true -> unlink(S), %% unlink from port prim_inet:setopt(S, active, A0), ok catch error:Reason -> {error, Reason} end end; Error -> Error end end. tcp_sync_input(S, Owner, Flag) -> receive {tcp, S, Data} -> Owner ! {tcp, S, Data}, tcp_sync_input(S, Owner, Flag); {tcp_closed, S} -> Owner ! {tcp_closed, S}, tcp_sync_input(S, Owner, true); {S, {data, Data}} -> Owner ! {S, {data, Data}}, tcp_sync_input(S, Owner, Flag); {inet_async, S, Ref, Status} -> Owner ! {inet_async, S, Ref, Status}, tcp_sync_input(S, Owner, Flag); {inet_reply, S, Status} -> Owner ! {inet_reply, S, Status}, tcp_sync_input(S, Owner, Flag) after 0 -> Flag end. %% Set controlling process for UDP or SCTP socket. udp_controlling_process(S, NewOwner) when is_port(S), is_pid(NewOwner) -> case erlang:port_info(S, connected) of {connected, Pid} when Pid =/= self() -> {error, not_owner}; _ -> {ok, A0} = prim_inet:getopt(S, active), prim_inet:setopt(S, active, false), udp_sync_input(S, NewOwner), try erlang:port_connect(S, NewOwner) of true -> unlink(S), prim_inet:setopt(S, active, A0), ok catch error:Reason -> {error, Reason} end end. udp_sync_input(S, Owner) -> receive {sctp, S, _, _, _}=Msg -> udp_sync_input(S, Owner, Msg); {udp, S, _, _, _}=Msg -> udp_sync_input(S, Owner, Msg); {udp_closed, S}=Msg -> udp_sync_input(S, Owner, Msg); {S, {data,_}}=Msg -> udp_sync_input(S, Owner, Msg); {inet_async, S, _, _}=Msg -> udp_sync_input(S, Owner, Msg); {inet_reply, S, _}=Msg -> udp_sync_input(S, Owner, Msg) after 0 -> ok end. udp_sync_input(S, Owner, Msg) -> Owner ! Msg, udp_sync_input(S, Owner). start_timer(infinity) -> false; start_timer(Timeout) -> erlang:start_timer(Timeout, self(), inet). timeout(false) -> infinity; timeout(Timer) -> case erlang:read_timer(Timer) of false -> 0; Time -> Time end. timeout(Time, false) -> Time; timeout(Time, Timer) -> TimerTime = timeout(Timer), if TimerTime < Time -> TimerTime; true -> Time end. stop_timer(false) -> false; stop_timer(Timer) -> case erlang:cancel_timer(Timer) of false -> receive {timeout,Timer,_} -> false after 0 -> false end; T -> T end.