Skip to content

Commit

Permalink
Merge pull request #4447 from esl/just_ssl_optimisations
Browse files Browse the repository at this point in the history
Optimise just_tls with hibernate_after
  • Loading branch information
jacekwegr authored Dec 24, 2024
2 parents 70b30f4 + 80e5e94 commit bf50db9
Show file tree
Hide file tree
Showing 3 changed files with 75 additions and 66 deletions.
11 changes: 6 additions & 5 deletions src/c2s/mongoose_c2s_ranch.erl
Original file line number Diff line number Diff line change
Expand Up @@ -48,15 +48,16 @@ socket_peername(#state{ip = Ip}) ->

-spec tcp_to_tls(state(), mongoose_listener:options()) ->
{ok, state()} | {error, term()}.
tcp_to_tls(#state{socket = TcpSocket} = State, #{tls := #{module := TlsMod} = TlsConfig}) ->
case tcp_to_tls(TlsMod, TcpSocket, TlsConfig) of
tcp_to_tls(#state{socket = TcpSocket} = State,
#{hibernate_after := HibernateAfter, tls := #{module := TlsMod} = TlsConfig}) ->
case tcp_to_tls(TlsMod, TcpSocket, TlsConfig, HibernateAfter) of
{ok, TlsSocket} ->
{ok, State#state{transport = TlsMod, socket = TlsSocket}};
{error, Reason} ->
{error, Reason}
end.

tcp_to_tls(fast_tls, TcpSocket, TlsConfig) ->
tcp_to_tls(fast_tls, TcpSocket, TlsConfig, _HibernateAfter) ->
PreparedOpts = mongoose_tls:prepare_options(fast_tls, maps:remove(module, TlsConfig)),
ranch_tcp:setopts(TcpSocket, [{active, false}]),
case fast_tls:tcp_to_tls(TcpSocket, PreparedOpts) of
Expand All @@ -65,8 +66,8 @@ tcp_to_tls(fast_tls, TcpSocket, TlsConfig) ->
{ok, TlsSocket};
Other -> Other
end;
tcp_to_tls(just_tls, TcpSocket, TlsConfig) ->
case just_tls:tcp_to_tls(TcpSocket, TlsConfig) of
tcp_to_tls(just_tls, TcpSocket, TlsConfig, HibernateAfter) ->
case just_tls:tcp_to_tls(TcpSocket, TlsConfig#{hibernate_after => HibernateAfter}) of
{ok, TlsSocket} -> {ok, TlsSocket};
Other -> Other
end.
Expand Down
129 changes: 68 additions & 61 deletions src/just_tls.erl
Original file line number Diff line number Diff line change
Expand Up @@ -50,8 +50,7 @@ tcp_to_tls(TCPSocket, Options) ->
{Ref, SSLOpts} = format_opts_with_ref(Options, false),
{Ref, ssl:connect(TCPSocket, SSLOpts)};
#{} ->
FailIfNoPeerCert = fail_if_no_peer_cert_opt(Options),
{Ref, SSLOpts} = format_opts_with_ref(Options, FailIfNoPeerCert),
{Ref, SSLOpts} = format_opts_with_ref(Options, fail_if_no_peer_cert),
{Ref, ssl:handshake(TCPSocket, SSLOpts, 5000)}
end,
VerifyResults = receive_verify_results(Ref1),
Expand All @@ -61,42 +60,36 @@ tcp_to_tls(TCPSocket, Options) ->
_ -> Ret
end.

%% -callback send(tls_socket(), binary()) -> ok | {error, any()}.
-spec send(tls_socket(), binary()) -> ok | {error, any()}.
send(#tls_socket{ssl_socket = SSLSocket}, Packet) -> ssl:send(SSLSocket, Packet).

%% -callback recv_data(tls_socket(), binary()) -> {ok, binary()} | {error, any()}.
recv_data(_, <<"">>) ->
-spec recv_data(tls_socket(), binary()) -> {ok, binary()} | {error, any()}.
recv_data(_, <<>>) ->
%% such call is required for fast_tls to accomplish
%% tls handshake, for just_tls we can ignore it
{ok, <<"">>};
{ok, <<>>};
recv_data(#tls_socket{ssl_socket = SSLSocket}, Data1) ->
case ssl:recv(SSLSocket, 0, 0) of
{ok, Data2} -> {ok, <<Data1/binary, Data2/binary>>};
_ -> {ok, Data1}
end.

%% -callback controlling_process(tls_socket(), pid()) -> ok | {error, any()}.
-spec controlling_process(tls_socket(), pid()) -> ok | {error, any()}.
controlling_process(#tls_socket{ssl_socket = SSLSocket}, Pid) ->
ssl:controlling_process(SSLSocket, Pid).


%% -callback sockname(tls_socket()) -> {ok, {inet:ip_address(), inet:port_number()}} |
%% {error, any()}.
-spec sockname(tls_socket()) -> {ok, {inet:ip_address(), inet:port_number()}} | {error, any()}.
sockname(#tls_socket{ssl_socket = SSLSocket}) -> ssl:sockname(SSLSocket).


%% -callback peername(tls_socket()) -> {ok, {inet:ip_address(), inet:port_number()}} |
%% {error, any()}.
-spec peername(tls_socket()) ->
{ok, {inet:ip_address(), inet:port_number()}} | {error, any()}.
peername(#tls_socket{ssl_socket = SSLSocket}) -> ssl:peername(SSLSocket).


%% -callback setopts(tls_socket(), Opts::list()) -> ok | {error, any()}.
-spec setopts(tls_socket(), Opts::list()) -> ok | {error, any()}.
setopts(#tls_socket{ssl_socket = SSLSocket}, Opts) -> ssl:setopts(SSLSocket, Opts).


%% -callback get_peer_certificate(tls_socket()) -> {ok, Cert::any()} |
%% {bad_cert, bitstring()} |
%% no_peer_cert.
-spec get_peer_certificate(tls_socket()) ->
{ok, Cert::any()} | {bad_cert, bitstring()} | no_peer_cert.
get_peer_certificate(#tls_socket{verify_results = [], ssl_socket = SSLSocket}) ->
case ssl:peercert(SSLSocket) of
{ok, PeerCert} ->
Expand All @@ -107,66 +100,74 @@ get_peer_certificate(#tls_socket{verify_results = [], ssl_socket = SSLSocket}) -
get_peer_certificate(#tls_socket{verify_results = [Err | _]}) ->
{bad_cert, error_to_list(Err)}.

%% -callback close(tls_socket()) -> ok.
close(#tls_socket{ssl_socket = SSLSocket}) -> ssl:close(SSLSocket).
-spec close(tls_socket()) -> ok.
close(#tls_socket{ssl_socket = SSLSocket}) ->
ssl:close(SSLSocket).

%% @doc Prepare SSL options for direct use of ssl:connect/2 (client side)
%% The `disconnect_on_failure' option is expected to be unset or true
-spec make_ssl_opts(mongoose_tls:options()) -> [ssl:tls_option()].
make_ssl_opts(Opts) ->
{dummy_ref, SSLOpts} = format_opts_with_ref(Opts, false),
SSLOpts.
make_ssl_opts(#{verify_mode := Mode} = Opts) ->
SslOpts = format_opts(Opts, false),
[{verify_fun, verify_fun(Mode)} | SslOpts].

%% @doc Prepare SSL options for direct use of ssl:handshake/2 (server side)
%% The `disconnect_on_failure' option is expected to be unset or true
-spec make_cowboy_ssl_opts(mongoose_tls:options()) -> [ssl:tls_option()].
make_cowboy_ssl_opts(Opts) ->
FailIfNoPeerCert = fail_if_no_peer_cert_opt(Opts),
{dummy_ref, SSLOpts} = format_opts_with_ref(Opts, FailIfNoPeerCert),
SSLOpts.
make_cowboy_ssl_opts(#{verify_mode := Mode} = Opts) ->
SslOpts = format_opts(Opts, fail_if_no_peer_cert),
[{verify_fun, verify_fun(Mode)} | SslOpts].

%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%% local functions
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%

format_opts_with_ref(Opts, FailIfNoPeerCert) ->
Verify = verify_opt(Opts),
SslOpts0 = format_opts(Opts, FailIfNoPeerCert),
{Ref, VerifyFun} = verify_fun_opt(Opts),
SNIOpts = sni_opts(Opts),
SSLOpts = maps:to_list(maps:with(ssl_option_keys(), Opts)),
{Ref, [{fail_if_no_peer_cert, FailIfNoPeerCert}, {verify, Verify}, {verify_fun, VerifyFun}] ++
SNIOpts ++ SSLOpts}.
SslOpts = [{verify_fun, VerifyFun} | SslOpts0],
{Ref, SslOpts}.

format_opts(Opts, FailIfNoPeerCert) ->
SslOpts0 = maps:to_list(maps:with(ssl_option_keys(), Opts)),
SslOpts1 = sni_opts(SslOpts0, Opts),
SslOpts2 = verify_opts(SslOpts1, Opts),
SslOpts3 = hibernate_opts(SslOpts2, Opts),
fail_if_no_peer_cert_opts(SslOpts3, Opts, FailIfNoPeerCert).

ssl_option_keys() ->
[certfile, cacertfile, ciphers, keyfile, password, versions, dhfile].

sni_opts(#{server_name_indication := SNIOpts}) ->
process_sni_opts(SNIOpts);
sni_opts(#{}) ->
[].

process_sni_opts(#{enabled := false}) ->
[{server_name_indication, disable}];
process_sni_opts(#{enabled := true, host := SNIHost, protocol := https}) ->
[{server_name_indication, SNIHost},
{customize_hostname_check, [{match_fun, public_key:pkix_verify_hostname_match_fun(https)}]}];
process_sni_opts(#{enabled := true, host := SNIHost, protocol := default}) ->
[{server_name_indication, SNIHost}];
process_sni_opts(#{enabled := true}) ->
[].

error_to_list(_Error) ->
%TODO: implement later if needed
"verify_fun failed".

verify_opt(#{verify_mode := none}) -> verify_none;
verify_opt(#{}) -> verify_peer.

%% accept empty peer certificate if explicitly requested not to fail
fail_if_no_peer_cert_opt(#{disconnect_on_failure := false}) -> false;
fail_if_no_peer_cert_opt(#{verify_mode := peer}) -> true;
fail_if_no_peer_cert_opt(#{verify_mode := selfsigned_peer}) -> true;
fail_if_no_peer_cert_opt(#{}) -> false.
fail_if_no_peer_cert_opts(Opts, #{}, false) ->
[{fail_if_no_peer_cert, false} | Opts];
fail_if_no_peer_cert_opts(Opts, #{disconnect_on_failure := false}, _) ->
[{fail_if_no_peer_cert, false} | Opts];
fail_if_no_peer_cert_opts(Opts, #{verify_mode := Mode}, _)
when Mode =:= peer; Mode =:= selfsigned_peer ->
[{fail_if_no_peer_cert, true} | Opts];
fail_if_no_peer_cert_opts(Opts, #{}, _) ->
[{fail_if_no_peer_cert, false} | Opts].

hibernate_opts(Opts, #{hibernate_after := Timeout}) ->
[{hibernate_after, Timeout} | Opts];
hibernate_opts(Opts, #{}) ->
Opts.

verify_opts(Opts, #{verify_mode := none}) ->
[{verify, verify_none} | Opts];
verify_opts(Opts, #{}) ->
[{verify, verify_peer} | Opts].

sni_opts(Opts, #{server_name_indication := #{enabled := false}}) ->
[{server_name_indication, disable} | Opts];
sni_opts(Opts, #{server_name_indication := #{enabled := true, host := SNIHost, protocol := default}}) ->
[{server_name_indication, SNIHost} | Opts];
sni_opts(Opts, #{server_name_indication := #{enabled := true, host := SNIHost, protocol := https}}) ->
[{server_name_indication, SNIHost},
{customize_hostname_check, [{match_fun, public_key:pkix_verify_hostname_match_fun(https)}]} | Opts];
sni_opts(Opts, #{}) ->
Opts.

%% This function translates TLS options to the function
%% which will later be used when TCP socket is upgraded to TLS
Expand Down Expand Up @@ -226,8 +227,10 @@ verify_fun(none) ->
send_verification_failure(Pid, Ref, Reason) ->
Pid ! {cert_verification_failure, Ref, Reason}.

receive_verify_results(dummy_ref) -> [];
receive_verify_results(Ref) -> receive_verify_results(Ref, []).
receive_verify_results(dummy_ref) ->
[];
receive_verify_results(Ref) ->
receive_verify_results(Ref, []).

receive_verify_results(Ref, Acc) ->
receive
Expand All @@ -236,3 +239,7 @@ receive_verify_results(Ref, Acc) ->
after 0 ->
lists:reverse(Acc)
end.

error_to_list(_Error) ->
%TODO: implement later if needed
"verify_fun failed".
1 change: 1 addition & 0 deletions src/mongoose_tls.erl
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,7 @@
password => string(),
versions => [atom()],
server_name_indication => sni_options(), % client-only
hibernate_after => timeout(),

% only for fast_tls
protocol_options => [string()]}.
Expand Down

0 comments on commit bf50db9

Please sign in to comment.