Browse Source

implement authentication

pull/2/head
aler9 6 years ago
parent
commit
88f2c5e869
  1. 14
      Makefile
  2. 21
      README.md
  3. 61
      client.go
  4. 16
      main.go

14
Makefile

@ -8,12 +8,12 @@ help:
@echo "" @echo ""
@echo "available actions:" @echo "available actions:"
@echo "" @echo ""
@echo " mod-tidy run go mod tidy" @echo " mod-tidy run go mod tidy"
@echo " format format source files" @echo " format format source files"
@echo " test run available tests" @echo " test run available tests"
@echo " run run app" @echo " run ARGS=args run app"
@echo " release build release assets" @echo " release build release assets"
@echo " travis-setup setup travis CI" @echo " travis-setup setup travis CI"
@echo "" @echo ""
mod-tidy: mod-tidy:
@ -64,7 +64,7 @@ run:
--network=host \ --network=host \
--name temp \ --name temp \
temp \ temp \
/out /out $(ARGS)
define DOCKERFILE_RELEASE define DOCKERFILE_RELEASE
FROM $(BASE_IMAGE) FROM $(BASE_IMAGE)

21
README.md

@ -13,6 +13,7 @@ Features:
* Publish multiple streams at once, each in a separate path, that can be read by multiple users * Publish multiple streams at once, each in a separate path, that can be read by multiple users
* Each stream can have multiple video and audio tracks * Each stream can have multiple video and audio tracks
* Supports the RTP/RTCP streaming protocol * Supports the RTP/RTCP streaming protocol
* Optional authentication schema for publishers
* Compatible with Linux and Windows, does not require any dependency or interpreter, it's a single executable * Compatible with Linux and Windows, does not require any dependency or interpreter, it's a single executable
@ -27,6 +28,8 @@ Precompiled binaries are available in the [release](https://github.com/aler9/rts
## Usage ## Usage
#### Basic usage
1. Start the server: 1. Start the server:
``` ```
./rtsp-simple-server ./rtsp-simple-server
@ -47,14 +50,24 @@ Precompiled binaries are available in the [release](https://github.com/aler9/rts
gst-launch-1.0 -v rtspsrc location=rtsp://localhost:8554/mystream ! rtph264depay ! decodebin ! autovideosink gst-launch-1.0 -v rtspsrc location=rtsp://localhost:8554/mystream ! rtph264depay ! decodebin ! autovideosink
``` ```
<br /> #### Publisher authentication
## Full command-line usage 1. Start the server and set a publish key:
```
./rtsp-simple-server --publish-key=IU23yyfaw6324
```
2. Only publishers which have the key will be able to publish:
```
ffmpeg -re -stream_loop -1 -i file.ts -c copy -f rtsp rtsp://localhost:8554/mystream?key=IU23yyfaw6324
```
#### Full command-line usage
``` ```
usage: rtsp-simple-server [<flags>] usage: rtsp-simple-server [<flags>]
rtsp-simple-server rtsp-simple-server v0.0.0
RTSP server. RTSP server.
@ -64,8 +77,10 @@ Flags:
--rtsp-port=8554 port of the RTSP TCP listener --rtsp-port=8554 port of the RTSP TCP listener
--rtp-port=8000 port of the RTP UDP listener --rtp-port=8000 port of the RTP UDP listener
--rtcp-port=8001 port of the RTCP UDP listener --rtcp-port=8001 port of the RTCP UDP listener
--publish-key="" optional authentication key required to publish
``` ```
<br /> <br />
## Links ## Links

61
client.go

@ -19,6 +19,7 @@ var (
errTeardown = errors.New("teardown") errTeardown = errors.New("teardown")
errPlay = errors.New("play") errPlay = errors.New("play")
errRecord = errors.New("record") errRecord = errors.New("record")
errWrongKey = errors.New("wrong key")
) )
func interleavedChannelToTrack(channel int) (int, trackFlow) { func interleavedChannelToTrack(channel int) (int, trackFlow) {
@ -170,7 +171,7 @@ func (c *client) run() {
return return
} }
// TEARDOWN, close connection silently // TEARDOWN: close connection silently
case errTeardown: case errTeardown:
return return
@ -258,7 +259,20 @@ func (c *client) run() {
} }
} }
// error: write and exit // wrong key: reply with 401 and exit
case errWrongKey:
c.log("ERR: %s", err)
c.rconn.WriteResponse(&rtsp.Response{
StatusCode: 401,
Status: "Unauthorized",
Headers: map[string]string{
"CSeq": req.Headers["CSeq"],
},
})
return
// generic error: reply with code 400 and exit
default: default:
c.log("ERR: %s", err) c.log("ERR: %s", err)
@ -287,30 +301,27 @@ func (c *client) handleRequest(req *rtsp.Request) (*rtsp.Response, error) {
return nil, fmt.Errorf("cseq missing") return nil, fmt.Errorf("cseq missing")
} }
path, err := func() (string, error) { ur, err := url.Parse(req.Url)
ur, err := url.Parse(req.Url) if err != nil {
if err != nil { return nil, fmt.Errorf("unable to parse path '%s'", req.Url)
return "", fmt.Errorf("unable to parse path '%s'", req.Url) }
}
path := ur.Path path := func() string {
ret := ur.Path
// remove leading slash // remove leading slash
if len(path) > 1 { if len(ret) > 1 {
path = path[1:] ret = ret[1:]
} }
// strip any subpath // strip any subpath
if n := strings.Index(path, "/"); n >= 0 { if n := strings.Index(ret, "/"); n >= 0 {
path = path[:n] ret = ret[:n]
} }
return path, nil return ret
}() }()
c.p.mutex.Lock()
c.path = path
c.p.mutex.Unlock()
switch req.Method { switch req.Method {
case "OPTIONS": case "OPTIONS":
// do not check state, since OPTIONS can be requested // do not check state, since OPTIONS can be requested
@ -397,6 +408,22 @@ func (c *client) handleRequest(req *rtsp.Request) (*rtsp.Response, error) {
return nil, fmt.Errorf("invalid SDP: %s", err) return nil, fmt.Errorf("invalid SDP: %s", err)
} }
if c.p.publishKey != "" {
q, err := url.ParseQuery(ur.RawQuery)
if err != nil {
return nil, fmt.Errorf("unable to parse query")
}
key, ok := q["key"]
if !ok || len(key) == 0 {
return nil, fmt.Errorf("key missing")
}
if key[0] != c.p.publishKey {
return nil, errWrongKey
}
}
err = func() error { err = func() error {
c.p.mutex.Lock() c.p.mutex.Lock()
defer c.p.mutex.Unlock() defer c.p.mutex.Unlock()

16
main.go

@ -5,6 +5,7 @@ import (
"log" "log"
"net" "net"
"os" "os"
"regexp"
"sync" "sync"
"gopkg.in/alecthomas/kingpin.v2" "gopkg.in/alecthomas/kingpin.v2"
@ -42,6 +43,7 @@ type program struct {
rtspPort int rtspPort int
rtpPort int rtpPort int
rtcpPort int rtcpPort int
publishKey string
mutex sync.RWMutex mutex sync.RWMutex
rtspl *rtspListener rtspl *rtspListener
rtpl *udpListener rtpl *udpListener
@ -50,11 +52,20 @@ type program struct {
publishers map[string]*client publishers map[string]*client
} }
func newProgram(rtspPort int, rtpPort int, rtcpPort int) (*program, error) { func newProgram(rtspPort int, rtpPort int, rtcpPort int, publishKey string) (*program, error) {
if publishKey != "" {
if !regexp.MustCompile("^[a-zA-Z0-9]+$").MatchString(publishKey) {
return nil, fmt.Errorf("publish key must be alphanumeric")
}
}
log.Printf("rtsp-simple-server %s", Version)
p := &program{ p := &program{
rtspPort: rtspPort, rtspPort: rtspPort,
rtpPort: rtpPort, rtpPort: rtpPort,
rtcpPort: rtcpPort, rtcpPort: rtcpPort,
publishKey: publishKey,
clients: make(map[*client]struct{}), clients: make(map[*client]struct{}),
publishers: make(map[string]*client), publishers: make(map[string]*client),
} }
@ -120,6 +131,7 @@ func main() {
rtspPort := kingpin.Flag("rtsp-port", "port of the RTSP TCP listener").Default("8554").Int() rtspPort := kingpin.Flag("rtsp-port", "port of the RTSP TCP listener").Default("8554").Int()
rtpPort := kingpin.Flag("rtp-port", "port of the RTP UDP listener").Default("8000").Int() rtpPort := kingpin.Flag("rtp-port", "port of the RTP UDP listener").Default("8000").Int()
rtcpPort := kingpin.Flag("rtcp-port", "port of the RTCP UDP listener").Default("8001").Int() rtcpPort := kingpin.Flag("rtcp-port", "port of the RTCP UDP listener").Default("8001").Int()
publishKey := kingpin.Flag("publish-key", "optional authentication key required to publish").Default("").String()
kingpin.Parse() kingpin.Parse()
@ -128,7 +140,7 @@ func main() {
os.Exit(0) os.Exit(0)
} }
p, err := newProgram(*rtspPort, *rtpPort, *rtcpPort) p, err := newProgram(*rtspPort, *rtpPort, *rtcpPort, *publishKey)
if err != nil { if err != nil {
log.Fatal("ERR: ", err) log.Fatal("ERR: ", err)
} }

Loading…
Cancel
Save