package main import ( "fmt" "log" "net" "net/http" _ "net/http/pprof" "os" "regexp" "strings" "time" "github.com/aler9/gortsplib" "gopkg.in/alecthomas/kingpin.v2" "gortc.io/sdp" ) var Version = "v0.0.0" func parseIpCidrList(in string) ([]interface{}, error) { if in == "" { return nil, nil } var ret []interface{} for _, t := range strings.Split(in, ",") { _, ipnet, err := net.ParseCIDR(t) if err == nil { ret = append(ret, ipnet) continue } ip := net.ParseIP(t) if ip != nil { ret = append(ret, ip) continue } return nil, fmt.Errorf("unable to parse ip/network '%s'", t) } return ret, nil } type trackFlowType int const ( _TRACK_FLOW_RTP trackFlowType = iota _TRACK_FLOW_RTCP ) type track struct { rtpPort int rtcpPort int } type streamProtocol int const ( _STREAM_PROTOCOL_UDP streamProtocol = iota _STREAM_PROTOCOL_TCP ) func (s streamProtocol) String() string { if s == _STREAM_PROTOCOL_UDP { return "udp" } return "tcp" } type programEvent interface { isProgramEvent() } type programEventClientNew struct { nconn net.Conn } func (programEventClientNew) isProgramEvent() {} type programEventClientClose struct { done chan struct{} client *serverClient } func (programEventClientClose) isProgramEvent() {} type programEventClientGetStreamSdp struct { path string res chan []byte } func (programEventClientGetStreamSdp) isProgramEvent() {} type programEventClientAnnounce struct { res chan error client *serverClient path string sdpText []byte sdpParsed *sdp.Message } func (programEventClientAnnounce) isProgramEvent() {} type programEventClientSetupPlay struct { res chan error client *serverClient path string protocol streamProtocol rtpPort int rtcpPort int } func (programEventClientSetupPlay) isProgramEvent() {} type programEventClientSetupRecord struct { res chan error client *serverClient protocol streamProtocol rtpPort int rtcpPort int } func (programEventClientSetupRecord) isProgramEvent() {} type programEventClientPlay1 struct { res chan error client *serverClient } func (programEventClientPlay1) isProgramEvent() {} type programEventClientPlay2 struct { res chan error client *serverClient } func (programEventClientPlay2) isProgramEvent() {} type programEventClientPause struct { res chan error client *serverClient } func (programEventClientPause) isProgramEvent() {} type programEventClientRecord struct { res chan error client *serverClient } func (programEventClientRecord) isProgramEvent() {} type programEventFrameUdp struct { trackFlowType trackFlowType addr *net.UDPAddr buf []byte } func (programEventFrameUdp) isProgramEvent() {} type programEventFrameTcp struct { path string trackId int trackFlowType trackFlowType buf []byte } func (programEventFrameTcp) isProgramEvent() {} type programEventTerminate struct{} func (programEventTerminate) isProgramEvent() {} type args struct { version bool protocolsStr string rtspPort int rtpPort int rtcpPort int readTimeout time.Duration writeTimeout time.Duration publishUser string publishPass string publishIps string readUser string readPass string readIps string preScript string postScript string pprof bool } type program struct { args args protocols map[streamProtocol]struct{} publishIps []interface{} readIps []interface{} tcpl *serverTcpListener udplRtp *serverUdpListener udplRtcp *serverUdpListener clients map[*serverClient]struct{} publishers map[string]*serverClient publisherCount int receiverCount int events chan programEvent done chan struct{} } func newProgram(sargs []string) (*program, error) { kingpin.CommandLine.Help = "rtsp-simple-server " + Version + "\n\n" + "RTSP server." argVersion := kingpin.Flag("version", "print version").Bool() argProtocolsStr := kingpin.Flag("protocols", "supported protocols").Default("udp,tcp").String() argRtspPort := kingpin.Flag("rtsp-port", "port of the RTSP TCP listener").Default("8554").Int() argRtpPort := kingpin.Flag("rtp-port", "port of the RTP UDP listener").Default("8000").Int() argRtcpPort := kingpin.Flag("rtcp-port", "port of the RTCP UDP listener").Default("8001").Int() argReadTimeout := kingpin.Flag("read-timeout", "timeout of read operations").Default("5s").Duration() argWriteTimeout := kingpin.Flag("write-timeout", "timeout of write operations").Default("5s").Duration() argPublishUser := kingpin.Flag("publish-user", "optional username required to publish").Default("").String() argPublishPass := kingpin.Flag("publish-pass", "optional password required to publish").Default("").String() argPublishIps := kingpin.Flag("publish-ips", "comma-separated list of IPs or networks (x.x.x.x/24) that can publish").Default("").String() argReadUser := kingpin.Flag("read-user", "optional username required to read").Default("").String() argReadPass := kingpin.Flag("read-pass", "optional password required to read").Default("").String() argReadIps := kingpin.Flag("read-ips", "comma-separated list of IPs or networks (x.x.x.x/24) that can read").Default("").String() argPreScript := kingpin.Flag("pre-script", "optional script to run on client connect").Default("").String() argPostScript := kingpin.Flag("post-script", "optional script to run on client disconnect").Default("").String() argPprof := kingpin.Flag("pprof", "enable pprof on port 9999 to monitor performance").Default("false").Bool() kingpin.MustParse(kingpin.CommandLine.Parse(sargs)) args := args{ version: *argVersion, protocolsStr: *argProtocolsStr, rtspPort: *argRtspPort, rtpPort: *argRtpPort, rtcpPort: *argRtcpPort, readTimeout: *argReadTimeout, writeTimeout: *argWriteTimeout, publishUser: *argPublishUser, publishPass: *argPublishPass, publishIps: *argPublishIps, readUser: *argReadUser, readPass: *argReadPass, readIps: *argReadIps, preScript: *argPreScript, postScript: *argPostScript, pprof: *argPprof, } if args.version == true { fmt.Println(Version) os.Exit(0) } protocols := make(map[streamProtocol]struct{}) for _, proto := range strings.Split(args.protocolsStr, ",") { switch proto { case "udp": protocols[_STREAM_PROTOCOL_UDP] = struct{}{} case "tcp": protocols[_STREAM_PROTOCOL_TCP] = struct{}{} default: return nil, fmt.Errorf("unsupported protocol: %s", proto) } } if len(protocols) == 0 { return nil, fmt.Errorf("no protocols provided") } if (args.rtpPort % 2) != 0 { return nil, fmt.Errorf("rtp port must be even") } if args.rtcpPort != (args.rtpPort + 1) { return nil, fmt.Errorf("rtcp and rtp ports must be consecutive") } if args.publishUser != "" { if !regexp.MustCompile("^[a-zA-Z0-9]+$").MatchString(args.publishUser) { return nil, fmt.Errorf("publish username must be alphanumeric") } } if args.publishPass != "" { if !regexp.MustCompile("^[a-zA-Z0-9]+$").MatchString(args.publishPass) { return nil, fmt.Errorf("publish password must be alphanumeric") } } publishIps, err := parseIpCidrList(args.publishIps) if err != nil { return nil, err } if args.readUser != "" && args.readPass == "" || args.readUser == "" && args.readPass != "" { return nil, fmt.Errorf("read username and password must be both filled") } if args.readUser != "" { if !regexp.MustCompile("^[a-zA-Z0-9]+$").MatchString(args.readUser) { return nil, fmt.Errorf("read username must be alphanumeric") } } if args.readPass != "" { if !regexp.MustCompile("^[a-zA-Z0-9]+$").MatchString(args.readPass) { return nil, fmt.Errorf("read password must be alphanumeric") } } if args.readUser != "" && args.readPass == "" || args.readUser == "" && args.readPass != "" { return nil, fmt.Errorf("read username and password must be both filled") } readIps, err := parseIpCidrList(args.readIps) if err != nil { return nil, err } p := &program{ args: args, protocols: protocols, publishIps: publishIps, readIps: readIps, clients: make(map[*serverClient]struct{}), publishers: make(map[string]*serverClient), events: make(chan programEvent), done: make(chan struct{}), } p.log("rtsp-simple-server %s", Version) if args.pprof { go func(mux *http.ServeMux) { server := &http.Server{ Addr: ":9999", Handler: mux, } p.log("pprof is available on :9999") panic(server.ListenAndServe()) }(http.DefaultServeMux) http.DefaultServeMux = http.NewServeMux() } p.udplRtp, err = newServerUdpListener(p, args.rtpPort, _TRACK_FLOW_RTP) if err != nil { return nil, err } p.udplRtcp, err = newServerUdpListener(p, args.rtcpPort, _TRACK_FLOW_RTCP) if err != nil { return nil, err } p.tcpl, err = newServerTcpListener(p) if err != nil { return nil, err } go p.udplRtp.run() go p.udplRtcp.run() go p.tcpl.run() go p.run() return p, nil } func (p *program) log(format string, args ...interface{}) { log.Printf("[%d/%d/%d] "+format, append([]interface{}{len(p.clients), p.publisherCount, p.receiverCount}, args...)...) } func (p *program) run() { outer: for rawEvt := range p.events { switch evt := rawEvt.(type) { case programEventClientNew: c := newServerClient(p, evt.nconn) p.clients[c] = struct{}{} c.log("connected") case programEventClientClose: // already deleted if _, ok := p.clients[evt.client]; !ok { close(evt.done) continue } delete(p.clients, evt.client) if evt.client.path != "" { if pub, ok := p.publishers[evt.client.path]; ok && pub == evt.client { delete(p.publishers, evt.client.path) // if the publisher has disconnected // close all other connections that share the same path for oc := range p.clients { if oc.path == evt.client.path { go oc.close() } } } } switch evt.client.state { case _CLIENT_STATE_PLAY: p.receiverCount -= 1 case _CLIENT_STATE_RECORD: p.publisherCount -= 1 } evt.client.log("disconnected") close(evt.done) case programEventClientGetStreamSdp: pub, ok := p.publishers[evt.path] if !ok { evt.res <- nil continue } evt.res <- pub.streamSdpText case programEventClientAnnounce: _, ok := p.publishers[evt.path] if ok { evt.res <- fmt.Errorf("another client is already publishing on path '%s'", evt.path) continue } evt.client.path = evt.path evt.client.streamSdpText = evt.sdpText evt.client.streamSdpParsed = evt.sdpParsed evt.client.state = _CLIENT_STATE_ANNOUNCE p.publishers[evt.path] = evt.client evt.res <- nil case programEventClientSetupPlay: pub, ok := p.publishers[evt.path] if !ok { evt.res <- fmt.Errorf("no one is streaming on path '%s'", evt.path) continue } if len(evt.client.streamTracks) >= len(pub.streamSdpParsed.Medias) { evt.res <- fmt.Errorf("all the tracks have already been setup") continue } evt.client.path = evt.path evt.client.streamProtocol = evt.protocol evt.client.streamTracks = append(evt.client.streamTracks, &track{ rtpPort: evt.rtpPort, rtcpPort: evt.rtcpPort, }) evt.client.state = _CLIENT_STATE_PRE_PLAY evt.res <- nil case programEventClientSetupRecord: evt.client.streamProtocol = evt.protocol evt.client.streamTracks = append(evt.client.streamTracks, &track{ rtpPort: evt.rtpPort, rtcpPort: evt.rtcpPort, }) evt.client.state = _CLIENT_STATE_PRE_RECORD evt.res <- nil case programEventClientPlay1: pub, ok := p.publishers[evt.client.path] if !ok { evt.res <- fmt.Errorf("no one is streaming on path '%s'", evt.client.path) continue } if len(evt.client.streamTracks) != len(pub.streamSdpParsed.Medias) { evt.res <- fmt.Errorf("not all tracks have been setup") continue } evt.res <- nil case programEventClientPlay2: p.receiverCount += 1 evt.client.state = _CLIENT_STATE_PLAY evt.res <- nil case programEventClientPause: p.receiverCount -= 1 evt.client.state = _CLIENT_STATE_PRE_PLAY evt.res <- nil case programEventClientRecord: p.publisherCount += 1 evt.client.state = _CLIENT_STATE_RECORD evt.res <- nil case programEventFrameUdp: // find publisher and track id from ip and port pub, trackId := func() (*serverClient, int) { for _, pub := range p.publishers { if pub.streamProtocol != _STREAM_PROTOCOL_UDP || pub.state != _CLIENT_STATE_RECORD || !pub.ip().Equal(evt.addr.IP) { continue } for i, t := range pub.streamTracks { if evt.trackFlowType == _TRACK_FLOW_RTP { if t.rtpPort == evt.addr.Port { return pub, i } } else { if t.rtcpPort == evt.addr.Port { return pub, i } } } } return nil, -1 }() if pub == nil { continue } pub.udpLastFrameTime = time.Now() p.forwardTrack(pub.path, trackId, evt.trackFlowType, evt.buf) case programEventFrameTcp: p.forwardTrack(evt.path, evt.trackId, evt.trackFlowType, evt.buf) case programEventTerminate: break outer } } go func() { for rawEvt := range p.events { switch evt := rawEvt.(type) { case programEventClientClose: close(evt.done) case programEventClientGetStreamSdp: evt.res <- nil case programEventClientAnnounce: evt.res <- fmt.Errorf("terminated") case programEventClientSetupPlay: evt.res <- fmt.Errorf("terminated") case programEventClientSetupRecord: evt.res <- fmt.Errorf("terminated") case programEventClientPlay1: evt.res <- fmt.Errorf("terminated") case programEventClientPlay2: evt.res <- fmt.Errorf("terminated") case programEventClientPause: evt.res <- fmt.Errorf("terminated") case programEventClientRecord: evt.res <- fmt.Errorf("terminated") } } }() p.tcpl.close() p.udplRtcp.close() p.udplRtp.close() for c := range p.clients { c.close() } close(p.events) close(p.done) } func (p *program) close() { p.events <- programEventTerminate{} <-p.done } func (p *program) forwardTrack(path string, id int, trackFlowType trackFlowType, frame []byte) { for c := range p.clients { if c.path == path && c.state == _CLIENT_STATE_PLAY { if c.streamProtocol == _STREAM_PROTOCOL_UDP { if trackFlowType == _TRACK_FLOW_RTP { p.udplRtp.write <- &udpWrite{ addr: &net.UDPAddr{ IP: c.ip(), Zone: c.zone(), Port: c.streamTracks[id].rtpPort, }, buf: frame, } } else { p.udplRtcp.write <- &udpWrite{ addr: &net.UDPAddr{ IP: c.ip(), Zone: c.zone(), Port: c.streamTracks[id].rtcpPort, }, buf: frame, } } } else { c.write <- &gortsplib.InterleavedFrame{ Channel: trackToInterleavedChannel(id, trackFlowType), Content: frame, } } } } } func main() { _, err := newProgram(os.Args[1:]) if err != nil { log.Fatal("ERR: ", err) } select {} }