caddy/modules/caddyhttp/http2listener.go
2025-05-23 10:15:30 +08:00

88 lines
2.0 KiB
Go

package caddyhttp
import (
"crypto/tls"
"io"
"net"
"go.uber.org/zap"
"golang.org/x/net/http2"
)
type connectionStater interface {
ConnectionState() tls.ConnectionState
}
// http2Listener wraps the listener to solve the following problems:
// 1. prevent genuine h2c connections from succeeding if h2c is not enabled
// and the connection doesn't implment connectionStater or the resulting NegotiatedProtocol
// isn't http2.
// This does allow a connection to pass as tls enabled even if it's not, listener wrappers
// can do this.
// 2. After wrapping the connection doesn't implement connectionStater, emit a warning so that listener
// wrapper authors will hopefully implement it.
type http2Listener struct {
useTLS bool
net.Listener
logger *zap.Logger
}
func (h *http2Listener) Accept() (net.Conn, error) {
conn, err := h.Listener.Accept()
if err != nil {
return nil, err
}
if h.useTLS {
// emit a warning
if _, ok := conn.(connectionStater); !ok {
h.logger.Warn("tls is enabled, but listener wrapper returns a connection that doesn't implement connectionStater")
}
return &http2Conn{
idx: len(http2.ClientPreface),
Conn: conn,
}, nil
}
if _, ok := conn.(connectionStater); ok {
h.logger.Warn("tls is disabled, but listener wrapper returns a connection that implements connectionStater")
return &http2Conn{
idx: len(http2.ClientPreface),
Conn: conn,
}, nil
}
return &http2Conn{
Conn: conn,
}, nil
}
type http2Conn struct {
// check h2 preface if it's smaller that the preface
idx int
// log if one such connection is detected
logger *zap.Logger
net.Conn
}
func (c *http2Conn) Read(p []byte) (int, error) {
if c.idx >= len(http2.ClientPreface) {
return c.Conn.Read(p)
}
n, err := c.Conn.Read(p)
for i := range n {
// mismatch
if p[i] != http2.ClientPreface[c.idx] {
c.idx = len(http2.ClientPreface)
return n, err
}
c.idx++
if c.idx == len(http2.ClientPreface) {
c.logger.Warn("h2c connection detected, but h2c is not enabled")
_ = c.Conn.Close()
return 0, io.EOF
}
}
return n, err
}