167 lines
4.1 KiB
Go
167 lines
4.1 KiB
Go
package attack_test
|
|
|
|
import (
|
|
"errors"
|
|
"net"
|
|
"net/netip"
|
|
"strings"
|
|
"testing"
|
|
|
|
"github.com/bluenviron/gortsplib/v5"
|
|
"github.com/bluenviron/gortsplib/v5/pkg/auth"
|
|
"github.com/bluenviron/gortsplib/v5/pkg/base"
|
|
"github.com/bluenviron/gortsplib/v5/pkg/description"
|
|
"github.com/bluenviron/gortsplib/v5/pkg/format"
|
|
"github.com/bluenviron/gortsplib/v5/pkg/headers"
|
|
"github.com/bluenviron/gortsplib/v5/pkg/liberrors"
|
|
"github.com/stretchr/testify/require"
|
|
)
|
|
|
|
type rtspServerConfig struct {
|
|
allowAll bool
|
|
allowedRoute string
|
|
requireAuth bool
|
|
username string
|
|
password string
|
|
authMethod headers.AuthMethod
|
|
authHeader base.HeaderValue
|
|
failOnAuth bool
|
|
setupStatus base.StatusCode
|
|
}
|
|
|
|
type testServerHandler struct {
|
|
stream *gortsplib.ServerStream
|
|
allowAll bool
|
|
allowedRoute string
|
|
requireAuth bool
|
|
username string
|
|
password string
|
|
authHeader base.HeaderValue
|
|
failOnAuth bool
|
|
setupStatus base.StatusCode
|
|
}
|
|
|
|
func (h *testServerHandler) OnDescribe(ctx *gortsplib.ServerHandlerOnDescribeCtx) (*base.Response, *gortsplib.ServerStream, error) {
|
|
if !h.routeAllowed(ctx.Path) {
|
|
return &base.Response{StatusCode: base.StatusNotFound}, nil, nil
|
|
}
|
|
|
|
if h.failOnAuth && len(ctx.Request.Header["Authorization"]) > 0 {
|
|
return &base.Response{StatusCode: base.StatusBadRequest}, nil, errors.New("forced auth failure")
|
|
}
|
|
|
|
if h.requireAuth && !ctx.Conn.VerifyCredentials(ctx.Request, h.username, h.password) {
|
|
return &base.Response{
|
|
StatusCode: base.StatusUnauthorized,
|
|
Header: base.Header{
|
|
"WWW-Authenticate": h.authHeader,
|
|
},
|
|
}, nil, liberrors.ErrServerAuth{}
|
|
}
|
|
|
|
return &base.Response{StatusCode: base.StatusOK}, h.stream, nil
|
|
}
|
|
|
|
func (h *testServerHandler) OnSetup(ctx *gortsplib.ServerHandlerOnSetupCtx) (*base.Response, *gortsplib.ServerStream, error) {
|
|
if !h.routeAllowed(ctx.Path) {
|
|
return &base.Response{StatusCode: base.StatusNotFound}, nil, nil
|
|
}
|
|
|
|
if h.requireAuth && !ctx.Conn.VerifyCredentials(ctx.Request, h.username, h.password) {
|
|
return &base.Response{
|
|
StatusCode: base.StatusUnauthorized,
|
|
Header: base.Header{
|
|
"WWW-Authenticate": h.authHeader,
|
|
},
|
|
}, nil, liberrors.ErrServerAuth{}
|
|
}
|
|
|
|
status := base.StatusOK
|
|
if h.setupStatus != 0 {
|
|
status = h.setupStatus
|
|
}
|
|
|
|
return &base.Response{StatusCode: status}, h.stream, nil
|
|
}
|
|
|
|
func (h *testServerHandler) routeAllowed(path string) bool {
|
|
path = strings.TrimLeft(path, "/")
|
|
return h.allowAll || path == h.allowedRoute
|
|
}
|
|
|
|
func startRTSPServer(t *testing.T, cfg rtspServerConfig) (netip.Addr, uint16) {
|
|
t.Helper()
|
|
|
|
handler := &testServerHandler{
|
|
allowAll: cfg.allowAll,
|
|
allowedRoute: cfg.allowedRoute,
|
|
requireAuth: cfg.requireAuth,
|
|
username: cfg.username,
|
|
password: cfg.password,
|
|
failOnAuth: cfg.failOnAuth,
|
|
setupStatus: cfg.setupStatus,
|
|
}
|
|
|
|
if len(cfg.authHeader) > 0 {
|
|
handler.authHeader = cfg.authHeader
|
|
} else {
|
|
authHeader := headers.Authenticate{
|
|
Method: cfg.authMethod,
|
|
Realm: "cameradar",
|
|
}
|
|
if cfg.authMethod == headers.AuthMethodDigest {
|
|
authHeader.Nonce = "nonce"
|
|
}
|
|
handler.authHeader = authHeader.Marshal()
|
|
}
|
|
|
|
server := &gortsplib.Server{
|
|
Handler: handler,
|
|
RTSPAddress: "127.0.0.1:0",
|
|
AuthMethods: authMethods(cfg.authMethod),
|
|
}
|
|
|
|
err := server.Start()
|
|
require.NoError(t, err)
|
|
t.Cleanup(server.Close)
|
|
|
|
desc := &description.Session{
|
|
Medias: []*description.Media{{
|
|
Type: description.MediaTypeVideo,
|
|
Formats: []format.Format{&format.H264{
|
|
PayloadTyp: 96,
|
|
PacketizationMode: 1,
|
|
}},
|
|
}},
|
|
}
|
|
|
|
stream := &gortsplib.ServerStream{
|
|
Server: server,
|
|
Desc: desc,
|
|
}
|
|
err = stream.Initialize()
|
|
require.NoError(t, err)
|
|
t.Cleanup(stream.Close)
|
|
|
|
handler.stream = stream
|
|
|
|
listener := server.NetListener()
|
|
require.NotNil(t, listener)
|
|
|
|
tcpAddr, ok := listener.Addr().(*net.TCPAddr)
|
|
require.True(t, ok)
|
|
|
|
return netip.MustParseAddr("127.0.0.1"), uint16(tcpAddr.Port)
|
|
}
|
|
|
|
func authMethods(method headers.AuthMethod) []auth.VerifyMethod {
|
|
switch method {
|
|
case headers.AuthMethodDigest:
|
|
return []auth.VerifyMethod{auth.VerifyMethodDigestMD5}
|
|
case headers.AuthMethodBasic:
|
|
return []auth.VerifyMethod{auth.VerifyMethodBasic}
|
|
default:
|
|
return nil
|
|
}
|
|
}
|