%% %% %CopyrightBegin% %% %% Copyright Ericsson AB 2016-2018. All Rights Reserved. %% %% Licensed under the Apache License, Version 2.0 (the "License"); %% you may not use this file except in compliance with the License. %% You may obtain a copy of the License at %% %% http://www.apache.org/licenses/LICENSE-2.0 %% %% Unless required by applicable law or agreed to in writing, software %% distributed under the License is distributed on an "AS IS" BASIS, %% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. %% See the License for the specific language governing permissions and %% limitations under the License. %% %% %CopyrightEnd% %% -module(dtls_socket). -include("ssl_internal.hrl"). -include("ssl_api.hrl"). -export([send/3, listen/2, accept/3, connect/4, socket/4, setopts/3, getopts/3, getstat/3, peername/2, sockname/2, port/2, close/2]). -export([emulated_options/0, emulated_options/1, internal_inet_values/0, default_inet_values/0, default_cb_info/0]). send(Transport, {{IP,Port},Socket}, Data) -> Transport:send(Socket, IP, Port, Data). listen(Port, #config{transport_info = TransportInfo, ssl = SslOpts, emulated = EmOpts, inet_user = Options} = Config) -> case dtls_listener_sup:start_child([Port, TransportInfo, emulated_socket_options(EmOpts, #socket_options{}), Options ++ internal_inet_values(), SslOpts]) of {ok, Pid} -> Socket = #sslsocket{pid = {dtls, Config#config{dtls_handler = {Pid, Port}}}}, check_active_n(EmOpts, Socket), {ok, Socket}; Err = {error, _} -> Err end. accept(dtls, #config{transport_info = {Transport,_,_,_,_}, connection_cb = ConnectionCb, dtls_handler = {Listner, _}}, _Timeout) -> case dtls_packet_demux:accept(Listner, self()) of {ok, Pid, Socket} -> {ok, socket([Pid], Transport, {Listner, Socket}, ConnectionCb)}; {error, Reason} -> {error, Reason} end. connect(Address, Port, #config{transport_info = {Transport, _, _, _, _} = CbInfo, connection_cb = ConnectionCb, ssl = SslOpts, emulated = EmOpts, inet_ssl = SocketOpts}, Timeout) -> case Transport:open(0, SocketOpts ++ internal_inet_values()) of {ok, Socket} -> ssl_connection:connect(ConnectionCb, Address, Port, {{Address, Port},Socket}, {SslOpts, emulated_socket_options(EmOpts, #socket_options{}), undefined}, self(), CbInfo, Timeout); {error, _} = Error-> Error end. close(gen_udp, {_Client, _Socket}) -> ok; close(Transport, {_Client, Socket}) -> Transport:close(Socket). socket(Pids, gen_udp = Transport, {{_, _}, Socket}, ConnectionCb) -> #sslsocket{pid = Pids, %% "The name "fd" is keept for backwards compatibility fd = {Transport, Socket, ConnectionCb}}; socket(Pids, Transport, Socket, ConnectionCb) -> #sslsocket{pid = Pids, %% "The name "fd" is keept for backwards compatibility fd = {Transport, Socket, ConnectionCb}}. setopts(_, Socket = #sslsocket{pid = {dtls, #config{dtls_handler = {ListenPid, _}}}}, Options) -> SplitOpts = {_, EmOpts} = tls_socket:split_options(Options), check_active_n(EmOpts, Socket), dtls_packet_demux:set_sock_opts(ListenPid, SplitOpts); %%% Following clauses will not be called for emulated options, they are handled in the connection process setopts(gen_udp, Socket, Options) -> inet:setopts(Socket, Options); setopts(Transport, Socket, Options) -> Transport:setopts(Socket, Options). check_active_n(EmulatedOpts, Socket = #sslsocket{pid = {dtls, #config{dtls_handler = {ListenPid, _}}}}) -> %% We check the resulting options to send an ssl_passive message if necessary. case proplists:lookup(active, EmulatedOpts) of %% The provided value is out of bound. {_, N} when is_integer(N), N < -32768 -> throw(einval); {_, N} when is_integer(N), N > 32767 -> throw(einval); {_, N} when is_integer(N) -> {ok, #socket_options{active = Active}, _} = dtls_packet_demux:get_all_opts(ListenPid), case Active of Atom when is_atom(Atom), N =< 0 -> self() ! {ssl_passive, Socket}; %% The result of the addition is out of bound. %% We do not need to check < -32768 because Active can't be below 1. A when is_integer(A), A + N > 32767 -> throw(einval); A when is_integer(A), A + N =< 0 -> self() ! {ssl_passive, Socket}; _ -> ok end; _ -> ok end. getopts(_, #sslsocket{pid = {dtls, #config{dtls_handler = {ListenPid, _}}}}, Options) -> SplitOpts = tls_socket:split_options(Options), dtls_packet_demux:get_sock_opts(ListenPid, SplitOpts); getopts(gen_udp, #sslsocket{pid = {Socket, #config{emulated = EmOpts}}}, Options) -> {SockOptNames, EmulatedOptNames} = tls_socket:split_options(Options), EmulatedOpts = get_emulated_opts(EmOpts, EmulatedOptNames), SocketOpts = tls_socket:get_socket_opts(Socket, SockOptNames, inet), {ok, EmulatedOpts ++ SocketOpts}; getopts(_Transport, #sslsocket{pid = {Socket, #config{emulated = EmOpts}}}, Options) -> {SockOptNames, EmulatedOptNames} = tls_socket:split_options(Options), EmulatedOpts = get_emulated_opts(EmOpts, EmulatedOptNames), SocketOpts = tls_socket:get_socket_opts(Socket, SockOptNames, inet), {ok, EmulatedOpts ++ SocketOpts}; %%% Following clauses will not be called for emulated options, they are handled in the connection process getopts(gen_udp, {_,{{_, _},Socket}}, Options) -> inet:getopts(Socket, Options); getopts(gen_udp, {_,Socket}, Options) -> inet:getopts(Socket, Options); getopts(Transport, Socket, Options) -> Transport:getopts(Socket, Options). getstat(gen_udp, {_,Socket}, Options) -> inet:getstat(Socket, Options); getstat(Transport, Socket, Options) -> Transport:getstat(Socket, Options). peername(_, undefined) -> {error, enotconn}; peername(gen_udp, {_, {Client, _Socket}}) -> {ok, Client}; peername(Transport, Socket) -> Transport:peername(Socket). sockname(gen_udp, {_, {_,Socket}}) -> inet:sockname(Socket); sockname(gen_udp, Socket) -> inet:sockname(Socket); sockname(Transport, Socket) -> Transport:sockname(Socket). port(gen_udp, {_,Socket}) -> inet:port(Socket); port(Transport, Socket) -> Transport:port(Socket). emulated_options() -> [mode, active, packet, packet_size]. emulated_options(Opts) -> emulated_options(Opts, internal_inet_values(), default_inet_values()). internal_inet_values() -> [{active, false}, {mode,binary}]. default_inet_values() -> [{active, true}, {mode, list}, {packet, 0}, {packet_size, 0}]. default_cb_info() -> {gen_udp, udp, udp_closed, udp_error, udp_passive}. get_emulated_opts(EmOpts, EmOptNames) -> lists:map(fun(Name) -> {value, Value} = lists:keysearch(Name, 1, EmOpts), Value end, EmOptNames). emulated_socket_options(InetValues, #socket_options{ mode = Mode, packet = Packet, packet_size = PacketSize, active = Active}) -> #socket_options{ mode = proplists:get_value(mode, InetValues, Mode), packet = proplists:get_value(packet, InetValues, Packet), packet_size = proplists:get_value(packet_size, InetValues, PacketSize), active = emulated_active_option(InetValues, Active) }. emulated_active_option([], Active) -> Active; emulated_active_option([{active, Active} | _], _) when Active =< 0 -> false; emulated_active_option([{active, Active} | _], _) -> Active; emulated_active_option([_|Tail], Active) -> emulated_active_option(Tail, Active). emulated_options([{mode, Value} = Opt |Opts], Inet, Emulated) -> validate_inet_option(mode, Value), emulated_options(Opts, Inet, [Opt | proplists:delete(mode, Emulated)]); emulated_options([{header, _} = Opt | _], _, _) -> throw({error, {options, {not_supported, Opt}}}); emulated_options([{active, Value} = Opt |Opts], Inet, Emulated) -> validate_inet_option(active, Value), emulated_options(Opts, Inet, [Opt | proplists:delete(active, Emulated)]); emulated_options([{packet, _} = Opt | _], _, _) -> throw({error, {options, {not_supported, Opt}}}); emulated_options([{packet_size, _} = Opt | _], _, _) -> throw({error, {options, {not_supported, Opt}}}); emulated_options([Opt|Opts], Inet, Emulated) -> emulated_options(Opts, [Opt|Inet], Emulated); emulated_options([], Inet,Emulated) -> {Inet, Emulated}. validate_inet_option(mode, Value) when Value =/= list, Value =/= binary -> throw({error, {options, {mode,Value}}}); validate_inet_option(active, Value) when Value >= -32768, Value =< 32767 -> ok; validate_inet_option(active, Value) when Value =/= true, Value =/= false, Value =/= once -> throw({error, {options, {active,Value}}}); validate_inet_option(_, _) -> ok.