From a94f7bd152a08a5d3fc43475f3fae7d886705b08 Mon Sep 17 00:00:00 2001 From: Fredrik Gustafsson Date: Tue, 6 Nov 2012 14:56:57 +0100 Subject: Option {bit_limit, integer()} to close sockets where clients are too slow --- .../src/http_server/httpd_request_handler.erl | 52 +++++++++++++++++----- 1 file changed, 41 insertions(+), 11 deletions(-) diff --git a/lib/inets/src/http_server/httpd_request_handler.erl b/lib/inets/src/http_server/httpd_request_handler.erl index b62c10bbc7..0d584c70ae 100644 --- a/lib/inets/src/http_server/httpd_request_handler.erl +++ b/lib/inets/src/http_server/httpd_request_handler.erl @@ -44,7 +44,9 @@ timeout, %% infinity | integer() > 0 timer, %% ref() - Request timer headers, %% #http_request_h{} - body %% binary() + body, %% binary() + data, %% The total data received in bits, checked after 10s + bit_limit %% Bit limit per second before kick out }). %%==================================================================== @@ -98,7 +100,6 @@ init([Manager, ConfigDB, AcceptTimeout]) -> [{socket_type, SocketType}, {socket, Socket}]), TimeOut = httpd_util:lookup(ConfigDB, keep_alive_timeout, 150000), - Then = erlang:now(), ?hdrd("negotiate", []), @@ -139,12 +140,11 @@ continue_init(Manager, ConfigDB, SocketType, Socket, TimeOut) -> mfa = MFA}, ?hdrt("activate request timeout", []), - NewState = activate_request_timeout(State), ?hdrt("set socket options (binary, packet & active)", []), http_transport:setopts(SocketType, Socket, [binary, {packet, 0}, {active, once}]), - + NewState = data_receive_counter(activate_request_timeout(State), httpd_util:lookup(ConfigDB, bit_limit, false)), ?hdrt("init done", []), gen_server:enter_loop(?MODULE, [], NewState). @@ -205,16 +205,25 @@ handle_info({Proto, Socket, Data}, ?hdrd("received data", [{data, Data}, {proto, Proto}, {socket, Socket}, {socket_type, SockType}, {mfa, MFA}]), - + %% case (catch Module:Function([Data | Args])) of PROCESSED = (catch Module:Function([Data | Args])), - + NewDataSize = case State#state.bit_limit of + undefined -> + undefined; + _ -> + State#state.data + bit_size(Data) + end, ?hdrt("data processed", [{processing_result, PROCESSED}]), - case PROCESSED of {ok, Result} -> ?hdrd("data processed", [{result, Result}]), - NewState = cancel_request_timeout(State), + NewState = case NewDataSize of + undefined -> + cancel_request_timeout(State); + _ -> + set_new_data_size(cancel_request_timeout(State), NewDataSize) + end, handle_http_msg(Result, NewState); {error, {uri_too_long, MaxSize}, Version} -> @@ -239,7 +248,12 @@ handle_info({Proto, Socket, Data}, NewMFA -> ?hdrd("data processed - reactivate socket", [{new_mfa, NewMFA}]), http_transport:setopts(SockType, Socket, [{active, once}]), - {noreply, State#state{mfa = NewMFA}} + case NewDataSize of + undefined -> + {noreply, State#state{mfa = NewMFA}}; + _ -> + {noreply, State#state{mfa = NewMFA, data = NewDataSize}} + end end; %% Error cases @@ -263,7 +277,14 @@ handle_info(timeout, #state{mod = ModData} = State) -> error_log("The client did not send the whole request before the " "server side timeout", ModData), {stop, normal, State#state{response_sent = true}}; - +handle_info(check_data, #state{data = Data, bit_limit = Bit_Limit} = State) -> + case Data >= (Bit_Limit * 10) of %% Ten seconds itnerval + true -> + erlang:send_after(10000, self(), check_data), + {noreply, State#state{data = 0}}; + _ -> + {stop, normal, State#state{response_sent = true}} + end; %% Default case handle_info(Info, #state{mod = ModData} = State) -> Error = lists:flatten( @@ -311,6 +332,8 @@ code_change(_OldVsn, State, _Extra) -> %%-------------------------------------------------------------------- %%% Internal functions %%-------------------------------------------------------------------- +set_new_data_size(State, NewData) -> + State#state{data = NewData}. await_socket_ownership_transfer(AcceptTimeout) -> receive {socket_ownership_transfered, SocketType, Socket} -> @@ -603,7 +626,14 @@ activate_request_timeout(#state{timeout = Time} = State) -> ?hdrt("activate request timeout", [{time, Time}]), Ref = erlang:send_after(Time, self(), timeout), State#state{timer = Ref}. - +data_receive_counter(State, Bit_limit) -> + case Bit_limit of + false -> + State#state{data = 0}; + Nr -> + erlang:send_after(10000, self(), check_data), + State#state{data = 0, bit_limit = Nr} + end. cancel_request_timeout(#state{timer = undefined} = State) -> State; cancel_request_timeout(#state{timer = Timer} = State) -> -- cgit v1.2.3 From 6291667b75dc66f57b1e00fed9aac356538b9ed9 Mon Sep 17 00:00:00 2001 From: Fredrik Gustafsson Date: Tue, 6 Nov 2012 16:00:52 +0100 Subject: Changed to Bytes Per Second when checking clients rate, new options {byte_limit, integer()} --- lib/inets/src/http_server/httpd_request_handler.erl | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/lib/inets/src/http_server/httpd_request_handler.erl b/lib/inets/src/http_server/httpd_request_handler.erl index 0d584c70ae..73ccd74917 100644 --- a/lib/inets/src/http_server/httpd_request_handler.erl +++ b/lib/inets/src/http_server/httpd_request_handler.erl @@ -46,7 +46,7 @@ headers, %% #http_request_h{} body, %% binary() data, %% The total data received in bits, checked after 10s - bit_limit %% Bit limit per second before kick out + byte_limit %% Bit limit per second before kick out }). %%==================================================================== @@ -144,7 +144,7 @@ continue_init(Manager, ConfigDB, SocketType, Socket, TimeOut) -> ?hdrt("set socket options (binary, packet & active)", []), http_transport:setopts(SocketType, Socket, [binary, {packet, 0}, {active, once}]), - NewState = data_receive_counter(activate_request_timeout(State), httpd_util:lookup(ConfigDB, bit_limit, false)), + NewState = data_receive_counter(activate_request_timeout(State), httpd_util:lookup(ConfigDB, byte_limit, false)), ?hdrt("init done", []), gen_server:enter_loop(?MODULE, [], NewState). @@ -208,11 +208,11 @@ handle_info({Proto, Socket, Data}, %% case (catch Module:Function([Data | Args])) of PROCESSED = (catch Module:Function([Data | Args])), - NewDataSize = case State#state.bit_limit of + NewDataSize = case State#state.byte_limit of undefined -> undefined; _ -> - State#state.data + bit_size(Data) + State#state.data + byte_size(Data) end, ?hdrt("data processed", [{processing_result, PROCESSED}]), case PROCESSED of @@ -277,8 +277,8 @@ handle_info(timeout, #state{mod = ModData} = State) -> error_log("The client did not send the whole request before the " "server side timeout", ModData), {stop, normal, State#state{response_sent = true}}; -handle_info(check_data, #state{data = Data, bit_limit = Bit_Limit} = State) -> - case Data >= (Bit_Limit * 10) of %% Ten seconds itnerval +handle_info(check_data, #state{data = Data, byte_limit = Byte_Limit} = State) -> + case Data >= (Byte_Limit * 10) of %% Ten seconds itnerval true -> erlang:send_after(10000, self(), check_data), {noreply, State#state{data = 0}}; @@ -626,13 +626,13 @@ activate_request_timeout(#state{timeout = Time} = State) -> ?hdrt("activate request timeout", [{time, Time}]), Ref = erlang:send_after(Time, self(), timeout), State#state{timer = Ref}. -data_receive_counter(State, Bit_limit) -> - case Bit_limit of +data_receive_counter(State, Byte_limit) -> + case Byte_limit of false -> State#state{data = 0}; Nr -> erlang:send_after(10000, self(), check_data), - State#state{data = 0, bit_limit = Nr} + State#state{data = 0, byte_limit = Nr} end. cancel_request_timeout(#state{timer = undefined} = State) -> State; -- cgit v1.2.3 From 40d2dd2e50cf245ae88e0a10b3d2f50386264ca5 Mon Sep 17 00:00:00 2001 From: Fredrik Gustafsson Date: Tue, 6 Nov 2012 16:42:09 +0100 Subject: Changed property to minimum_bytes_per_second and checking if is integer on start --- lib/inets/src/http_server/httpd_conf.erl | 15 +++++++++++++-- lib/inets/src/http_server/httpd_request_handler.erl | 2 +- 2 files changed, 14 insertions(+), 3 deletions(-) diff --git a/lib/inets/src/http_server/httpd_conf.erl b/lib/inets/src/http_server/httpd_conf.erl index b575d7331b..884e3defb8 100644 --- a/lib/inets/src/http_server/httpd_conf.erl +++ b/lib/inets/src/http_server/httpd_conf.erl @@ -483,7 +483,7 @@ validate_properties(Properties) -> case mandatory_properties(Properties) of ok -> %% Second, check that property dependency are ok - {ok, validate_properties2(Properties)}; + {ok, check_minimum_bytes_per_second(validate_properties2(Properties))}; Error -> throw(Error) end. @@ -522,7 +522,18 @@ validate_properties2(Properties) -> throw(Error) end end. - +check_minimum_bytes_per_second(Properties) -> + case proplists:get_value(minimum_bytes_per_second, Properties, false) of + false -> + Properties; + Nr -> + case is_integer(Nr) of + false -> + throw({error, {minimum_bytes_per_second, is_not_integer}}); + _ -> + Properties + end + end. mandatory_properties(ConfigList) -> a_must(ConfigList, [server_name, port, server_root, document_root]). diff --git a/lib/inets/src/http_server/httpd_request_handler.erl b/lib/inets/src/http_server/httpd_request_handler.erl index 73ccd74917..0f9e82fd29 100644 --- a/lib/inets/src/http_server/httpd_request_handler.erl +++ b/lib/inets/src/http_server/httpd_request_handler.erl @@ -144,7 +144,7 @@ continue_init(Manager, ConfigDB, SocketType, Socket, TimeOut) -> ?hdrt("set socket options (binary, packet & active)", []), http_transport:setopts(SocketType, Socket, [binary, {packet, 0}, {active, once}]), - NewState = data_receive_counter(activate_request_timeout(State), httpd_util:lookup(ConfigDB, byte_limit, false)), + NewState = data_receive_counter(activate_request_timeout(State), httpd_util:lookup(ConfigDB, minimum_bytes_per_second, false)), ?hdrt("init done", []), gen_server:enter_loop(?MODULE, [], NewState). -- cgit v1.2.3 From 3b452c27de38356a3926f716c448cbffb3763a69 Mon Sep 17 00:00:00 2001 From: Fredrik Gustafsson Date: Wed, 7 Nov 2012 13:56:48 +0100 Subject: Documentation for minimum_bytes_per_seconds option --- lib/inets/doc/src/httpd.xml | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/lib/inets/doc/src/httpd.xml b/lib/inets/doc/src/httpd.xml index 7e21229fcf..8497d91549 100644 --- a/lib/inets/doc/src/httpd.xml +++ b/lib/inets/doc/src/httpd.xml @@ -178,7 +178,13 @@

Note that this option is only used when the option socket_type has the value ip_comm.

- + + {minimum_bytes_per_second, integer()} + +

If given, sets a minimum bytes per second value for connections.

+

If the value is not reached, the socket will close for that connection.

+

The option is good for reducing the risk of "slow dos" attacks.

+
-- cgit v1.2.3 From 39537f88c45304d0fbd1cd61882fc7df7473041a Mon Sep 17 00:00:00 2001 From: Fredrik Gustafsson Date: Fri, 9 Nov 2012 12:04:48 +0100 Subject: Reduced checking of bytes per second to every second --- lib/inets/src/http_server/httpd_request_handler.erl | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/lib/inets/src/http_server/httpd_request_handler.erl b/lib/inets/src/http_server/httpd_request_handler.erl index 0f9e82fd29..56c934a0f6 100644 --- a/lib/inets/src/http_server/httpd_request_handler.erl +++ b/lib/inets/src/http_server/httpd_request_handler.erl @@ -278,9 +278,9 @@ handle_info(timeout, #state{mod = ModData} = State) -> "server side timeout", ModData), {stop, normal, State#state{response_sent = true}}; handle_info(check_data, #state{data = Data, byte_limit = Byte_Limit} = State) -> - case Data >= (Byte_Limit * 10) of %% Ten seconds itnerval + case Data >= Byte_Limit of true -> - erlang:send_after(10000, self(), check_data), + erlang:send_after(1000, self(), check_data), {noreply, State#state{data = 0}}; _ -> {stop, normal, State#state{response_sent = true}} @@ -631,7 +631,7 @@ data_receive_counter(State, Byte_limit) -> false -> State#state{data = 0}; Nr -> - erlang:send_after(10000, self(), check_data), + erlang:send_after(1000, self(), check_data), State#state{data = 0, byte_limit = Nr} end. cancel_request_timeout(#state{timer = undefined} = State) -> -- cgit v1.2.3 From 6cd69c84f6c44434b4a09ad4050320fce9ea077f Mon Sep 17 00:00:00 2001 From: Fredrik Gustafsson Date: Fri, 9 Nov 2012 15:14:36 +0100 Subject: =?UTF-8?q?Added=20testcases=20f=C3=B6r=20minimum=5Fbytes=5Fper=5F?= =?UTF-8?q?second=20option?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- lib/inets/test/httpd_basic_SUITE.erl | 13 +++++++++++-- 1 file changed, 11 insertions(+), 2 deletions(-) diff --git a/lib/inets/test/httpd_basic_SUITE.erl b/lib/inets/test/httpd_basic_SUITE.erl index 7a476ea14a..110293b307 100644 --- a/lib/inets/test/httpd_basic_SUITE.erl +++ b/lib/inets/test/httpd_basic_SUITE.erl @@ -34,7 +34,8 @@ all() -> [ uri_too_long_414, header_too_long_413, - escaped_url_in_error_body + escaped_url_in_error_body, + slowdose ]. groups() -> @@ -278,7 +279,15 @@ escaped_url_in_error_body(Config) when is_list(Config) -> inets:stop(httpd, Pid), tsp("escaped_url_in_error_body -> done"), ok. - +slowdose(Config) when is_list(Config) -> + {ok, Pid} = inets:start(httpd, [{port, 0}, {server_name, "test"}, {server_root, "/tmp"}, {document_root, "/tmp"}, {minimum_bytes_per_second, 200}]), + Info = httpd:info(Pid), + Port = proplists:get_value(port, Info), + {ok, Socket} = gen_tcp:connect("localhost", Port, []), + receive + after 2000 -> + {error, closed} = gen_tcp:send(Socket, "Hey") + end. find_URL_path([]) -> ""; find_URL_path(["URL", URL | _]) -> -- cgit v1.2.3 From cf199dd9898a636f03630950c4ea1a4c5c21ce81 Mon Sep 17 00:00:00 2001 From: Fredrik Gustafsson Date: Fri, 9 Nov 2012 15:39:13 +0100 Subject: Bumped version nr --- lib/inets/src/inets_app/inets.appup.src | 14 ++++++++++++++ lib/inets/vsn.mk | 2 +- 2 files changed, 15 insertions(+), 1 deletion(-) diff --git a/lib/inets/src/inets_app/inets.appup.src b/lib/inets/src/inets_app/inets.appup.src index 2adb2a0fc8..ffd0ed622f 100644 --- a/lib/inets/src/inets_app/inets.appup.src +++ b/lib/inets/src/inets_app/inets.appup.src @@ -18,8 +18,14 @@ {"%VSN%", [ + {"5.9.1", + [ + {load_module, httpd_request_handler, soft_purge, soft_purge, []} + ] + }, {"5.9", [ + {load_module, httpd_request_handler, soft_purge, soft_purge, []}, {load_module, tftp, soft_purge, soft_purge, [inets_service]}, {load_module, inets_service, soft_purge, soft_purge, []}, {load_module, httpc, soft_purge, soft_purge, [httpc_manager]}, @@ -29,6 +35,7 @@ }, {"5.8.1", [ + {load_module, httpd_request_handler, soft_purge, soft_purge, []}, {load_module, tftp, soft_purge, soft_purge, [inets_service]}, {load_module, inets_service, soft_purge, soft_purge, []}, @@ -64,8 +71,14 @@ } ], [ + {"5.9.1", + [ + {load_module, httpd_request_handler, soft_purge, soft_purge, []} + ] + }, {"5.9", [ + {load_module, httpd_request_handler, soft_purge, soft_purge, []}, {load_module, tftp, soft_purge, soft_purge, [inets_service]}, {load_module, inets_service, soft_purge, soft_purge, []}, {load_module, httpc, soft_purge, soft_purge, [httpc_manager]}, @@ -75,6 +88,7 @@ }, {"5.8.1", [ + {load_module, httpd_request_handler, soft_purge, soft_purge, []}, {load_module, tftp, soft_purge, soft_purge, [inets_service]}, {load_module, inets_service, soft_purge, soft_purge, []}, diff --git a/lib/inets/vsn.mk b/lib/inets/vsn.mk index 949eceea7f..0c7cb5e7c2 100644 --- a/lib/inets/vsn.mk +++ b/lib/inets/vsn.mk @@ -18,7 +18,7 @@ # %CopyrightEnd% APPLICATION = inets -INETS_VSN = 5.9.1 +INETS_VSN = 5.9.2 PRE_VSN = APP_VSN = "$(APPLICATION)-$(INETS_VSN)$(PRE_VSN)" -- cgit v1.2.3 From 7f9bc436433f8feefeb44d05ef92868fa6d3e1c9 Mon Sep 17 00:00:00 2001 From: Fredrik Gustafsson Date: Fri, 9 Nov 2012 15:46:18 +0100 Subject: Doc for test case slowdose --- lib/inets/test/httpd_basic_SUITE.erl | 2 ++ 1 file changed, 2 insertions(+) diff --git a/lib/inets/test/httpd_basic_SUITE.erl b/lib/inets/test/httpd_basic_SUITE.erl index 110293b307..326a485e31 100644 --- a/lib/inets/test/httpd_basic_SUITE.erl +++ b/lib/inets/test/httpd_basic_SUITE.erl @@ -279,6 +279,8 @@ escaped_url_in_error_body(Config) when is_list(Config) -> inets:stop(httpd, Pid), tsp("escaped_url_in_error_body -> done"), ok. +slowdose(doc) -> + ["Testing minimum bytes per second option"]; slowdose(Config) when is_list(Config) -> {ok, Pid} = inets:start(httpd, [{port, 0}, {server_name, "test"}, {server_root, "/tmp"}, {document_root, "/tmp"}, {minimum_bytes_per_second, 200}]), Info = httpd:info(Pid), -- cgit v1.2.3 From c593b38ccf24ccf09b9371518da6e48d31dbc55f Mon Sep 17 00:00:00 2001 From: Fredrik Gustafsson Date: Mon, 12 Nov 2012 10:08:52 +0100 Subject: Changes to slowdos testcase --- lib/inets/test/httpd_basic_SUITE.erl | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/lib/inets/test/httpd_basic_SUITE.erl b/lib/inets/test/httpd_basic_SUITE.erl index 326a485e31..8f29745f31 100644 --- a/lib/inets/test/httpd_basic_SUITE.erl +++ b/lib/inets/test/httpd_basic_SUITE.erl @@ -282,7 +282,8 @@ escaped_url_in_error_body(Config) when is_list(Config) -> slowdose(doc) -> ["Testing minimum bytes per second option"]; slowdose(Config) when is_list(Config) -> - {ok, Pid} = inets:start(httpd, [{port, 0}, {server_name, "test"}, {server_root, "/tmp"}, {document_root, "/tmp"}, {minimum_bytes_per_second, 200}]), + HttpdConf = ?config(httpd_conf, Config), + {ok, Pid} = inets:start(httpd, [{port, 0}, {minimum_bytes_per_second, 200}|HttpdConf]), Info = httpd:info(Pid), Port = proplists:get_value(port, Info), {ok, Socket} = gen_tcp:connect("localhost", Port, []), -- cgit v1.2.3 From fec356cb10757fbeb32abcfdb07f5152ca18c0de Mon Sep 17 00:00:00 2001 From: Fredrik Gustafsson Date: Wed, 14 Nov 2012 16:08:00 +0100 Subject: Fine adjustments of slowdos alg --- lib/inets/src/http_server/httpd_request_handler.erl | 10 +++++++++- lib/inets/test/httpd_basic_SUITE.erl | 2 +- 2 files changed, 10 insertions(+), 2 deletions(-) diff --git a/lib/inets/src/http_server/httpd_request_handler.erl b/lib/inets/src/http_server/httpd_request_handler.erl index 56c934a0f6..5e0bd39cb3 100644 --- a/lib/inets/src/http_server/httpd_request_handler.erl +++ b/lib/inets/src/http_server/httpd_request_handler.erl @@ -277,6 +277,14 @@ handle_info(timeout, #state{mod = ModData} = State) -> error_log("The client did not send the whole request before the " "server side timeout", ModData), {stop, normal, State#state{response_sent = true}}; +handle_info(check_data_first, #state{data = Data, byte_limit = Byte_Limit} = State) -> + case Data >= (Byte_Limit*3) of + true -> + erlang:send_after(1000, self(), check_data), + {noreply, State#state{data = 0}}; + _ -> + {stop, normal, State#state{response_sent = true}} + end; handle_info(check_data, #state{data = Data, byte_limit = Byte_Limit} = State) -> case Data >= Byte_Limit of true -> @@ -631,7 +639,7 @@ data_receive_counter(State, Byte_limit) -> false -> State#state{data = 0}; Nr -> - erlang:send_after(1000, self(), check_data), + erlang:send_after(3000, self(), check_data_first), State#state{data = 0, byte_limit = Nr} end. cancel_request_timeout(#state{timer = undefined} = State) -> diff --git a/lib/inets/test/httpd_basic_SUITE.erl b/lib/inets/test/httpd_basic_SUITE.erl index 8f29745f31..f1c1c41dcb 100644 --- a/lib/inets/test/httpd_basic_SUITE.erl +++ b/lib/inets/test/httpd_basic_SUITE.erl @@ -288,7 +288,7 @@ slowdose(Config) when is_list(Config) -> Port = proplists:get_value(port, Info), {ok, Socket} = gen_tcp:connect("localhost", Port, []), receive - after 2000 -> + after 4000 -> {error, closed} = gen_tcp:send(Socket, "Hey") end. find_URL_path([]) -> -- cgit v1.2.3 From 5431f00a394e4ebfd2b1691afcffdc617bb06fd1 Mon Sep 17 00:00:00 2001 From: Fredrik Gustafsson Date: Thu, 15 Nov 2012 15:20:10 +0100 Subject: Increased waiting time to check if socket is down --- lib/inets/test/httpd_basic_SUITE.erl | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/inets/test/httpd_basic_SUITE.erl b/lib/inets/test/httpd_basic_SUITE.erl index f1c1c41dcb..523cf9d38c 100644 --- a/lib/inets/test/httpd_basic_SUITE.erl +++ b/lib/inets/test/httpd_basic_SUITE.erl @@ -288,7 +288,7 @@ slowdose(Config) when is_list(Config) -> Port = proplists:get_value(port, Info), {ok, Socket} = gen_tcp:connect("localhost", Port, []), receive - after 4000 -> + after 6000 -> {error, closed} = gen_tcp:send(Socket, "Hey") end. find_URL_path([]) -> -- cgit v1.2.3