transparent mode: re-use client SNI when connecting upstream (#4680)

This commit is contained in:
Maximilian Hils 2021-07-15 11:33:20 +02:00 committed by GitHub
parent a78069f907
commit 18ca5a6369
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
3 changed files with 88 additions and 3 deletions

View file

@ -710,8 +710,22 @@ class HttpLayer(layer.Layer):
yield from self.event_to_child(self.streams[stream_id], event)
elif isinstance(event, events.ConnectionEvent):
if event.connection == self.context.server and self.context.server not in self.connections:
# We didn't do anything with this connection yet, now the peer has closed it - let's close it too!
yield commands.CloseConnection(event.connection)
# We didn't do anything with this connection yet, now the peer is doing something.
if isinstance(event, events.ConnectionClosed):
# The peer has closed it - let's close it too!
yield commands.CloseConnection(event.connection)
elif isinstance(event, events.DataReceived):
# The peer has sent data. This can happen with HTTP/2 servers that already send a settings frame.
child_layer: HttpConnection
if self.context.server.alpn == b"h2":
child_layer = Http2Client(self.context.fork())
else:
child_layer = Http1Client(self.context.fork())
self.connections[self.context.server] = child_layer
yield from self.event_to_child(child_layer, events.Start())
yield from self.event_to_child(child_layer, event)
else:
raise AssertionError(f"Unexpected event: {event}")
else:
handler = self.connections[event.connection]
yield from self.event_to_child(handler, event)
@ -811,7 +825,12 @@ class HttpLayer(layer.Layer):
send_connect = event.tls or self.mode != HTTPMode.upstream
stack /= _upstream_proxy.HttpUpstreamProxy.make(context, send_connect)
if event.tls:
context.server.sni = event.address[0]
# Assume that we are in transparent mode and lazily did not open a connection yet.
# We don't want the IP (which is the address) as the upstream SNI, but the client's SNI instead.
if self.mode == HTTPMode.transparent and event.address == self.context.server.address:
context.server.sni = self.context.client.sni or event.address[0]
else:
context.server.sni = event.address[0]
stack /= tls.ServerTLSLayer(context)
stack /= HttpClient(context)

View file

@ -1174,3 +1174,35 @@ def test_reuse_error(tctx):
)
assert b"502 Bad Gateway" in error_html()
assert b"tls verify failed" in error_html()
def test_transparent_sni(tctx):
"""Test that we keep the SNI in lazy transparent mode."""
tctx.client.sni = "example.com"
tctx.server.address = ("192.0.2.42", 443)
tctx.server.tls = True
flow = Placeholder(HTTPFlow)
server = Placeholder(Server)
assert (
Playbook(http.HttpLayer(tctx, HTTPMode.transparent))
>> DataReceived(tctx.client, b"GET / HTTP/1.1\r\n\r\n")
<< http.HttpRequestHeadersHook(flow)
>> reply()
<< http.HttpRequestHook(flow)
>> reply()
<< OpenConnection(server)
)
assert server().address == ("192.0.2.42", 443)
assert server().sni == "example.com"
def test_original_server_disconnects(tctx):
"""Test that we correctly handle the case where the initial server conn is just closed."""
tctx.server.state = ConnectionState.OPEN
assert (
Playbook(http.HttpLayer(tctx, HTTPMode.transparent))
>> ConnectionClosed(tctx.server)
<< CloseConnection(tctx.server)
)

View file

@ -693,3 +693,37 @@ class TestClient:
>> DataReceived(tctx.server, frame_factory.build_data_frame(b"foo").serialize())
<< SendData(tctx.server, frame_factory.build_rst_stream_frame(1, ErrorCodes.STREAM_CLOSED).serialize())
) # important: no ResponseData event here!
def test_early_server_data(tctx):
playbook, cff = start_h2_client(tctx)
sff = FrameFactory()
tctx.server.address = ("example.com", 80)
tctx.server.state = ConnectionState.OPEN
tctx.server.alpn = b"h2"
flow = Placeholder(HTTPFlow)
server1 = Placeholder(bytes)
server2 = Placeholder(bytes)
assert (
playbook
>> DataReceived(tctx.client,
cff.build_headers_frame(example_request_headers, flags=["END_STREAM"]).serialize())
<< http.HttpRequestHeadersHook(flow)
>> reply()
<< (h := http.HttpRequestHook(flow))
# Surprise! We get data from the server before the request hook finishes.
>> DataReceived(tctx.server, sff.build_settings_frame({}).serialize())
<< SendData(tctx.server, server1)
# Request hook finishes...
>> reply(to=h)
<< SendData(tctx.server, server2)
)
assert [type(x) for x in decode_frames(server1())] == [
hyperframe.frame.SettingsFrame,
hyperframe.frame.SettingsFrame,
]
assert [type(x) for x in decode_frames(server2())] == [
hyperframe.frame.HeadersFrame,
]