http2: disable priority forwarding

This commit is contained in:
Thomas Kriechbaumer 2017-02-18 14:30:08 +01:00
parent 4158a1ae55
commit 83c2de8849
4 changed files with 79 additions and 145 deletions

View File

@ -54,6 +54,7 @@ class Options(optmanager.OptManager):
server_replay_ignore_params: Sequence[str] = [], server_replay_ignore_params: Sequence[str] = [],
server_replay_ignore_payload_params: Sequence[str] = [], server_replay_ignore_payload_params: Sequence[str] = [],
server_replay_ignore_host: bool = False, server_replay_ignore_host: bool = False,
# Proxy options # Proxy options
auth_nonanonymous: bool = False, auth_nonanonymous: bool = False,
auth_singleuser: Optional[str] = None, auth_singleuser: Optional[str] = None,
@ -65,15 +66,18 @@ class Options(optmanager.OptManager):
ciphers_client: str=DEFAULT_CLIENT_CIPHERS, ciphers_client: str=DEFAULT_CLIENT_CIPHERS,
ciphers_server: Optional[str]=None, ciphers_server: Optional[str]=None,
clientcerts: Optional[str] = None, clientcerts: Optional[str] = None,
http2: bool = True,
ignore_hosts: Sequence[str] = [], ignore_hosts: Sequence[str] = [],
listen_host: str = "", listen_host: str = "",
listen_port: int = LISTEN_PORT, listen_port: int = LISTEN_PORT,
upstream_bind_address: str = "", upstream_bind_address: str = "",
mode: str = "regular", mode: str = "regular",
no_upstream_cert: bool = False, no_upstream_cert: bool = False,
rawtcp: bool = False,
http2: bool = True,
http2_priority: bool = False,
websocket: bool = True, websocket: bool = True,
rawtcp: bool = False,
spoof_source_address: bool = False, spoof_source_address: bool = False,
upstream_server: Optional[str] = None, upstream_server: Optional[str] = None,
upstream_auth: Optional[str] = None, upstream_auth: Optional[str] = None,
@ -152,15 +156,18 @@ class Options(optmanager.OptManager):
self.ciphers_client = ciphers_client self.ciphers_client = ciphers_client
self.ciphers_server = ciphers_server self.ciphers_server = ciphers_server
self.clientcerts = clientcerts self.clientcerts = clientcerts
self.http2 = http2
self.ignore_hosts = ignore_hosts self.ignore_hosts = ignore_hosts
self.listen_host = listen_host self.listen_host = listen_host
self.listen_port = listen_port self.listen_port = listen_port
self.upstream_bind_address = upstream_bind_address self.upstream_bind_address = upstream_bind_address
self.mode = mode self.mode = mode
self.no_upstream_cert = no_upstream_cert self.no_upstream_cert = no_upstream_cert
self.rawtcp = rawtcp
self.http2 = http2
self.http2_priority = http2_priority
self.websocket = websocket self.websocket = websocket
self.rawtcp = rawtcp
self.spoof_source_address = spoof_source_address self.spoof_source_address = spoof_source_address
self.upstream_server = upstream_server self.upstream_server = upstream_server
self.upstream_auth = upstream_auth self.upstream_auth = upstream_auth

View File

@ -268,6 +268,10 @@ class Http2Layer(base.Layer):
return True return True
def _handle_priority_updated(self, eid, event): def _handle_priority_updated(self, eid, event):
if not self.config.options.http2_priority:
self.log("HTTP/2 PRIORITY frame surpressed. Use --http2-priority to enable forwarding.", "debug")
return True
if eid in self.streams and self.streams[eid].handled_priority_event is event: if eid in self.streams and self.streams[eid].handled_priority_event is event:
# this event was already handled during stream creation # this event was already handled during stream creation
# HeadersFrame + Priority information as RequestReceived # HeadersFrame + Priority information as RequestReceived
@ -527,9 +531,12 @@ class Http2SingleStreamLayer(httpbase._HttpTransmissionLayer, basethread.BaseThr
if self.handled_priority_event: if self.handled_priority_event:
# only send priority information if they actually came with the original HeadersFrame # only send priority information if they actually came with the original HeadersFrame
# and not if they got updated before/after with a PriorityFrame # and not if they got updated before/after with a PriorityFrame
priority_exclusive = self.priority_exclusive if not self.config.options.http2_priority:
priority_depends_on = self._map_depends_on_stream_id(self.server_stream_id, self.priority_depends_on) self.log("HTTP/2 PRIORITY information in HEADERS frame surpressed. Use --http2-priority to enable forwarding.", "debug")
priority_weight = self.priority_weight else:
priority_exclusive = self.priority_exclusive
priority_depends_on = self._map_depends_on_stream_id(self.server_stream_id, self.priority_depends_on)
priority_weight = self.priority_weight
try: try:
self.connections[self.server_conn].safe_send_headers( self.connections[self.server_conn].safe_send_headers(
@ -610,7 +617,7 @@ class Http2SingleStreamLayer(httpbase._HttpTransmissionLayer, basethread.BaseThr
chunks chunks
) )
def __call__(self): def __call__(self): # pragma: no cover
raise EnvironmentError('Http2SingleStreamLayer must be run as thread') raise EnvironmentError('Http2SingleStreamLayer must be run as thread')
def run(self): def run(self):

View File

@ -137,7 +137,6 @@ def get_common_options(args):
ciphers_client = args.ciphers_client, ciphers_client = args.ciphers_client,
ciphers_server = args.ciphers_server, ciphers_server = args.ciphers_server,
clientcerts = args.clientcerts, clientcerts = args.clientcerts,
http2 = args.http2,
ignore_hosts = args.ignore_hosts, ignore_hosts = args.ignore_hosts,
listen_host = args.addr, listen_host = args.addr,
listen_port = args.port, listen_port = args.port,
@ -145,8 +144,12 @@ def get_common_options(args):
mode = mode, mode = mode,
no_upstream_cert = args.no_upstream_cert, no_upstream_cert = args.no_upstream_cert,
spoof_source_address = args.spoof_source_address, spoof_source_address = args.spoof_source_address,
rawtcp = args.rawtcp,
http2 = args.http2,
http2_priority = args.http2_priority,
websocket = args.websocket, websocket = args.websocket,
rawtcp = args.rawtcp,
upstream_server = upstream_server, upstream_server = upstream_server,
upstream_auth = args.upstream_auth, upstream_auth = args.upstream_auth,
ssl_version_client = args.ssl_version_client, ssl_version_client = args.ssl_version_client,
@ -334,18 +337,26 @@ def proxy_options(parser):
) )
http2 = group.add_mutually_exclusive_group() http2 = group.add_mutually_exclusive_group()
http2.add_argument("--no-http2", action="store_false", dest="http2", http2.add_argument("--no-http2", action="store_false", dest="http2")
http2.add_argument("--http2", action="store_true", dest="http2",
help="Explicitly enable/disable HTTP/2 support. " help="Explicitly enable/disable HTTP/2 support. "
"Enabled by default." "HTTP/2 support is enabled by default.",
) )
http2.add_argument("--http2", action="store_true", dest="http2")
http2_priority = group.add_mutually_exclusive_group()
http2_priority.add_argument("--http2-priority", action="store_true", dest="http2_priority")
http2_priority.add_argument("--no-http2-priority", action="store_false", dest="http2_priority",
help="Explicitly enable/disable PRIORITY forwarding for HTTP/2 connections. "
"PRIORITY forwarding is disabled by default, "
"because some webservers fail at implementing the RFC properly.",
)
websocket = group.add_mutually_exclusive_group() websocket = group.add_mutually_exclusive_group()
websocket.add_argument("--no-websocket", action="store_false", dest="websocket", websocket.add_argument("--no-websocket", action="store_false", dest="websocket")
websocket.add_argument("--websocket", action="store_true", dest="websocket",
help="Explicitly enable/disable WebSocket support. " help="Explicitly enable/disable WebSocket support. "
"Enabled by default." "WebSocket support is enabled by default.",
) )
websocket.add_argument("--websocket", action="store_true", dest="websocket")
parser.add_argument( parser.add_argument(
"--upstream-auth", "--upstream-auth",

View File

@ -4,7 +4,7 @@
import os import os
import tempfile import tempfile
import traceback import traceback
import pytest
import h2 import h2
from mitmproxy import options from mitmproxy import options
@ -369,7 +369,15 @@ class TestRequestWithPriority(_Http2Test):
wfile.flush() wfile.flush()
return True return True
def test_request_with_priority(self): @pytest.mark.parametrize("http2_priority_enabled, priority, expected_priority", [
(True, (True, 42424242, 42), ('True', '42424242', '42')),
(False, (True, 42424242, 42), (None, None, None)),
(True, (None, None, None), (None, None, None)),
(False, (None, None, None), (None, None, None)),
])
def test_request_with_priority(self, http2_priority_enabled, priority, expected_priority):
self.config.options.http2_priority = http2_priority_enabled
client, h2_conn = self._setup_connection() client, h2_conn = self._setup_connection()
self._send_request( self._send_request(
@ -381,9 +389,9 @@ class TestRequestWithPriority(_Http2Test):
(':scheme', 'https'), (':scheme', 'https'),
(':path', '/'), (':path', '/'),
], ],
priority_exclusive=True, priority_exclusive=priority[0],
priority_depends_on=42424242, priority_depends_on=priority[1],
priority_weight=42, priority_weight=priority[2],
) )
done = False done = False
@ -407,123 +415,15 @@ class TestRequestWithPriority(_Http2Test):
client.wfile.flush() client.wfile.flush()
assert len(self.master.state.flows) == 1 assert len(self.master.state.flows) == 1
assert self.master.state.flows[0].response.headers['priority_exclusive'] == 'True'
assert self.master.state.flows[0].response.headers['priority_depends_on'] == '42424242'
assert self.master.state.flows[0].response.headers['priority_weight'] == '42'
def test_request_without_priority(self): resp = self.master.state.flows[0].response
client, h2_conn = self._setup_connection() assert resp.headers.get('priority_exclusive', None) == expected_priority[0]
assert resp.headers.get('priority_depends_on', None) == expected_priority[1]
self._send_request( assert resp.headers.get('priority_weight', None) == expected_priority[2]
client.wfile,
h2_conn,
headers=[
(':authority', "127.0.0.1:{}".format(self.server.server.address.port)),
(':method', 'GET'),
(':scheme', 'https'),
(':path', '/'),
],
)
done = False
while not done:
try:
raw = b''.join(http2.read_raw_frame(client.rfile))
events = h2_conn.receive_data(raw)
except exceptions.HttpException:
print(traceback.format_exc())
assert False
client.wfile.write(h2_conn.data_to_send())
client.wfile.flush()
for event in events:
if isinstance(event, h2.events.StreamEnded):
done = True
h2_conn.close_connection()
client.wfile.write(h2_conn.data_to_send())
client.wfile.flush()
assert len(self.master.state.flows) == 1
assert 'priority_exclusive' not in self.master.state.flows[0].response.headers
assert 'priority_depends_on' not in self.master.state.flows[0].response.headers
assert 'priority_weight' not in self.master.state.flows[0].response.headers
@requires_alpn @requires_alpn
class TestPriority(_Http2Test): class TestPriority(_Http2Test):
priority_data = None
@classmethod
def handle_server_event(cls, event, h2_conn, rfile, wfile):
if isinstance(event, h2.events.ConnectionTerminated):
return False
elif isinstance(event, h2.events.PriorityUpdated):
cls.priority_data = (event.exclusive, event.depends_on, event.weight)
elif isinstance(event, h2.events.RequestReceived):
import warnings
with warnings.catch_warnings():
# Ignore UnicodeWarning:
# h2/utilities.py:64: UnicodeWarning: Unicode equal comparison
# failed to convert both arguments to Unicode - interpreting
# them as being unequal.
# elif header[0] in (b'cookie', u'cookie') and len(header[1]) < 20:
warnings.simplefilter("ignore")
headers = [(':status', '200')]
h2_conn.send_headers(event.stream_id, headers)
h2_conn.end_stream(event.stream_id)
wfile.write(h2_conn.data_to_send())
wfile.flush()
return True
def test_priority(self):
client, h2_conn = self._setup_connection()
h2_conn.prioritize(1, exclusive=True, depends_on=0, weight=42)
client.wfile.write(h2_conn.data_to_send())
client.wfile.flush()
self._send_request(
client.wfile,
h2_conn,
headers=[
(':authority', "127.0.0.1:{}".format(self.server.server.address.port)),
(':method', 'GET'),
(':scheme', 'https'),
(':path', '/'),
],
)
done = False
while not done:
try:
raw = b''.join(http2.read_raw_frame(client.rfile))
events = h2_conn.receive_data(raw)
except exceptions.HttpException:
print(traceback.format_exc())
assert False
client.wfile.write(h2_conn.data_to_send())
client.wfile.flush()
for event in events:
if isinstance(event, h2.events.StreamEnded):
done = True
h2_conn.close_connection()
client.wfile.write(h2_conn.data_to_send())
client.wfile.flush()
assert len(self.master.state.flows) == 1
assert self.priority_data == (True, 0, 42)
@requires_alpn
class TestPriorityWithExistingStream(_Http2Test):
priority_data = []
@classmethod @classmethod
def handle_server_event(cls, event, h2_conn, rfile, wfile): def handle_server_event(cls, event, h2_conn, rfile, wfile):
@ -532,8 +432,6 @@ class TestPriorityWithExistingStream(_Http2Test):
elif isinstance(event, h2.events.PriorityUpdated): elif isinstance(event, h2.events.PriorityUpdated):
cls.priority_data.append((event.exclusive, event.depends_on, event.weight)) cls.priority_data.append((event.exclusive, event.depends_on, event.weight))
elif isinstance(event, h2.events.RequestReceived): elif isinstance(event, h2.events.RequestReceived):
assert not event.priority_updated
import warnings import warnings
with warnings.catch_warnings(): with warnings.catch_warnings():
# Ignore UnicodeWarning: # Ignore UnicodeWarning:
@ -546,17 +444,27 @@ class TestPriorityWithExistingStream(_Http2Test):
headers = [(':status', '200')] headers = [(':status', '200')]
h2_conn.send_headers(event.stream_id, headers) h2_conn.send_headers(event.stream_id, headers)
wfile.write(h2_conn.data_to_send())
wfile.flush()
elif isinstance(event, h2.events.StreamEnded):
h2_conn.end_stream(event.stream_id) h2_conn.end_stream(event.stream_id)
wfile.write(h2_conn.data_to_send()) wfile.write(h2_conn.data_to_send())
wfile.flush() wfile.flush()
return True return True
def test_priority_with_existing_stream(self): @pytest.mark.parametrize("prioritize_before", [True, False])
@pytest.mark.parametrize("http2_priority_enabled, priority, expected_priority", [
(True, (True, 42424242, 42), [(True, 42424242, 42)]),
(False, (True, 42424242, 42), []),
])
def test_priority(self, prioritize_before, http2_priority_enabled, priority, expected_priority):
self.config.options.http2_priority = http2_priority_enabled
self.__class__.priority_data = []
client, h2_conn = self._setup_connection() client, h2_conn = self._setup_connection()
if prioritize_before:
h2_conn.prioritize(1, exclusive=priority[0], depends_on=priority[1], weight=priority[2])
client.wfile.write(h2_conn.data_to_send())
client.wfile.flush()
self._send_request( self._send_request(
client.wfile, client.wfile,
h2_conn, h2_conn,
@ -566,13 +474,14 @@ class TestPriorityWithExistingStream(_Http2Test):
(':scheme', 'https'), (':scheme', 'https'),
(':path', '/'), (':path', '/'),
], ],
end_stream=False, end_stream=prioritize_before,
) )
h2_conn.prioritize(1, exclusive=True, depends_on=0, weight=42) if not prioritize_before:
h2_conn.end_stream(1) h2_conn.prioritize(1, exclusive=priority[0], depends_on=priority[1], weight=priority[2])
client.wfile.write(h2_conn.data_to_send()) h2_conn.end_stream(1)
client.wfile.flush() client.wfile.write(h2_conn.data_to_send())
client.wfile.flush()
done = False done = False
while not done: while not done:
@ -595,7 +504,7 @@ class TestPriorityWithExistingStream(_Http2Test):
client.wfile.flush() client.wfile.flush()
assert len(self.master.state.flows) == 1 assert len(self.master.state.flows) == 1
assert self.priority_data == [(True, 0, 42)] assert self.priority_data == expected_priority
@requires_alpn @requires_alpn