2017-01-02 19:36:36 +01:00
|
|
|
%% Copyright (c) 2015-2017, Loïc Hoguin <essen@ninenines.eu>
|
2015-06-11 17:04:21 +02:00
|
|
|
%%
|
|
|
|
%% Permission to use, copy, modify, and/or distribute this software for any
|
|
|
|
%% purpose with or without fee is hereby granted, provided that the above
|
|
|
|
%% copyright notice and this permission notice appear in all copies.
|
|
|
|
%%
|
|
|
|
%% THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
|
|
|
|
%% WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
|
|
|
|
%% MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
|
|
|
|
%% ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
|
|
|
|
%% WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
|
|
|
|
%% ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
|
|
|
|
%% OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
|
|
|
|
|
|
|
|
-module(cowboy_http2).
|
|
|
|
|
2018-06-27 17:52:25 +02:00
|
|
|
-ifdef(OTP_RELEASE).
|
|
|
|
-compile({nowarn_deprecated_function, [{erlang, get_stacktrace, 0}]}).
|
|
|
|
-endif.
|
|
|
|
|
2018-10-30 23:30:05 +01:00
|
|
|
-export([init/6]).
|
|
|
|
-export([init/10]).
|
|
|
|
-export([init/12]).
|
2015-06-11 17:04:21 +02:00
|
|
|
|
|
|
|
-export([system_continue/3]).
|
|
|
|
-export([system_terminate/4]).
|
|
|
|
-export([system_code_change/4]).
|
|
|
|
|
2017-05-05 13:48:25 +02:00
|
|
|
-type opts() :: #{
|
2018-11-15 10:11:36 +01:00
|
|
|
compress_buffering => boolean(),
|
2018-02-17 16:43:46 +03:00
|
|
|
compress_threshold => non_neg_integer(),
|
2017-05-05 13:48:25 +02:00
|
|
|
connection_type => worker | supervisor,
|
2019-09-02 14:48:28 +02:00
|
|
|
connection_window_margin_size => 0..16#7fffffff,
|
|
|
|
connection_window_update_threshold => 0..16#7fffffff,
|
2018-04-23 14:34:53 +02:00
|
|
|
enable_connect_protocol => boolean(),
|
2017-05-05 13:48:25 +02:00
|
|
|
env => cowboy_middleware:env(),
|
2018-11-16 16:30:57 +01:00
|
|
|
idle_timeout => timeout(),
|
2017-05-05 13:48:25 +02:00
|
|
|
inactivity_timeout => timeout(),
|
2018-04-26 22:08:05 +02:00
|
|
|
initial_connection_window_size => 65535..16#7fffffff,
|
|
|
|
initial_stream_window_size => 0..16#7fffffff,
|
2018-07-09 10:08:48 +02:00
|
|
|
logger => module(),
|
2018-04-25 21:32:58 +02:00
|
|
|
max_concurrent_streams => non_neg_integer() | infinity,
|
Fix HTTP/2 CVEs
A number of HTTP/2 CVEs were documented recently:
https://www.kb.cert.org/vuls/id/605641/
This commit, along with a few changes and additions in Cowlib,
fix or improve protection against all of them.
For CVE-2019-9511, also known as Data Dribble, the new option
stream_window_data_threshold can be used to control how little
the DATA frames that Cowboy sends can get.
For CVE-2019-9516, also known as 0-Length Headers Leak, Cowboy
will now simply reject streams containing 0-length header names.
For CVE-2019-9517, also known as Internal Data Buffering, the
backpressure changes were already pretty good at preventing this
issue, but a new option max_connection_buffer_size was added for
even better control over how much memory we are willing to allocate.
For CVE-2019-9512, also known as Ping Flood; CVE-2019-9515, also
known as Settings Flood; CVE-2019-9518, also known as Empty Frame
Flooding; and similar undocumented scenarios, a frame rate limiting
mechanism was added. By default Cowboy will now allow 1000 frames
every 10 seconds. This can be configured via max_received_frame_rate.
For CVE-2019-9514, also known as Reset Flood, another rate limiting
mechanism was added and can be configured via max_reset_stream_rate.
By default Cowboy will do up to 10 stream resets every 10 seconds.
Finally, nothing was done for CVE-2019-9513, also known as Resource
Loop, because Cowboy does not currently implement the HTTP/2
priority mechanism (in parts because these issues were well known
from the start).
Tests were added for all cases except Internal Data Buffering,
which I'm not sure how to test, and Resource Loop, which is not
currently relevant.
2019-10-02 10:44:45 +02:00
|
|
|
max_connection_buffer_size => non_neg_integer(),
|
2019-09-02 14:48:28 +02:00
|
|
|
max_connection_window_size => 0..16#7fffffff,
|
2018-04-25 16:55:52 +02:00
|
|
|
max_decode_table_size => non_neg_integer(),
|
|
|
|
max_encode_table_size => non_neg_integer(),
|
2018-04-27 17:58:11 +02:00
|
|
|
max_frame_size_received => 16384..16777215,
|
|
|
|
max_frame_size_sent => 16384..16777215 | infinity,
|
Fix HTTP/2 CVEs
A number of HTTP/2 CVEs were documented recently:
https://www.kb.cert.org/vuls/id/605641/
This commit, along with a few changes and additions in Cowlib,
fix or improve protection against all of them.
For CVE-2019-9511, also known as Data Dribble, the new option
stream_window_data_threshold can be used to control how little
the DATA frames that Cowboy sends can get.
For CVE-2019-9516, also known as 0-Length Headers Leak, Cowboy
will now simply reject streams containing 0-length header names.
For CVE-2019-9517, also known as Internal Data Buffering, the
backpressure changes were already pretty good at preventing this
issue, but a new option max_connection_buffer_size was added for
even better control over how much memory we are willing to allocate.
For CVE-2019-9512, also known as Ping Flood; CVE-2019-9515, also
known as Settings Flood; CVE-2019-9518, also known as Empty Frame
Flooding; and similar undocumented scenarios, a frame rate limiting
mechanism was added. By default Cowboy will now allow 1000 frames
every 10 seconds. This can be configured via max_received_frame_rate.
For CVE-2019-9514, also known as Reset Flood, another rate limiting
mechanism was added and can be configured via max_reset_stream_rate.
By default Cowboy will do up to 10 stream resets every 10 seconds.
Finally, nothing was done for CVE-2019-9513, also known as Resource
Loop, because Cowboy does not currently implement the HTTP/2
priority mechanism (in parts because these issues were well known
from the start).
Tests were added for all cases except Internal Data Buffering,
which I'm not sure how to test, and Resource Loop, which is not
currently relevant.
2019-10-02 10:44:45 +02:00
|
|
|
max_received_frame_rate => {pos_integer(), timeout()},
|
|
|
|
max_reset_stream_rate => {pos_integer(), timeout()},
|
2019-09-13 14:20:04 +02:00
|
|
|
max_stream_buffer_size => non_neg_integer(),
|
2019-09-02 14:48:28 +02:00
|
|
|
max_stream_window_size => 0..16#7fffffff,
|
2018-10-31 11:45:04 +01:00
|
|
|
metrics_callback => cowboy_metrics_h:metrics_callback(),
|
2019-10-07 09:59:36 +02:00
|
|
|
metrics_req_filter => fun((cowboy_req:req()) -> map()),
|
|
|
|
metrics_resp_headers_filter => fun((cowboy:http_headers()) -> cowboy:http_headers()),
|
2017-05-05 13:48:25 +02:00
|
|
|
middlewares => [module()],
|
|
|
|
preface_timeout => timeout(),
|
2018-10-31 11:45:04 +01:00
|
|
|
proxy_header => boolean(),
|
2018-11-03 18:55:40 +01:00
|
|
|
sendfile => boolean(),
|
2018-04-28 10:59:56 +02:00
|
|
|
settings_timeout => timeout(),
|
2017-05-05 13:48:25 +02:00
|
|
|
shutdown_timeout => timeout(),
|
2018-10-31 11:45:04 +01:00
|
|
|
stream_handlers => [module()],
|
Fix HTTP/2 CVEs
A number of HTTP/2 CVEs were documented recently:
https://www.kb.cert.org/vuls/id/605641/
This commit, along with a few changes and additions in Cowlib,
fix or improve protection against all of them.
For CVE-2019-9511, also known as Data Dribble, the new option
stream_window_data_threshold can be used to control how little
the DATA frames that Cowboy sends can get.
For CVE-2019-9516, also known as 0-Length Headers Leak, Cowboy
will now simply reject streams containing 0-length header names.
For CVE-2019-9517, also known as Internal Data Buffering, the
backpressure changes were already pretty good at preventing this
issue, but a new option max_connection_buffer_size was added for
even better control over how much memory we are willing to allocate.
For CVE-2019-9512, also known as Ping Flood; CVE-2019-9515, also
known as Settings Flood; CVE-2019-9518, also known as Empty Frame
Flooding; and similar undocumented scenarios, a frame rate limiting
mechanism was added. By default Cowboy will now allow 1000 frames
every 10 seconds. This can be configured via max_received_frame_rate.
For CVE-2019-9514, also known as Reset Flood, another rate limiting
mechanism was added and can be configured via max_reset_stream_rate.
By default Cowboy will do up to 10 stream resets every 10 seconds.
Finally, nothing was done for CVE-2019-9513, also known as Resource
Loop, because Cowboy does not currently implement the HTTP/2
priority mechanism (in parts because these issues were well known
from the start).
Tests were added for all cases except Internal Data Buffering,
which I'm not sure how to test, and Resource Loop, which is not
currently relevant.
2019-10-02 10:44:45 +02:00
|
|
|
stream_window_data_threshold => 0..16#7fffffff,
|
2019-09-02 14:48:28 +02:00
|
|
|
stream_window_margin_size => 0..16#7fffffff,
|
|
|
|
stream_window_update_threshold => 0..16#7fffffff,
|
2018-10-31 11:45:04 +01:00
|
|
|
tracer_callback => cowboy_tracer_h:tracer_callback(),
|
2018-02-17 16:43:46 +03:00
|
|
|
tracer_match_specs => cowboy_tracer_h:tracer_match_specs(),
|
|
|
|
%% Open ended because configured stream handlers might add options.
|
|
|
|
_ => _
|
2017-05-05 13:48:25 +02:00
|
|
|
}.
|
|
|
|
-export_type([opts/0]).
|
|
|
|
|
2019-09-02 14:48:28 +02:00
|
|
|
-record(stream, {
|
|
|
|
%% Whether the stream is currently stopping.
|
|
|
|
status = running :: running | stopping,
|
|
|
|
|
|
|
|
%% Flow requested for this stream.
|
|
|
|
flow = 0 :: non_neg_integer(),
|
|
|
|
|
|
|
|
%% Stream state.
|
|
|
|
state :: {module, any()}
|
|
|
|
}).
|
|
|
|
|
2015-06-11 17:04:21 +02:00
|
|
|
-record(state, {
|
|
|
|
parent = undefined :: pid(),
|
|
|
|
ref :: ranch:ref(),
|
|
|
|
socket = undefined :: inet:socket(),
|
|
|
|
transport :: module(),
|
2018-10-30 23:30:05 +01:00
|
|
|
proxy_header :: undefined | ranch_proxy_header:proxy_info(),
|
2017-05-05 13:48:25 +02:00
|
|
|
opts = #{} :: opts(),
|
2015-06-11 17:04:21 +02:00
|
|
|
|
2018-11-16 16:30:57 +01:00
|
|
|
%% Timer for idle_timeout.
|
|
|
|
timer = undefined :: undefined | reference(),
|
|
|
|
|
2016-06-20 17:28:59 +02:00
|
|
|
%% Remote address and port for the connection.
|
|
|
|
peer = undefined :: {inet:ip_address(), inet:port_number()},
|
|
|
|
|
2017-10-25 20:17:21 +01:00
|
|
|
%% Local address and port for the connection.
|
|
|
|
sock = undefined :: {inet:ip_address(), inet:port_number()},
|
|
|
|
|
|
|
|
%% Client certificate (TLS only).
|
|
|
|
cert :: undefined | binary(),
|
|
|
|
|
2018-10-26 10:18:57 +02:00
|
|
|
%% HTTP/2 state machine.
|
2019-08-16 16:37:04 +02:00
|
|
|
http2_status :: sequence | settings | upgrade | connected | closing,
|
2018-10-26 10:18:57 +02:00
|
|
|
http2_machine :: cow_http2_machine:http2_machine(),
|
2015-06-11 17:04:21 +02:00
|
|
|
|
Fix HTTP/2 CVEs
A number of HTTP/2 CVEs were documented recently:
https://www.kb.cert.org/vuls/id/605641/
This commit, along with a few changes and additions in Cowlib,
fix or improve protection against all of them.
For CVE-2019-9511, also known as Data Dribble, the new option
stream_window_data_threshold can be used to control how little
the DATA frames that Cowboy sends can get.
For CVE-2019-9516, also known as 0-Length Headers Leak, Cowboy
will now simply reject streams containing 0-length header names.
For CVE-2019-9517, also known as Internal Data Buffering, the
backpressure changes were already pretty good at preventing this
issue, but a new option max_connection_buffer_size was added for
even better control over how much memory we are willing to allocate.
For CVE-2019-9512, also known as Ping Flood; CVE-2019-9515, also
known as Settings Flood; CVE-2019-9518, also known as Empty Frame
Flooding; and similar undocumented scenarios, a frame rate limiting
mechanism was added. By default Cowboy will now allow 1000 frames
every 10 seconds. This can be configured via max_received_frame_rate.
For CVE-2019-9514, also known as Reset Flood, another rate limiting
mechanism was added and can be configured via max_reset_stream_rate.
By default Cowboy will do up to 10 stream resets every 10 seconds.
Finally, nothing was done for CVE-2019-9513, also known as Resource
Loop, because Cowboy does not currently implement the HTTP/2
priority mechanism (in parts because these issues were well known
from the start).
Tests were added for all cases except Internal Data Buffering,
which I'm not sure how to test, and Resource Loop, which is not
currently relevant.
2019-10-02 10:44:45 +02:00
|
|
|
%% HTTP/2 frame rate flood protection.
|
2019-10-02 12:01:40 +02:00
|
|
|
frame_rate_num :: undefined | pos_integer(),
|
|
|
|
frame_rate_time :: undefined | integer(),
|
Fix HTTP/2 CVEs
A number of HTTP/2 CVEs were documented recently:
https://www.kb.cert.org/vuls/id/605641/
This commit, along with a few changes and additions in Cowlib,
fix or improve protection against all of them.
For CVE-2019-9511, also known as Data Dribble, the new option
stream_window_data_threshold can be used to control how little
the DATA frames that Cowboy sends can get.
For CVE-2019-9516, also known as 0-Length Headers Leak, Cowboy
will now simply reject streams containing 0-length header names.
For CVE-2019-9517, also known as Internal Data Buffering, the
backpressure changes were already pretty good at preventing this
issue, but a new option max_connection_buffer_size was added for
even better control over how much memory we are willing to allocate.
For CVE-2019-9512, also known as Ping Flood; CVE-2019-9515, also
known as Settings Flood; CVE-2019-9518, also known as Empty Frame
Flooding; and similar undocumented scenarios, a frame rate limiting
mechanism was added. By default Cowboy will now allow 1000 frames
every 10 seconds. This can be configured via max_received_frame_rate.
For CVE-2019-9514, also known as Reset Flood, another rate limiting
mechanism was added and can be configured via max_reset_stream_rate.
By default Cowboy will do up to 10 stream resets every 10 seconds.
Finally, nothing was done for CVE-2019-9513, also known as Resource
Loop, because Cowboy does not currently implement the HTTP/2
priority mechanism (in parts because these issues were well known
from the start).
Tests were added for all cases except Internal Data Buffering,
which I'm not sure how to test, and Resource Loop, which is not
currently relevant.
2019-10-02 10:44:45 +02:00
|
|
|
|
|
|
|
%% HTTP/2 reset stream flood protection.
|
2019-10-02 12:01:40 +02:00
|
|
|
reset_rate_num :: undefined | pos_integer(),
|
|
|
|
reset_rate_time :: undefined | integer(),
|
Fix HTTP/2 CVEs
A number of HTTP/2 CVEs were documented recently:
https://www.kb.cert.org/vuls/id/605641/
This commit, along with a few changes and additions in Cowlib,
fix or improve protection against all of them.
For CVE-2019-9511, also known as Data Dribble, the new option
stream_window_data_threshold can be used to control how little
the DATA frames that Cowboy sends can get.
For CVE-2019-9516, also known as 0-Length Headers Leak, Cowboy
will now simply reject streams containing 0-length header names.
For CVE-2019-9517, also known as Internal Data Buffering, the
backpressure changes were already pretty good at preventing this
issue, but a new option max_connection_buffer_size was added for
even better control over how much memory we are willing to allocate.
For CVE-2019-9512, also known as Ping Flood; CVE-2019-9515, also
known as Settings Flood; CVE-2019-9518, also known as Empty Frame
Flooding; and similar undocumented scenarios, a frame rate limiting
mechanism was added. By default Cowboy will now allow 1000 frames
every 10 seconds. This can be configured via max_received_frame_rate.
For CVE-2019-9514, also known as Reset Flood, another rate limiting
mechanism was added and can be configured via max_reset_stream_rate.
By default Cowboy will do up to 10 stream resets every 10 seconds.
Finally, nothing was done for CVE-2019-9513, also known as Resource
Loop, because Cowboy does not currently implement the HTTP/2
priority mechanism (in parts because these issues were well known
from the start).
Tests were added for all cases except Internal Data Buffering,
which I'm not sure how to test, and Resource Loop, which is not
currently relevant.
2019-10-02 10:44:45 +02:00
|
|
|
|
2019-09-02 14:48:28 +02:00
|
|
|
%% Flow requested for all streams.
|
|
|
|
flow = 0 :: non_neg_integer(),
|
|
|
|
|
2015-06-11 17:04:21 +02:00
|
|
|
%% Currently active HTTP/2 streams. Streams may be initiated either
|
|
|
|
%% by the client or by the server through PUSH_PROMISE frames.
|
2019-09-02 14:48:28 +02:00
|
|
|
streams = #{} :: #{cow_http2:streamid() => #stream{}},
|
2018-04-27 20:45:34 +02:00
|
|
|
|
2015-06-11 17:04:21 +02:00
|
|
|
%% Streams can spawn zero or more children which are then managed
|
|
|
|
%% by this module if operating as a supervisor.
|
2018-10-26 10:18:57 +02:00
|
|
|
children = cowboy_children:init() :: cowboy_children:children()
|
2015-06-11 17:04:21 +02:00
|
|
|
}).
|
|
|
|
|
2018-10-30 23:30:05 +01:00
|
|
|
-spec init(pid(), ranch:ref(), inet:socket(), module(),
|
2018-10-31 11:45:04 +01:00
|
|
|
ranch_proxy_header:proxy_info() | undefined, cowboy:opts()) -> ok.
|
2018-10-30 23:30:05 +01:00
|
|
|
init(Parent, Ref, Socket, Transport, ProxyHeader, Opts) ->
|
2017-10-25 20:17:21 +01:00
|
|
|
Peer0 = Transport:peername(Socket),
|
|
|
|
Sock0 = Transport:sockname(Socket),
|
|
|
|
Cert1 = case Transport:name() of
|
|
|
|
ssl ->
|
|
|
|
case ssl:peercert(Socket) of
|
|
|
|
{error, no_peercert} ->
|
|
|
|
{ok, undefined};
|
|
|
|
Cert0 ->
|
|
|
|
Cert0
|
|
|
|
end;
|
|
|
|
_ ->
|
|
|
|
{ok, undefined}
|
|
|
|
end,
|
|
|
|
case {Peer0, Sock0, Cert1} of
|
|
|
|
{{ok, Peer}, {ok, Sock}, {ok, Cert}} ->
|
2018-10-30 23:30:05 +01:00
|
|
|
init(Parent, Ref, Socket, Transport, ProxyHeader, Opts, Peer, Sock, Cert, <<>>);
|
2017-10-25 20:17:21 +01:00
|
|
|
{{error, Reason}, _, _} ->
|
|
|
|
terminate(undefined, {socket_error, Reason,
|
|
|
|
'A socket error occurred when retrieving the peer name.'});
|
|
|
|
{_, {error, Reason}, _} ->
|
|
|
|
terminate(undefined, {socket_error, Reason,
|
|
|
|
'A socket error occurred when retrieving the sock name.'});
|
|
|
|
{_, _, {error, Reason}} ->
|
|
|
|
terminate(undefined, {socket_error, Reason,
|
|
|
|
'A socket error occurred when retrieving the client TLS certificate.'})
|
2016-06-20 17:28:59 +02:00
|
|
|
end.
|
2016-03-10 23:30:49 +01:00
|
|
|
|
2018-10-30 23:30:05 +01:00
|
|
|
-spec init(pid(), ranch:ref(), inet:socket(), module(),
|
2018-10-31 11:45:04 +01:00
|
|
|
ranch_proxy_header:proxy_info() | undefined, cowboy:opts(),
|
2017-10-25 20:17:21 +01:00
|
|
|
{inet:ip_address(), inet:port_number()}, {inet:ip_address(), inet:port_number()},
|
|
|
|
binary() | undefined, binary()) -> ok.
|
2018-10-30 23:30:05 +01:00
|
|
|
init(Parent, Ref, Socket, Transport, ProxyHeader, Opts, Peer, Sock, Cert, Buffer) ->
|
2018-10-26 10:18:57 +02:00
|
|
|
{ok, Preface, HTTP2Machine} = cow_http2_machine:init(server, Opts),
|
Fix HTTP/2 CVEs
A number of HTTP/2 CVEs were documented recently:
https://www.kb.cert.org/vuls/id/605641/
This commit, along with a few changes and additions in Cowlib,
fix or improve protection against all of them.
For CVE-2019-9511, also known as Data Dribble, the new option
stream_window_data_threshold can be used to control how little
the DATA frames that Cowboy sends can get.
For CVE-2019-9516, also known as 0-Length Headers Leak, Cowboy
will now simply reject streams containing 0-length header names.
For CVE-2019-9517, also known as Internal Data Buffering, the
backpressure changes were already pretty good at preventing this
issue, but a new option max_connection_buffer_size was added for
even better control over how much memory we are willing to allocate.
For CVE-2019-9512, also known as Ping Flood; CVE-2019-9515, also
known as Settings Flood; CVE-2019-9518, also known as Empty Frame
Flooding; and similar undocumented scenarios, a frame rate limiting
mechanism was added. By default Cowboy will now allow 1000 frames
every 10 seconds. This can be configured via max_received_frame_rate.
For CVE-2019-9514, also known as Reset Flood, another rate limiting
mechanism was added and can be configured via max_reset_stream_rate.
By default Cowboy will do up to 10 stream resets every 10 seconds.
Finally, nothing was done for CVE-2019-9513, also known as Resource
Loop, because Cowboy does not currently implement the HTTP/2
priority mechanism (in parts because these issues were well known
from the start).
Tests were added for all cases except Internal Data Buffering,
which I'm not sure how to test, and Resource Loop, which is not
currently relevant.
2019-10-02 10:44:45 +02:00
|
|
|
State = set_timeout(init_rate_limiting(#state{parent=Parent, ref=Ref, socket=Socket,
|
2018-10-30 23:30:05 +01:00
|
|
|
transport=Transport, proxy_header=ProxyHeader,
|
|
|
|
opts=Opts, peer=Peer, sock=Sock, cert=Cert,
|
Fix HTTP/2 CVEs
A number of HTTP/2 CVEs were documented recently:
https://www.kb.cert.org/vuls/id/605641/
This commit, along with a few changes and additions in Cowlib,
fix or improve protection against all of them.
For CVE-2019-9511, also known as Data Dribble, the new option
stream_window_data_threshold can be used to control how little
the DATA frames that Cowboy sends can get.
For CVE-2019-9516, also known as 0-Length Headers Leak, Cowboy
will now simply reject streams containing 0-length header names.
For CVE-2019-9517, also known as Internal Data Buffering, the
backpressure changes were already pretty good at preventing this
issue, but a new option max_connection_buffer_size was added for
even better control over how much memory we are willing to allocate.
For CVE-2019-9512, also known as Ping Flood; CVE-2019-9515, also
known as Settings Flood; CVE-2019-9518, also known as Empty Frame
Flooding; and similar undocumented scenarios, a frame rate limiting
mechanism was added. By default Cowboy will now allow 1000 frames
every 10 seconds. This can be configured via max_received_frame_rate.
For CVE-2019-9514, also known as Reset Flood, another rate limiting
mechanism was added and can be configured via max_reset_stream_rate.
By default Cowboy will do up to 10 stream resets every 10 seconds.
Finally, nothing was done for CVE-2019-9513, also known as Resource
Loop, because Cowboy does not currently implement the HTTP/2
priority mechanism (in parts because these issues were well known
from the start).
Tests were added for all cases except Internal Data Buffering,
which I'm not sure how to test, and Resource Loop, which is not
currently relevant.
2019-10-02 10:44:45 +02:00
|
|
|
http2_status=sequence, http2_machine=HTTP2Machine})),
|
2018-10-26 10:18:57 +02:00
|
|
|
Transport:send(Socket, Preface),
|
2016-03-10 23:30:49 +01:00
|
|
|
case Buffer of
|
2018-11-16 16:30:57 +01:00
|
|
|
<<>> -> loop(State, Buffer);
|
2016-03-10 23:30:49 +01:00
|
|
|
_ -> parse(State, Buffer)
|
|
|
|
end.
|
|
|
|
|
Fix HTTP/2 CVEs
A number of HTTP/2 CVEs were documented recently:
https://www.kb.cert.org/vuls/id/605641/
This commit, along with a few changes and additions in Cowlib,
fix or improve protection against all of them.
For CVE-2019-9511, also known as Data Dribble, the new option
stream_window_data_threshold can be used to control how little
the DATA frames that Cowboy sends can get.
For CVE-2019-9516, also known as 0-Length Headers Leak, Cowboy
will now simply reject streams containing 0-length header names.
For CVE-2019-9517, also known as Internal Data Buffering, the
backpressure changes were already pretty good at preventing this
issue, but a new option max_connection_buffer_size was added for
even better control over how much memory we are willing to allocate.
For CVE-2019-9512, also known as Ping Flood; CVE-2019-9515, also
known as Settings Flood; CVE-2019-9518, also known as Empty Frame
Flooding; and similar undocumented scenarios, a frame rate limiting
mechanism was added. By default Cowboy will now allow 1000 frames
every 10 seconds. This can be configured via max_received_frame_rate.
For CVE-2019-9514, also known as Reset Flood, another rate limiting
mechanism was added and can be configured via max_reset_stream_rate.
By default Cowboy will do up to 10 stream resets every 10 seconds.
Finally, nothing was done for CVE-2019-9513, also known as Resource
Loop, because Cowboy does not currently implement the HTTP/2
priority mechanism (in parts because these issues were well known
from the start).
Tests were added for all cases except Internal Data Buffering,
which I'm not sure how to test, and Resource Loop, which is not
currently relevant.
2019-10-02 10:44:45 +02:00
|
|
|
init_rate_limiting(State=#state{opts=Opts}) ->
|
|
|
|
{FrameRateNum, FrameRatePeriod} = maps:get(max_received_frame_rate, Opts, {1000, 10000}),
|
|
|
|
{ResetRateNum, ResetRatePeriod} = maps:get(max_reset_stream_rate, Opts, {10, 10000}),
|
|
|
|
CurrentTime = erlang:monotonic_time(millisecond),
|
|
|
|
State#state{
|
|
|
|
frame_rate_num=FrameRateNum, frame_rate_time=add_period(CurrentTime, FrameRatePeriod),
|
|
|
|
reset_rate_num=ResetRateNum, reset_rate_time=add_period(CurrentTime, ResetRatePeriod)
|
|
|
|
}.
|
|
|
|
|
|
|
|
add_period(_, infinity) -> infinity;
|
|
|
|
add_period(Time, Period) -> Time + Period.
|
|
|
|
|
2016-03-12 18:25:35 +01:00
|
|
|
%% @todo Add an argument for the request body.
|
2018-10-30 23:30:05 +01:00
|
|
|
-spec init(pid(), ranch:ref(), inet:socket(), module(),
|
2018-10-31 11:45:04 +01:00
|
|
|
ranch_proxy_header:proxy_info() | undefined, cowboy:opts(),
|
2017-10-25 20:17:21 +01:00
|
|
|
{inet:ip_address(), inet:port_number()}, {inet:ip_address(), inet:port_number()},
|
|
|
|
binary() | undefined, binary(), map() | undefined, cowboy_req:req()) -> ok.
|
2018-10-30 23:30:05 +01:00
|
|
|
init(Parent, Ref, Socket, Transport, ProxyHeader, Opts, Peer, Sock, Cert, Buffer,
|
2018-10-26 10:18:57 +02:00
|
|
|
_Settings, Req=#{method := Method}) ->
|
|
|
|
{ok, Preface, HTTP2Machine0} = cow_http2_machine:init(server, Opts),
|
|
|
|
{ok, StreamID, HTTP2Machine}
|
|
|
|
= cow_http2_machine:init_upgrade_stream(Method, HTTP2Machine0),
|
2016-03-12 18:25:35 +01:00
|
|
|
State0 = #state{parent=Parent, ref=Ref, socket=Socket,
|
2018-10-30 23:30:05 +01:00
|
|
|
transport=Transport, proxy_header=ProxyHeader,
|
|
|
|
opts=Opts, peer=Peer, sock=Sock, cert=Cert,
|
2019-08-16 16:37:04 +02:00
|
|
|
http2_status=upgrade, http2_machine=HTTP2Machine},
|
2018-10-26 10:18:57 +02:00
|
|
|
State1 = headers_frame(State0#state{
|
|
|
|
http2_machine=HTTP2Machine}, StreamID, Req),
|
2017-04-18 14:06:34 +02:00
|
|
|
%% We assume that the upgrade will be applied. A stream handler
|
|
|
|
%% must not prevent the normal operations of the server.
|
2018-10-27 00:16:13 +02:00
|
|
|
State2 = info(State1, 1, {switch_protocol, #{
|
2017-04-18 14:06:34 +02:00
|
|
|
<<"connection">> => <<"Upgrade">>,
|
|
|
|
<<"upgrade">> => <<"h2c">>
|
|
|
|
}, ?MODULE, undefined}), %% @todo undefined or #{}?
|
Fix HTTP/2 CVEs
A number of HTTP/2 CVEs were documented recently:
https://www.kb.cert.org/vuls/id/605641/
This commit, along with a few changes and additions in Cowlib,
fix or improve protection against all of them.
For CVE-2019-9511, also known as Data Dribble, the new option
stream_window_data_threshold can be used to control how little
the DATA frames that Cowboy sends can get.
For CVE-2019-9516, also known as 0-Length Headers Leak, Cowboy
will now simply reject streams containing 0-length header names.
For CVE-2019-9517, also known as Internal Data Buffering, the
backpressure changes were already pretty good at preventing this
issue, but a new option max_connection_buffer_size was added for
even better control over how much memory we are willing to allocate.
For CVE-2019-9512, also known as Ping Flood; CVE-2019-9515, also
known as Settings Flood; CVE-2019-9518, also known as Empty Frame
Flooding; and similar undocumented scenarios, a frame rate limiting
mechanism was added. By default Cowboy will now allow 1000 frames
every 10 seconds. This can be configured via max_received_frame_rate.
For CVE-2019-9514, also known as Reset Flood, another rate limiting
mechanism was added and can be configured via max_reset_stream_rate.
By default Cowboy will do up to 10 stream resets every 10 seconds.
Finally, nothing was done for CVE-2019-9513, also known as Resource
Loop, because Cowboy does not currently implement the HTTP/2
priority mechanism (in parts because these issues were well known
from the start).
Tests were added for all cases except Internal Data Buffering,
which I'm not sure how to test, and Resource Loop, which is not
currently relevant.
2019-10-02 10:44:45 +02:00
|
|
|
State = set_timeout(init_rate_limiting(State2#state{http2_status=sequence})),
|
2018-10-26 10:18:57 +02:00
|
|
|
Transport:send(Socket, Preface),
|
2016-03-12 18:25:35 +01:00
|
|
|
case Buffer of
|
2018-11-16 16:30:57 +01:00
|
|
|
<<>> -> loop(State, Buffer);
|
2016-03-12 18:25:35 +01:00
|
|
|
_ -> parse(State, Buffer)
|
|
|
|
end.
|
|
|
|
|
2016-03-13 23:14:57 +01:00
|
|
|
loop(State=#state{parent=Parent, socket=Socket, transport=Transport,
|
2018-11-16 16:30:57 +01:00
|
|
|
opts=Opts, timer=TimerRef, children=Children}, Buffer) ->
|
|
|
|
%% @todo This should only be called when data was read.
|
2015-06-11 17:04:21 +02:00
|
|
|
Transport:setopts(Socket, [{active, once}]),
|
2019-06-06 17:52:34 +02:00
|
|
|
Messages = Transport:messages(),
|
2017-05-05 13:48:25 +02:00
|
|
|
InactivityTimeout = maps:get(inactivity_timeout, Opts, 300000),
|
2015-06-11 17:04:21 +02:00
|
|
|
receive
|
|
|
|
%% Socket messages.
|
2019-06-06 17:52:34 +02:00
|
|
|
{OK, Socket, Data} when OK =:= element(1, Messages) ->
|
2018-11-16 16:30:57 +01:00
|
|
|
parse(set_timeout(State), << Buffer/binary, Data/binary >>);
|
2019-06-06 17:52:34 +02:00
|
|
|
{Closed, Socket} when Closed =:= element(2, Messages) ->
|
2015-06-11 17:04:21 +02:00
|
|
|
terminate(State, {socket_error, closed, 'The socket has been closed.'});
|
2019-06-06 17:52:34 +02:00
|
|
|
{Error, Socket, Reason} when Error =:= element(3, Messages) ->
|
2015-06-11 17:04:21 +02:00
|
|
|
terminate(State, {socket_error, Reason, 'An error has occurred on the socket.'});
|
|
|
|
%% System messages.
|
|
|
|
{'EXIT', Parent, Reason} ->
|
2019-08-16 16:37:04 +02:00
|
|
|
%% @todo Graceful shutdown here as well?
|
2018-10-28 10:20:43 +01:00
|
|
|
terminate(State, {stop, {exit, Reason}, 'Parent process terminated.'});
|
2015-06-11 17:04:21 +02:00
|
|
|
{system, From, Request} ->
|
|
|
|
sys:handle_system_msg(Request, From, Parent, ?MODULE, [], {State, Buffer});
|
2017-08-08 16:59:33 +02:00
|
|
|
%% Timeouts.
|
2018-11-16 16:30:57 +01:00
|
|
|
{timeout, TimerRef, idle_timeout} ->
|
|
|
|
terminate(State, {stop, timeout,
|
|
|
|
'Connection idle longer than configuration allows.'});
|
2017-08-08 16:59:33 +02:00
|
|
|
{timeout, Ref, {shutdown, Pid}} ->
|
|
|
|
cowboy_children:shutdown_timeout(Children, Ref, Pid),
|
|
|
|
loop(State, Buffer);
|
2018-10-27 00:16:13 +02:00
|
|
|
{timeout, TRef, {cow_http2_machine, Name}} ->
|
|
|
|
loop(timeout(State, Name, TRef), Buffer);
|
2015-06-11 17:04:21 +02:00
|
|
|
%% Messages pertaining to a stream.
|
Initial commit with connection/streams
Breaking changes with previous commit. This is a very large change,
and I am giving up on making a single commit that fixes everything.
More commits will follow slowly adding back features, introducing
new tests and fixing the documentation.
This change contains most of the work toward unifying the interface
for handling both HTTP/1.1 and HTTP/2. HTTP/1.1 connections are now
no longer 1 process per connection; instead by default 1 process per
request is also created. This has a number of pros and cons.
Because it has cons, we also allow users to use a lower-level API
that acts on "streams" (requests/responses) directly at the connection
process-level. If performance is a concern, one can always write a
stream handler. The performance in this case will be even greater
than with Cowboy 1, although all the special handlers are unavailable.
When switching to Websocket, after the handler returns from init/2,
Cowboy stops the stream and the Websocket protocol takes over the
connection process. Websocket then calls websocket_init/2 for any
additional initialization such as timers, because the process is
different in init/2 and websocket_*/* functions. This however would
allow us to use websocket_init/2 for sending messages on connect,
instead of sending ourselves a message and be subject to races.
Note that websocket_init/2 is optional.
This is all a big change and while most of the tests pass, some
functionality currently doesn't. SPDY is broken and will be removed
soon in favor of HTTP/2. Automatic compression is currently disabled.
The cowboy_req interface probably still have a few functions that
need to be updated. The docs and examples do not refer the current
functionality anymore.
Everything will be fixed over time. Feedback is more than welcome.
Open a ticket!
2016-02-10 17:28:32 +01:00
|
|
|
{{Pid, StreamID}, Msg} when Pid =:= self() ->
|
2015-06-11 17:04:21 +02:00
|
|
|
loop(info(State, StreamID, Msg), Buffer);
|
|
|
|
%% Exit signal from children.
|
|
|
|
Msg = {'EXIT', Pid, _} ->
|
|
|
|
loop(down(State, Pid, Msg), Buffer);
|
|
|
|
%% Calls from supervisor module.
|
2018-03-13 10:40:14 +01:00
|
|
|
{'$gen_call', From, Call} ->
|
|
|
|
cowboy_children:handle_supervisor_call(Call, From, Children, ?MODULE),
|
2015-06-11 17:04:21 +02:00
|
|
|
loop(State, Buffer);
|
|
|
|
Msg ->
|
2018-06-28 17:10:18 +02:00
|
|
|
cowboy:log(warning, "Received stray message ~p.", [Msg], Opts),
|
2015-06-11 17:04:21 +02:00
|
|
|
loop(State, Buffer)
|
2017-05-05 13:48:25 +02:00
|
|
|
after InactivityTimeout ->
|
2015-06-11 17:04:21 +02:00
|
|
|
terminate(State, {internal_error, timeout, 'No message or data received before timeout.'})
|
|
|
|
end.
|
|
|
|
|
2018-11-16 16:30:57 +01:00
|
|
|
set_timeout(State=#state{opts=Opts, timer=TimerRef0}) ->
|
|
|
|
ok = case TimerRef0 of
|
|
|
|
undefined -> ok;
|
|
|
|
_ -> erlang:cancel_timer(TimerRef0, [{async, true}, {info, false}])
|
|
|
|
end,
|
|
|
|
TimerRef = case maps:get(idle_timeout, Opts, 60000) of
|
|
|
|
infinity -> undefined;
|
|
|
|
Timeout -> erlang:start_timer(Timeout, self(), idle_timeout)
|
|
|
|
end,
|
|
|
|
State#state{timer=TimerRef}.
|
|
|
|
|
2018-10-26 10:18:57 +02:00
|
|
|
%% HTTP/2 protocol parsing.
|
|
|
|
|
2019-08-16 16:37:04 +02:00
|
|
|
parse(State=#state{http2_status=sequence}, Data) ->
|
2018-10-26 18:51:32 +02:00
|
|
|
case cow_http2:parse_sequence(Data) of
|
|
|
|
{ok, Rest} ->
|
2019-08-16 16:37:04 +02:00
|
|
|
parse(State#state{http2_status=settings}, Rest);
|
2018-10-26 18:51:32 +02:00
|
|
|
more ->
|
2018-11-16 16:30:57 +01:00
|
|
|
loop(State, Data);
|
2018-10-26 18:51:32 +02:00
|
|
|
Error = {connection_error, _, _} ->
|
|
|
|
terminate(State, Error)
|
2015-06-11 17:04:21 +02:00
|
|
|
end;
|
2019-08-16 16:37:04 +02:00
|
|
|
parse(State=#state{http2_status=Status, http2_machine=HTTP2Machine, streams=Streams}, Data) ->
|
2018-10-26 10:18:57 +02:00
|
|
|
MaxFrameSize = cow_http2_machine:get_local_setting(max_frame_size, HTTP2Machine),
|
2017-02-25 20:05:31 +01:00
|
|
|
case cow_http2:parse(Data, MaxFrameSize) of
|
2015-06-11 17:04:21 +02:00
|
|
|
{ok, Frame, Rest} ->
|
Fix HTTP/2 CVEs
A number of HTTP/2 CVEs were documented recently:
https://www.kb.cert.org/vuls/id/605641/
This commit, along with a few changes and additions in Cowlib,
fix or improve protection against all of them.
For CVE-2019-9511, also known as Data Dribble, the new option
stream_window_data_threshold can be used to control how little
the DATA frames that Cowboy sends can get.
For CVE-2019-9516, also known as 0-Length Headers Leak, Cowboy
will now simply reject streams containing 0-length header names.
For CVE-2019-9517, also known as Internal Data Buffering, the
backpressure changes were already pretty good at preventing this
issue, but a new option max_connection_buffer_size was added for
even better control over how much memory we are willing to allocate.
For CVE-2019-9512, also known as Ping Flood; CVE-2019-9515, also
known as Settings Flood; CVE-2019-9518, also known as Empty Frame
Flooding; and similar undocumented scenarios, a frame rate limiting
mechanism was added. By default Cowboy will now allow 1000 frames
every 10 seconds. This can be configured via max_received_frame_rate.
For CVE-2019-9514, also known as Reset Flood, another rate limiting
mechanism was added and can be configured via max_reset_stream_rate.
By default Cowboy will do up to 10 stream resets every 10 seconds.
Finally, nothing was done for CVE-2019-9513, also known as Resource
Loop, because Cowboy does not currently implement the HTTP/2
priority mechanism (in parts because these issues were well known
from the start).
Tests were added for all cases except Internal Data Buffering,
which I'm not sure how to test, and Resource Loop, which is not
currently relevant.
2019-10-02 10:44:45 +02:00
|
|
|
parse(frame_rate(State, Frame), Rest);
|
2017-02-26 13:24:15 +01:00
|
|
|
{ignore, Rest} ->
|
Fix HTTP/2 CVEs
A number of HTTP/2 CVEs were documented recently:
https://www.kb.cert.org/vuls/id/605641/
This commit, along with a few changes and additions in Cowlib,
fix or improve protection against all of them.
For CVE-2019-9511, also known as Data Dribble, the new option
stream_window_data_threshold can be used to control how little
the DATA frames that Cowboy sends can get.
For CVE-2019-9516, also known as 0-Length Headers Leak, Cowboy
will now simply reject streams containing 0-length header names.
For CVE-2019-9517, also known as Internal Data Buffering, the
backpressure changes were already pretty good at preventing this
issue, but a new option max_connection_buffer_size was added for
even better control over how much memory we are willing to allocate.
For CVE-2019-9512, also known as Ping Flood; CVE-2019-9515, also
known as Settings Flood; CVE-2019-9518, also known as Empty Frame
Flooding; and similar undocumented scenarios, a frame rate limiting
mechanism was added. By default Cowboy will now allow 1000 frames
every 10 seconds. This can be configured via max_received_frame_rate.
For CVE-2019-9514, also known as Reset Flood, another rate limiting
mechanism was added and can be configured via max_reset_stream_rate.
By default Cowboy will do up to 10 stream resets every 10 seconds.
Finally, nothing was done for CVE-2019-9513, also known as Resource
Loop, because Cowboy does not currently implement the HTTP/2
priority mechanism (in parts because these issues were well known
from the start).
Tests were added for all cases except Internal Data Buffering,
which I'm not sure how to test, and Resource Loop, which is not
currently relevant.
2019-10-02 10:44:45 +02:00
|
|
|
parse(frame_rate(State, ignore), Rest);
|
2015-06-11 17:04:21 +02:00
|
|
|
{stream_error, StreamID, Reason, Human, Rest} ->
|
2018-10-28 11:47:49 +01:00
|
|
|
parse(reset_stream(State, StreamID, {stream_error, Reason, Human}), Rest);
|
2015-06-11 17:04:21 +02:00
|
|
|
Error = {connection_error, _, _} ->
|
|
|
|
terminate(State, Error);
|
2019-08-16 16:37:04 +02:00
|
|
|
%% Terminate the connection if we are closing and all streams have completed.
|
|
|
|
more when Status =:= closing, Streams =:= #{} ->
|
|
|
|
terminate(State, {stop, normal, 'The connection is going away.'});
|
2015-06-11 17:04:21 +02:00
|
|
|
more ->
|
2018-11-16 16:30:57 +01:00
|
|
|
loop(State, Data)
|
2015-06-11 17:04:21 +02:00
|
|
|
end.
|
|
|
|
|
Fix HTTP/2 CVEs
A number of HTTP/2 CVEs were documented recently:
https://www.kb.cert.org/vuls/id/605641/
This commit, along with a few changes and additions in Cowlib,
fix or improve protection against all of them.
For CVE-2019-9511, also known as Data Dribble, the new option
stream_window_data_threshold can be used to control how little
the DATA frames that Cowboy sends can get.
For CVE-2019-9516, also known as 0-Length Headers Leak, Cowboy
will now simply reject streams containing 0-length header names.
For CVE-2019-9517, also known as Internal Data Buffering, the
backpressure changes were already pretty good at preventing this
issue, but a new option max_connection_buffer_size was added for
even better control over how much memory we are willing to allocate.
For CVE-2019-9512, also known as Ping Flood; CVE-2019-9515, also
known as Settings Flood; CVE-2019-9518, also known as Empty Frame
Flooding; and similar undocumented scenarios, a frame rate limiting
mechanism was added. By default Cowboy will now allow 1000 frames
every 10 seconds. This can be configured via max_received_frame_rate.
For CVE-2019-9514, also known as Reset Flood, another rate limiting
mechanism was added and can be configured via max_reset_stream_rate.
By default Cowboy will do up to 10 stream resets every 10 seconds.
Finally, nothing was done for CVE-2019-9513, also known as Resource
Loop, because Cowboy does not currently implement the HTTP/2
priority mechanism (in parts because these issues were well known
from the start).
Tests were added for all cases except Internal Data Buffering,
which I'm not sure how to test, and Resource Loop, which is not
currently relevant.
2019-10-02 10:44:45 +02:00
|
|
|
%% Frame rate flood protection.
|
|
|
|
|
|
|
|
frame_rate(State0=#state{opts=Opts, frame_rate_num=Num0, frame_rate_time=Time}, Frame) ->
|
|
|
|
{Result, State} = case Num0 - 1 of
|
|
|
|
0 ->
|
|
|
|
CurrentTime = erlang:monotonic_time(millisecond),
|
|
|
|
if
|
|
|
|
CurrentTime < Time ->
|
|
|
|
{error, State0};
|
|
|
|
true ->
|
|
|
|
%% When the option has a period of infinity we cannot reach this clause.
|
|
|
|
{Num, Period} = maps:get(max_received_frame_rate, Opts, {1000, 10000}),
|
|
|
|
{ok, State0#state{frame_rate_num=Num, frame_rate_time=CurrentTime + Period}}
|
|
|
|
end;
|
|
|
|
Num ->
|
|
|
|
{ok, State0#state{frame_rate_num=Num}}
|
|
|
|
end,
|
|
|
|
case {Result, Frame} of
|
|
|
|
{ok, ignore} -> ignored_frame(State);
|
|
|
|
{ok, _} -> frame(State, Frame);
|
|
|
|
{error, _} -> terminate(State, {connection_error, enhance_your_calm,
|
|
|
|
'Frame rate larger than configuration allows. Flood? (CVE-2019-9512, CVE-2019-9515, CVE-2019-9518)'})
|
|
|
|
end.
|
|
|
|
|
2018-10-26 10:18:57 +02:00
|
|
|
%% Frames received.
|
|
|
|
|
2019-10-02 10:09:30 +02:00
|
|
|
%% We do nothing when receiving a lingering DATA frame.
|
|
|
|
%% We already removed the stream flow from the connection
|
|
|
|
%% flow and are therefore already accounting for the window
|
|
|
|
%% being reduced by these frames.
|
2018-10-26 10:18:57 +02:00
|
|
|
frame(State=#state{http2_machine=HTTP2Machine0}, Frame) ->
|
|
|
|
case cow_http2_machine:frame(Frame, HTTP2Machine0) of
|
|
|
|
{ok, HTTP2Machine} ->
|
2018-10-27 00:16:13 +02:00
|
|
|
maybe_ack(State#state{http2_machine=HTTP2Machine}, Frame);
|
2018-10-26 10:18:57 +02:00
|
|
|
{ok, {data, StreamID, IsFin, Data}, HTTP2Machine} ->
|
|
|
|
data_frame(State#state{http2_machine=HTTP2Machine}, StreamID, IsFin, Data);
|
|
|
|
{ok, {headers, StreamID, IsFin, Headers, PseudoHeaders, BodyLen}, HTTP2Machine} ->
|
|
|
|
headers_frame(State#state{http2_machine=HTTP2Machine},
|
|
|
|
StreamID, IsFin, Headers, PseudoHeaders, BodyLen);
|
|
|
|
{ok, {trailers, _StreamID, _Trailers}, HTTP2Machine} ->
|
|
|
|
%% @todo Propagate trailers.
|
|
|
|
State#state{http2_machine=HTTP2Machine};
|
|
|
|
{ok, {rst_stream, StreamID, Reason}, HTTP2Machine} ->
|
|
|
|
rst_stream_frame(State#state{http2_machine=HTTP2Machine}, StreamID, Reason);
|
2019-08-16 16:37:04 +02:00
|
|
|
{ok, GoAway={goaway, _, _, _}, HTTP2Machine} ->
|
|
|
|
goaway(State#state{http2_machine=HTTP2Machine}, GoAway);
|
2018-10-26 10:18:57 +02:00
|
|
|
{send, SendData, HTTP2Machine} ->
|
2019-09-13 14:20:04 +02:00
|
|
|
%% We may need to send an alarm for each of the streams sending data.
|
|
|
|
lists:foldl(
|
|
|
|
fun({StreamID, _, _}, S) -> maybe_send_data_alarm(S, HTTP2Machine0, StreamID) end,
|
|
|
|
send_data(maybe_ack(State#state{http2_machine=HTTP2Machine}, Frame), SendData),
|
|
|
|
SendData);
|
2018-10-26 10:18:57 +02:00
|
|
|
{error, {stream_error, StreamID, Reason, Human}, HTTP2Machine} ->
|
2018-10-28 11:47:49 +01:00
|
|
|
reset_stream(State#state{http2_machine=HTTP2Machine},
|
2018-10-26 10:18:57 +02:00
|
|
|
StreamID, {stream_error, Reason, Human});
|
|
|
|
{error, Error={connection_error, _, _}, HTTP2Machine} ->
|
|
|
|
terminate(State#state{http2_machine=HTTP2Machine}, Error)
|
|
|
|
end.
|
|
|
|
|
2019-08-16 16:37:04 +02:00
|
|
|
%% We use this opportunity to mark the HTTP/2 status as connected
|
|
|
|
%% if we were still waiting for a SETTINGS frame.
|
|
|
|
maybe_ack(State=#state{http2_status=settings}, Frame) ->
|
|
|
|
maybe_ack(State#state{http2_status=connected}, Frame);
|
2018-10-26 10:18:57 +02:00
|
|
|
maybe_ack(State=#state{socket=Socket, transport=Transport}, Frame) ->
|
|
|
|
case Frame of
|
|
|
|
{settings, _} -> Transport:send(Socket, cow_http2:settings_ack());
|
|
|
|
{ping, Opaque} -> Transport:send(Socket, cow_http2:ping_ack(Opaque));
|
|
|
|
_ -> ok
|
|
|
|
end,
|
|
|
|
State.
|
|
|
|
|
2019-09-02 14:48:28 +02:00
|
|
|
data_frame(State0=#state{opts=Opts, flow=Flow, streams=Streams}, StreamID, IsFin, Data) ->
|
2018-10-26 10:18:57 +02:00
|
|
|
case Streams of
|
2019-09-02 14:48:28 +02:00
|
|
|
#{StreamID := Stream=#stream{status=running, flow=StreamFlow, state=StreamState0}} ->
|
2018-10-26 10:18:57 +02:00
|
|
|
try cowboy_stream:data(StreamID, IsFin, Data, StreamState0) of
|
|
|
|
{Commands, StreamState} ->
|
2019-09-02 14:48:28 +02:00
|
|
|
%% Remove the amount of data received from the flow.
|
|
|
|
%% We may receive more data than we requested. We ensure
|
|
|
|
%% that the flow value doesn't go lower than 0.
|
|
|
|
Size = byte_size(Data),
|
|
|
|
State = update_window(State0#state{flow=max(0, Flow - Size),
|
|
|
|
streams=Streams#{StreamID => Stream#stream{
|
|
|
|
flow=max(0, StreamFlow - Size), state=StreamState}}},
|
|
|
|
StreamID),
|
|
|
|
commands(State, StreamID, Commands)
|
2018-10-26 10:18:57 +02:00
|
|
|
catch Class:Exception ->
|
|
|
|
cowboy:log(cowboy_stream:make_error_log(data,
|
|
|
|
[StreamID, IsFin, Data, StreamState0],
|
|
|
|
Class, Exception, erlang:get_stacktrace()), Opts),
|
2019-09-02 14:48:28 +02:00
|
|
|
reset_stream(State0, StreamID, {internal_error, {Class, Exception},
|
2018-10-26 10:18:57 +02:00
|
|
|
'Unhandled exception in cowboy_stream:data/4.'})
|
|
|
|
end;
|
|
|
|
%% We ignore DATA frames for streams that are stopping.
|
|
|
|
#{} ->
|
2019-09-02 14:48:28 +02:00
|
|
|
State0
|
2018-10-26 10:18:57 +02:00
|
|
|
end.
|
2015-06-11 17:04:21 +02:00
|
|
|
|
2018-10-26 10:18:57 +02:00
|
|
|
headers_frame(State, StreamID, IsFin, Headers,
|
|
|
|
PseudoHeaders=#{method := <<"CONNECT">>}, _)
|
|
|
|
when map_size(PseudoHeaders) =:= 2 ->
|
|
|
|
early_error(State, StreamID, IsFin, Headers, PseudoHeaders, 501,
|
|
|
|
'The CONNECT method is currently not implemented. (RFC7231 4.3.6)');
|
|
|
|
headers_frame(State, StreamID, IsFin, Headers,
|
|
|
|
PseudoHeaders=#{method := <<"TRACE">>}, _) ->
|
|
|
|
early_error(State, StreamID, IsFin, Headers, PseudoHeaders, 501,
|
|
|
|
'The TRACE method is currently not implemented. (RFC7231 4.3.8)');
|
2019-01-22 17:50:40 +01:00
|
|
|
headers_frame(State, StreamID, IsFin, Headers, PseudoHeaders=#{authority := Authority}, BodyLen) ->
|
|
|
|
headers_frame_parse_host(State, StreamID, IsFin, Headers, PseudoHeaders, BodyLen, Authority);
|
|
|
|
headers_frame(State, StreamID, IsFin, Headers, PseudoHeaders, BodyLen) ->
|
|
|
|
case lists:keyfind(<<"host">>, 1, Headers) of
|
|
|
|
{_, Authority} ->
|
|
|
|
headers_frame_parse_host(State, StreamID, IsFin, Headers, PseudoHeaders, BodyLen, Authority);
|
|
|
|
_ ->
|
|
|
|
reset_stream(State, StreamID, {stream_error, protocol_error,
|
|
|
|
'Requests translated from HTTP/1.1 must include a host header. (RFC7540 8.1.2.3, RFC7230 5.4)'})
|
|
|
|
end.
|
|
|
|
|
|
|
|
headers_frame_parse_host(State=#state{ref=Ref, peer=Peer, sock=Sock, cert=Cert, proxy_header=ProxyHeader},
|
|
|
|
StreamID, IsFin, Headers, PseudoHeaders=#{method := Method, scheme := Scheme, path := PathWithQs},
|
|
|
|
BodyLen, Authority) ->
|
2018-10-26 10:18:57 +02:00
|
|
|
try cow_http_hd:parse_host(Authority) of
|
|
|
|
{Host, Port0} ->
|
|
|
|
Port = ensure_port(Scheme, Port0),
|
|
|
|
try cow_http:parse_fullpath(PathWithQs) of
|
|
|
|
{<<>>, _} ->
|
2018-10-28 11:47:49 +01:00
|
|
|
reset_stream(State, StreamID, {stream_error, protocol_error,
|
2018-10-28 11:42:18 +01:00
|
|
|
'The path component must not be empty. (RFC7540 8.1.2.3)'});
|
2018-10-26 10:18:57 +02:00
|
|
|
{Path, Qs} ->
|
|
|
|
Req0 = #{
|
|
|
|
ref => Ref,
|
|
|
|
pid => self(),
|
|
|
|
streamid => StreamID,
|
|
|
|
peer => Peer,
|
|
|
|
sock => Sock,
|
|
|
|
cert => Cert,
|
|
|
|
method => Method,
|
|
|
|
scheme => Scheme,
|
|
|
|
host => Host,
|
|
|
|
port => Port,
|
|
|
|
path => Path,
|
|
|
|
qs => Qs,
|
|
|
|
version => 'HTTP/2',
|
|
|
|
headers => headers_to_map(Headers, #{}),
|
|
|
|
has_body => IsFin =:= nofin,
|
|
|
|
body_length => BodyLen
|
|
|
|
},
|
2018-10-30 23:30:05 +01:00
|
|
|
%% We add the PROXY header information if any.
|
|
|
|
Req1 = case ProxyHeader of
|
|
|
|
undefined -> Req0;
|
|
|
|
_ -> Req0#{proxy_header => ProxyHeader}
|
|
|
|
end,
|
2018-10-26 10:18:57 +02:00
|
|
|
%% We add the protocol information for extended CONNECTs.
|
|
|
|
Req = case PseudoHeaders of
|
2018-10-30 23:30:05 +01:00
|
|
|
#{protocol := Protocol} -> Req1#{protocol => Protocol};
|
|
|
|
_ -> Req1
|
2018-10-26 10:18:57 +02:00
|
|
|
end,
|
|
|
|
headers_frame(State, StreamID, Req)
|
|
|
|
catch _:_ ->
|
2018-10-28 11:47:49 +01:00
|
|
|
reset_stream(State, StreamID, {stream_error, protocol_error,
|
2018-10-28 11:42:18 +01:00
|
|
|
'The :path pseudo-header is invalid. (RFC7540 8.1.2.3)'})
|
2018-10-26 10:18:57 +02:00
|
|
|
end
|
|
|
|
catch _:_ ->
|
2018-10-28 11:47:49 +01:00
|
|
|
reset_stream(State, StreamID, {stream_error, protocol_error,
|
2018-10-28 11:42:18 +01:00
|
|
|
'The :authority pseudo-header is invalid. (RFC7540 8.1.2.3)'})
|
2018-04-30 13:47:33 +02:00
|
|
|
end.
|
|
|
|
|
2018-10-26 10:18:57 +02:00
|
|
|
ensure_port(<<"http">>, undefined) -> 80;
|
|
|
|
ensure_port(<<"https">>, undefined) -> 443;
|
|
|
|
ensure_port(_, Port) -> Port.
|
|
|
|
|
|
|
|
%% This function is necessary to properly handle duplicate headers
|
|
|
|
%% and the special-case cookie header.
|
|
|
|
headers_to_map([], Acc) ->
|
|
|
|
Acc;
|
|
|
|
headers_to_map([{Name, Value}|Tail], Acc0) ->
|
|
|
|
Acc = case Acc0 of
|
|
|
|
%% The cookie header does not use proper HTTP header lists.
|
|
|
|
#{Name := Value0} when Name =:= <<"cookie">> ->
|
|
|
|
Acc0#{Name => << Value0/binary, "; ", Value/binary >>};
|
|
|
|
#{Name := Value0} ->
|
|
|
|
Acc0#{Name => << Value0/binary, ", ", Value/binary >>};
|
|
|
|
_ ->
|
|
|
|
Acc0#{Name => Value}
|
|
|
|
end,
|
|
|
|
headers_to_map(Tail, Acc).
|
|
|
|
|
|
|
|
headers_frame(State=#state{opts=Opts, streams=Streams}, StreamID, Req) ->
|
|
|
|
try cowboy_stream:init(StreamID, Req, Opts) of
|
2018-04-30 13:47:33 +02:00
|
|
|
{Commands, StreamState} ->
|
2018-10-26 10:18:57 +02:00
|
|
|
commands(State#state{
|
2019-09-02 14:48:28 +02:00
|
|
|
streams=Streams#{StreamID => #stream{state=StreamState}}},
|
2018-10-26 10:18:57 +02:00
|
|
|
StreamID, Commands)
|
2018-04-30 13:47:33 +02:00
|
|
|
catch Class:Exception ->
|
2018-10-26 10:18:57 +02:00
|
|
|
cowboy:log(cowboy_stream:make_error_log(init,
|
|
|
|
[StreamID, Req, Opts],
|
2018-06-28 17:10:18 +02:00
|
|
|
Class, Exception, erlang:get_stacktrace()), Opts),
|
2018-10-28 11:47:49 +01:00
|
|
|
reset_stream(State, StreamID, {internal_error, {Class, Exception},
|
2018-10-26 10:18:57 +02:00
|
|
|
'Unhandled exception in cowboy_stream:init/3.'})
|
|
|
|
end.
|
|
|
|
|
|
|
|
early_error(State0=#state{ref=Ref, opts=Opts, peer=Peer},
|
2019-10-03 15:42:58 +02:00
|
|
|
StreamID, _IsFin, Headers, #{method := Method},
|
2018-10-26 10:18:57 +02:00
|
|
|
StatusCode0, HumanReadable) ->
|
|
|
|
%% We automatically terminate the stream but it is not an error
|
|
|
|
%% per se (at least not in the first implementation).
|
|
|
|
Reason = {stream_error, no_error, HumanReadable},
|
|
|
|
%% The partial Req is minimal for now. We only have one case
|
|
|
|
%% where it can be called (when a method is completely disabled).
|
|
|
|
%% @todo Fill in the other elements.
|
|
|
|
PartialReq = #{
|
|
|
|
ref => Ref,
|
|
|
|
peer => Peer,
|
2019-10-03 15:42:58 +02:00
|
|
|
method => Method,
|
|
|
|
headers => headers_to_map(Headers, #{})
|
2018-10-26 10:18:57 +02:00
|
|
|
},
|
|
|
|
Resp = {response, StatusCode0, RespHeaders0=#{<<"content-length">> => <<"0">>}, <<>>},
|
|
|
|
try cowboy_stream:early_error(StreamID, Reason, PartialReq, Resp, Opts) of
|
|
|
|
{response, StatusCode, RespHeaders, RespBody} ->
|
|
|
|
send_response(State0, StreamID, StatusCode, RespHeaders, RespBody)
|
|
|
|
catch Class:Exception ->
|
|
|
|
cowboy:log(cowboy_stream:make_error_log(early_error,
|
|
|
|
[StreamID, Reason, PartialReq, Resp, Opts],
|
|
|
|
Class, Exception, erlang:get_stacktrace()), Opts),
|
|
|
|
%% We still need to send an error response, so send what we initially
|
|
|
|
%% wanted to send. It's better than nothing.
|
|
|
|
send_headers(State0, StreamID, fin, StatusCode0, RespHeaders0)
|
2018-04-30 13:47:33 +02:00
|
|
|
end.
|
|
|
|
|
2018-10-26 10:18:57 +02:00
|
|
|
rst_stream_frame(State=#state{streams=Streams0, children=Children0}, StreamID, Reason) ->
|
|
|
|
case maps:take(StreamID, Streams0) of
|
2019-09-02 14:48:28 +02:00
|
|
|
{#stream{state=StreamState}, Streams} ->
|
2018-10-28 11:47:49 +01:00
|
|
|
terminate_stream_handler(State, StreamID, Reason, StreamState),
|
2018-10-26 10:18:57 +02:00
|
|
|
Children = cowboy_children:shutdown(Children0, StreamID),
|
|
|
|
State#state{streams=Streams, children=Children};
|
|
|
|
error ->
|
|
|
|
State
|
|
|
|
end.
|
2018-04-30 13:47:33 +02:00
|
|
|
|
2018-10-26 10:18:57 +02:00
|
|
|
ignored_frame(State=#state{http2_machine=HTTP2Machine0}) ->
|
|
|
|
case cow_http2_machine:ignored_frame(HTTP2Machine0) of
|
|
|
|
{ok, HTTP2Machine} ->
|
|
|
|
State#state{http2_machine=HTTP2Machine};
|
|
|
|
{error, Error={connection_error, _, _}, HTTP2Machine} ->
|
|
|
|
terminate(State#state{http2_machine=HTTP2Machine}, Error)
|
|
|
|
end.
|
|
|
|
|
2018-10-27 00:16:13 +02:00
|
|
|
%% HTTP/2 timeouts.
|
|
|
|
|
2018-10-27 10:07:34 +02:00
|
|
|
timeout(State=#state{http2_machine=HTTP2Machine0}, Name, TRef) ->
|
2018-10-27 00:16:13 +02:00
|
|
|
case cow_http2_machine:timeout(Name, TRef, HTTP2Machine0) of
|
|
|
|
{ok, HTTP2Machine} ->
|
|
|
|
State#state{http2_machine=HTTP2Machine};
|
|
|
|
{error, Error={connection_error, _, _}, HTTP2Machine} ->
|
|
|
|
terminate(State#state{http2_machine=HTTP2Machine}, Error)
|
|
|
|
end.
|
|
|
|
|
2018-10-26 10:18:57 +02:00
|
|
|
%% Erlang messages.
|
2015-06-11 17:04:21 +02:00
|
|
|
|
2018-06-28 17:10:18 +02:00
|
|
|
down(State=#state{opts=Opts, children=Children0}, Pid, Msg) ->
|
2017-08-08 16:59:33 +02:00
|
|
|
case cowboy_children:down(Children0, Pid) of
|
|
|
|
%% The stream was terminated already.
|
|
|
|
{ok, undefined, Children} ->
|
|
|
|
State#state{children=Children};
|
|
|
|
%% The stream is still running.
|
|
|
|
{ok, StreamID, Children} ->
|
2015-06-11 17:04:21 +02:00
|
|
|
info(State#state{children=Children}, StreamID, Msg);
|
2017-08-08 16:59:33 +02:00
|
|
|
%% The process was unknown.
|
|
|
|
error ->
|
2018-06-28 17:10:18 +02:00
|
|
|
cowboy:log(warning, "Received EXIT signal ~p for unknown process ~p.~n",
|
|
|
|
[Msg, Pid], Opts),
|
2015-06-11 17:04:21 +02:00
|
|
|
State
|
|
|
|
end.
|
|
|
|
|
2018-10-26 10:18:57 +02:00
|
|
|
info(State=#state{opts=Opts, streams=Streams}, StreamID, Msg) ->
|
|
|
|
case Streams of
|
2019-09-02 14:48:28 +02:00
|
|
|
#{StreamID := Stream=#stream{state=StreamState0}} ->
|
2017-01-16 14:22:43 +01:00
|
|
|
try cowboy_stream:info(StreamID, Msg, StreamState0) of
|
2015-06-11 17:04:21 +02:00
|
|
|
{Commands, StreamState} ->
|
2019-09-02 14:48:28 +02:00
|
|
|
commands(State#state{streams=Streams#{StreamID => Stream#stream{state=StreamState}}},
|
2018-10-26 10:18:57 +02:00
|
|
|
StreamID, Commands)
|
2017-09-21 12:53:21 +02:00
|
|
|
catch Class:Exception ->
|
2018-06-28 17:10:18 +02:00
|
|
|
cowboy:log(cowboy_stream:make_error_log(info,
|
2017-09-21 12:53:21 +02:00
|
|
|
[StreamID, Msg, StreamState0],
|
2018-06-28 17:10:18 +02:00
|
|
|
Class, Exception, erlang:get_stacktrace()), Opts),
|
2018-10-28 11:47:49 +01:00
|
|
|
reset_stream(State, StreamID, {internal_error, {Class, Exception},
|
2017-09-27 14:17:27 +02:00
|
|
|
'Unhandled exception in cowboy_stream:info/3.'})
|
2015-06-11 17:04:21 +02:00
|
|
|
end;
|
2018-10-26 10:18:57 +02:00
|
|
|
_ ->
|
|
|
|
cowboy:log(warning, "Received message ~p for unknown or terminated stream ~p.",
|
2018-06-28 17:10:18 +02:00
|
|
|
[Msg, StreamID], Opts),
|
2015-06-11 17:04:21 +02:00
|
|
|
State
|
|
|
|
end.
|
|
|
|
|
2018-10-26 10:18:57 +02:00
|
|
|
%% Stream handler commands.
|
|
|
|
%%
|
2018-04-30 18:39:27 +02:00
|
|
|
%% @todo Kill the stream if it tries to send a response, headers,
|
|
|
|
%% data or push promise when the stream is closed or half-closed.
|
|
|
|
|
2018-10-26 10:18:57 +02:00
|
|
|
commands(State, _, []) ->
|
|
|
|
State;
|
2016-08-10 17:15:02 +02:00
|
|
|
%% Error responses are sent only if a response wasn't sent already.
|
2018-10-26 10:18:57 +02:00
|
|
|
commands(State=#state{http2_machine=HTTP2Machine}, StreamID,
|
|
|
|
[{error_response, StatusCode, Headers, Body}|Tail]) ->
|
|
|
|
case cow_http2_machine:get_stream_local_state(StreamID, HTTP2Machine) of
|
|
|
|
{ok, idle, _} ->
|
|
|
|
commands(State, StreamID, [{response, StatusCode, Headers, Body}|Tail]);
|
|
|
|
_ ->
|
|
|
|
commands(State, StreamID, Tail)
|
|
|
|
end;
|
2017-10-29 19:52:27 +00:00
|
|
|
%% Send an informational response.
|
2018-10-26 10:18:57 +02:00
|
|
|
commands(State0, StreamID, [{inform, StatusCode, Headers}|Tail]) ->
|
|
|
|
State = send_headers(State0, StreamID, idle, StatusCode, Headers),
|
|
|
|
commands(State, StreamID, Tail);
|
2015-06-11 17:04:21 +02:00
|
|
|
%% Send response headers.
|
2018-10-26 10:18:57 +02:00
|
|
|
commands(State0, StreamID, [{response, StatusCode, Headers, Body}|Tail]) ->
|
|
|
|
State = send_response(State0, StreamID, StatusCode, Headers, Body),
|
|
|
|
commands(State, StreamID, Tail);
|
2017-09-25 13:52:58 +02:00
|
|
|
%% Send response headers.
|
2018-10-26 10:18:57 +02:00
|
|
|
commands(State0, StreamID, [{headers, StatusCode, Headers}|Tail]) ->
|
|
|
|
State = send_headers(State0, StreamID, nofin, StatusCode, Headers),
|
|
|
|
commands(State, StreamID, Tail);
|
2015-06-11 17:04:21 +02:00
|
|
|
%% Send a response body chunk.
|
2018-10-26 10:18:57 +02:00
|
|
|
commands(State0, StreamID, [{data, IsFin, Data}|Tail]) ->
|
|
|
|
State = maybe_send_data(State0, StreamID, IsFin, Data),
|
|
|
|
commands(State, StreamID, Tail);
|
2017-11-15 14:58:49 +01:00
|
|
|
%% Send trailers.
|
2018-10-26 10:18:57 +02:00
|
|
|
commands(State0, StreamID, [{trailers, Trailers}|Tail]) ->
|
|
|
|
State = maybe_send_data(State0, StreamID, fin, {trailers, maps:to_list(Trailers)}),
|
|
|
|
commands(State, StreamID, Tail);
|
2015-06-11 17:04:21 +02:00
|
|
|
%% Send a push promise.
|
|
|
|
%%
|
2018-10-26 10:18:57 +02:00
|
|
|
%% @todo Responses sent as a result of a push_promise request
|
|
|
|
%% must not send push_promise frames themselves.
|
2019-08-16 16:37:04 +02:00
|
|
|
%%
|
|
|
|
%% @todo We should not send push_promise frames when we are
|
|
|
|
%% in the closing http2_status.
|
2018-10-26 10:18:57 +02:00
|
|
|
commands(State0=#state{socket=Socket, transport=Transport, http2_machine=HTTP2Machine0},
|
|
|
|
StreamID, [{push, Method, Scheme, Host, Port, Path, Qs, Headers0}|Tail]) ->
|
2016-08-10 11:49:31 +02:00
|
|
|
Authority = case {Scheme, Port} of
|
|
|
|
{<<"http">>, 80} -> Host;
|
|
|
|
{<<"https">>, 443} -> Host;
|
2017-07-26 17:31:12 +02:00
|
|
|
_ -> iolist_to_binary([Host, $:, integer_to_binary(Port)])
|
2016-08-10 11:49:31 +02:00
|
|
|
end,
|
2017-11-30 15:01:01 +01:00
|
|
|
PathWithQs = iolist_to_binary(case Qs of
|
2016-08-10 11:49:31 +02:00
|
|
|
<<>> -> Path;
|
|
|
|
_ -> [Path, $?, Qs]
|
2017-11-30 15:01:01 +01:00
|
|
|
end),
|
2018-10-26 10:18:57 +02:00
|
|
|
PseudoHeaders = #{
|
|
|
|
method => Method,
|
|
|
|
scheme => Scheme,
|
|
|
|
authority => Authority,
|
|
|
|
path => PathWithQs
|
|
|
|
},
|
2017-07-26 17:31:12 +02:00
|
|
|
%% We need to make sure the header value is binary before we can
|
2018-10-26 10:18:57 +02:00
|
|
|
%% create the Req object, as it expects them to be flat.
|
|
|
|
Headers = maps:to_list(maps:map(fun(_, V) -> iolist_to_binary(V) end, Headers0)),
|
|
|
|
State = case cow_http2_machine:prepare_push_promise(StreamID, HTTP2Machine0,
|
|
|
|
PseudoHeaders, Headers) of
|
|
|
|
{ok, PromisedStreamID, HeaderBlock, HTTP2Machine} ->
|
|
|
|
Transport:send(Socket, cow_http2:push_promise(
|
|
|
|
StreamID, PromisedStreamID, HeaderBlock)),
|
|
|
|
headers_frame(State0#state{http2_machine=HTTP2Machine},
|
|
|
|
PromisedStreamID, fin, Headers, PseudoHeaders, 0);
|
|
|
|
{error, no_push} ->
|
|
|
|
State0
|
|
|
|
end,
|
|
|
|
commands(State, StreamID, Tail);
|
2019-09-02 14:48:28 +02:00
|
|
|
%% Read the request body.
|
|
|
|
commands(State0=#state{flow=Flow, streams=Streams}, StreamID, [{flow, Size}|Tail]) ->
|
|
|
|
#{StreamID := Stream=#stream{flow=StreamFlow}} = Streams,
|
|
|
|
State = update_window(State0#state{flow=Flow + Size,
|
|
|
|
streams=Streams#{StreamID => Stream#stream{flow=StreamFlow + Size}}},
|
|
|
|
StreamID),
|
|
|
|
commands(State, StreamID, Tail);
|
2015-06-11 17:04:21 +02:00
|
|
|
%% Supervise a child process.
|
2018-10-26 10:18:57 +02:00
|
|
|
commands(State=#state{children=Children}, StreamID, [{spawn, Pid, Shutdown}|Tail]) ->
|
2017-08-08 16:59:33 +02:00
|
|
|
commands(State#state{children=cowboy_children:up(Children, Pid, StreamID, Shutdown)},
|
2018-10-26 10:18:57 +02:00
|
|
|
StreamID, Tail);
|
2016-06-06 17:28:35 +02:00
|
|
|
%% Error handling.
|
2018-10-26 10:18:57 +02:00
|
|
|
commands(State, StreamID, [Error = {internal_error, _, _}|_Tail]) ->
|
2016-06-16 19:46:57 +02:00
|
|
|
%% @todo Do we want to run the commands after an internal_error?
|
|
|
|
%% @todo Do we even allow commands after?
|
2016-06-06 17:28:35 +02:00
|
|
|
%% @todo Only reset when the stream still exists.
|
2018-10-28 11:47:49 +01:00
|
|
|
reset_stream(State, StreamID, Error);
|
2017-04-18 14:06:34 +02:00
|
|
|
%% Upgrade to HTTP/2. This is triggered by cowboy_http2 itself.
|
2019-08-16 16:37:04 +02:00
|
|
|
commands(State=#state{socket=Socket, transport=Transport, http2_status=upgrade},
|
2018-10-26 10:18:57 +02:00
|
|
|
StreamID, [{switch_protocol, Headers, ?MODULE, _}|Tail]) ->
|
|
|
|
%% @todo This 101 response needs to be passed through stream handlers.
|
2017-04-18 14:06:34 +02:00
|
|
|
Transport:send(Socket, cow_http:response(101, 'HTTP/1.1', maps:to_list(Headers))),
|
2018-10-27 00:16:13 +02:00
|
|
|
commands(State, StreamID, Tail);
|
2018-04-04 17:23:37 +02:00
|
|
|
%% Use a different protocol within the stream (CONNECT :protocol).
|
|
|
|
%% @todo Make sure we error out when the feature is disabled.
|
2018-10-26 10:18:57 +02:00
|
|
|
commands(State0, StreamID, [{switch_protocol, Headers, _Mod, _ModState}|Tail]) ->
|
|
|
|
State = info(State0, StreamID, {headers, 200, Headers}),
|
|
|
|
commands(State, StreamID, Tail);
|
2018-11-16 13:09:01 +01:00
|
|
|
%% Set options dynamically.
|
|
|
|
commands(State, StreamID, [{set_options, _Opts}|Tail]) ->
|
|
|
|
commands(State, StreamID, Tail);
|
2018-10-26 10:18:57 +02:00
|
|
|
commands(State, StreamID, [stop|_Tail]) ->
|
2016-03-12 18:25:35 +01:00
|
|
|
%% @todo Do we want to run the commands after a stop?
|
2016-06-16 19:46:57 +02:00
|
|
|
%% @todo Do we even allow commands after?
|
2018-10-26 10:18:57 +02:00
|
|
|
stop_stream(State, StreamID);
|
2018-06-28 17:10:18 +02:00
|
|
|
%% Log event.
|
|
|
|
commands(State=#state{opts=Opts}, StreamID, [Log={log, _, _, _}|Tail]) ->
|
|
|
|
cowboy:log(Log, Opts),
|
|
|
|
commands(State, StreamID, Tail).
|
2016-06-16 19:46:57 +02:00
|
|
|
|
2019-09-02 14:48:28 +02:00
|
|
|
%% Tentatively update the window after the flow was updated.
|
|
|
|
|
|
|
|
update_window(State=#state{socket=Socket, transport=Transport,
|
|
|
|
http2_machine=HTTP2Machine0, flow=Flow, streams=Streams}, StreamID) ->
|
|
|
|
#{StreamID := #stream{flow=StreamFlow}} = Streams,
|
|
|
|
{Data1, HTTP2Machine2} = case cow_http2_machine:ensure_window(Flow, HTTP2Machine0) of
|
|
|
|
ok -> {<<>>, HTTP2Machine0};
|
|
|
|
{ok, Increment1, HTTP2Machine1} -> {cow_http2:window_update(Increment1), HTTP2Machine1}
|
|
|
|
end,
|
|
|
|
{Data2, HTTP2Machine} = case cow_http2_machine:ensure_window(StreamID, StreamFlow, HTTP2Machine2) of
|
|
|
|
ok -> {<<>>, HTTP2Machine2};
|
|
|
|
{ok, Increment2, HTTP2Machine3} -> {cow_http2:window_update(StreamID, Increment2), HTTP2Machine3}
|
|
|
|
end,
|
|
|
|
case {Data1, Data2} of
|
|
|
|
{<<>>, <<>>} -> ok;
|
|
|
|
_ -> Transport:send(Socket, [Data1, Data2])
|
|
|
|
end,
|
|
|
|
State#state{http2_machine=HTTP2Machine}.
|
|
|
|
|
2018-10-26 10:18:57 +02:00
|
|
|
%% Send the response, trailers or data.
|
2017-05-19 20:18:00 +02:00
|
|
|
|
2018-10-26 10:18:57 +02:00
|
|
|
send_response(State0, StreamID, StatusCode, Headers, Body) ->
|
|
|
|
Size = case Body of
|
|
|
|
{sendfile, _, Bytes, _} -> Bytes;
|
|
|
|
_ -> iolist_size(Body)
|
|
|
|
end,
|
|
|
|
case Size of
|
|
|
|
0 ->
|
|
|
|
State = send_headers(State0, StreamID, fin, StatusCode, Headers),
|
|
|
|
maybe_terminate_stream(State, StreamID, fin);
|
2017-10-23 14:49:33 +01:00
|
|
|
_ ->
|
2018-10-26 10:18:57 +02:00
|
|
|
State = send_headers(State0, StreamID, nofin, StatusCode, Headers),
|
|
|
|
maybe_send_data(State, StreamID, fin, Body)
|
2017-10-23 14:49:33 +01:00
|
|
|
end.
|
2017-05-19 20:18:00 +02:00
|
|
|
|
2018-10-26 10:18:57 +02:00
|
|
|
send_headers(State=#state{socket=Socket, transport=Transport,
|
|
|
|
http2_machine=HTTP2Machine0}, StreamID, IsFin0, StatusCode, Headers) ->
|
|
|
|
{ok, IsFin, HeaderBlock, HTTP2Machine}
|
|
|
|
= cow_http2_machine:prepare_headers(StreamID, HTTP2Machine0, IsFin0,
|
|
|
|
#{status => cow_http:status_to_integer(StatusCode)},
|
|
|
|
headers_to_list(Headers)),
|
|
|
|
Transport:send(Socket, cow_http2:headers(StreamID, IsFin, HeaderBlock)),
|
|
|
|
State#state{http2_machine=HTTP2Machine}.
|
2017-08-21 16:55:30 +02:00
|
|
|
|
2018-10-26 10:18:57 +02:00
|
|
|
%% The set-cookie header is special; we can only send one cookie per header.
|
|
|
|
headers_to_list(Headers0=#{<<"set-cookie">> := SetCookies}) ->
|
|
|
|
Headers = maps:to_list(maps:remove(<<"set-cookie">>, Headers0)),
|
|
|
|
Headers ++ [{<<"set-cookie">>, Value} || Value <- SetCookies];
|
|
|
|
headers_to_list(Headers) ->
|
|
|
|
maps:to_list(Headers).
|
|
|
|
|
2019-08-16 16:37:04 +02:00
|
|
|
maybe_send_data(State0=#state{http2_machine=HTTP2Machine0}, StreamID, IsFin, Data0) ->
|
2018-10-26 10:18:57 +02:00
|
|
|
Data = case is_tuple(Data0) of
|
|
|
|
false -> {data, Data0};
|
|
|
|
true -> Data0
|
|
|
|
end,
|
|
|
|
case cow_http2_machine:send_or_queue_data(StreamID, HTTP2Machine0, IsFin, Data) of
|
|
|
|
{ok, HTTP2Machine} ->
|
2019-09-13 14:20:04 +02:00
|
|
|
maybe_send_data_alarm(State0#state{http2_machine=HTTP2Machine}, HTTP2Machine0, StreamID);
|
2018-10-26 10:18:57 +02:00
|
|
|
{send, SendData, HTTP2Machine} ->
|
2019-08-16 16:37:04 +02:00
|
|
|
State = #state{http2_status=Status, streams=Streams}
|
|
|
|
= send_data(State0#state{http2_machine=HTTP2Machine}, SendData),
|
|
|
|
%% Terminate the connection if we are closing and all streams have completed.
|
|
|
|
if
|
|
|
|
Status =:= closing, Streams =:= #{} ->
|
|
|
|
terminate(State, {stop, normal, 'The connection is going away.'});
|
|
|
|
true ->
|
2019-09-13 14:20:04 +02:00
|
|
|
maybe_send_data_alarm(State, HTTP2Machine0, StreamID)
|
2019-08-16 16:37:04 +02:00
|
|
|
end
|
2017-02-05 13:45:35 +01:00
|
|
|
end.
|
|
|
|
|
2018-10-26 10:18:57 +02:00
|
|
|
send_data(State, []) ->
|
|
|
|
State;
|
|
|
|
send_data(State0, [{StreamID, IsFin, SendData}|Tail]) ->
|
|
|
|
State = send_data(State0, StreamID, IsFin, SendData),
|
|
|
|
send_data(State, Tail).
|
|
|
|
|
|
|
|
send_data(State0, StreamID, IsFin, [Data]) ->
|
|
|
|
State = send_data_frame(State0, StreamID, IsFin, Data),
|
|
|
|
maybe_terminate_stream(State, StreamID, IsFin);
|
|
|
|
send_data(State0, StreamID, IsFin, [Data|Tail]) ->
|
|
|
|
State = send_data_frame(State0, StreamID, nofin, Data),
|
|
|
|
send_data(State, StreamID, IsFin, Tail).
|
|
|
|
|
|
|
|
send_data_frame(State=#state{socket=Socket, transport=Transport},
|
|
|
|
StreamID, IsFin, {data, Data}) ->
|
|
|
|
Transport:send(Socket, cow_http2:data(StreamID, IsFin, Data)),
|
|
|
|
State;
|
2018-11-03 18:55:40 +01:00
|
|
|
send_data_frame(State=#state{socket=Socket, transport=Transport, opts=Opts},
|
2018-10-26 10:18:57 +02:00
|
|
|
StreamID, IsFin, {sendfile, Offset, Bytes, Path}) ->
|
|
|
|
Transport:send(Socket, cow_http2:data_header(StreamID, IsFin, Bytes)),
|
2018-11-03 18:55:40 +01:00
|
|
|
%% When sendfile is disabled we explicitly use the fallback.
|
|
|
|
_ = case maps:get(sendfile, Opts, true) of
|
|
|
|
true -> Transport:sendfile(Socket, Path, Offset, Bytes);
|
|
|
|
false -> ranch_transport:sendfile(Transport, Socket, Path, Offset, Bytes, [])
|
|
|
|
end,
|
2018-10-26 10:18:57 +02:00
|
|
|
State;
|
|
|
|
%% The stream is terminated in cow_http2_machine:prepare_trailers.
|
|
|
|
send_data_frame(State=#state{socket=Socket, transport=Transport,
|
|
|
|
http2_machine=HTTP2Machine0}, StreamID, nofin, {trailers, Trailers}) ->
|
|
|
|
{ok, HeaderBlock, HTTP2Machine}
|
|
|
|
= cow_http2_machine:prepare_trailers(StreamID, HTTP2Machine0, Trailers),
|
2017-11-20 15:46:23 +01:00
|
|
|
Transport:send(Socket, cow_http2:headers(StreamID, fin, HeaderBlock)),
|
2018-10-26 10:18:57 +02:00
|
|
|
State#state{http2_machine=HTTP2Machine}.
|
2017-05-19 20:18:00 +02:00
|
|
|
|
2019-09-13 14:20:04 +02:00
|
|
|
%% After we have sent or queued data we may need to set or clear an alarm.
|
|
|
|
%% We do this by comparing the HTTP2Machine buffer state before/after for
|
|
|
|
%% the relevant streams.
|
|
|
|
maybe_send_data_alarm(State=#state{opts=Opts, http2_machine=HTTP2Machine}, HTTP2Machine0, StreamID) ->
|
Fix HTTP/2 CVEs
A number of HTTP/2 CVEs were documented recently:
https://www.kb.cert.org/vuls/id/605641/
This commit, along with a few changes and additions in Cowlib,
fix or improve protection against all of them.
For CVE-2019-9511, also known as Data Dribble, the new option
stream_window_data_threshold can be used to control how little
the DATA frames that Cowboy sends can get.
For CVE-2019-9516, also known as 0-Length Headers Leak, Cowboy
will now simply reject streams containing 0-length header names.
For CVE-2019-9517, also known as Internal Data Buffering, the
backpressure changes were already pretty good at preventing this
issue, but a new option max_connection_buffer_size was added for
even better control over how much memory we are willing to allocate.
For CVE-2019-9512, also known as Ping Flood; CVE-2019-9515, also
known as Settings Flood; CVE-2019-9518, also known as Empty Frame
Flooding; and similar undocumented scenarios, a frame rate limiting
mechanism was added. By default Cowboy will now allow 1000 frames
every 10 seconds. This can be configured via max_received_frame_rate.
For CVE-2019-9514, also known as Reset Flood, another rate limiting
mechanism was added and can be configured via max_reset_stream_rate.
By default Cowboy will do up to 10 stream resets every 10 seconds.
Finally, nothing was done for CVE-2019-9513, also known as Resource
Loop, because Cowboy does not currently implement the HTTP/2
priority mechanism (in parts because these issues were well known
from the start).
Tests were added for all cases except Internal Data Buffering,
which I'm not sure how to test, and Resource Loop, which is not
currently relevant.
2019-10-02 10:44:45 +02:00
|
|
|
ConnBufferSizeBefore = cow_http2_machine:get_connection_local_buffer_size(HTTP2Machine0),
|
|
|
|
ConnBufferSizeAfter = cow_http2_machine:get_connection_local_buffer_size(HTTP2Machine),
|
|
|
|
{ok, StreamBufferSizeBefore} = cow_http2_machine:get_stream_local_buffer_size(StreamID, HTTP2Machine0),
|
2019-09-13 14:20:04 +02:00
|
|
|
%% When the stream ends up closed after it finished sending data,
|
|
|
|
%% we do not want to trigger an alarm. We act as if the buffer
|
|
|
|
%% size did not change.
|
Fix HTTP/2 CVEs
A number of HTTP/2 CVEs were documented recently:
https://www.kb.cert.org/vuls/id/605641/
This commit, along with a few changes and additions in Cowlib,
fix or improve protection against all of them.
For CVE-2019-9511, also known as Data Dribble, the new option
stream_window_data_threshold can be used to control how little
the DATA frames that Cowboy sends can get.
For CVE-2019-9516, also known as 0-Length Headers Leak, Cowboy
will now simply reject streams containing 0-length header names.
For CVE-2019-9517, also known as Internal Data Buffering, the
backpressure changes were already pretty good at preventing this
issue, but a new option max_connection_buffer_size was added for
even better control over how much memory we are willing to allocate.
For CVE-2019-9512, also known as Ping Flood; CVE-2019-9515, also
known as Settings Flood; CVE-2019-9518, also known as Empty Frame
Flooding; and similar undocumented scenarios, a frame rate limiting
mechanism was added. By default Cowboy will now allow 1000 frames
every 10 seconds. This can be configured via max_received_frame_rate.
For CVE-2019-9514, also known as Reset Flood, another rate limiting
mechanism was added and can be configured via max_reset_stream_rate.
By default Cowboy will do up to 10 stream resets every 10 seconds.
Finally, nothing was done for CVE-2019-9513, also known as Resource
Loop, because Cowboy does not currently implement the HTTP/2
priority mechanism (in parts because these issues were well known
from the start).
Tests were added for all cases except Internal Data Buffering,
which I'm not sure how to test, and Resource Loop, which is not
currently relevant.
2019-10-02 10:44:45 +02:00
|
|
|
StreamBufferSizeAfter = case cow_http2_machine:get_stream_local_buffer_size(StreamID, HTTP2Machine) of
|
2019-09-13 14:20:04 +02:00
|
|
|
{ok, BSA} -> BSA;
|
Fix HTTP/2 CVEs
A number of HTTP/2 CVEs were documented recently:
https://www.kb.cert.org/vuls/id/605641/
This commit, along with a few changes and additions in Cowlib,
fix or improve protection against all of them.
For CVE-2019-9511, also known as Data Dribble, the new option
stream_window_data_threshold can be used to control how little
the DATA frames that Cowboy sends can get.
For CVE-2019-9516, also known as 0-Length Headers Leak, Cowboy
will now simply reject streams containing 0-length header names.
For CVE-2019-9517, also known as Internal Data Buffering, the
backpressure changes were already pretty good at preventing this
issue, but a new option max_connection_buffer_size was added for
even better control over how much memory we are willing to allocate.
For CVE-2019-9512, also known as Ping Flood; CVE-2019-9515, also
known as Settings Flood; CVE-2019-9518, also known as Empty Frame
Flooding; and similar undocumented scenarios, a frame rate limiting
mechanism was added. By default Cowboy will now allow 1000 frames
every 10 seconds. This can be configured via max_received_frame_rate.
For CVE-2019-9514, also known as Reset Flood, another rate limiting
mechanism was added and can be configured via max_reset_stream_rate.
By default Cowboy will do up to 10 stream resets every 10 seconds.
Finally, nothing was done for CVE-2019-9513, also known as Resource
Loop, because Cowboy does not currently implement the HTTP/2
priority mechanism (in parts because these issues were well known
from the start).
Tests were added for all cases except Internal Data Buffering,
which I'm not sure how to test, and Resource Loop, which is not
currently relevant.
2019-10-02 10:44:45 +02:00
|
|
|
{error, closed} -> StreamBufferSizeBefore
|
2019-09-13 14:20:04 +02:00
|
|
|
end,
|
Fix HTTP/2 CVEs
A number of HTTP/2 CVEs were documented recently:
https://www.kb.cert.org/vuls/id/605641/
This commit, along with a few changes and additions in Cowlib,
fix or improve protection against all of them.
For CVE-2019-9511, also known as Data Dribble, the new option
stream_window_data_threshold can be used to control how little
the DATA frames that Cowboy sends can get.
For CVE-2019-9516, also known as 0-Length Headers Leak, Cowboy
will now simply reject streams containing 0-length header names.
For CVE-2019-9517, also known as Internal Data Buffering, the
backpressure changes were already pretty good at preventing this
issue, but a new option max_connection_buffer_size was added for
even better control over how much memory we are willing to allocate.
For CVE-2019-9512, also known as Ping Flood; CVE-2019-9515, also
known as Settings Flood; CVE-2019-9518, also known as Empty Frame
Flooding; and similar undocumented scenarios, a frame rate limiting
mechanism was added. By default Cowboy will now allow 1000 frames
every 10 seconds. This can be configured via max_received_frame_rate.
For CVE-2019-9514, also known as Reset Flood, another rate limiting
mechanism was added and can be configured via max_reset_stream_rate.
By default Cowboy will do up to 10 stream resets every 10 seconds.
Finally, nothing was done for CVE-2019-9513, also known as Resource
Loop, because Cowboy does not currently implement the HTTP/2
priority mechanism (in parts because these issues were well known
from the start).
Tests were added for all cases except Internal Data Buffering,
which I'm not sure how to test, and Resource Loop, which is not
currently relevant.
2019-10-02 10:44:45 +02:00
|
|
|
MaxConnBufferSize = maps:get(max_connection_buffer_size, Opts, 16000000),
|
|
|
|
MaxStreamBufferSize = maps:get(max_stream_buffer_size, Opts, 8000000),
|
|
|
|
%% I do not want to document these internal events yet. I am not yet
|
2019-09-13 14:20:04 +02:00
|
|
|
%% convinced it should be {alarm, Name, on|off} and not {internal_event, E}
|
Fix HTTP/2 CVEs
A number of HTTP/2 CVEs were documented recently:
https://www.kb.cert.org/vuls/id/605641/
This commit, along with a few changes and additions in Cowlib,
fix or improve protection against all of them.
For CVE-2019-9511, also known as Data Dribble, the new option
stream_window_data_threshold can be used to control how little
the DATA frames that Cowboy sends can get.
For CVE-2019-9516, also known as 0-Length Headers Leak, Cowboy
will now simply reject streams containing 0-length header names.
For CVE-2019-9517, also known as Internal Data Buffering, the
backpressure changes were already pretty good at preventing this
issue, but a new option max_connection_buffer_size was added for
even better control over how much memory we are willing to allocate.
For CVE-2019-9512, also known as Ping Flood; CVE-2019-9515, also
known as Settings Flood; CVE-2019-9518, also known as Empty Frame
Flooding; and similar undocumented scenarios, a frame rate limiting
mechanism was added. By default Cowboy will now allow 1000 frames
every 10 seconds. This can be configured via max_received_frame_rate.
For CVE-2019-9514, also known as Reset Flood, another rate limiting
mechanism was added and can be configured via max_reset_stream_rate.
By default Cowboy will do up to 10 stream resets every 10 seconds.
Finally, nothing was done for CVE-2019-9513, also known as Resource
Loop, because Cowboy does not currently implement the HTTP/2
priority mechanism (in parts because these issues were well known
from the start).
Tests were added for all cases except Internal Data Buffering,
which I'm not sure how to test, and Resource Loop, which is not
currently relevant.
2019-10-02 10:44:45 +02:00
|
|
|
%% or something else entirely. Though alarms are probably right.
|
2019-09-13 14:20:04 +02:00
|
|
|
if
|
Fix HTTP/2 CVEs
A number of HTTP/2 CVEs were documented recently:
https://www.kb.cert.org/vuls/id/605641/
This commit, along with a few changes and additions in Cowlib,
fix or improve protection against all of them.
For CVE-2019-9511, also known as Data Dribble, the new option
stream_window_data_threshold can be used to control how little
the DATA frames that Cowboy sends can get.
For CVE-2019-9516, also known as 0-Length Headers Leak, Cowboy
will now simply reject streams containing 0-length header names.
For CVE-2019-9517, also known as Internal Data Buffering, the
backpressure changes were already pretty good at preventing this
issue, but a new option max_connection_buffer_size was added for
even better control over how much memory we are willing to allocate.
For CVE-2019-9512, also known as Ping Flood; CVE-2019-9515, also
known as Settings Flood; CVE-2019-9518, also known as Empty Frame
Flooding; and similar undocumented scenarios, a frame rate limiting
mechanism was added. By default Cowboy will now allow 1000 frames
every 10 seconds. This can be configured via max_received_frame_rate.
For CVE-2019-9514, also known as Reset Flood, another rate limiting
mechanism was added and can be configured via max_reset_stream_rate.
By default Cowboy will do up to 10 stream resets every 10 seconds.
Finally, nothing was done for CVE-2019-9513, also known as Resource
Loop, because Cowboy does not currently implement the HTTP/2
priority mechanism (in parts because these issues were well known
from the start).
Tests were added for all cases except Internal Data Buffering,
which I'm not sure how to test, and Resource Loop, which is not
currently relevant.
2019-10-02 10:44:45 +02:00
|
|
|
ConnBufferSizeBefore >= MaxConnBufferSize, ConnBufferSizeAfter < MaxConnBufferSize ->
|
|
|
|
connection_alarm(State, connection_buffer_full, off);
|
|
|
|
ConnBufferSizeBefore < MaxConnBufferSize, ConnBufferSizeAfter >= MaxConnBufferSize ->
|
|
|
|
connection_alarm(State, connection_buffer_full, on);
|
|
|
|
StreamBufferSizeBefore >= MaxStreamBufferSize, StreamBufferSizeAfter < MaxStreamBufferSize ->
|
|
|
|
stream_alarm(State, StreamID, stream_buffer_full, off);
|
|
|
|
StreamBufferSizeBefore < MaxStreamBufferSize, StreamBufferSizeAfter >= MaxStreamBufferSize ->
|
|
|
|
stream_alarm(State, StreamID, stream_buffer_full, on);
|
2019-09-13 14:20:04 +02:00
|
|
|
true ->
|
|
|
|
State
|
|
|
|
end.
|
|
|
|
|
Fix HTTP/2 CVEs
A number of HTTP/2 CVEs were documented recently:
https://www.kb.cert.org/vuls/id/605641/
This commit, along with a few changes and additions in Cowlib,
fix or improve protection against all of them.
For CVE-2019-9511, also known as Data Dribble, the new option
stream_window_data_threshold can be used to control how little
the DATA frames that Cowboy sends can get.
For CVE-2019-9516, also known as 0-Length Headers Leak, Cowboy
will now simply reject streams containing 0-length header names.
For CVE-2019-9517, also known as Internal Data Buffering, the
backpressure changes were already pretty good at preventing this
issue, but a new option max_connection_buffer_size was added for
even better control over how much memory we are willing to allocate.
For CVE-2019-9512, also known as Ping Flood; CVE-2019-9515, also
known as Settings Flood; CVE-2019-9518, also known as Empty Frame
Flooding; and similar undocumented scenarios, a frame rate limiting
mechanism was added. By default Cowboy will now allow 1000 frames
every 10 seconds. This can be configured via max_received_frame_rate.
For CVE-2019-9514, also known as Reset Flood, another rate limiting
mechanism was added and can be configured via max_reset_stream_rate.
By default Cowboy will do up to 10 stream resets every 10 seconds.
Finally, nothing was done for CVE-2019-9513, also known as Resource
Loop, because Cowboy does not currently implement the HTTP/2
priority mechanism (in parts because these issues were well known
from the start).
Tests were added for all cases except Internal Data Buffering,
which I'm not sure how to test, and Resource Loop, which is not
currently relevant.
2019-10-02 10:44:45 +02:00
|
|
|
connection_alarm(State0=#state{streams=Streams}, Name, Value) ->
|
|
|
|
lists:foldl(fun(StreamID, State) ->
|
|
|
|
stream_alarm(State, StreamID, Name, Value)
|
|
|
|
end, State0, maps:keys(Streams)).
|
|
|
|
|
|
|
|
stream_alarm(State, StreamID, Name, Value) ->
|
|
|
|
info(State, StreamID, {alarm, Name, Value}).
|
|
|
|
|
2018-10-26 10:18:57 +02:00
|
|
|
%% Terminate a stream or the connection.
|
2017-11-29 14:54:47 +01:00
|
|
|
|
2019-08-16 16:37:04 +02:00
|
|
|
%% We may have to cancel streams even if we receive multiple
|
|
|
|
%% GOAWAY frames as the LastStreamID value may be lower than
|
|
|
|
%% the one previously received.
|
|
|
|
goaway(State0=#state{socket=Socket, transport=Transport, http2_machine=HTTP2Machine,
|
|
|
|
http2_status=Status, streams=Streams0}, {goaway, LastStreamID, Reason, _})
|
|
|
|
when Status =:= connected; Status =:= closing ->
|
|
|
|
Streams = goaway_streams(State0, maps:to_list(Streams0), LastStreamID,
|
|
|
|
{stop, {goaway, Reason}, 'The connection is going away.'}, []),
|
|
|
|
State = State0#state{streams=maps:from_list(Streams)},
|
|
|
|
case Status of
|
|
|
|
connected ->
|
|
|
|
Transport:send(Socket, cow_http2:goaway(
|
|
|
|
cow_http2_machine:get_last_streamid(HTTP2Machine),
|
|
|
|
no_error, <<>>)),
|
|
|
|
State#state{http2_status=closing};
|
|
|
|
_ ->
|
|
|
|
State
|
|
|
|
end;
|
|
|
|
%% We terminate the connection immediately if it hasn't fully been initialized.
|
|
|
|
goaway(State, {goaway, _, Reason, _}) ->
|
|
|
|
terminate(State, {stop, {goaway, Reason}, 'The connection is going away.'}).
|
|
|
|
|
|
|
|
%% Cancel client-initiated streams that are above LastStreamID.
|
|
|
|
goaway_streams(_, [], _, _, Acc) ->
|
|
|
|
Acc;
|
2019-09-02 14:48:28 +02:00
|
|
|
goaway_streams(State, [{StreamID, #stream{state=StreamState}}|Tail], LastStreamID, Reason, Acc)
|
2019-08-16 16:37:04 +02:00
|
|
|
when StreamID > LastStreamID, (StreamID rem 2) =:= 0 ->
|
|
|
|
terminate_stream_handler(State, StreamID, Reason, StreamState),
|
|
|
|
goaway_streams(State, Tail, LastStreamID, Reason, Acc);
|
|
|
|
goaway_streams(State, [Stream|Tail], LastStreamID, Reason, Acc) ->
|
|
|
|
goaway_streams(State, Tail, LastStreamID, Reason, [Stream|Acc]).
|
|
|
|
|
2017-01-02 18:27:03 +01:00
|
|
|
-spec terminate(#state{}, _) -> no_return().
|
2017-02-25 20:05:31 +01:00
|
|
|
terminate(undefined, Reason) ->
|
|
|
|
exit({shutdown, Reason});
|
2019-08-16 16:37:04 +02:00
|
|
|
terminate(State=#state{socket=Socket, transport=Transport, http2_status=Status,
|
|
|
|
http2_machine=HTTP2Machine, streams=Streams, children=Children}, Reason)
|
|
|
|
when Status =:= connected; Status =:= closing ->
|
2017-02-25 20:05:31 +01:00
|
|
|
%% @todo We might want to optionally send the Reason value
|
|
|
|
%% as debug data in the GOAWAY frame here. Perhaps more.
|
2019-08-16 16:37:04 +02:00
|
|
|
case Status of
|
|
|
|
connected ->
|
|
|
|
Transport:send(Socket, cow_http2:goaway(
|
|
|
|
cow_http2_machine:get_last_streamid(HTTP2Machine),
|
|
|
|
terminate_reason(Reason), <<>>));
|
|
|
|
%% We already sent the GOAWAY frame.
|
|
|
|
closing ->
|
|
|
|
ok
|
|
|
|
end,
|
2018-10-26 10:18:57 +02:00
|
|
|
terminate_all_streams(State, maps:to_list(Streams), Reason),
|
2017-08-08 16:59:33 +02:00
|
|
|
cowboy_children:terminate(Children),
|
2018-10-26 10:18:57 +02:00
|
|
|
Transport:close(Socket),
|
|
|
|
exit({shutdown, Reason});
|
|
|
|
terminate(#state{socket=Socket, transport=Transport}, Reason) ->
|
2015-06-11 17:04:21 +02:00
|
|
|
Transport:close(Socket),
|
|
|
|
exit({shutdown, Reason}).
|
|
|
|
|
2017-02-25 20:05:31 +01:00
|
|
|
terminate_reason({connection_error, Reason, _}) -> Reason;
|
|
|
|
terminate_reason({stop, _, _}) -> no_error;
|
|
|
|
terminate_reason({socket_error, _, _}) -> internal_error;
|
|
|
|
terminate_reason({internal_error, _, _}) -> internal_error.
|
|
|
|
|
2018-06-28 17:10:18 +02:00
|
|
|
terminate_all_streams(_, [], _) ->
|
2015-06-11 17:04:21 +02:00
|
|
|
ok;
|
2019-09-02 14:48:28 +02:00
|
|
|
terminate_all_streams(State, [{StreamID, #stream{state=StreamState}}|Tail], Reason) ->
|
2018-10-28 11:47:49 +01:00
|
|
|
terminate_stream_handler(State, StreamID, Reason, StreamState),
|
2018-06-28 17:10:18 +02:00
|
|
|
terminate_all_streams(State, Tail, Reason).
|
2015-06-11 17:04:21 +02:00
|
|
|
|
2017-10-23 14:49:33 +01:00
|
|
|
%% @todo Don't send an RST_STREAM if one was already sent.
|
Fix HTTP/2 CVEs
A number of HTTP/2 CVEs were documented recently:
https://www.kb.cert.org/vuls/id/605641/
This commit, along with a few changes and additions in Cowlib,
fix or improve protection against all of them.
For CVE-2019-9511, also known as Data Dribble, the new option
stream_window_data_threshold can be used to control how little
the DATA frames that Cowboy sends can get.
For CVE-2019-9516, also known as 0-Length Headers Leak, Cowboy
will now simply reject streams containing 0-length header names.
For CVE-2019-9517, also known as Internal Data Buffering, the
backpressure changes were already pretty good at preventing this
issue, but a new option max_connection_buffer_size was added for
even better control over how much memory we are willing to allocate.
For CVE-2019-9512, also known as Ping Flood; CVE-2019-9515, also
known as Settings Flood; CVE-2019-9518, also known as Empty Frame
Flooding; and similar undocumented scenarios, a frame rate limiting
mechanism was added. By default Cowboy will now allow 1000 frames
every 10 seconds. This can be configured via max_received_frame_rate.
For CVE-2019-9514, also known as Reset Flood, another rate limiting
mechanism was added and can be configured via max_reset_stream_rate.
By default Cowboy will do up to 10 stream resets every 10 seconds.
Finally, nothing was done for CVE-2019-9513, also known as Resource
Loop, because Cowboy does not currently implement the HTTP/2
priority mechanism (in parts because these issues were well known
from the start).
Tests were added for all cases except Internal Data Buffering,
which I'm not sure how to test, and Resource Loop, which is not
currently relevant.
2019-10-02 10:44:45 +02:00
|
|
|
reset_stream(State0=#state{socket=Socket, transport=Transport,
|
2018-10-26 10:18:57 +02:00
|
|
|
http2_machine=HTTP2Machine0}, StreamID, Error) ->
|
|
|
|
Reason = case Error of
|
2017-10-23 14:49:33 +01:00
|
|
|
{internal_error, _, _} -> internal_error;
|
|
|
|
{stream_error, Reason0, _} -> Reason0
|
|
|
|
end,
|
2015-06-11 17:04:21 +02:00
|
|
|
Transport:send(Socket, cow_http2:rst_stream(StreamID, Reason)),
|
Fix HTTP/2 CVEs
A number of HTTP/2 CVEs were documented recently:
https://www.kb.cert.org/vuls/id/605641/
This commit, along with a few changes and additions in Cowlib,
fix or improve protection against all of them.
For CVE-2019-9511, also known as Data Dribble, the new option
stream_window_data_threshold can be used to control how little
the DATA frames that Cowboy sends can get.
For CVE-2019-9516, also known as 0-Length Headers Leak, Cowboy
will now simply reject streams containing 0-length header names.
For CVE-2019-9517, also known as Internal Data Buffering, the
backpressure changes were already pretty good at preventing this
issue, but a new option max_connection_buffer_size was added for
even better control over how much memory we are willing to allocate.
For CVE-2019-9512, also known as Ping Flood; CVE-2019-9515, also
known as Settings Flood; CVE-2019-9518, also known as Empty Frame
Flooding; and similar undocumented scenarios, a frame rate limiting
mechanism was added. By default Cowboy will now allow 1000 frames
every 10 seconds. This can be configured via max_received_frame_rate.
For CVE-2019-9514, also known as Reset Flood, another rate limiting
mechanism was added and can be configured via max_reset_stream_rate.
By default Cowboy will do up to 10 stream resets every 10 seconds.
Finally, nothing was done for CVE-2019-9513, also known as Resource
Loop, because Cowboy does not currently implement the HTTP/2
priority mechanism (in parts because these issues were well known
from the start).
Tests were added for all cases except Internal Data Buffering,
which I'm not sure how to test, and Resource Loop, which is not
currently relevant.
2019-10-02 10:44:45 +02:00
|
|
|
State1 = case cow_http2_machine:reset_stream(StreamID, HTTP2Machine0) of
|
2018-10-26 10:18:57 +02:00
|
|
|
{ok, HTTP2Machine} ->
|
Fix HTTP/2 CVEs
A number of HTTP/2 CVEs were documented recently:
https://www.kb.cert.org/vuls/id/605641/
This commit, along with a few changes and additions in Cowlib,
fix or improve protection against all of them.
For CVE-2019-9511, also known as Data Dribble, the new option
stream_window_data_threshold can be used to control how little
the DATA frames that Cowboy sends can get.
For CVE-2019-9516, also known as 0-Length Headers Leak, Cowboy
will now simply reject streams containing 0-length header names.
For CVE-2019-9517, also known as Internal Data Buffering, the
backpressure changes were already pretty good at preventing this
issue, but a new option max_connection_buffer_size was added for
even better control over how much memory we are willing to allocate.
For CVE-2019-9512, also known as Ping Flood; CVE-2019-9515, also
known as Settings Flood; CVE-2019-9518, also known as Empty Frame
Flooding; and similar undocumented scenarios, a frame rate limiting
mechanism was added. By default Cowboy will now allow 1000 frames
every 10 seconds. This can be configured via max_received_frame_rate.
For CVE-2019-9514, also known as Reset Flood, another rate limiting
mechanism was added and can be configured via max_reset_stream_rate.
By default Cowboy will do up to 10 stream resets every 10 seconds.
Finally, nothing was done for CVE-2019-9513, also known as Resource
Loop, because Cowboy does not currently implement the HTTP/2
priority mechanism (in parts because these issues were well known
from the start).
Tests were added for all cases except Internal Data Buffering,
which I'm not sure how to test, and Resource Loop, which is not
currently relevant.
2019-10-02 10:44:45 +02:00
|
|
|
terminate_stream(State0#state{http2_machine=HTTP2Machine}, StreamID, Error);
|
2018-10-26 10:18:57 +02:00
|
|
|
{error, not_found} ->
|
Fix HTTP/2 CVEs
A number of HTTP/2 CVEs were documented recently:
https://www.kb.cert.org/vuls/id/605641/
This commit, along with a few changes and additions in Cowlib,
fix or improve protection against all of them.
For CVE-2019-9511, also known as Data Dribble, the new option
stream_window_data_threshold can be used to control how little
the DATA frames that Cowboy sends can get.
For CVE-2019-9516, also known as 0-Length Headers Leak, Cowboy
will now simply reject streams containing 0-length header names.
For CVE-2019-9517, also known as Internal Data Buffering, the
backpressure changes were already pretty good at preventing this
issue, but a new option max_connection_buffer_size was added for
even better control over how much memory we are willing to allocate.
For CVE-2019-9512, also known as Ping Flood; CVE-2019-9515, also
known as Settings Flood; CVE-2019-9518, also known as Empty Frame
Flooding; and similar undocumented scenarios, a frame rate limiting
mechanism was added. By default Cowboy will now allow 1000 frames
every 10 seconds. This can be configured via max_received_frame_rate.
For CVE-2019-9514, also known as Reset Flood, another rate limiting
mechanism was added and can be configured via max_reset_stream_rate.
By default Cowboy will do up to 10 stream resets every 10 seconds.
Finally, nothing was done for CVE-2019-9513, also known as Resource
Loop, because Cowboy does not currently implement the HTTP/2
priority mechanism (in parts because these issues were well known
from the start).
Tests were added for all cases except Internal Data Buffering,
which I'm not sure how to test, and Resource Loop, which is not
currently relevant.
2019-10-02 10:44:45 +02:00
|
|
|
terminate_stream(State0, StreamID, Error)
|
|
|
|
end,
|
|
|
|
case reset_rate(State1) of
|
|
|
|
{ok, State} ->
|
|
|
|
State;
|
|
|
|
error ->
|
|
|
|
terminate(State1, {connection_error, enhance_your_calm,
|
|
|
|
'Stream reset rate larger than configuration allows. Flood? (CVE-2019-9514)'})
|
|
|
|
end.
|
|
|
|
|
|
|
|
reset_rate(State0=#state{opts=Opts, reset_rate_num=Num0, reset_rate_time=Time}) ->
|
|
|
|
case Num0 - 1 of
|
|
|
|
0 ->
|
|
|
|
CurrentTime = erlang:monotonic_time(millisecond),
|
|
|
|
if
|
|
|
|
CurrentTime < Time ->
|
|
|
|
error;
|
|
|
|
true ->
|
|
|
|
%% When the option has a period of infinity we cannot reach this clause.
|
|
|
|
{Num, Period} = maps:get(max_reset_stream_rate, Opts, {10, 10000}),
|
|
|
|
{ok, State0#state{reset_rate_num=Num, reset_rate_time=CurrentTime + Period}}
|
|
|
|
end;
|
|
|
|
Num ->
|
|
|
|
{ok, State0#state{reset_rate_num=Num}}
|
2018-10-26 10:18:57 +02:00
|
|
|
end.
|
2018-04-27 20:45:34 +02:00
|
|
|
|
2018-10-26 10:18:57 +02:00
|
|
|
stop_stream(State=#state{http2_machine=HTTP2Machine}, StreamID) ->
|
|
|
|
case cow_http2_machine:get_stream_local_state(StreamID, HTTP2Machine) of
|
2017-04-18 16:07:01 +02:00
|
|
|
%% When the stream terminates normally (without sending RST_STREAM)
|
|
|
|
%% and no response was sent, we need to send a proper response back to the client.
|
2018-10-26 10:18:57 +02:00
|
|
|
%% We delay the termination of the stream until the response is fully sent.
|
|
|
|
{ok, idle, _} ->
|
|
|
|
info(stopping(State, StreamID), StreamID, {response, 204, #{}, <<>>});
|
2017-04-18 16:07:01 +02:00
|
|
|
%% When a response was sent but not terminated, we need to close the stream.
|
2018-10-26 10:18:57 +02:00
|
|
|
%% We delay the termination of the stream until the response is fully sent.
|
|
|
|
{ok, nofin, fin} ->
|
|
|
|
stopping(State, StreamID);
|
|
|
|
%% We only send a final DATA frame if there isn't one queued yet.
|
|
|
|
{ok, nofin, _} ->
|
|
|
|
info(stopping(State, StreamID), StreamID, {data, fin, <<>>});
|
|
|
|
%% When a response was sent fully we can terminate the stream,
|
|
|
|
%% regardless of the stream being in half-closed or closed state.
|
|
|
|
_ ->
|
|
|
|
terminate_stream(State, StreamID)
|
2015-06-11 17:04:21 +02:00
|
|
|
end.
|
|
|
|
|
2018-10-26 10:18:57 +02:00
|
|
|
stopping(State=#state{streams=Streams}, StreamID) ->
|
2019-09-02 14:48:28 +02:00
|
|
|
#{StreamID := Stream} = Streams,
|
|
|
|
State#state{streams=Streams#{StreamID => Stream#stream{status=stopping}}}.
|
2018-10-26 10:18:57 +02:00
|
|
|
|
|
|
|
%% If we finished sending data and the stream is stopping, terminate it.
|
|
|
|
maybe_terminate_stream(State=#state{streams=Streams}, StreamID, fin) ->
|
|
|
|
case Streams of
|
2019-09-02 14:48:28 +02:00
|
|
|
#{StreamID := #stream{status=stopping}} ->
|
2018-10-26 10:18:57 +02:00
|
|
|
terminate_stream(State, StreamID);
|
|
|
|
_ ->
|
|
|
|
State
|
|
|
|
end;
|
|
|
|
maybe_terminate_stream(State, _, _) ->
|
|
|
|
State.
|
|
|
|
|
2017-10-23 14:49:33 +01:00
|
|
|
%% When the stream stops normally without reading the request
|
|
|
|
%% body fully we need to tell the client to stop sending it.
|
|
|
|
%% We do this by sending an RST_STREAM with reason NO_ERROR. (RFC7540 8.1.0)
|
2018-10-26 10:18:57 +02:00
|
|
|
terminate_stream(State0=#state{socket=Socket, transport=Transport,
|
|
|
|
http2_machine=HTTP2Machine0}, StreamID) ->
|
|
|
|
State = case cow_http2_machine:get_stream_local_state(StreamID, HTTP2Machine0) of
|
|
|
|
{ok, fin, _} ->
|
|
|
|
Transport:send(Socket, cow_http2:rst_stream(StreamID, no_error)),
|
|
|
|
{ok, HTTP2Machine} = cow_http2_machine:reset_stream(StreamID, HTTP2Machine0),
|
|
|
|
State0#state{http2_machine=HTTP2Machine};
|
|
|
|
{error, closed} ->
|
|
|
|
State0
|
|
|
|
end,
|
|
|
|
terminate_stream(State, StreamID, normal).
|
|
|
|
|
2019-09-02 14:48:28 +02:00
|
|
|
%% We remove the stream flow from the connection flow. Any further
|
|
|
|
%% data received for this stream is therefore fully contained within
|
|
|
|
%% the extra window we allocated for this stream.
|
|
|
|
terminate_stream(State=#state{flow=Flow, streams=Streams0, children=Children0}, StreamID, Reason) ->
|
2018-10-26 10:18:57 +02:00
|
|
|
case maps:take(StreamID, Streams0) of
|
2019-09-02 14:48:28 +02:00
|
|
|
{#stream{flow=StreamFlow, state=StreamState}, Streams} ->
|
2018-10-28 11:47:49 +01:00
|
|
|
terminate_stream_handler(State, StreamID, Reason, StreamState),
|
2018-10-26 10:18:57 +02:00
|
|
|
Children = cowboy_children:shutdown(Children0, StreamID),
|
2019-09-02 14:48:28 +02:00
|
|
|
State#state{flow=Flow - StreamFlow, streams=Streams, children=Children};
|
2018-10-26 10:18:57 +02:00
|
|
|
error ->
|
|
|
|
State
|
|
|
|
end.
|
2017-10-23 14:49:33 +01:00
|
|
|
|
2018-10-28 11:47:49 +01:00
|
|
|
terminate_stream_handler(#state{opts=Opts}, StreamID, Reason, StreamState) ->
|
2015-06-11 17:04:21 +02:00
|
|
|
try
|
2017-01-16 14:22:43 +01:00
|
|
|
cowboy_stream:terminate(StreamID, Reason, StreamState)
|
2017-09-21 12:53:21 +02:00
|
|
|
catch Class:Exception ->
|
2018-06-28 17:10:18 +02:00
|
|
|
cowboy:log(cowboy_stream:make_error_log(terminate,
|
2017-09-21 12:53:21 +02:00
|
|
|
[StreamID, Reason, StreamState],
|
2018-06-28 17:10:18 +02:00
|
|
|
Class, Exception, erlang:get_stacktrace()), Opts)
|
2015-06-11 17:04:21 +02:00
|
|
|
end.
|
|
|
|
|
|
|
|
%% System callbacks.
|
|
|
|
|
2017-01-02 18:27:03 +01:00
|
|
|
-spec system_continue(_, _, {#state{}, binary()}) -> ok.
|
2015-06-11 17:04:21 +02:00
|
|
|
system_continue(_, _, {State, Buffer}) ->
|
|
|
|
loop(State, Buffer).
|
|
|
|
|
2017-08-08 16:59:33 +02:00
|
|
|
-spec system_terminate(any(), _, _, {#state{}, binary()}) -> no_return().
|
|
|
|
system_terminate(Reason, _, _, {State, _}) ->
|
2019-08-16 16:37:04 +02:00
|
|
|
%% @todo Graceful shutdown here as well?
|
2018-10-28 10:20:43 +01:00
|
|
|
terminate(State, {stop, {exit, Reason}, 'sys:terminate/2,3 was called.'}).
|
2015-06-11 17:04:21 +02:00
|
|
|
|
|
|
|
-spec system_code_change(Misc, _, _, _) -> {ok, Misc} when Misc::{#state{}, binary()}.
|
|
|
|
system_code_change(Misc, _, _, _) ->
|
|
|
|
{ok, Misc}.
|