def get_connection(self, event: GetHttpConnection, *, reuse: bool = True) -> layer.CommandGenerator[None]: # Do we already have a connection we can re-use? if reuse: for connection in self.connections: # see "tricky multiplexing edge case" in make_http_connection for an explanation conn_is_pending_or_h2 = (connection.alpn == b"h2" or connection in self.waiting_for_establishment) h2_to_h1 = self.context.client.alpn == b"h2" and not conn_is_pending_or_h2 connection_suitable = ( event.connection_spec_matches(connection) and not h2_to_h1) if connection_suitable: if connection in self.waiting_for_establishment: self.waiting_for_establishment[connection].append( event) else: stream = self.command_sources.pop(event) yield from self.event_to_child( stream, GetHttpConnectionReply(event, (connection, None))) return can_use_context_connection = ( self.context.server not in self.connections and self.context.server.connected and event.connection_spec_matches(self.context.server)) context = self.context.fork() stack = tunnel.LayerStack() if not can_use_context_connection: context.server = Server(event.address) if event.tls: context.server.sni = event.address[0].encode() if event.via: assert event.via.scheme in ("http", "https") http_proxy = Server(event.via.address) if event.via.scheme == "https": http_proxy.alpn_offers = tls.HTTP_ALPNS http_proxy.sni = event.via.address[0].encode() stack /= tls.ServerTLSLayer(context, http_proxy) send_connect = not (self.mode == HTTPMode.upstream and not event.tls) stack /= _upstream_proxy.HttpUpstreamProxy( context, http_proxy, send_connect) if event.tls: stack /= tls.ServerTLSLayer(context) stack /= HttpClient(context) self.connections[context.server] = stack[0] self.waiting_for_establishment[context.server].append(event) yield from self.event_to_child(stack[0], events.Start())
def test_tunnel_handshake_start(tctx: Context, success): server = Server(("proxy", 1234)) server.state = ConnectionState.OPEN tl = TTunnelLayer(tctx, server, tctx.server) tl.child_layer = TChildLayer(tctx) assert repr(tl) playbook = Playbook(tl, logs=True) (playbook << SendData(server, b"handshake-hello") >> DataReceived( tctx.client, b"client-hello") >> DataReceived( server, b"handshake-" + success.encode()) << SendData( server, b"handshake-" + success.encode())) if success == "success": playbook << Log("Got start. Server state: OPEN") else: playbook << CloseConnection(server) playbook << Log("Got start. Server state: CLOSED") playbook << SendData(tctx.client, b"client-hello-reply") if success == "success": playbook >> DataReceived(server, b"tunneled-server-hello") playbook << SendData(server, b"tunneled-server-hello-reply") assert playbook
def handle_connect_upstream(self): assert self.context.server.via.scheme in ("http", "https") http_proxy = Server(self.context.server.via.address) stack = tunnel.LayerStack() if self.context.server.via.scheme == "https": http_proxy.sni = self.context.server.via.address[0].encode() stack /= tls.ServerTLSLayer(self.context, http_proxy) stack /= _upstream_proxy.HttpUpstreamProxy(self.context, http_proxy, True) self.child_layer = stack[0] yield from self.handle_connect_finish()
def test_tunnel_handshake_command(tctx: Context, success): server = Server(("proxy", 1234)) tl = TTunnelLayer(tctx, server, tctx.server) tl.child_layer = TChildLayer(tctx) playbook = Playbook(tl, logs=True) (playbook << Log("Got start. Server state: CLOSED") >> DataReceived( tctx.client, b"client-hello") << SendData( tctx.client, b"client-hello-reply") >> DataReceived( tctx.client, b"open") << OpenConnection(server) >> reply(None) << SendData(server, b"handshake-hello") >> DataReceived( server, b"handshake-" + success.encode()) << SendData( server, b"handshake-" + success.encode())) if success == "success": assert (playbook << Log(f"Opened: err=None. Server state: OPEN") >> DataReceived(server, b"tunneled-server-hello") << SendData( server, b"tunneled-server-hello-reply") >> ConnectionClosed(tctx.client) << Log("Got client close.") << CloseConnection(tctx.client)) assert tl.tunnel_state is tunnel.TunnelState.OPEN assert (playbook >> ConnectionClosed(server) << Log("Got server close.") << CloseConnection(server)) assert tl.tunnel_state is tunnel.TunnelState.CLOSED else: assert (playbook << CloseConnection(server) << Log("Opened: err='handshake error'. Server state: CLOSED")) assert tl.tunnel_state is tunnel.TunnelState.CLOSED
def test_disconnect_during_handshake_command(tctx: Context, disconnect): server = Server(("proxy", 1234)) tl = TTunnelLayer(tctx, server, tctx.server) tl.child_layer = TChildLayer(tctx) playbook = Playbook(tl, logs=True) assert (playbook << Log("Got start. Server state: CLOSED") >> DataReceived( tctx.client, b"client-hello") << SendData( tctx.client, b"client-hello-reply") >> DataReceived( tctx.client, b"open") << OpenConnection(server) >> reply(None) << SendData(server, b"handshake-hello")) if disconnect == "client": assert ( playbook >> ConnectionClosed(tctx.client) >> ConnectionClosed( server ) # proxyserver will cancel all other connections as well. << CloseConnection(server) << Log("Opened: err='connection closed without notice'. Server state: CLOSED" ) << Log("Got client close.") << CloseConnection(tctx.client)) else: assert (playbook >> ConnectionClosed(server) << CloseConnection( server ) << Log( "Opened: err='connection closed without notice'. Server state: CLOSED" ))
def test_disconnect_during_handshake_start(tctx: Context, disconnect): server = Server(("proxy", 1234)) server.state = ConnectionState.OPEN tl = TTunnelLayer(tctx, server, tctx.server) tl.child_layer = TChildLayer(tctx) playbook = Playbook(tl, logs=True) assert (playbook << SendData(server, b"handshake-hello")) if disconnect == "client": assert (playbook >> ConnectionClosed(tctx.client) >> ConnectionClosed( server) # proxyserver will cancel all other connections as well. << CloseConnection(server) << Log("Got start. Server state: CLOSED") << Log("Got client close.") << CloseConnection(tctx.client)) else: assert (playbook >> ConnectionClosed(server) << CloseConnection(server) << Log("Got start. Server state: CLOSED"))
def test_tunnel_openconnection_error(tctx: Context): server = Server(("proxy", 1234)) tl = TTunnelLayer(tctx, server, tctx.server) tl.child_layer = TChildLayer(tctx) playbook = Playbook(tl, logs=True) assert (playbook << Log("Got start. Server state: CLOSED") >> DataReceived( tctx.client, b"open") << OpenConnection(server)) assert tl.tunnel_state is tunnel.TunnelState.ESTABLISHING assert (playbook >> reply("IPoAC packet dropped.") << Log("Opened: err='IPoAC packet dropped.'. Server state: CLOSED")) assert tl.tunnel_state is tunnel.TunnelState.CLOSED
def test_tunnel_default_impls(tctx: Context): """ Some tunnels don't need certain features, so the default behaviour should be to be transparent. """ server = Server(None) server.state = ConnectionState.OPEN tl = tunnel.TunnelLayer(tctx, server, tctx.server) tl.child_layer = TChildLayer(tctx) playbook = Playbook(tl, logs=True) assert (playbook << Log("Got start. Server state: OPEN") >> DataReceived( server, b"server-hello") << SendData(server, b"server-hello-reply")) assert tl.tunnel_state is tunnel.TunnelState.OPEN assert (playbook >> ConnectionClosed(server) << Log("Got server close.") << CloseConnection(server)) assert tl.tunnel_state is tunnel.TunnelState.CLOSED assert ( playbook >> DataReceived( tctx.client, b"open") << OpenConnection(server) >> reply(None) << Log("Opened: err=None. Server state: OPEN") >> DataReceived( server, b"half-close") << CloseConnection(server, half_close=True))
def __init__(self, flow: http.HTTPFlow, options: Options) -> None: client = flow.client_conn.copy() client.state = ConnectionState.OPEN context = Context(client, options) context.server = Server((flow.request.host, flow.request.port)) context.server.tls = flow.request.scheme == "https" if options.mode.startswith("upstream:"): context.server.via = server_spec.parse_with_mode(options.mode)[1] super().__init__(context) self.layer = layers.HttpLayer(context, HTTPMode.transparent) self.layer.connections[client] = MockServer(flow, context.fork()) self.flow = flow self.done = asyncio.Event()