Skip to content

Commit

Permalink
add ServerConn.ValidateCredentials()
Browse files Browse the repository at this point in the history
  • Loading branch information
aler9 committed May 3, 2024
1 parent 1d60608 commit 72abde3
Show file tree
Hide file tree
Showing 7 changed files with 378 additions and 42 deletions.
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -92,6 +92,7 @@ Features:
* [client-record-format-vp9](examples/client-record-format-vp9/main.go)
* [server](examples/server/main.go)
* [server-tls](examples/server-tls/main.go)
* [server-auth](examples/server-auth/main.go)
* [server-h264-save-to-disk](examples/server-h264-save-to-disk/main.go)
* [proxy](examples/proxy/main.go)

Expand Down
44 changes: 25 additions & 19 deletions examples/client-record-format-mjpeg-from-image/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,28 @@ import (
// 4. generate RTP/M-JPEG packets from the JPEG image
// 5. write packets to the server

func createRandomImage(i int) *image.RGBA {
img := image.NewRGBA(image.Rect(0, 0, 640, 480))

var cl color.RGBA
switch i {
case 0:
cl = color.RGBA{255, 0, 0, 0}
case 1:
cl = color.RGBA{0, 255, 0, 0}
case 2:
cl = color.RGBA{0, 0, 255, 0}
}

for y := 0; y < img.Rect.Dy(); y++ {
for x := 0; x < img.Rect.Dx(); x++ {
img.SetRGBA(x, y, cl)
}
}

return img
}

func main() {
// create a description that contains a M-JPEG format
forma := &format.MJPEG{}
Expand Down Expand Up @@ -59,29 +81,13 @@ func main() {
i := 0

for range ticker.C {
// create a RGBA image
image := image.NewRGBA(image.Rect(0, 0, 640, 480))

// fill the image
var cl color.RGBA
switch i {
case 0:
cl = color.RGBA{255, 0, 0, 0}
case 1:
cl = color.RGBA{0, 255, 0, 0}
case 2:
cl = color.RGBA{0, 0, 255, 0}
}
for y := 0; y < image.Rect.Dy(); y++ {
for x := 0; x < image.Rect.Dx(); x++ {
image.SetRGBA(x, y, cl)
}
}
// create a random image
img := createRandomImage(i)
i = (i + 1) % 3

// encode the image with JPEG
var buf bytes.Buffer
err := jpeg.Encode(&buf, image, &jpeg.Options{Quality: 80})
err := jpeg.Encode(&buf, img, &jpeg.Options{Quality: 80})
if err != nil {
panic(err)
}
Expand Down
182 changes: 182 additions & 0 deletions examples/server-auth/main.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,182 @@
package main

import (
"log"
"sync"

"github.com/pion/rtp"

"github.com/bluenviron/gortsplib/v4"
"github.com/bluenviron/gortsplib/v4/pkg/base"
"github.com/bluenviron/gortsplib/v4/pkg/description"
"github.com/bluenviron/gortsplib/v4/pkg/format"
)

// This example shows how to
// 1. create a RTSP server which accepts plain connections
// 2. allow a single client to authenticate and publish a stream with TCP or UDP
// 3. allow multiple clients to authenticate and read that stream with TCP, UDP or UDP-multicast

const (
readUser = "readuser"
readPass = "readpass"
publishUser = "publishuser"
publishPass = "publishpass"
)

type serverHandler struct {
s *gortsplib.Server
mutex sync.Mutex
stream *gortsplib.ServerStream
publisher *gortsplib.ServerSession
noncePerConns map[*gortsplib.ServerConn]string
}

// called when a connection is opened.
func (sh *serverHandler) OnConnOpen(ctx *gortsplib.ServerHandlerOnConnOpenCtx) {
log.Printf("conn opened")
}

// called when a connection is closed.
func (sh *serverHandler) OnConnClose(ctx *gortsplib.ServerHandlerOnConnCloseCtx) {
log.Printf("conn closed (%v)", ctx.Error)

delete(sh.noncePerConns, ctx.Conn)
}

// called when a session is opened.
func (sh *serverHandler) OnSessionOpen(ctx *gortsplib.ServerHandlerOnSessionOpenCtx) {
log.Printf("session opened")
}

// called when a session is closed.
func (sh *serverHandler) OnSessionClose(ctx *gortsplib.ServerHandlerOnSessionCloseCtx) {
log.Printf("session closed")

sh.mutex.Lock()
defer sh.mutex.Unlock()

// if the session is the publisher,
// close the stream and disconnect any reader.
if sh.stream != nil && ctx.Session == sh.publisher {
sh.stream.Close()
sh.stream = nil
}
}

// called when receiving a DESCRIBE request.
func (sh *serverHandler) OnDescribe(ctx *gortsplib.ServerHandlerOnDescribeCtx) (*base.Response, *gortsplib.ServerStream, error) {
log.Printf("describe request")

res, err := ctx.Conn.ValidateCredentials(ctx.Request, readUser, readPass, nil, nil)
if err != nil {
return res, nil, err
}

sh.mutex.Lock()
defer sh.mutex.Unlock()

// no one is publishing yet
if sh.stream == nil {
return &base.Response{
StatusCode: base.StatusNotFound,
}, nil, nil
}

// send medias that are being published to the client
return &base.Response{
StatusCode: base.StatusOK,
}, sh.stream, nil
}

// called when receiving an ANNOUNCE request.
func (sh *serverHandler) OnAnnounce(ctx *gortsplib.ServerHandlerOnAnnounceCtx) (*base.Response, error) {
log.Printf("announce request")

res, err := ctx.Conn.ValidateCredentials(ctx.Request, publishUser, publishPass, nil, nil)
if err != nil {
return res, err
}

sh.mutex.Lock()
defer sh.mutex.Unlock()

// disconnect existing publisher
if sh.stream != nil {
sh.stream.Close()
sh.publisher.Close()
}

// create the stream and save the publisher
sh.stream = gortsplib.NewServerStream(sh.s, ctx.Description)
sh.publisher = ctx.Session

return &base.Response{
StatusCode: base.StatusOK,
}, nil
}

// called when receiving a SETUP request.
func (sh *serverHandler) OnSetup(ctx *gortsplib.ServerHandlerOnSetupCtx) (*base.Response, *gortsplib.ServerStream, error) {
log.Printf("setup request")

res, err := ctx.Conn.ValidateCredentials(ctx.Request, readUser, readPass, nil, nil)
if err != nil {
return res, nil, err
}

// no one is publishing yet
if sh.stream == nil {
return &base.Response{
StatusCode: base.StatusNotFound,
}, nil, nil
}

return &base.Response{
StatusCode: base.StatusOK,
}, sh.stream, nil
}

// called when receiving a PLAY request.
func (sh *serverHandler) OnPlay(ctx *gortsplib.ServerHandlerOnPlayCtx) (*base.Response, error) {
log.Printf("play request")

return &base.Response{
StatusCode: base.StatusOK,
}, nil
}

// called when receiving a RECORD request.
func (sh *serverHandler) OnRecord(ctx *gortsplib.ServerHandlerOnRecordCtx) (*base.Response, error) {
log.Printf("record request")

// called when receiving a RTP packet
ctx.Session.OnPacketRTPAny(func(medi *description.Media, forma format.Format, pkt *rtp.Packet) {
// route the RTP packet to all readers
sh.stream.WritePacketRTP(medi, pkt)
})

return &base.Response{
StatusCode: base.StatusOK,
}, nil
}

func main() {
// configure the server
h := &serverHandler{
noncePerConns: make(map[*gortsplib.ServerConn]string),
}
h.s = &gortsplib.Server{
Handler: h,
RTSPAddress: ":8554",
UDPRTPAddress: ":8000",
UDPRTCPAddress: ":8001",
MulticastIPRange: "224.1.0.0/16",
MulticastRTPPort: 8002,
MulticastRTCPPort: 8003,
}

// start server and wait until a fatal error
log.Printf("server is ready")
panic(h.s.StartAndWait())
}
16 changes: 16 additions & 0 deletions pkg/liberrors/server.go
Original file line number Diff line number Diff line change
Expand Up @@ -266,3 +266,19 @@ func (ErrServerPathNoSlash) Error() string {
"This typically happens when VLC fails a request, and then switches to an " +
"unsupported RTSP dialect"
}

// ErrServerFatalAuth is an error that can be returned by a server.
type ErrServerFatalAuth struct{}

// Error implements the error interface.
func (e ErrServerFatalAuth) Error() string {
return "authentication error"

Check warning on line 275 in pkg/liberrors/server.go

View check run for this annotation

Codecov / codecov/patch

pkg/liberrors/server.go#L274-L275

Added lines #L274 - L275 were not covered by tests
}

// ErrServerNonFatalAuth is an error that can be returned by a server.
type ErrServerNonFatalAuth struct{}

// Error implements the error interface.
func (e ErrServerNonFatalAuth) Error() string {
return "non-fatal authentication error"

Check warning on line 283 in pkg/liberrors/server.go

View check run for this annotation

Codecov / codecov/patch

pkg/liberrors/server.go#L282-L283

Added lines #L282 - L283 were not covered by tests
}
4 changes: 4 additions & 0 deletions server.go
Original file line number Diff line number Diff line change
Expand Up @@ -115,6 +115,7 @@ type Server struct {
receiverReportPeriod time.Duration
sessionTimeout time.Duration
checkStreamPeriod time.Duration
authRealm string

ctx context.Context
ctxCancel func()
Expand Down Expand Up @@ -181,6 +182,9 @@ func (s *Server) Start() error {
if s.checkStreamPeriod == 0 {
s.checkStreamPeriod = 1 * time.Second
}
if s.authRealm == "" {
s.authRealm = "ipcam"
}

if s.TLSConfig != nil && s.UDPRTPAddress != "" {
return fmt.Errorf("TLS can't be used with UDP")
Expand Down
Loading

0 comments on commit 72abde3

Please sign in to comment.