[quic] fix next layer handling

This commit is contained in:
Manuel Meitinger 2022-11-07 03:49:32 +01:00
parent 201f03082a
commit 97ca30ce6f
6 changed files with 35 additions and 21 deletions

View File

@ -314,15 +314,21 @@ class NextLayer:
if scheme in ("udp", "dtls"):
return layers.UDPLayer(context)
elif scheme == "http3":
return layers.HttpLayer(context, HTTPMode.transparent)
if isinstance(context.layers[-1], layers.ClientQuicLayer):
return layers.HttpLayer(context, HTTPMode.transparent)
else:
return layers.ClientQuicLayer(context)
elif scheme == "quic":
# if the client supports QUIC, we use QUIC raw layer,
# otherwise we only use the QUIC datagram only
return (
layers.RawQuicLayer(context)
if isinstance(context.layers[-1], layers.ClientQuicLayer) else
layers.UDPLayer(context)
)
if isinstance(context.layers[-1], layers.ClientQuicLayer):
# the client supports QUIC, use raw layer
return layers.RawQuicLayer(context)
elif data_client:
# we have received data, which was not a handshake, use UDP
# on the client, and send datagrams over QUIC to the server
return layers.UDPLayer(context)
else:
# wait for client data to make a decision
return None
elif scheme == "dns":
return layers.DNSLayer(context)
else:

View File

@ -5,6 +5,7 @@ from pathlib import Path
import ssl
from typing import Any, Optional, TypedDict
from aioquic.h3.connection import H3_ALPN
from aioquic.tls import CipherSuite
from OpenSSL import SSL, crypto
from mitmproxy import certs, ctx, exceptions, connection, tls
@ -360,7 +361,8 @@ class TlsConfig:
if client.alpn_offers:
server.alpn_offers = tuple(client.alpn_offers)
else:
server.alpn_offers = []
# aioquic fails if no ALPN is offered, so use H3
server.alpn_offers = tuple(alpn.encode("ascii") for alpn in H3_ALPN)
if not server.cipher_list and ctx.options.ciphers_server:
server.cipher_list = ctx.options.ciphers_server.split(":")

View File

@ -7,7 +7,7 @@ from typing import Optional
from mitmproxy import connection
from mitmproxy.proxy import commands, events, layer
from mitmproxy.proxy.commands import StartHook
from mitmproxy.proxy.layers import dns, quic, tls, udp
from mitmproxy.proxy.layers import quic, tls
from mitmproxy.proxy.mode_specs import ReverseMode
from mitmproxy.proxy.utils import expect
@ -68,12 +68,8 @@ class ReverseProxy(DestinationKnown):
if not self.context.options.keep_host_header:
self.context.server.sni = spec.address[0]
self.child_layer = tls.ServerTLSLayer(self.context)
elif spec.scheme == "udp":
self.child_layer = udp.UDPLayer(self.context)
elif spec.scheme == "http" or spec.scheme == "tcp":
elif spec.scheme in ("tcp", "http", "udp", "dns"):
self.child_layer = layer.NextLayer(self.context)
elif spec.scheme == "dns":
self.child_layer = dns.DNSLayer(self.context)
else:
raise AssertionError(spec.scheme) # pragma: no cover

View File

@ -1057,8 +1057,8 @@ class ClientQuicLayer(QuicLayer):
return False, f"Cannot parse ClientHello: {str(e)} ({data.hex()})"
# copy the client hello information
self.context.client.sni = client_hello.sni
self.context.client.alpn_offers = client_hello.alpn_protocols
self.conn.sni = client_hello.sni
self.conn.alpn_offers = client_hello.alpn_protocols
# check with addons what we shall do
tls_clienthello = ClientHelloData(self.context, client_hello)
@ -1068,7 +1068,7 @@ class ClientQuicLayer(QuicLayer):
if tls_clienthello.ignore_connection:
self.conn = self.tunnel_connection = connection.Client(
("ignore-conn", 0), ("ignore-conn", 0), time.time(),
transport_protocol="udp", proxy_mode=self.context.client.proxy_mode
transport_protocol="udp"
)
# we need to replace the server layer as well, if there is one

View File

@ -289,7 +289,13 @@ class TestNextLayer:
layers.modes.ReverseProxy(ctx),
layers.ServerQuicLayer(ctx),
]
assert isinstance(nl._next_layer(ctx, b"", b""), layers.UDPLayer)
assert nl._next_layer(ctx, b"", b"") is None
assert isinstance(nl._next_layer(ctx, b"notahandshake", b""), layers.UDPLayer)
ctx.layers = [
layers.modes.ReverseProxy(ctx),
layers.ServerQuicLayer(ctx),
]
assert isinstance(nl._next_layer(ctx, quic_client_hello, b""), layers.ClientQuicLayer)
def test_next_layer_reverse_http3_mode(self):
nl = NextLayer()
@ -301,8 +307,8 @@ class TestNextLayer:
ctx.layers = [
layers.modes.ReverseProxy(ctx),
layers.ServerQuicLayer(ctx),
layers.ClientQuicLayer(ctx),
]
assert isinstance(nl._next_layer(ctx, b"notahandshakebutignore", b""), layers.ClientQuicLayer)
decision = nl._next_layer(ctx, b"", b"")
assert isinstance(decision, layers.HttpLayer)
assert decision.mode is HTTPMode.transparent

View File

@ -161,6 +161,8 @@ def test_reverse_dns(tctx):
assert (
Playbook(modes.ReverseProxy(tctx), hooks=False)
>> DataReceived(tctx.client, tflow.tdnsreq().packed)
<< NextLayerHook(Placeholder(NextLayer))
>> reply_next_layer(layers.DNSLayer)
<< layers.dns.DnsRequestHook(f)
>> reply(None)
<< OpenConnection(server)
@ -202,9 +204,11 @@ def test_udp(tctx: Context):
Playbook(modes.ReverseProxy(tctx))
<< OpenConnection(tctx.server)
>> reply(None)
>> DataReceived(tctx.client, b"test-input")
<< NextLayerHook(Placeholder(NextLayer))
>> reply_next_layer(layers.UDPLayer)
<< udp.UdpStartHook(flow)
>> reply()
>> DataReceived(tctx.client, b"test-input")
<< udp.UdpMessageHook(flow)
>> reply()
<< SendData(tctx.server, b"test-input")