implement authentication
This commit is contained in:
parent
f64a8d25cb
commit
88f2c5e869
14
Makefile
14
Makefile
|
@ -8,12 +8,12 @@ help:
|
|||
@echo ""
|
||||
@echo "available actions:"
|
||||
@echo ""
|
||||
@echo " mod-tidy run go mod tidy"
|
||||
@echo " format format source files"
|
||||
@echo " test run available tests"
|
||||
@echo " run run app"
|
||||
@echo " release build release assets"
|
||||
@echo " travis-setup setup travis CI"
|
||||
@echo " mod-tidy run go mod tidy"
|
||||
@echo " format format source files"
|
||||
@echo " test run available tests"
|
||||
@echo " run ARGS=args run app"
|
||||
@echo " release build release assets"
|
||||
@echo " travis-setup setup travis CI"
|
||||
@echo ""
|
||||
|
||||
mod-tidy:
|
||||
|
@ -64,7 +64,7 @@ run:
|
|||
--network=host \
|
||||
--name temp \
|
||||
temp \
|
||||
/out
|
||||
/out $(ARGS)
|
||||
|
||||
define DOCKERFILE_RELEASE
|
||||
FROM $(BASE_IMAGE)
|
||||
|
|
21
README.md
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
|
||||
* Each stream can have multiple video and audio tracks
|
||||
* 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
|
||||
|
||||
|
||||
|
@ -27,6 +28,8 @@ Precompiled binaries are available in the [release](https://github.com/aler9/rts
|
|||
|
||||
## Usage
|
||||
|
||||
#### Basic usage
|
||||
|
||||
1. Start the 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
|
||||
```
|
||||
|
||||
<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>]
|
||||
|
||||
rtsp-simple-server
|
||||
rtsp-simple-server v0.0.0
|
||||
|
||||
RTSP server.
|
||||
|
||||
|
@ -64,8 +77,10 @@ Flags:
|
|||
--rtsp-port=8554 port of the RTSP TCP listener
|
||||
--rtp-port=8000 port of the RTP UDP listener
|
||||
--rtcp-port=8001 port of the RTCP UDP listener
|
||||
--publish-key="" optional authentication key required to publish
|
||||
```
|
||||
|
||||
|
||||
<br />
|
||||
|
||||
## Links
|
||||
|
|
61
client.go
61
client.go
|
@ -19,6 +19,7 @@ var (
|
|||
errTeardown = errors.New("teardown")
|
||||
errPlay = errors.New("play")
|
||||
errRecord = errors.New("record")
|
||||
errWrongKey = errors.New("wrong key")
|
||||
)
|
||||
|
||||
func interleavedChannelToTrack(channel int) (int, trackFlow) {
|
||||
|
@ -170,7 +171,7 @@ func (c *client) run() {
|
|||
return
|
||||
}
|
||||
|
||||
// TEARDOWN, close connection silently
|
||||
// TEARDOWN: close connection silently
|
||||
case errTeardown:
|
||||
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:
|
||||
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")
|
||||
}
|
||||
|
||||
path, err := func() (string, error) {
|
||||
ur, err := url.Parse(req.Url)
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("unable to parse path '%s'", req.Url)
|
||||
}
|
||||
path := ur.Path
|
||||
ur, err := url.Parse(req.Url)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("unable to parse path '%s'", req.Url)
|
||||
}
|
||||
|
||||
path := func() string {
|
||||
ret := ur.Path
|
||||
|
||||
// remove leading slash
|
||||
if len(path) > 1 {
|
||||
path = path[1:]
|
||||
if len(ret) > 1 {
|
||||
ret = ret[1:]
|
||||
}
|
||||
|
||||
// strip any subpath
|
||||
if n := strings.Index(path, "/"); n >= 0 {
|
||||
path = path[:n]
|
||||
if n := strings.Index(ret, "/"); n >= 0 {
|
||||
ret = ret[:n]
|
||||
}
|
||||
|
||||
return path, nil
|
||||
return ret
|
||||
}()
|
||||
|
||||
c.p.mutex.Lock()
|
||||
c.path = path
|
||||
c.p.mutex.Unlock()
|
||||
|
||||
switch req.Method {
|
||||
case "OPTIONS":
|
||||
// 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)
|
||||
}
|
||||
|
||||
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 {
|
||||
c.p.mutex.Lock()
|
||||
defer c.p.mutex.Unlock()
|
||||
|
|
16
main.go
16
main.go
|
@ -5,6 +5,7 @@ import (
|
|||
"log"
|
||||
"net"
|
||||
"os"
|
||||
"regexp"
|
||||
"sync"
|
||||
|
||||
"gopkg.in/alecthomas/kingpin.v2"
|
||||
|
@ -42,6 +43,7 @@ type program struct {
|
|||
rtspPort int
|
||||
rtpPort int
|
||||
rtcpPort int
|
||||
publishKey string
|
||||
mutex sync.RWMutex
|
||||
rtspl *rtspListener
|
||||
rtpl *udpListener
|
||||
|
@ -50,11 +52,20 @@ type program struct {
|
|||
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{
|
||||
rtspPort: rtspPort,
|
||||
rtpPort: rtpPort,
|
||||
rtcpPort: rtcpPort,
|
||||
publishKey: publishKey,
|
||||
clients: make(map[*client]struct{}),
|
||||
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()
|
||||
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()
|
||||
publishKey := kingpin.Flag("publish-key", "optional authentication key required to publish").Default("").String()
|
||||
|
||||
kingpin.Parse()
|
||||
|
||||
|
@ -128,7 +140,7 @@ func main() {
|
|||
os.Exit(0)
|
||||
}
|
||||
|
||||
p, err := newProgram(*rtspPort, *rtpPort, *rtcpPort)
|
||||
p, err := newProgram(*rtspPort, *rtpPort, *rtcpPort, *publishKey)
|
||||
if err != nil {
|
||||
log.Fatal("ERR: ", err)
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue