diff options
Diffstat (limited to 'modules/caddyhttp/app.go')
-rw-r--r-- | modules/caddyhttp/app.go | 232 |
1 files changed, 155 insertions, 77 deletions
diff --git a/modules/caddyhttp/app.go b/modules/caddyhttp/app.go index 5dbecf9b2..673ebcb8e 100644 --- a/modules/caddyhttp/app.go +++ b/modules/caddyhttp/app.go @@ -18,6 +18,7 @@ import ( "context" "crypto/tls" "fmt" + "maps" "net" "net/http" "strconv" @@ -203,15 +204,73 @@ func (app *App) Provision(ctx caddy.Context) error { } } + // if no protocols configured explicitly, enable all except h2c + if len(srv.Protocols) == 0 { + srv.Protocols = []string{"h1", "h2", "h3"} + } + + srvProtocolsUnique := map[string]struct{}{} + for _, srvProtocol := range srv.Protocols { + srvProtocolsUnique[srvProtocol] = struct{}{} + } + _, h1ok := srvProtocolsUnique["h1"] + _, h2ok := srvProtocolsUnique["h2"] + _, h2cok := srvProtocolsUnique["h2c"] + // the Go standard library does not let us serve only HTTP/2 using // http.Server; we would probably need to write our own server - if !srv.protocol("h1") && (srv.protocol("h2") || srv.protocol("h2c")) { + if !h1ok && (h2ok || h2cok) { return fmt.Errorf("server %s: cannot enable HTTP/2 or H2C without enabling HTTP/1.1; add h1 to protocols or remove h2/h2c", srvName) } - // if no protocols configured explicitly, enable all except h2c - if len(srv.Protocols) == 0 { - srv.Protocols = []string{"h1", "h2", "h3"} + if srv.ListenProtocols != nil { + if len(srv.ListenProtocols) != len(srv.Listen) { + return fmt.Errorf("server %s: listener protocols count does not match address count: %d != %d", + srvName, len(srv.ListenProtocols), len(srv.Listen)) + } + + for i, lnProtocols := range srv.ListenProtocols { + if lnProtocols != nil { + // populate empty listen protocols with server protocols + lnProtocolsDefault := false + var lnProtocolsInclude []string + srvProtocolsInclude := maps.Clone(srvProtocolsUnique) + + // keep existing listener protocols unless they are empty + for _, lnProtocol := range lnProtocols { + if lnProtocol == "" { + lnProtocolsDefault = true + } else { + lnProtocolsInclude = append(lnProtocolsInclude, lnProtocol) + delete(srvProtocolsInclude, lnProtocol) + } + } + + // append server protocols to listener protocols if any listener protocols were empty + if lnProtocolsDefault { + for _, srvProtocol := range srv.Protocols { + if _, ok := srvProtocolsInclude[srvProtocol]; ok { + lnProtocolsInclude = append(lnProtocolsInclude, srvProtocol) + } + } + } + + lnProtocolsIncludeUnique := map[string]struct{}{} + for _, lnProtocol := range lnProtocolsInclude { + lnProtocolsIncludeUnique[lnProtocol] = struct{}{} + } + _, h1ok := lnProtocolsIncludeUnique["h1"] + _, h2ok := lnProtocolsIncludeUnique["h2"] + _, h2cok := lnProtocolsIncludeUnique["h2c"] + + // check if any listener protocols contain h2 or h2c without h1 + if !h1ok && (h2ok || h2cok) { + return fmt.Errorf("server %s, listener %d: cannot enable HTTP/2 or H2C without enabling HTTP/1.1; add h1 to protocols or remove h2/h2c", srvName, i) + } + + srv.ListenProtocols[i] = lnProtocolsInclude + } + } } // if not explicitly configured by the user, disallow TLS @@ -344,7 +403,7 @@ func (app *App) Validate() error { // check that every address in the port range is unique to this server; // we do not use <= here because PortRangeSize() adds 1 to EndPort for us for i := uint(0); i < listenAddr.PortRangeSize(); i++ { - addr := caddy.JoinNetworkAddress(listenAddr.Network, listenAddr.Host, strconv.Itoa(int(listenAddr.StartPort+i))) + addr := caddy.JoinNetworkAddress(listenAddr.Network, listenAddr.Host, strconv.FormatUint(uint64(listenAddr.StartPort+i), 10)) if sn, ok := lnAddrs[addr]; ok { return fmt.Errorf("server %s: listener address repeated: %s (already claimed by server '%s')", srvName, addr, sn) } @@ -422,99 +481,118 @@ func (app *App) Start() error { srv.server.Handler = h2c.NewHandler(srv, h2server) } - for _, lnAddr := range srv.Listen { + for lnIndex, lnAddr := range srv.Listen { listenAddr, err := caddy.ParseNetworkAddress(lnAddr) if err != nil { return fmt.Errorf("%s: parsing listen address '%s': %v", srvName, lnAddr, err) } + srv.addresses = append(srv.addresses, listenAddr) + protocols := srv.Protocols + if srv.ListenProtocols != nil && srv.ListenProtocols[lnIndex] != nil { + protocols = srv.ListenProtocols[lnIndex] + } + + protocolsUnique := map[string]struct{}{} + for _, protocol := range protocols { + protocolsUnique[protocol] = struct{}{} + } + _, h1ok := protocolsUnique["h1"] + _, h2ok := protocolsUnique["h2"] + _, h2cok := protocolsUnique["h2c"] + _, h3ok := protocolsUnique["h3"] + for portOffset := uint(0); portOffset < listenAddr.PortRangeSize(); portOffset++ { - // create the listener for this socket hostport := listenAddr.JoinHostPort(portOffset) - lnAny, err := listenAddr.Listen(app.ctx, portOffset, net.ListenConfig{KeepAlive: time.Duration(srv.KeepAliveInterval)}) - if err != nil { - return fmt.Errorf("listening on %s: %v", listenAddr.At(portOffset), err) - } - ln := lnAny.(net.Listener) - - // wrap listener before TLS (up to the TLS placeholder wrapper) - var lnWrapperIdx int - for i, lnWrapper := range srv.listenerWrappers { - if _, ok := lnWrapper.(*tlsPlaceholderWrapper); ok { - lnWrapperIdx = i + 1 // mark the next wrapper's spot - break - } - ln = lnWrapper.WrapListener(ln) - } // enable TLS if there is a policy and if this is not the HTTP port useTLS := len(srv.TLSConnPolicies) > 0 && int(listenAddr.StartPort+portOffset) != app.httpPort() - if useTLS { - // create TLS listener - this enables and terminates TLS - ln = tls.NewListener(ln, tlsCfg) - - // enable HTTP/3 if configured - if srv.protocol("h3") { - // Can't serve HTTP/3 on the same socket as HTTP/1 and 2 because it uses - // a different transport mechanism... which is fine, but the OS doesn't - // differentiate between a SOCK_STREAM file and a SOCK_DGRAM file; they - // are still one file on the system. So even though "unixpacket" and - // "unixgram" are different network types just as "tcp" and "udp" are, - // the OS will not let us use the same file as both STREAM and DGRAM. - if len(srv.Protocols) > 1 && listenAddr.IsUnixNetwork() { - app.logger.Warn("HTTP/3 disabled because Unix can't multiplex STREAM and DGRAM on same socket", - zap.String("file", hostport)) - for i := range srv.Protocols { - if srv.Protocols[i] == "h3" { - srv.Protocols = append(srv.Protocols[:i], srv.Protocols[i+1:]...) - break - } - } - } else { - app.logger.Info("enabling HTTP/3 listener", zap.String("addr", hostport)) - if err := srv.serveHTTP3(listenAddr.At(portOffset), tlsCfg); err != nil { - return err - } - } + + // enable HTTP/3 if configured + if h3ok && useTLS { + app.logger.Info("enabling HTTP/3 listener", zap.String("addr", hostport)) + if err := srv.serveHTTP3(listenAddr.At(portOffset), tlsCfg); err != nil { + return err } } - // finish wrapping listener where we left off before TLS - for i := lnWrapperIdx; i < len(srv.listenerWrappers); i++ { - ln = srv.listenerWrappers[i].WrapListener(ln) + if h3ok && !useTLS { + // Can only serve h3 with TLS enabled + app.logger.Warn("HTTP/3 skipped because it requires TLS", + zap.String("network", listenAddr.Network), + zap.String("addr", hostport)) } - // handle http2 if use tls listener wrapper - if useTLS { - http2lnWrapper := &http2Listener{ - Listener: ln, - server: srv.server, - h2server: h2server, + if h1ok || h2ok && useTLS || h2cok { + // create the listener for this socket + lnAny, err := listenAddr.Listen(app.ctx, portOffset, net.ListenConfig{KeepAlive: time.Duration(srv.KeepAliveInterval)}) + if err != nil { + return fmt.Errorf("listening on %s: %v", listenAddr.At(portOffset), err) + } + ln, ok := lnAny.(net.Listener) + if !ok { + return fmt.Errorf("network '%s' cannot handle HTTP/1 or HTTP/2 connections", listenAddr.Network) } - srv.h2listeners = append(srv.h2listeners, http2lnWrapper) - ln = http2lnWrapper - } - // if binding to port 0, the OS chooses a port for us; - // but the user won't know the port unless we print it - if !listenAddr.IsUnixNetwork() && listenAddr.StartPort == 0 && listenAddr.EndPort == 0 { - app.logger.Info("port 0 listener", - zap.String("input_address", lnAddr), - zap.String("actual_address", ln.Addr().String())) - } + if useTLS { + // create TLS listener - this enables and terminates TLS + ln = tls.NewListener(ln, tlsCfg) + } + + // wrap listener before TLS (up to the TLS placeholder wrapper) + var lnWrapperIdx int + for i, lnWrapper := range srv.listenerWrappers { + if _, ok := lnWrapper.(*tlsPlaceholderWrapper); ok { + lnWrapperIdx = i + 1 // mark the next wrapper's spot + break + } + ln = lnWrapper.WrapListener(ln) + } + + // finish wrapping listener where we left off before TLS + for i := lnWrapperIdx; i < len(srv.listenerWrappers); i++ { + ln = srv.listenerWrappers[i].WrapListener(ln) + } + + // handle http2 if use tls listener wrapper + if h2ok { + http2lnWrapper := &http2Listener{ + Listener: ln, + server: srv.server, + h2server: h2server, + } + srv.h2listeners = append(srv.h2listeners, http2lnWrapper) + ln = http2lnWrapper + } + + // if binding to port 0, the OS chooses a port for us; + // but the user won't know the port unless we print it + if !listenAddr.IsUnixNetwork() && !listenAddr.IsFdNetwork() && listenAddr.StartPort == 0 && listenAddr.EndPort == 0 { + app.logger.Info("port 0 listener", + zap.String("input_address", lnAddr), + zap.String("actual_address", ln.Addr().String())) + } - app.logger.Debug("starting server loop", - zap.String("address", ln.Addr().String()), - zap.Bool("tls", useTLS), - zap.Bool("http3", srv.h3server != nil)) + app.logger.Debug("starting server loop", + zap.String("address", ln.Addr().String()), + zap.Bool("tls", useTLS), + zap.Bool("http3", srv.h3server != nil)) - srv.listeners = append(srv.listeners, ln) + srv.listeners = append(srv.listeners, ln) + + // enable HTTP/1 if configured + if h1ok { + //nolint:errcheck + go srv.server.Serve(ln) + } + } - // enable HTTP/1 if configured - if srv.protocol("h1") { - //nolint:errcheck - go srv.server.Serve(ln) + if h2ok && !useTLS { + // Can only serve h2 with TLS enabled + app.logger.Warn("HTTP/2 skipped because it requires TLS", + zap.String("network", listenAddr.Network), + zap.String("addr", hostport)) } } } |