Support PROXY protocol v1/v2 on RTMP, RTMPS, RTSP, and RTSPS TCP listeners so real client IPs are visible when running behind L4 proxies (nginx stream, HAProxy, AWS NLB). --------- Co-authored-by: aler9 <46489434+aler9@users.noreply.github.com>
206 lines
4.0 KiB
Go
206 lines
4.0 KiB
Go
// Package httpp contains HTTP utilities.
|
|
package httpp
|
|
|
|
import (
|
|
"crypto/tls"
|
|
"fmt"
|
|
"log"
|
|
"net"
|
|
"net/http"
|
|
"os"
|
|
"strings"
|
|
"time"
|
|
|
|
"github.com/bluenviron/mediamtx/internal/certloader"
|
|
"github.com/bluenviron/mediamtx/internal/logger"
|
|
"github.com/bluenviron/mediamtx/internal/packetdumper"
|
|
"github.com/bluenviron/mediamtx/internal/restrictnetwork"
|
|
"golang.org/x/net/http2"
|
|
)
|
|
|
|
type nilWriter struct{}
|
|
|
|
func (nilWriter) Write(p []byte) (int, error) {
|
|
return len(p), nil
|
|
}
|
|
|
|
// Server is a wrapper around http.Server that provides:
|
|
// - net.Listener creation and destruction
|
|
// - TLS initialization and hot reload
|
|
// - exit on panic
|
|
// - logging
|
|
// - server header
|
|
// - filtering of invalid requests
|
|
type Server struct {
|
|
Address string
|
|
AllowOrigins []string
|
|
DumpPackets bool
|
|
DumpPacketsPrefix string
|
|
ReadTimeout time.Duration
|
|
WriteTimeout time.Duration
|
|
Encryption bool
|
|
ServerCert string
|
|
ServerKey string
|
|
AllowAutoCert bool
|
|
Handler http.Handler
|
|
Parent logger.Writer
|
|
|
|
ln net.Listener
|
|
inner *http.Server
|
|
loader *certloader.CertLoader
|
|
tracker *handlerTracker
|
|
}
|
|
|
|
// Initialize initializes a Server.
|
|
func (s *Server) Initialize() error {
|
|
if s.ReadTimeout == 0 {
|
|
return fmt.Errorf("invalid ReadTimeout")
|
|
}
|
|
if s.WriteTimeout == 0 {
|
|
return fmt.Errorf("invalid WriteTimeout")
|
|
}
|
|
|
|
var tlsConfig *tls.Config
|
|
|
|
if s.Encryption {
|
|
if s.ServerCert == "" {
|
|
return fmt.Errorf("server cert is missing")
|
|
}
|
|
|
|
s.loader = &certloader.CertLoader{
|
|
CertPath: s.ServerCert,
|
|
KeyPath: s.ServerKey,
|
|
AllowAuto: s.AllowAutoCert,
|
|
Parent: s.Parent,
|
|
}
|
|
err := s.loader.Initialize()
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
tlsConfig = &tls.Config{
|
|
GetCertificate: s.loader.GetCertificate(),
|
|
}
|
|
}
|
|
|
|
var network string
|
|
var address string
|
|
|
|
if strings.HasPrefix(s.Address, "unix://") {
|
|
network = "unix"
|
|
address = s.Address[len("unix://"):]
|
|
} else {
|
|
network, address = restrictnetwork.Restrict("tcp", s.Address)
|
|
}
|
|
|
|
if network == "unix" {
|
|
os.Remove(address)
|
|
}
|
|
|
|
h := s.Handler
|
|
h = &handlerOrigin{h, s.AllowOrigins}
|
|
h = &handlerServerHeader{h}
|
|
h = &handlerFilterRequests{h}
|
|
h = &handlerLogger{h, s.Parent}
|
|
h = &handlerExitOnPanic{h}
|
|
h = &handlerWriteTimeout{h, s.WriteTimeout}
|
|
s.tracker = &handlerTracker{h: h}
|
|
h = s.tracker
|
|
|
|
s.inner = &http.Server{
|
|
Handler: h,
|
|
TLSConfig: tlsConfig,
|
|
|
|
// applied before reading any request
|
|
ReadTimeout: s.ReadTimeout,
|
|
|
|
// applied after HTTP handler has returned
|
|
IdleTimeout: 30 * time.Second,
|
|
|
|
ErrorLog: log.New(&nilWriter{}, "", 0),
|
|
}
|
|
|
|
if tlsConfig != nil {
|
|
err := http2.ConfigureServer(s.inner, &http2.Server{})
|
|
if err != nil {
|
|
if s.loader != nil {
|
|
s.loader.Close()
|
|
}
|
|
return err
|
|
}
|
|
}
|
|
|
|
listen := func(network string, address string) (net.Listener, error) {
|
|
ln, err := net.Listen(network, address)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
if s.DumpPackets {
|
|
ln = &packetdumper.Listener{
|
|
Wrapped: ln,
|
|
Prefix: s.DumpPacketsPrefix,
|
|
}
|
|
}
|
|
|
|
return ln, nil
|
|
}
|
|
|
|
tlsListen := func(network string, laddr string, config *tls.Config) (net.Listener, error) {
|
|
ln, err := listen(network, laddr)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
if s.DumpPackets {
|
|
ln = &packetdumper.TLSListener{
|
|
Wrapped: ln,
|
|
TLSConfig: config,
|
|
}
|
|
} else {
|
|
ln = tls.NewListener(ln, config)
|
|
}
|
|
|
|
return ln, nil
|
|
}
|
|
|
|
if tlsConfig != nil {
|
|
var err error
|
|
s.ln, err = tlsListen(network, address, tlsConfig)
|
|
if err != nil {
|
|
if s.loader != nil {
|
|
s.loader.Close()
|
|
}
|
|
return err
|
|
}
|
|
} else {
|
|
var err error
|
|
s.ln, err = listen(network, address)
|
|
if err != nil {
|
|
if s.loader != nil {
|
|
s.loader.Close()
|
|
}
|
|
return err
|
|
}
|
|
}
|
|
|
|
if network == "unix" {
|
|
os.Chmod(address, 0o755) //nolint:errcheck
|
|
}
|
|
|
|
go s.inner.Serve(s.ln)
|
|
|
|
return nil
|
|
}
|
|
|
|
// Close closes all resources and waits for all routines to return.
|
|
func (s *Server) Close() {
|
|
s.ln.Close()
|
|
s.inner.Close() //nolint:errcheck
|
|
s.tracker.close()
|
|
|
|
if s.loader != nil {
|
|
s.loader.Close()
|
|
}
|
|
}
|