diff options
-rw-r--r-- | lib/inets/doc/src/httpd.xml | 8 | ||||
-rw-r--r-- | lib/inets/src/http_server/httpd_conf.erl | 15 | ||||
-rw-r--r-- | lib/inets/src/http_server/httpd_request_handler.erl | 60 | ||||
-rw-r--r-- | lib/inets/src/inets_app/inets.appup.src | 14 | ||||
-rw-r--r-- | lib/inets/test/httpd_basic_SUITE.erl | 16 | ||||
-rw-r--r-- | lib/inets/vsn.mk | 2 |
6 files changed, 98 insertions, 17 deletions
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 @@ <p>Note that this option is only used when the option <c>socket_type</c> has the value <c>ip_comm</c>. </p> </item> - + <marker id="prop_minimum_bytes_per_second"></marker> + <tag>{minimum_bytes_per_second, integer()}</tag> + <item> + <p>If given, sets a minimum bytes per second value for connections.</p> + <p>If the value is not reached, the socket will close for that connection.</p> + <p>The option is good for reducing the risk of "slow dos" attacks.</p> + </item> </taglist> <marker id="props_api_modules"></marker> diff --git a/lib/inets/src/http_server/httpd_conf.erl b/lib/inets/src/http_server/httpd_conf.erl index 747118431e..a97bbd9b25 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 b62c10bbc7..5e0bd39cb3 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 + byte_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, minimum_bytes_per_second, 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.byte_limit of + undefined -> + undefined; + _ -> + State#state.data + byte_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,22 @@ 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 -> + erlang:send_after(1000, 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 +340,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 +634,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, Byte_limit) -> + case Byte_limit of + false -> + State#state{data = 0}; + Nr -> + erlang:send_after(3000, self(), check_data_first), + State#state{data = 0, byte_limit = Nr} + end. cancel_request_timeout(#state{timer = undefined} = State) -> State; cancel_request_timeout(#state{timer = Timer} = State) -> 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/test/httpd_basic_SUITE.erl b/lib/inets/test/httpd_basic_SUITE.erl index 7a476ea14a..523cf9d38c 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,18 @@ 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) -> + 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, []), + receive + after 6000 -> + {error, closed} = gen_tcp:send(Socket, "Hey") + end. find_URL_path([]) -> ""; find_URL_path(["URL", URL | _]) -> 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)" |