feat: v6 rewrite
This commit is contained in:
@@ -0,0 +1,166 @@
|
||||
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
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user