From 6ffa07e8e884a2d842cc6a2c1faf066f792e1b78 Mon Sep 17 00:00:00 2001 From: krystiancha Date: Sun, 3 Aug 2025 12:30:26 +0200 Subject: [PATCH] Add support for HTTP over unix sockets (#4241) This adds support for using unix scheme in xxxAddress config options. For example `hlsAddress: "unix://hls.sock"`. --------- Co-authored-by: aler9 <46489434+aler9@users.noreply.github.com> --- internal/api/api.go | 8 +--- internal/metrics/metrics.go | 8 +--- internal/playback/server.go | 8 +--- internal/pprof/pprof.go | 8 +--- internal/protocols/httpp/server.go | 24 ++++++++++- internal/protocols/httpp/server_test.go | 57 +++++++++++++++++++++---- internal/servers/hls/http_server.go | 6 +-- internal/servers/webrtc/http_server.go | 6 +-- 8 files changed, 81 insertions(+), 44 deletions(-) diff --git a/internal/api/api.go b/internal/api/api.go index 6544d3f5..11256196 100644 --- a/internal/api/api.go +++ b/internal/api/api.go @@ -23,7 +23,6 @@ import ( "github.com/bluenviron/mediamtx/internal/logger" "github.com/bluenviron/mediamtx/internal/protocols/httpp" "github.com/bluenviron/mediamtx/internal/recordstore" - "github.com/bluenviron/mediamtx/internal/restrictnetwork" "github.com/bluenviron/mediamtx/internal/servers/hls" "github.com/bluenviron/mediamtx/internal/servers/rtmp" "github.com/bluenviron/mediamtx/internal/servers/rtsp" @@ -189,11 +188,8 @@ func (a *API) Initialize() error { group.GET("/recordings/get/*name", a.onRecordingsGet) group.DELETE("/recordings/deletesegment", a.onRecordingDeleteSegment) - network, address := restrictnetwork.Restrict("tcp", a.Address) - a.httpServer = &httpp.Server{ - Network: network, - Address: address, + Address: a.Address, ReadTimeout: time.Duration(a.ReadTimeout), Encryption: a.Encryption, ServerCert: a.ServerCert, @@ -206,7 +202,7 @@ func (a *API) Initialize() error { return err } - a.Log(logger.Info, "listener opened on "+address) + a.Log(logger.Info, "listener opened on "+a.Address) return nil } diff --git a/internal/metrics/metrics.go b/internal/metrics/metrics.go index 1a1fdcf5..0b446d2e 100644 --- a/internal/metrics/metrics.go +++ b/internal/metrics/metrics.go @@ -18,7 +18,6 @@ import ( "github.com/bluenviron/mediamtx/internal/defs" "github.com/bluenviron/mediamtx/internal/logger" "github.com/bluenviron/mediamtx/internal/protocols/httpp" - "github.com/bluenviron/mediamtx/internal/restrictnetwork" ) func interfaceIsEmpty(i interface{}) bool { @@ -103,11 +102,8 @@ func (m *Metrics) Initialize() error { router.GET("/metrics", m.onMetrics) - network, address := restrictnetwork.Restrict("tcp", m.Address) - m.httpServer = &httpp.Server{ - Network: network, - Address: address, + Address: m.Address, ReadTimeout: time.Duration(m.ReadTimeout), Encryption: m.Encryption, ServerCert: m.ServerCert, @@ -120,7 +116,7 @@ func (m *Metrics) Initialize() error { return err } - m.Log(logger.Info, "listener opened on "+address) + m.Log(logger.Info, "listener opened on "+m.Address) return nil } diff --git a/internal/playback/server.go b/internal/playback/server.go index 07b17e05..9f757857 100644 --- a/internal/playback/server.go +++ b/internal/playback/server.go @@ -11,7 +11,6 @@ import ( "github.com/bluenviron/mediamtx/internal/conf" "github.com/bluenviron/mediamtx/internal/logger" "github.com/bluenviron/mediamtx/internal/protocols/httpp" - "github.com/bluenviron/mediamtx/internal/restrictnetwork" "github.com/gin-gonic/gin" ) @@ -46,11 +45,8 @@ func (s *Server) Initialize() error { router.GET("/list", s.onList) router.GET("/get", s.onGet) - network, address := restrictnetwork.Restrict("tcp", s.Address) - s.httpServer = &httpp.Server{ - Network: network, - Address: address, + Address: s.Address, ReadTimeout: time.Duration(s.ReadTimeout), Encryption: s.Encryption, ServerCert: s.ServerCert, @@ -63,7 +59,7 @@ func (s *Server) Initialize() error { return err } - s.Log(logger.Info, "listener opened on "+address) + s.Log(logger.Info, "listener opened on "+s.Address) return nil } diff --git a/internal/pprof/pprof.go b/internal/pprof/pprof.go index cfa4182e..3c154bfd 100644 --- a/internal/pprof/pprof.go +++ b/internal/pprof/pprof.go @@ -13,7 +13,6 @@ import ( "github.com/bluenviron/mediamtx/internal/conf" "github.com/bluenviron/mediamtx/internal/logger" "github.com/bluenviron/mediamtx/internal/protocols/httpp" - "github.com/bluenviron/mediamtx/internal/restrictnetwork" ) type pprofAuthManager interface { @@ -49,11 +48,8 @@ func (pp *PPROF) Initialize() error { pprof.Register(router) - network, address := restrictnetwork.Restrict("tcp", pp.Address) - pp.httpServer = &httpp.Server{ - Network: network, - Address: address, + Address: pp.Address, ReadTimeout: time.Duration(pp.ReadTimeout), Encryption: pp.Encryption, ServerCert: pp.ServerCert, @@ -66,7 +62,7 @@ func (pp *PPROF) Initialize() error { return err } - pp.Log(logger.Info, "listener opened on "+address) + pp.Log(logger.Info, "listener opened on "+pp.Address) return nil } diff --git a/internal/protocols/httpp/server.go b/internal/protocols/httpp/server.go index 913d8acb..6b10f31f 100644 --- a/internal/protocols/httpp/server.go +++ b/internal/protocols/httpp/server.go @@ -8,10 +8,13 @@ import ( "log" "net" "net/http" + "os" + "strings" "time" "github.com/bluenviron/mediamtx/internal/certloader" "github.com/bluenviron/mediamtx/internal/logger" + "github.com/bluenviron/mediamtx/internal/restrictnetwork" ) type nilWriter struct{} @@ -28,7 +31,6 @@ func (nilWriter) Write(p []byte) (int, error) { // - server header // - filtering of invalid requests type Server struct { - Network string Address string ReadTimeout time.Duration Encryption bool @@ -65,12 +67,30 @@ func (s *Server) Initialize() error { } } + 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) + } + var err error - s.ln, err = net.Listen(s.Network, s.Address) + s.ln, err = net.Listen(network, address) if err != nil { return err } + if network == "unix" { + os.Chmod(address, 0o755) //nolint:errcheck + } + h := s.Handler h = &handlerFilterRequests{h} h = &handlerFilterRequests{h} diff --git a/internal/protocols/httpp/server_test.go b/internal/protocols/httpp/server_test.go index a8ace896..a225dca4 100644 --- a/internal/protocols/httpp/server_test.go +++ b/internal/protocols/httpp/server_test.go @@ -1,8 +1,10 @@ package httpp import ( - "io" "net" + "net/http" + "os" + "strings" "testing" "time" @@ -13,10 +15,12 @@ import ( func TestFilterEmptyPath(t *testing.T) { s := &Server{ - Network: "tcp", Address: "localhost:4555", ReadTimeout: 10 * time.Second, Parent: test.NilLogger, + Handler: http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) { + w.WriteHeader(http.StatusOK) + }), } err := s.Initialize() require.NoError(t, err) @@ -26,13 +30,50 @@ func TestFilterEmptyPath(t *testing.T) { require.NoError(t, err) defer conn.Close() - _, err = conn.Write([]byte("OPTIONS http://localhost HTTP/1.1\n" + - "Host: localhost:8889\n" + - "Accept-Encoding: gzip\n" + - "User-Agent: Go-http-client/1.1\n\n")) + _, err = conn.Write([]byte("OPTIONS / HTTP/1.1\n" + + "Host: localhost:8889\n\n")) require.NoError(t, err) - buf := make([]byte, 20) - _, err = io.ReadFull(conn, buf) + buf := make([]byte, 200) + n, err := conn.Read(buf) require.NoError(t, err) + + res := strings.Split(string(buf[:n]), "\r\n") + require.Equal(t, "HTTP/1.1 200 OK", res[0]) +} + +func TestUnixSocket(t *testing.T) { + s := &Server{ + Address: "unix://http.sock", + ReadTimeout: 10 * time.Second, + Parent: test.NilLogger, + Handler: http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) { + w.WriteHeader(http.StatusOK) + }), + } + err := s.Initialize() + require.NoError(t, err) + + _, err = os.Stat("http.sock") + require.NoError(t, err) + + conn, err := net.Dial("unix", "http.sock") + require.NoError(t, err) + + _, err = conn.Write([]byte("OPTIONS / HTTP/1.1\n" + + "Host: localhost:8889\n\n")) + require.NoError(t, err) + + buf := make([]byte, 200) + n, err := conn.Read(buf) + require.NoError(t, err) + + res := strings.Split(string(buf[:n]), "\r\n") + require.Equal(t, "HTTP/1.1 200 OK", res[0]) + + conn.Close() + s.Close() + + _, err = os.Stat("http.sock") + require.EqualError(t, err, "stat http.sock: no such file or directory") } diff --git a/internal/servers/hls/http_server.go b/internal/servers/hls/http_server.go index 7616355f..c02d6b5b 100644 --- a/internal/servers/hls/http_server.go +++ b/internal/servers/hls/http_server.go @@ -16,7 +16,6 @@ import ( "github.com/bluenviron/mediamtx/internal/defs" "github.com/bluenviron/mediamtx/internal/logger" "github.com/bluenviron/mediamtx/internal/protocols/httpp" - "github.com/bluenviron/mediamtx/internal/restrictnetwork" ) //go:generate go run ./hlsjsdownloader @@ -57,11 +56,8 @@ func (s *httpServer) initialize() error { router.Use(s.onRequest) - network, address := restrictnetwork.Restrict("tcp", s.address) - s.inner = &httpp.Server{ - Network: network, - Address: address, + Address: s.address, ReadTimeout: time.Duration(s.readTimeout), Encryption: s.encryption, ServerCert: s.serverCert, diff --git a/internal/servers/webrtc/http_server.go b/internal/servers/webrtc/http_server.go index e3e8c3ce..0a22ca64 100644 --- a/internal/servers/webrtc/http_server.go +++ b/internal/servers/webrtc/http_server.go @@ -20,7 +20,6 @@ import ( "github.com/bluenviron/mediamtx/internal/logger" "github.com/bluenviron/mediamtx/internal/protocols/httpp" "github.com/bluenviron/mediamtx/internal/protocols/whip" - "github.com/bluenviron/mediamtx/internal/restrictnetwork" ) //go:embed publish_index.html @@ -94,11 +93,8 @@ func (s *httpServer) initialize() error { router.Use(s.onRequest) - network, address := restrictnetwork.Restrict("tcp", s.address) - s.inner = &httpp.Server{ - Network: network, - Address: address, + Address: s.address, ReadTimeout: time.Duration(s.readTimeout), Encryption: s.encryption, ServerCert: s.serverCert,