diff --git a/example/main.go b/example/main.go index 9f53b5f9d..d29097de4 100644 --- a/example/main.go +++ b/example/main.go @@ -144,6 +144,7 @@ func main() { flag.Var(&bs, "bind", "bind to") www := flag.String("www", "", "www data") tcp := flag.Bool("tcp", false, "also listen on TCP") + customAltSvcPort := flag.Uint("customAltSvcPort", 0, "use custom Alt-Svc header port value") enableQlog := flag.Bool("qlog", false, "output a qlog (in the same directory)") flag.Parse() @@ -182,7 +183,12 @@ func main() { var err error if *tcp { certFile, keyFile := testdata.GetCertificatePaths() - err = http3.ListenAndServe(bCap, certFile, keyFile, handler) + if *customAltSvcPort != 0 { + logger.Infof("using customAltSvcPort = %v", *customAltSvcPort) + err = http3.ListenAndServeWithCustomAltSvcPort(bCap, certFile, keyFile, handler, uint32(*customAltSvcPort)) + } else { + err = http3.ListenAndServe(bCap, certFile, keyFile, handler) + } } else { server := http3.Server{ Server: &http.Server{Handler: handler, Addr: bCap}, diff --git a/http3/server.go b/http3/server.go index b798abd42..df3584cc1 100644 --- a/http3/server.go +++ b/http3/server.go @@ -92,7 +92,8 @@ type Server struct { // See https://www.ietf.org/archive/id/draft-schinazi-masque-h3-datagram-02.html. EnableDatagrams bool - port uint32 // used atomically + port uint32 // used atomically + customAltSvcPort uint32 // custom port used for Alt-Svc response header mutex sync.Mutex listeners map[*quic.EarlyListener]struct{} @@ -439,16 +440,22 @@ func (s *Server) SetQuicHeaders(hdr http.Header) error { port := atomic.LoadUint32(&s.port) if port == 0 { - // Extract port from s.Server.Addr - _, portStr, err := net.SplitHostPort(s.Server.Addr) - if err != nil { - return err + if s.customAltSvcPort != 0 { + // Use customAltSvcPort if set + port = s.customAltSvcPort + } else { + // Extract port from s.Server.Addr + _, portStr, err := net.SplitHostPort(s.Server.Addr) + if err != nil { + return err + } + portInt, err := net.LookupPort("tcp", portStr) + if err != nil { + return err + } + port = uint32(portInt) } - portInt, err := net.LookupPort("tcp", portStr) - if err != nil { - return err - } - port = uint32(portInt) + atomic.StoreUint32(&s.port, port) } @@ -486,6 +493,17 @@ func ListenAndServeQUIC(addr, certFile, keyFile string, handler http.Handler) er // http.DefaultServeMux is used when handler is nil. // The correct Alt-Svc headers for QUIC are set. func ListenAndServe(addr, certFile, keyFile string, handler http.Handler) error { + return ListenAndServeWithCustomAltSvcPort(addr, certFile, keyFile, handler, 0) +} + +// ListenAndServeWithCustomAltSvcPort listens on the given network address for both, TLS and QUIC +// connetions in parallel. It returns if one of the two returns an error. +// http.DefaultServeMux is used when handler is nil. +// The correct Alt-Svc headers for QUIC are set. +// customAltSvcPort is used to override the default port value in the Alt-Svc response header. +// This is useful when a Layer 4 firewall is redirecting UDP traffic and clients must use +// a port different from the port the QUIC server itself is listening on. +func ListenAndServeWithCustomAltSvcPort(addr, certFile, keyFile string, handler http.Handler, customAltSvcPort uint32) error { // Load certs var err error certs := make([]tls.Certificate, 1) @@ -530,7 +548,8 @@ func ListenAndServe(addr, certFile, keyFile string, handler http.Handler) error } quicServer := &Server{ - Server: httpServer, + Server: httpServer, + customAltSvcPort: customAltSvcPort, } if handler == nil {