%%-------------------------------------------------------------------- %% %% %CopyrightBegin% %% %% Copyright Ericsson AB 1997-2015. All Rights Reserved. %% %% Licensed under the Apache License, Version 2.0 (the "License"); %% you may not use this file except in compliance with the License. %% You may obtain a copy of the License at %% %% http://www.apache.org/licenses/LICENSE-2.0 %% %% Unless required by applicable law or agreed to in writing, software %% distributed under the License is distributed on an "AS IS" BASIS, %% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. %% See the License for the specific language governing permissions and %% limitations under the License. %%%% %CopyrightEnd% %% %% %%----------------------------------------------------------------- %% File: orber_socket.erl %% %% Description: %% This file contains a standard interface to the sockets to handle the differences %% between the implementations used. %% %%----------------------------------------------------------------- -module(orber_socket). -include_lib("orber/include/corba.hrl"). -include_lib("orber/src/orber_iiop.hrl"). %%----------------------------------------------------------------- %% External exports %%----------------------------------------------------------------- -export([start/0, connect/4, listen/3, listen/4, accept/2, accept/3, write/3, controlling_process/3, close/2, peername/2, sockname/2, peerdata/2, peercert/2, sockdata/2, setopts/3, clear/2, shutdown/3, post_accept/2, post_accept/3, get_ip_family_opts/1]). %%----------------------------------------------------------------- %% Internal exports %%----------------------------------------------------------------- -export([]). %%----------------------------------------------------------------- %% Internal defines %%----------------------------------------------------------------- -define(DEBUG_LEVEL, 6). %%----------------------------------------------------------------- %% External functions %%----------------------------------------------------------------- start() -> inet_db:start(). %%----------------------------------------------------------------- %% Invoke the required setopts (i.e., inet or ssl) setopts(normal, Socket, Opts) -> inet:setopts(Socket, Opts); setopts(ssl, Socket, Opts) -> ssl:setopts(Socket, Opts). %%----------------------------------------------------------------- %% Connect to IIOP Port at Host in CDR mode, in order to %% establish a connection. %% connect(Type, Host, Port, Options) -> Timeout = orber:iiop_setup_connection_timeout(), Generation = orber_env:ssl_generation(), Options1 = check_options(Type, Options, Generation), Options2 = case Type of normal -> [{keepalive, orber_env:iiop_out_keepalive()}|Options1]; _ -> Options1 end, case orber:iiop_out_ports() of {Min, Max} when Type == normal -> multi_connect(get_port_sequence(Min, Max), orber_env:iiop_out_ports_attempts(), Type, Host, Port, [binary, {reuseaddr, true}, {packet,cdr}| Options2], Timeout); {Min, Max} when Generation > 2 -> multi_connect(get_port_sequence(Min, Max), orber_env:iiop_out_ports_attempts(), Type, Host, Port, [binary, {reuseaddr, true}, {packet,cdr}| Options2], Timeout); {Min, Max} -> %% reuseaddr not available for older SSL versions multi_connect(get_port_sequence(Min, Max), orber_env:iiop_out_ports_attempts(), Type, Host, Port, [binary, {packet,cdr}| Options2], Timeout); _ -> connect(Type, Host, Port, [binary, {packet,cdr}| Options2], Timeout) end. connect(normal, Host, Port, Options, Timeout) -> case catch gen_tcp:connect(Host, Port, Options, Timeout) of {ok, Socket} -> Socket; {error, timeout} -> orber:dbg("[~p] orber_socket:connect(normal, ~p, ~p, ~p);~n" "Timeout after ~p msec.", [?LINE, Host, Port, Options, Timeout], ?DEBUG_LEVEL), corba:raise(#'COMM_FAILURE'{minor=(?ORBER_VMCID bor 4), completion_status=?COMPLETED_NO}); Error -> orber:dbg("[~p] orber_socket:connect(normal, ~p, ~p, ~p);~n" "Failed with reason: ~p", [?LINE, Host, Port, Options, Error], ?DEBUG_LEVEL), corba:raise(#'COMM_FAILURE'{completion_status=?COMPLETED_NO}) end; connect(ssl, Host, Port, Options, Timeout) -> case catch ssl:connect(Host, Port, Options, Timeout) of {ok, Socket} -> Socket; {error, timeout} -> orber:dbg("[~p] orber_socket:connect(ssl, ~p, ~p, ~p);~n" "Timeout after ~p msec.", [?LINE, Host, Port, Options, Timeout], ?DEBUG_LEVEL), corba:raise(#'COMM_FAILURE'{minor=(?ORBER_VMCID bor 4), completion_status=?COMPLETED_NO}); Error -> orber:dbg("[~p] orber_socket:connect(ssl, ~p, ~p, ~p);~n" "Failed with reason: ~p", [?LINE, Host, Port, Options, Error], ?DEBUG_LEVEL), corba:raise(#'COMM_FAILURE'{completion_status=?COMPLETED_NO}) end. multi_connect([], _Retries, Type, Host, Port, Options, _) -> orber:dbg("[~p] orber_socket:multi_connect(~p, ~p, ~p, ~p);~n" "Unable to use any of the sockets defined by 'iiop_out_ports'.~n" "Either all ports are in use or to many connections already exists.", [?LINE, Type, Host, Port, Options], ?DEBUG_LEVEL), corba:raise(#'IMP_LIMIT'{minor=(?ORBER_VMCID bor 1), completion_status=?COMPLETED_NO}); multi_connect([CurrentPort|Rest], Retries, normal, Host, Port, Options, Timeout) -> case catch gen_tcp:connect(Host, Port, [{port, CurrentPort}|Options], Timeout) of {ok, Socket} -> Socket; {error, timeout} when Retries =< 1 -> orber:dbg("[~p] orber_socket:multi_connect(normal, ~p, ~p, ~p);~n" "Timeout after ~p msec.", [?LINE, Host, Port, [{port, CurrentPort}|Options], Timeout], ?DEBUG_LEVEL), corba:raise(#'COMM_FAILURE'{minor=(?ORBER_VMCID bor 4), completion_status=?COMPLETED_NO}); _ -> multi_connect(Rest, Retries - 1, normal, Host, Port, Options, Timeout) end; multi_connect([CurrentPort|Rest], Retries, ssl, Host, Port, Options, Timeout) -> case catch ssl:connect(Host, Port, [{port, CurrentPort}|Options], Timeout) of {ok, Socket} -> Socket; {error, timeout} when Retries =< 1 -> orber:dbg("[~p] orber_socket:multi_connect(ssl, ~p, ~p, ~p);~n" "Timeout after ~p msec.", [?LINE, Host, Port, [{port, CurrentPort}|Options], Timeout], ?DEBUG_LEVEL), corba:raise(#'COMM_FAILURE'{minor=(?ORBER_VMCID bor 4), completion_status=?COMPLETED_NO}); _ -> multi_connect(Rest, Retries - 1, ssl, Host, Port, Options, Timeout) end. get_port_sequence(Min, Max) -> case orber_env:iiop_out_ports_random() of true -> Seq = lists:seq(Min, Max), random_sequence((Max - Min) + 1, Seq, []); _ -> lists:seq(Min, Max) end. random_sequence(0, _, Acc) -> Acc; random_sequence(Length, Seq, Acc) -> Nth = rand:uniform(Length), Value = lists:nth(Nth, Seq), NewSeq = lists:delete(Value, Seq), random_sequence(Length-1, NewSeq, [Value|Acc]). %%----------------------------------------------------------------- %% Create a listen socket at Port in CDR mode for %% data connection. %% listen(Type, Port, Options) -> listen(Type, Port, Options, true). listen(normal, Port, Options, Exception) -> Options1 = check_options(normal, Options, 0), Backlog = orber:iiop_backlog(), Keepalive = orber_env:iiop_in_keepalive(), Options2 = case orber:iiop_max_in_requests() of infinity -> Options1; _MaxRequests -> [{active, once}|Options1] end, Options3 = case orber_env:iiop_packet_size() of infinity -> Options2; MaxSize -> [{packet_size, MaxSize}|Options2] end, Options4 = [binary, {packet,cdr}, {keepalive, Keepalive}, {reuseaddr,true}, {backlog, Backlog} | Options3], case catch gen_tcp:listen(Port, Options4) of {ok, ListenSocket} -> {ok, ListenSocket, check_port(Port, normal, ListenSocket)}; {error, Reason} when Exception == false -> {error, Reason}; {error, eaddrinuse} -> orber:dbg("[~p] orber_socket:listen(normal, ~p, ~p);~n" "Looks like the listen port is already in use.~n" "Check if another Orber is started~n" "on the same node and uses the same listen port (iiop_port). But it may also~n" "be used by any other application; confirm with 'netstat'.", [?LINE, Port, Options4], ?DEBUG_LEVEL), corba:raise(#'COMM_FAILURE'{completion_status=?COMPLETED_NO}); Error -> orber:dbg("[~p] orber_socket:listen(normal, ~p, ~p);~n" "Failed with reason: ~p", [?LINE, Port, Options4, Error], ?DEBUG_LEVEL), corba:raise(#'COMM_FAILURE'{completion_status=?COMPLETED_NO}) end; listen(ssl, Port, Options, Exception) -> Backlog = orber:iiop_ssl_backlog(), Generation = orber_env:ssl_generation(), Options1 = check_options(ssl, Options, Generation), Options2 = case orber:iiop_max_in_requests() of infinity -> Options1; _MaxRequests -> [{active, once}|Options1] end, Options3 = case orber_env:iiop_packet_size() of infinity -> Options2; MaxSize -> [{packet_size, MaxSize}|Options2] end, Options4 = if Generation > 2 -> [{reuseaddr, true} |Options3]; true -> Options3 end, Options5 = [binary, {packet,cdr}, {backlog, Backlog} | Options4], case catch ssl:listen(Port, Options5) of {ok, ListenSocket} -> {ok, ListenSocket, check_port(Port, ssl, ListenSocket)}; {error, Reason} when Exception == false -> {error, Reason}; {error, eaddrinuse} -> orber:dbg("[~p] orber_socket:listen(ssl, ~p, ~p);~n" "Looks like the listen port is already in use. Check if~n" "another Orber is started on the same node and uses the~n" "same listen port (iiop_port). But it may also~n" "be used by any other application; confirm with 'netstat'.", [?LINE, Port, Options5], ?DEBUG_LEVEL), corba:raise(#'COMM_FAILURE'{completion_status=?COMPLETED_NO}); Error -> orber:dbg("[~p] orber_socket:listen(ssl, ~p, ~p);~n" "Failed with reason: ~p", [?LINE, Port, Options5, Error], ?DEBUG_LEVEL), corba:raise(#'COMM_FAILURE'{completion_status=?COMPLETED_NO}) end. %%----------------------------------------------------------------- %% Wait in accept on the socket %% accept(Type, ListenSocket) -> accept(Type, ListenSocket, infinity). accept(normal, ListenSocket, _Timeout) -> case catch gen_tcp:accept(ListenSocket) of {ok, S} -> S; Error -> orber:dbg("[~p] orber_socket:accept(normal, ~p);~n" "Failed with reason: ~p", [?LINE, ListenSocket, Error], ?DEBUG_LEVEL), corba:raise(#'COMM_FAILURE'{completion_status=?COMPLETED_NO}) end; accept(ssl, ListenSocket, Timeout) -> case catch ssl:transport_accept(ListenSocket, Timeout) of {ok, S} -> S; Error -> orber:dbg("[~p] orber_socket:accept(ssl, ~p);~n" "Failed with reason: ~p", [?LINE, ListenSocket, Error], ?DEBUG_LEVEL), corba:raise(#'COMM_FAILURE'{completion_status=?COMPLETED_NO}) end. post_accept(Type, Socket) -> post_accept(Type, Socket, infinity). post_accept(normal, _Socket, _Timeout) -> ok; post_accept(ssl, Socket, Timeout) -> case catch ssl:ssl_accept(Socket, Timeout) of ok -> ok; Error -> orber:dbg("[~p] orber_socket:post_accept(ssl, ~p);~n" "Failed with reason: ~p", [?LINE, Socket, Error], ?DEBUG_LEVEL), corba:raise(#'COMM_FAILURE'{completion_status=?COMPLETED_NO}) end. %%----------------------------------------------------------------- %% Close the socket %% close(normal, Socket) -> (catch gen_tcp:close(Socket)); close(ssl, Socket) -> (catch ssl:close(Socket)). %%----------------------------------------------------------------- %% Write to socket %% write(normal, Socket, Bytes) -> gen_tcp:send(Socket, Bytes); write(ssl, Socket, Bytes) -> ssl:send(Socket, Bytes). %%----------------------------------------------------------------- %% Change the controlling process for the socket %% controlling_process(normal, Socket, Pid) -> gen_tcp:controlling_process(Socket, Pid); controlling_process(ssl, Socket, Pid) -> ssl:controlling_process(Socket, Pid). %%----------------------------------------------------------------- %% Get peername %% peername(normal, Socket) -> inet:peername(Socket); peername(ssl, Socket) -> ssl:peername(Socket). %%----------------------------------------------------------------- %% Get peercert %% peercert(ssl, Socket) -> ssl:peercert(Socket); peercert(Type, _Socket) -> orber:dbg("[~p] orber_socket:peercert(~p);~n" "Only available for SSL sockets.", [?LINE, Type], ?DEBUG_LEVEL), {error, ebadsocket}. %%----------------------------------------------------------------- %% Get peerdata %% peerdata(normal, Socket) -> create_data(inet:peername(Socket)); peerdata(ssl, Socket) -> create_data(ssl:peername(Socket)). %%----------------------------------------------------------------- %% Get sockname %% sockname(normal, Socket) -> inet:sockname(Socket); sockname(ssl, Socket) -> ssl:sockname(Socket). %%----------------------------------------------------------------- %% Get sockdata %% sockdata(normal, Socket) -> create_data(inet:sockname(Socket)); sockdata(ssl, Socket) -> create_data(ssl:sockname(Socket)). create_data({ok, {Addr, Port}}) -> {orber_env:addr2str(Addr), Port}; create_data(What) -> orber:dbg("[~p] orber_socket:peername() or orber_socket:sockname();~n" "Failed with reason: ~p", [?LINE, What], ?DEBUG_LEVEL), {"Unable to lookup peer- or sockname", 0}. %%----------------------------------------------------------------- %% Shutdown Connection %% How = read | write | read_write shutdown(normal, Socket, How) -> gen_tcp:shutdown(Socket, How); shutdown(ssl, Socket, How) -> Generation = orber_env:ssl_generation(), if Generation > 2 -> ssl:shutdown(Socket, How); How == read_write -> %% Older versions of SSL do no support shutdown. %% For now we'll use this solution instead. close(ssl, Socket); true -> {error, undefined} end. %%----------------------------------------------------------------- %% Remove Messages from queue %% clear(normal, Socket) -> tcp_clear(Socket); clear(ssl, Socket) -> ssl_clear(Socket). %% Inet also checks for the following messages: %% * {S, {data, Data}} %% * {inet_async, S, Ref, Status}, %% * {inet_reply, S, Status} %% SSL doesn't. tcp_clear(Socket) -> receive {tcp, Socket, _Data} -> tcp_clear(Socket); {tcp_closed, Socket} -> tcp_clear(Socket); {tcp_error, Socket, _Reason} -> tcp_clear(Socket) after 0 -> ok end. ssl_clear(Socket) -> receive {ssl, Socket, _Data} -> ssl_clear(Socket); {ssl_closed, Socket} -> ssl_clear(Socket); {ssl_error, Socket, _Reason} -> ssl_clear(Socket) after 0 -> ok end. %%----------------------------------------------------------------- %% Check Port. If the user supplies 0 we pick any vacant port. But then %% we must change the associated environment variable check_port(0, normal, Socket) -> case inet:port(Socket) of {ok, Port} -> orber:configure_override(iiop_port, Port), Port; What -> orber:dbg("[~p] orber_socket:check_port(~p);~n" "Unable to extract the port number via inet:port/1~n", [?LINE, What], ?DEBUG_LEVEL), corba:raise(#'COMM_FAILURE'{completion_status=?COMPLETED_NO}) end; check_port(0, ssl, Socket) -> case ssl:sockname(Socket) of {ok, {_Address, Port}} -> orber:configure_override(iiop_ssl_port, Port), Port; What -> orber:dbg("[~p] orber_socket:check_port(~p);~n" "Unable to extract the port number via ssl:sockname/1~n", [?LINE, What], ?DEBUG_LEVEL), corba:raise(#'COMM_FAILURE'{completion_status=?COMPLETED_NO}) end; check_port(Port, _, _) -> Port. %%----------------------------------------------------------------- %% Check Options. check_options(normal, Options, _Generation) -> Options; check_options(ssl, Options, Generation) -> if Generation > 2 -> [{ssl_imp, new}|Options]; true -> [{ssl_imp, old}|Options] end. %%----------------------------------------------------------------- %% Check IP Family. get_ip_family_opts(Host) -> case inet:parse_address(Host) of {ok, {_,_,_,_}} -> [inet]; {ok, {_,_,_,_,_,_,_,_}} -> [inet6]; {error, einval} -> check_family_for_name(Host, orber_env:ip_version()) end. check_family_for_name(Host, inet) -> case inet:getaddr(Host, inet) of {ok, _Address} -> [inet]; {error, _} -> case inet:getaddr(Host, inet6) of {ok, _Address} -> [inet6]; {error, _} -> [inet] end end; check_family_for_name(Host, inet6) -> case inet:getaddr(Host, inet6) of {ok, _Address} -> [inet6]; {error, _} -> case inet:getaddr(Host, inet) of {ok, _Address} -> [inet]; {error, _} -> [inet6] end end.