@@ -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