Skip to content

Commit

Permalink
Merge pull request #145 from mochi/ssl-transport-accept
Browse files Browse the repository at this point in the history
mitigate SSL and emfile related conditions per #138
  • Loading branch information
etrepum committed Jan 12, 2015
2 parents 0dccaaa + 4ce9fb1 commit 4270ab6
Show file tree
Hide file tree
Showing 5 changed files with 62 additions and 35 deletions.
6 changes: 1 addition & 5 deletions .travis.yml
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,7 @@ language: erlang
notifications:
email: false
otp_release:
- 17.1
- 17.0
- R16B03-1
- R16B03
- R16B02
- R16B01
- R16B
- R15B03
- R15B02
9 changes: 9 additions & 0 deletions CHANGES.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,12 @@
Version 2.11.0 released 2015-01-12

* Perform SSL handshake after releasing acceptor back into the pool,
and slow accept rate when file descriptors are not available,
to mitigate a potential DoS attack. Adds new mochiweb_socket
functions transport_accept/1 and finish_accept/1 which should be
used in preference to the now deprecated accept/1 function.
https://github.com/mochi/mochiweb/issues/138

Version 2.10.1 released 2015-01-11

* Fixes issue with SSL and mochiweb_websocket. Note that
Expand Down
2 changes: 1 addition & 1 deletion src/mochiweb.app.src
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
%% This is generated from src/mochiweb.app.src
{application, mochiweb,
[{description, "MochiMedia Web Server"},
{vsn, "2.10.1"},
{vsn, "2.11.0"},
{modules, []},
{registered, []},
{env, []},
Expand Down
40 changes: 26 additions & 14 deletions src/mochiweb_acceptor.erl
Original file line number Diff line number Diff line change
Expand Up @@ -10,25 +10,44 @@

-export([start_link/3, start_link/4, init/4]).

-define(EMFILE_SLEEP_MSEC, 100).

start_link(Server, Listen, Loop) ->
start_link(Server, Listen, Loop, []).

start_link(Server, Listen, Loop, Opts) ->
proc_lib:spawn_link(?MODULE, init, [Server, Listen, Loop, Opts]).

init(Server, Listen, Loop, Opts) ->
do_accept(Server, Listen) ->
T1 = os:timestamp(),
case catch mochiweb_socket:accept(Listen) of
case mochiweb_socket:transport_accept(Listen) of
{ok, Socket} ->
gen_server:cast(Server, {accepted, self(), timer:now_diff(os:timestamp(), T1)}),
mochiweb_socket:finish_accept(Socket);
Other ->
Other
end.

init(Server, Listen, Loop, Opts) ->
case catch do_accept(Server, Listen) of
{ok, Socket} ->
call_loop(Loop, Socket, Opts);
{error, closed} ->
exit(normal);
{error, timeout} ->
init(Server, Listen, Loop, Opts);
{error, esslaccept} ->
{error, Err} when Err =:= closed orelse
Err =:= esslaccept orelse
Err =:= timeout ->
exit(normal);
Other ->
%% Mitigate out of file descriptor scenario by sleeping for a
%% short time to slow error rate
case Other of
{error, emfile} ->
receive
after ?EMFILE_SLEEP_MSEC ->
ok
end;
_ ->
ok
end,
error_logger:error_report(
[{application, mochiweb},
"Accept failed error",
Expand All @@ -44,10 +63,3 @@ call_loop({M, F, A}, Socket, Opts) ->
erlang:apply(M, F, [Socket, Opts | A]);
call_loop(Loop, Socket, Opts) ->
Loop(Socket, Opts).

%%
%% Tests
%%
-ifdef(TEST).
-include_lib("eunit/include/eunit.hrl").
-endif.
40 changes: 25 additions & 15 deletions src/mochiweb_socket.erl
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,9 @@

-module(mochiweb_socket).

-export([listen/4, accept/1, recv/3, send/2, close/1, port/1, peername/1,
-export([listen/4,
accept/1, transport_accept/1, finish_accept/1,
recv/3, send/2, close/1, port/1, peername/1,
setopts/2, getopts/2, type/1]).

-define(ACCEPT_TIMEOUT, 2000).
Expand Down Expand Up @@ -66,27 +68,35 @@ filter_unsafe_protcol_versions(Versions) ->
end,
Versions).

%% Provided for backwards compatibility only
accept(ListenSocket) ->
case transport_accept(ListenSocket) of
{ok, Socket} ->
finish_accept(Socket);
{error, _} = Err ->
Err
end.

accept({ssl, ListenSocket}) ->
% There's a bug in ssl:transport_accept/2 at the moment, which is the
% reason for the try...catch block. Should be fixed in OTP R14.
try ssl:transport_accept(ListenSocket, ?SSL_TIMEOUT) of
transport_accept({ssl, ListenSocket}) ->
case ssl:transport_accept(ListenSocket, ?SSL_TIMEOUT) of
{ok, Socket} ->
case ssl:ssl_accept(Socket, ?SSL_HANDSHAKE_TIMEOUT) of
ok ->
{ok, {ssl, Socket}};
{error, _} = Err ->
Err
end;
{ok, {ssl, Socket}};
{error, _} = Err ->
Err
catch
error:{badmatch, {error, Reason}} ->
{error, Reason}
end;
accept(ListenSocket) ->
transport_accept(ListenSocket) ->
gen_tcp:accept(ListenSocket, ?ACCEPT_TIMEOUT).

finish_accept({ssl, Socket}) ->
case ssl:ssl_accept(Socket, ?SSL_HANDSHAKE_TIMEOUT) of
ok ->
{ok, {ssl, Socket}};
{error, _} = Err ->
Err
end;
finish_accept(Socket) ->
{ok, Socket}.

recv({ssl, Socket}, Length, Timeout) ->
ssl:recv(Socket, Length, Timeout);
recv(Socket, Length, Timeout) ->
Expand Down

0 comments on commit 4270ab6

Please sign in to comment.