@@ -508,6 +508,93 @@ def handler(reader, writer):
508508 asyncio .ensure_future (stream_handler (reader , writer , ** vars (self ), ** args ))
509509 return aioquic .asyncio .serve (self .host_name , self .port , configuration = self .quicserver , stream_handler = handler )
510510
511+ class ProxyH3 (ProxyQUIC ):
512+ def get_stream (self , conn , stream_id ):
513+ remote_addr = conn ._quic ._network_paths [0 ].addr
514+ reader = asyncio .StreamReader ()
515+ class StreamWriter ():
516+ def __init__ (self ):
517+ self .closed = False
518+ self .headers = asyncio .get_event_loop ().create_future ()
519+ def get_extra_info (self , key ):
520+ return dict (peername = remote_addr , sockname = remote_addr ).get (key )
521+ def write (self , data ):
522+ conn .http .send_data (stream_id , data , False )
523+ conn .transmit ()
524+ async def drain (self ):
525+ conn .transmit ()
526+ def is_closing (self ):
527+ return self .closed
528+ def close (self ):
529+ if not self .closed :
530+ conn .http .send_data (stream_id , b'' , True )
531+ conn .transmit ()
532+ conn .close_stream (stream_id )
533+ self .closed = True
534+ def send_headers (self , headers ):
535+ conn .http .send_headers (stream_id , [(i .encode (), j .encode ()) for i , j in headers ])
536+ conn .transmit ()
537+ return reader , StreamWriter ()
538+ def get_protocol (self , server_side = False , handler = None ):
539+ import aioquic .asyncio , aioquic .quic .events , aioquic .h3 .connection , aioquic .h3 .events
540+ class Protocol (aioquic .asyncio .QuicConnectionProtocol ):
541+ def __init__ (s , * args , ** kw ):
542+ super ().__init__ (* args , ** kw )
543+ s .http = aioquic .h3 .connection .H3Connection (s ._quic )
544+ s .streams = {}
545+ def quic_event_received (s , event ):
546+ if not server_side :
547+ if isinstance (event , aioquic .quic .events .HandshakeCompleted ):
548+ self .handshake .set_result (s )
549+ elif isinstance (event , aioquic .quic .events .ConnectionTerminated ):
550+ self .handshake = None
551+ self .quic_egress_acm = None
552+ if s .http is not None :
553+ for http_event in s .http .handle_event (event ):
554+ s .http_event_received (http_event )
555+ def http_event_received (s , event ):
556+ if isinstance (event , aioquic .h3 .events .HeadersReceived ):
557+ if event .stream_id not in s .streams and server_side :
558+ reader , writer = s .create_stream (event .stream_id )
559+ writer .headers .set_result (event .headers )
560+ asyncio .ensure_future (handler (reader , writer ))
561+ elif isinstance (event , aioquic .h3 .events .DataReceived ) and event .stream_id in s .streams :
562+ reader , writer = s .streams [event .stream_id ]
563+ if event .data :
564+ reader .feed_data (event .data )
565+ if event .stream_ended :
566+ reader .feed_eof ()
567+ s .close_stream (event .stream_id )
568+ def create_stream (s , stream_id = None ):
569+ if stream_id is None :
570+ stream_id = s ._quic .get_next_available_stream_id (False )
571+ s ._quic ._get_or_create_stream_for_send (stream_id )
572+ reader , writer = self .get_stream (s , stream_id )
573+ s .streams [stream_id ] = (reader , writer )
574+ return reader , writer
575+ def close_stream (s , stream_id ):
576+ if stream_id in s .streams :
577+ reader , writer = s .streams [stream_id ]
578+ if reader .at_eof () and writer .is_closing ():
579+ s .streams .pop (stream_id )
580+ return Protocol
581+ async def wait_h3_connection (self ):
582+ if self .handshake is not None :
583+ if not self .handshake .done ():
584+ await self .handshake
585+ else :
586+ import aioquic .asyncio
587+ self .handshake = asyncio .get_event_loop ().create_future ()
588+ self .quic_egress_acm = aioquic .asyncio .connect (self .host_name , self .port , create_protocol = self .get_protocol (), configuration = self .quicclient )
589+ conn = await self .quic_egress_acm .__aenter__ ()
590+ await self .handshake
591+ async def wait_open_connection (self , * args ):
592+ await self .wait_h3_connection ()
593+ return self .handshake .result ().create_stream ()
594+ def start_server (self , args , stream_handler = stream_handler ):
595+ import aioquic .asyncio
596+ return aioquic .asyncio .serve (self .host_name , self .port , configuration = self .quicserver , create_protocol = self .get_protocol (True , functools .partial (stream_handler , ** vars (self ), ** args )))
597+
511598class ProxySSH (ProxySimple ):
512599 def __init__ (self , ** kw ):
513600 super ().__init__ (** kw )
@@ -670,6 +757,7 @@ def proxy_by_uri(uri, jump):
670757 url = urllib .parse .urlparse ('s://' + uri )
671758 rawprotos = [i .lower () for i in scheme .split ('+' )]
672759 err_str , protos = proto .get_protos (rawprotos )
760+ protonames = [i .name for i in protos ]
673761 if err_str :
674762 raise argparse .ArgumentTypeError (err_str )
675763 if 'ssl' in rawprotos or 'secure' in rawprotos :
@@ -683,7 +771,7 @@ def proxy_by_uri(uri, jump):
683771 sslcontexts .append (sslclient )
684772 else :
685773 sslserver = sslclient = None
686- if 'quic' in rawprotos :
774+ if 'quic' in rawprotos or 'h3' in protonames :
687775 try :
688776 import ssl , aioquic .quic .configuration
689777 except Exception :
@@ -698,7 +786,6 @@ def proxy_by_uri(uri, jump):
698786 import h2
699787 except Exception :
700788 raise Exception ('Missing library: "pip3 install h2"' )
701- protonames = [i .name for i in protos ]
702789 urlpath , _ , plugins = url .path .partition (',' )
703790 urlpath , _ , lbind = urlpath .partition ('@' )
704791 plugins = plugins .split (',' ) if plugins else None
@@ -740,6 +827,8 @@ def proxy_by_uri(uri, jump):
740827 host_name = host_name , port = port , unix = not loc , lbind = lbind , sslclient = sslclient , sslserver = sslserver )
741828 if 'quic' in rawprotos :
742829 proxy = ProxyQUIC (quicserver , quicclient , ** params )
830+ elif 'h3' in protonames :
831+ proxy = ProxyH3 (quicserver , quicclient , ** params )
743832 elif 'h2' in protonames :
744833 proxy = ProxyH2 (** params )
745834 elif 'ssh' in protonames :
0 commit comments