Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

add ServerConn.ValidateCredentials() #555

Open
wants to merge 1 commit into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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
177 changes: 177 additions & 0 deletions examples/server-auth/main.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,177 @@
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
}

// 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)
}

// 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{}
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 @@
"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
Loading