From 1b36c2a873cd18e9c37969e02285959f8d71025d Mon Sep 17 00:00:00 2001 From: Renovate Bot Date: Tue, 1 Sep 2020 21:30:21 +0000 Subject: [PATCH 1/9] Update golang Docker tag to v1.14.8 Generated by renovateBot --- docker/avp.Dockerfile | 2 +- docker/biz.Dockerfile | 2 +- docker/islb.Dockerfile | 2 +- docker/sfu.Dockerfile | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/docker/avp.Dockerfile b/docker/avp.Dockerfile index b87430151..bd86f59e3 100644 --- a/docker/avp.Dockerfile +++ b/docker/avp.Dockerfile @@ -1,4 +1,4 @@ -FROM golang:1.14.5-stretch +FROM golang:1.14.8-stretch ENV GO111MODULE=on diff --git a/docker/biz.Dockerfile b/docker/biz.Dockerfile index 1873fcaa4..583f31050 100644 --- a/docker/biz.Dockerfile +++ b/docker/biz.Dockerfile @@ -1,4 +1,4 @@ -FROM golang:1.14.5-stretch +FROM golang:1.14.8-stretch ENV GO111MODULE=on diff --git a/docker/islb.Dockerfile b/docker/islb.Dockerfile index ce06bcaa3..733952b47 100644 --- a/docker/islb.Dockerfile +++ b/docker/islb.Dockerfile @@ -1,4 +1,4 @@ -FROM golang:1.14.5-stretch +FROM golang:1.14.8-stretch ENV GO111MODULE=on diff --git a/docker/sfu.Dockerfile b/docker/sfu.Dockerfile index b95ff7258..54050c230 100644 --- a/docker/sfu.Dockerfile +++ b/docker/sfu.Dockerfile @@ -1,4 +1,4 @@ -FROM golang:1.14.5-stretch +FROM golang:1.14.8-stretch ENV GO111MODULE=on From efa76f40a95330c674fbd8dca5d19e2feb9b26bb Mon Sep 17 00:00:00 2001 From: Renovate Bot Date: Sun, 30 Aug 2020 15:40:15 +0000 Subject: [PATCH 2/9] Update module google/uuid to v1.1.2 Generated by renovateBot --- go.mod | 2 +- go.sum | 2 ++ 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/go.mod b/go.mod index 1cf46057f..6dfcc87a9 100644 --- a/go.mod +++ b/go.mod @@ -10,7 +10,7 @@ require ( github.com/dustin/go-humanize v1.0.0 // indirect github.com/go-ole/go-ole v1.2.4 // indirect github.com/go-redis/redis/v7 v7.4.0 - github.com/google/uuid v1.1.1 + github.com/google/uuid v1.1.2 github.com/klauspost/reedsolomon v1.9.9 // indirect github.com/mmcloughlin/avo v0.0.0-20200523190732-4439b6b2c061 // indirect github.com/notedit/sdp v0.0.4 diff --git a/go.sum b/go.sum index 88daf3174..92cdbd6ae 100644 --- a/go.sum +++ b/go.sum @@ -115,6 +115,8 @@ github.com/google/pprof v0.0.0-20190515194954-54271f7e092f/go.mod h1:zfwlbNMJ+OI github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI= github.com/google/uuid v1.1.1 h1:Gkbcsh/GbpXz7lPftLA3P6TYMwjCLYm83jiFQZF/3gY= github.com/google/uuid v1.1.1/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= +github.com/google/uuid v1.1.2 h1:EVhdT+1Kseyi1/pUmXKaFxYsDNy9RQYkMWRH68J/W7Y= +github.com/google/uuid v1.1.2/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/googleapis/gax-go/v2 v2.0.4/go.mod h1:0Wqv26UfaUD9n4G6kQubkQ+KchISgw+vpHVxEJEs9eg= github.com/googleapis/gax-go/v2 v2.0.5/go.mod h1:DWXyrwAJ9X0FpwwEdw+IPEYBICEFu5mhpdKc/us6bOk= github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1 h1:EGx4pi6eqNxGaHF6qqu48+N2wcFQ5qg5FXgOdqsJ5d8= From 8f211b2bdac1fe98b0ac166fbdcd471d3026a0a9 Mon Sep 17 00:00:00 2001 From: Renovate Bot Date: Tue, 1 Sep 2020 01:17:28 +0000 Subject: [PATCH 3/9] Update golang.org/x/crypto commit hash to 5c72a88 Generated by renovateBot --- go.mod | 2 +- go.sum | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/go.mod b/go.mod index 6dfcc87a9..d062307f6 100644 --- a/go.mod +++ b/go.mod @@ -27,6 +27,6 @@ require ( github.com/tjfoc/gmsm v1.3.2 // indirect github.com/xtaci/kcp-go v5.4.20+incompatible go.etcd.io/etcd v3.3.22+incompatible - golang.org/x/crypto v0.0.0-20200728195943-123391ffb6de + golang.org/x/crypto v0.0.0-20200820211705-5c72a883971a sigs.k8s.io/yaml v1.2.0 // indirect ) diff --git a/go.sum b/go.sum index 92cdbd6ae..48a8ca71d 100644 --- a/go.sum +++ b/go.sum @@ -402,8 +402,8 @@ golang.org/x/crypto v0.0.0-20200602180216-279210d13fed/go.mod h1:LzIPMQfyMNhhGPh golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9 h1:psW17arqaxU48Z5kZ0CQnkZWQJsqcURM6tKiBApRjXI= golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/crypto v0.0.0-20200709230013-948cd5f35899/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= -golang.org/x/crypto v0.0.0-20200728195943-123391ffb6de h1:ikNHVSjEfnvz6sxdSPCaPt572qowuyMDMJLLm3Db3ig= -golang.org/x/crypto v0.0.0-20200728195943-123391ffb6de/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= +golang.org/x/crypto v0.0.0-20200820211705-5c72a883971a h1:vclmkQCjlDX5OydZ9wv8rBCcS0QyQY66Mpf/7BZbInM= +golang.org/x/crypto v0.0.0-20200820211705-5c72a883971a/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/exp v0.0.0-20190510132918-efd6b22b2522/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8= From 0795404d8af71ae2ba162e889e81460306681d38 Mon Sep 17 00:00:00 2001 From: Renovate Bot Date: Tue, 1 Sep 2020 04:57:39 +0000 Subject: [PATCH 4/9] Update module shirou/gopsutil to v2.20.8 Generated by renovateBot --- go.mod | 2 +- go.sum | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/go.mod b/go.mod index d062307f6..fa9f3bc56 100644 --- a/go.mod +++ b/go.mod @@ -22,7 +22,7 @@ require ( github.com/pion/transport v0.10.1 github.com/pion/webrtc/v2 v2.2.23 github.com/rs/zerolog v1.19.0 - github.com/shirou/gopsutil v2.20.6+incompatible + github.com/shirou/gopsutil v2.20.8+incompatible github.com/spf13/viper v1.7.1 github.com/tjfoc/gmsm v1.3.2 // indirect github.com/xtaci/kcp-go v5.4.20+incompatible diff --git a/go.sum b/go.sum index 48a8ca71d..f90ecba7d 100644 --- a/go.sum +++ b/go.sum @@ -323,8 +323,8 @@ github.com/rs/zerolog v1.19.0/go.mod h1:IzD0RJ65iWH0w97OQQebJEvTZYvsCUm9WVLWBQrJ github.com/ryanuber/columnize v0.0.0-20160712163229-9b3edd62028f/go.mod h1:sm1tb6uqfes/u+d4ooFouqFdy9/2g9QGwK3SQygK0Ts= github.com/sclevine/agouti v3.0.0+incompatible/go.mod h1:b4WX9W9L1sfQKXeJf1mUTLZKJ48R1S7H23Ji7oFO5Bw= github.com/sean-/seed v0.0.0-20170313163322-e2103e2c3529/go.mod h1:DxrIzT+xaE7yg65j358z/aeFdxmN0P9QXhEzd20vsDc= -github.com/shirou/gopsutil v2.20.6+incompatible h1:P37G9YH8M4vqkKcwBosp+URN5O8Tay67D2MbR361ioY= -github.com/shirou/gopsutil v2.20.6+incompatible/go.mod h1:5b4v6he4MtMOwMlS0TUMTu2PcXUg8+E1lC7eC3UO/RA= +github.com/shirou/gopsutil v2.20.8+incompatible h1:8c7Atn0FAUZJo+f4wYbN0iVpdWniCQk7IYwGtgdh1mY= +github.com/shirou/gopsutil v2.20.8+incompatible/go.mod h1:5b4v6he4MtMOwMlS0TUMTu2PcXUg8+E1lC7eC3UO/RA= github.com/sirupsen/logrus v1.2.0 h1:juTguoYk5qI21pwyTXY3B3Y5cOTH3ZUyZCg1v/mihuo= github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo= github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d h1:zE9ykElWQ6/NYmHa3jpm/yHnI4xSofP+UP6SpjHcSeM= From ecc2ab9c627c31da038049531afaed185c2d8a39 Mon Sep 17 00:00:00 2001 From: Renovate Bot Date: Sat, 19 Sep 2020 21:52:20 +0000 Subject: [PATCH 5/9] Update module pion/webrtc/v2 to v2.2.26 Generated by renovateBot --- go.mod | 2 +- go.sum | 12 ++++++------ 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/go.mod b/go.mod index fa9f3bc56..87c16410f 100644 --- a/go.mod +++ b/go.mod @@ -20,7 +20,7 @@ require ( github.com/pion/rtp v1.6.0 github.com/pion/stun v0.3.5 github.com/pion/transport v0.10.1 - github.com/pion/webrtc/v2 v2.2.23 + github.com/pion/webrtc/v2 v2.2.26 github.com/rs/zerolog v1.19.0 github.com/shirou/gopsutil v2.20.8+incompatible github.com/spf13/viper v1.7.1 diff --git a/go.sum b/go.sum index f90ecba7d..d88686f78 100644 --- a/go.sum +++ b/go.sum @@ -229,8 +229,8 @@ github.com/pelletier/go-toml v1.2.0 h1:T5zMGML61Wp+FlcbWjRDT7yAxhJNAiPPLOFECq181 github.com/pelletier/go-toml v1.2.0/go.mod h1:5z9KED0ma1S8pY6P1sdut58dfprrGBbd/94hg7ilaic= github.com/pion/datachannel v1.4.17 h1:8CChK5VrJoGrwKCysoTscoWvshCAFpUkgY11Tqgz5hE= github.com/pion/datachannel v1.4.17/go.mod h1:+vPQfypU9vSsyPXogYj1hBThWQ6MNXEQoQAzxoPvjYM= -github.com/pion/datachannel v1.4.19 h1:IcOmm5fdDzJVCMgFYDCMtFC+lrjG78KcMYXH+gOo6ys= -github.com/pion/datachannel v1.4.19/go.mod h1:JzKF/zzeWgkOYwQ+KFb8JzbrUt8s63um+Qunu8VqTyw= +github.com/pion/datachannel v1.4.21 h1:3ZvhNyfmxsAqltQrApLPQMhSFNA+aT87RqyCq4OXmf0= +github.com/pion/datachannel v1.4.21/go.mod h1:oiNyP4gHx2DIwRzX/MFyH0Rz/Gz05OgBlayAI2hAWjg= github.com/pion/dtls/v2 v2.0.0/go.mod h1:VkY5VL2wtsQQOG60xQ4lkV5pdn0wwBBTzCfRJqXhp3A= github.com/pion/dtls/v2 v2.0.1 h1:ddE7+V0faYRbyh4uPsRZ2vLdRrjVZn+wmCfI7jlBfaA= github.com/pion/dtls/v2 v2.0.1/go.mod h1:uMQkz2W0cSqY00xav7WByQ4Hb+18xeQh2oH2fRezr5U= @@ -262,8 +262,8 @@ github.com/pion/rtp v1.6.0 h1:4Ssnl/T5W2LzxHj9ssYpGVEQh3YYhQFNVmSWO88MMwk= github.com/pion/rtp v1.6.0/go.mod h1:QgfogHsMBVE/RFNno467U/KBqfUywEH+HK+0rtnwsdI= github.com/pion/sctp v1.7.6 h1:8qZTdJtbKfAns/Hv5L0PAj8FyXcsKhMH1pKUCGisQg4= github.com/pion/sctp v1.7.6/go.mod h1:ichkYQ5tlgCQwEwvgfdcAolqx1nHbYCxo4D7zK/K0X8= -github.com/pion/sctp v1.7.8 h1:tEWel2BKXLZitU+LxY3GDeQXoKeTafYasiu/X+XBKNM= -github.com/pion/sctp v1.7.8/go.mod h1:EhpTUQu1/lcK3xI+eriS6/96fWetHGCvBi9MSsnaBN0= +github.com/pion/sctp v1.7.10 h1:o3p3/hZB5Cx12RMGyWmItevJtZ6o2cpuxaw6GOS4x+8= +github.com/pion/sctp v1.7.10/go.mod h1:EhpTUQu1/lcK3xI+eriS6/96fWetHGCvBi9MSsnaBN0= github.com/pion/sdp/v2 v2.3.8 h1:iXlpgsRx3dFGdU8vhSOKZiiTVp2BjIXxh0ims6MqBzk= github.com/pion/sdp/v2 v2.3.8/go.mod h1:VyECSprlbQuv07sO/d0grXEX890kF13YLQt2F0NOqYA= github.com/pion/sdp/v2 v2.4.0 h1:luUtaETR5x2KNNpvEMv/r4Y+/kzImzbz4Lm1z8eQNQI= @@ -289,8 +289,8 @@ github.com/pion/udp v0.1.0 h1:uGxQsNyrqG3GLINv36Ff60covYmfrLoxzwnCsIYspXI= github.com/pion/udp v0.1.0/go.mod h1:BPELIjbwE9PRbd/zxI/KYBnbo7B6+oA6YuEaNE8lths= github.com/pion/webrtc/v2 v2.2.17 h1:2nkMUXFHfdOp1zOLmBgrbFrKDgTxDGk/GVcI7PQefp4= github.com/pion/webrtc/v2 v2.2.17/go.mod h1:/ZI6Xm5uU5ENLa5NRGESDAUIwPbOkzMgdUeD7uNFCyM= -github.com/pion/webrtc/v2 v2.2.23 h1:rZdOC95fwUCoQFVjHooPAayx/vhs3SLHFz8J/iRkAuk= -github.com/pion/webrtc/v2 v2.2.23/go.mod h1:1lN/3EcATkQxc7GJSQbISCGC2l64Xu2VSLpwEG3c/tM= +github.com/pion/webrtc/v2 v2.2.26 h1:01hWE26pL3LgqfxvQ1fr6O4ZtyRFFJmQEZK39pHWfFc= +github.com/pion/webrtc/v2 v2.2.26/go.mod h1:XMZbZRNHyPDe1gzTIHFcQu02283YO45CbiwFgKvXnmc= github.com/pion/webrtc/v3 v3.0.0-20200625164527-89d7de178734/go.mod h1:Lw2j6j+ORAHFC8nDyGwslmWMvDyvTXNZclvCFIBBdkY= github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= From 1562756447e9c353e7328a49b1017dc594d38832 Mon Sep 17 00:00:00 2001 From: Renovate Bot Date: Mon, 28 Sep 2020 04:55:43 +0000 Subject: [PATCH 6/9] Update module pion/rtp to v1.6.1 Generated by renovateBot --- go.mod | 2 +- go.sum | 2 ++ 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/go.mod b/go.mod index 87c16410f..85e0c81fd 100644 --- a/go.mod +++ b/go.mod @@ -17,7 +17,7 @@ require ( github.com/pion/ion-avp v0.0.0-20200628194531-b7b82b17ae83 github.com/pion/ion-sfu v0.0.0-20200628194606-99b1a0028619 // indirect github.com/pion/rtcp v1.2.3 - github.com/pion/rtp v1.6.0 + github.com/pion/rtp v1.6.1 github.com/pion/stun v0.3.5 github.com/pion/transport v0.10.1 github.com/pion/webrtc/v2 v2.2.26 diff --git a/go.sum b/go.sum index d88686f78..296fad9f5 100644 --- a/go.sum +++ b/go.sum @@ -260,6 +260,8 @@ github.com/pion/rtp v1.5.5 h1:WTqWdmBuIj+luh8Wg6XVX+w7OytZHAIgtC7uSvgEl9Y= github.com/pion/rtp v1.5.5/go.mod h1:bg60AL5GotNOlYZsqycbhDtEV3TkfbpXG0KBiUq29Mg= github.com/pion/rtp v1.6.0 h1:4Ssnl/T5W2LzxHj9ssYpGVEQh3YYhQFNVmSWO88MMwk= github.com/pion/rtp v1.6.0/go.mod h1:QgfogHsMBVE/RFNno467U/KBqfUywEH+HK+0rtnwsdI= +github.com/pion/rtp v1.6.1 h1:2Y2elcVBrahYnHKN2X7rMHX/r1R4TEBMP1LaVu/wNhk= +github.com/pion/rtp v1.6.1/go.mod h1:bDb5n+BFZxXx0Ea7E5qe+klMuqiBrP+w8XSjiWtCUko= github.com/pion/sctp v1.7.6 h1:8qZTdJtbKfAns/Hv5L0PAj8FyXcsKhMH1pKUCGisQg4= github.com/pion/sctp v1.7.6/go.mod h1:ichkYQ5tlgCQwEwvgfdcAolqx1nHbYCxo4D7zK/K0X8= github.com/pion/sctp v1.7.10 h1:o3p3/hZB5Cx12RMGyWmItevJtZ6o2cpuxaw6GOS4x+8= From 2b1ef3e684d9150fe43e3c7ed751eebaba142980 Mon Sep 17 00:00:00 2001 From: Jin Gong <40499268+cgojin@users.noreply.github.com> Date: Mon, 19 Oct 2020 17:31:11 +0800 Subject: [PATCH 7/9] Authentication, reconnects, mac hardware encoding, and fix leaky subscriptions (#413) * Auth (#1) * support jwt auth * readme * Fix blocking call to start NewWebSocketServer preventing main() * Launch biz serviceWatcher in bg (like other nodes) * Rework auth config structure, add support for room auth, add support for key validation * Update naming connectionAuth (more consistent) * Fix assignment typo * Add some debug logging * Use interface for authenticatable test on msg, add example key * Fix connection auth (use mapClaims) * Add authentication support to kubernetes chart * Update docs * Simplify claims Co-authored-by: Tarrence van As * cb4597 - Allow rejoining the room with different peer (#1) * Allow rejoining the room with different peer Currently we are ignoring join message from participant who already joined the room. This is causing issue in case where participant lost connectivity and needed to open new websocket (ip address change for example). In this PR we will kick out old participant in that case, close the transport and use new peer to relay messages to. * address possible npe * Add config values in kube chart for pli/remb cycle * Add config options * Use updated 264 profile for ExternalEncoder * cb5993 Fix stale subscriptions (#4) * Handle websocket closure (#2) When websocket is connection is lost, protoo sometimes sends 100 error code (instead of 100x). This ends up not removing client from the room. As consequence client is left spinning indefinitelly. * Fix stale subscriptions * remove that >100 change that exists in twilight zone * Syncing all streams on join (#5) Co-authored-by: Billy Lindeman Co-authored-by: Tarrence van As Co-authored-by: Nikola Novakovic Co-authored-by: Tim Su --- cmd/biz/main.go | 13 ++- configs/biz.toml | 16 +++- configs/docker/biz.toml | 17 +++- docs/authentication.md | 35 +++++++++ go.mod | 3 + go.sum | 1 + kube/ion-chart/templates/config.yaml | 17 +++- kube/ion-chart/values.yaml | 11 +++ pkg/conf/biz/conf.go | 29 +++++-- pkg/node/biz/client.go | 74 ++++++++++++------ pkg/node/biz/dispatch.go | 71 ++++++++++++++--- pkg/node/biz/init.go | 5 +- pkg/node/sfu/internal.go | 21 +++-- pkg/proto/biz.go | 25 ++++++ pkg/proto/islb.go | 2 +- pkg/rtc/router.go | 23 +++++- pkg/rtc/rtc.go | 6 +- pkg/rtc/transport/webrtctransport.go | 8 +- pkg/signal/handle.go | 46 +++++++++-- pkg/signal/init.go | 20 ++--- pkg/signal/room.go | 9 +++ pkg/signal/server.go | 113 +++++++++++++++++++++++++++ 22 files changed, 477 insertions(+), 88 deletions(-) create mode 100644 docs/authentication.md create mode 100644 pkg/signal/server.go diff --git a/cmd/biz/main.go b/cmd/biz/main.go index 7c1f8b65c..24674ffbe 100644 --- a/cmd/biz/main.go +++ b/cmd/biz/main.go @@ -13,7 +13,14 @@ import ( func init() { log.Init(conf.Log.Level) - signal.Init(conf.Signal.Host, conf.Signal.Port, conf.Signal.Cert, conf.Signal.Key, conf.Signal.AllowDisconnected, biz.Entry) + signal.Init(signal.WebSocketServerConfig{ + Host: conf.Signal.Host, + Port: conf.Signal.Port, + CertFile: conf.Signal.Cert, + KeyFile: conf.Signal.Key, + WebSocketPath: conf.Signal.WebSocketPath, + AuthConnection: conf.Signal.AuthConnection, + }, conf.Signal.AllowDisconnected, biz.Entry) } func close() { @@ -38,10 +45,10 @@ func main() { rpcID := serviceNode.GetRPCChannel() eventID := serviceNode.GetEventChannel() - biz.Init(conf.Global.Dc, serviceNode.NodeInfo().ID, rpcID, eventID, conf.Nats.URL) + biz.Init(conf.Global.Dc, serviceNode.NodeInfo().ID, rpcID, eventID, conf.Nats.URL, conf.Signal.AuthRoom) serviceWatcher := discovery.NewServiceWatcher(conf.Etcd.Addrs, conf.Global.Dc) - serviceWatcher.WatchServiceNode("islb", biz.WatchServiceNodes) + go serviceWatcher.WatchServiceNode("islb", biz.WatchServiceNodes) defer close() select {} diff --git a/configs/biz.toml b/configs/biz.toml index 382e17842..e176e62f5 100644 --- a/configs/biz.toml +++ b/configs/biz.toml @@ -8,8 +8,8 @@ addr = "127.0.0.1" dc = "dc1" [log] -level = "info" -# level = "debug" +#level = "info" +level = "debug" [etcd] # ["ip:port", "ip:port"] @@ -21,8 +21,18 @@ host = "0.0.0.0" port = "8443" # cert= "configs/certs/cert.pem" # key= "configs/certs/key.pem" +path = "/ws" allow_disconnected = false +[signal.auth_connection] +enabled = false +key_type = "HMAC" # this selects the Signing method https://godoc.org/github.com/dgrijalva/jwt-go#SigningMethod +key = "1q2dGu5pzikcrECJgW3ADfXX3EsmoD99SYvSVCpDsJrAqxou5tUNbHPvkEFI4bTS" + +[signal.auth_room] +enabled = false +key_type = "HMAC" +key = "1q2dGu5pzikcrECJgW3ADfXX3EsmoD99SYvSVCpDsJrAqxou5tUNbHPvkEFI4bTS" + [nats] url = "nats://127.0.0.1:4223" - diff --git a/configs/docker/biz.toml b/configs/docker/biz.toml index d5f70951f..58f5d7182 100644 --- a/configs/docker/biz.toml +++ b/configs/docker/biz.toml @@ -8,8 +8,8 @@ addr = "127.0.0.1" dc = "dc1" [log] -level = "info" -# level = "debug" +# level = "info" +level = "debug" [etcd] # ["ip:port", "ip:port"] @@ -21,6 +21,19 @@ host = "0.0.0.0" port = "8443" # cert= "/configs/certs/cert.pem" # key= "/configs/certs/key.pem" +path = "/ws" +allow_disconnected = false + +[signal.auth_connection] +enabled = true +key_type = "HMAC" # this selects the Signing method https://godoc.org/github.com/dgrijalva/jwt-go#SigningMethod +key = "1q2dGu5pzikcrECJgW3ADfXX3EsmoD99SYvSVCpDsJrAqxou5tUNbHPvkEFI4bTS" + +[signal.auth_room] +enabled = true +key_type = "HMAC" +key = "1q2dGu5pzikcrECJgW3ADfXX3EsmoD99SYvSVCpDsJrAqxou5tUNbHPvkEFI4bTS" + [nats] url = "nats://nats:4222" diff --git a/docs/authentication.md b/docs/authentication.md new file mode 100644 index 000000000..a7056a71e --- /dev/null +++ b/docs/authentication.md @@ -0,0 +1,35 @@ +# Authentication + +Ion supports JWT based authorization. Token issuance is not handled by ION, but the token signature is validated using the key set in the config. Both connection and message level authentication is supported (for rooms). + +To enable authentication, set enabled to true in the config for the type you want + +```toml +[signal.auth_connection] +enabled = true +key_type = "HMAC" +key = "$HMAC_SECRET" + +[signal.auth_room] +enabled = true +key_type = "HMAC" +key = "$HMAC_SECRET" +``` + +*Currently only HMAC is supported, but new key types can be added in the future* + + +## Connection Auth +JWT tokens should be passed using an `access_token` query parameter in the websocket connection url to `biz`. This does not currently validate any claims, just that the token has a valid signature. + + + +## Room Auth +JWT Tokens should be passed as a `"token"` parameter on the JoinMsg and Publish requests via websocket. +```json +{ + "rid": "...", // room id + "videopublish": true, // can a peer publish video + "audiopublish": true // can a peer publish audio +} +``` diff --git a/go.mod b/go.mod index 85e0c81fd..2a6f4e05c 100644 --- a/go.mod +++ b/go.mod @@ -7,10 +7,12 @@ require ( github.com/cloudwebrtc/go-protoo v0.0.0-20200602160428-0a199e23f7e0 github.com/cloudwebrtc/nats-protoo v0.0.0-20200604135451-87b43396e8de github.com/coreos/etcd v3.3.22+incompatible // indirect + github.com/dgrijalva/jwt-go v3.2.0+incompatible github.com/dustin/go-humanize v1.0.0 // indirect github.com/go-ole/go-ole v1.2.4 // indirect github.com/go-redis/redis/v7 v7.4.0 github.com/google/uuid v1.1.2 + github.com/gorilla/websocket v1.4.2 github.com/klauspost/reedsolomon v1.9.9 // indirect github.com/mmcloughlin/avo v0.0.0-20200523190732-4439b6b2c061 // indirect github.com/notedit/sdp v0.0.4 @@ -21,6 +23,7 @@ require ( github.com/pion/stun v0.3.5 github.com/pion/transport v0.10.1 github.com/pion/webrtc/v2 v2.2.26 + github.com/pkg/errors v0.9.1 github.com/rs/zerolog v1.19.0 github.com/shirou/gopsutil v2.20.8+incompatible github.com/spf13/viper v1.7.1 diff --git a/go.sum b/go.sum index 296fad9f5..e668a21d4 100644 --- a/go.sum +++ b/go.sum @@ -40,6 +40,7 @@ github.com/chuckpreslar/emission v0.0.0-20170206194824-a7ddd980baf9/go.mod h1:2w github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw= github.com/cloudwebrtc/go-protoo v0.0.0-20200602160428-0a199e23f7e0 h1:7lmqBSdb1ILwUJalqJdIoWPH0cnnQt4NshxXnVvrmx0= github.com/cloudwebrtc/go-protoo v0.0.0-20200602160428-0a199e23f7e0/go.mod h1:Q0DiItmsD5iCBdeID9Xu03ok8bemc78XJ+0rYATQbuQ= +github.com/cloudwebrtc/go-protoo v0.0.0-20200926140535-79ecde67b906 h1:CXJrfUVNhSAKWnb+oSvd8MBCQJHK6+RS7jLvx2z3ba0= github.com/cloudwebrtc/nats-protoo v0.0.0-20200604135451-87b43396e8de h1:yPvjphU5iEeYTOOPzSwU9TNoy0nOwtv5LYMZMVtOrPY= github.com/cloudwebrtc/nats-protoo v0.0.0-20200604135451-87b43396e8de/go.mod h1:zwKwTqbrcBl9o2AHopHYlMh4KM3wMQHYeR6TrtoRCQ0= github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc= diff --git a/kube/ion-chart/templates/config.yaml b/kube/ion-chart/templates/config.yaml index 146d675ca..c7d5f5f66 100644 --- a/kube/ion-chart/templates/config.yaml +++ b/kube/ion-chart/templates/config.yaml @@ -27,8 +27,19 @@ data: #listen ip port host = "0.0.0.0" port = "8443" + path = "/ws" #allow_disconnected = true + [signal.auth_connection] + enabled = {{ .Values.biz.auth.connection.enabled }} + key_type = "HMAC" + key = "{{.Values.biz.auth.connection.hmac}}" + + [signal.auth_room] + enabled = {{ .Values.biz.auth.room.enabled }} + key_type = "HMAC" + key = "{{.Values.biz.auth.room.hmac}}" + [nats] url = "nats://{{ .Release.Name }}-nats-client:4222" @@ -71,11 +82,11 @@ data: [plugins.jitterbuffer] on = true # the remb cycle sending to pub, this told the pub it's bandwidth - rembcycle = 2 + rembcycle = {{ .Values.sfu.jitterbuffer.rembcycle }} # pli cycle sending to pub, and pub will send a key frame - plicycle = 1 + plicycle = {{ .Values.sfu.jitterbuffer.plicycle }} # this limit the remb bandwidth - maxbandwidth = 1000 + maxbandwidth = {{ .Values.sfu.jitterbuffer.maxbandwidth }} # max buffer time by ms maxbuffertime = 1000 diff --git a/kube/ion-chart/values.yaml b/kube/ion-chart/values.yaml index 24ffec38a..8779dc036 100644 --- a/kube/ion-chart/values.yaml +++ b/kube/ion-chart/values.yaml @@ -67,12 +67,23 @@ tolerations: [] affinity: {} sfu: + jitterbuffer: + plicycle: 5 + rembcycle: 2 + maxbandwidth: 10000 image: repository: pionwebrtc/ion-sfu pullPolicy: IfNotPresent # Overrides the image tag whose default is the chart appVersion. tag: "v0.4.6" biz: + auth: + connection: + enabled: false + hmac: 1q2dGu5pzikcrECJgW3ADfXX3EsmoD99SYvSVCpDsJrAqxou5tUNbHPvkEFI4bTS + room: + enabled: false + hmac: 1q2dGu5pzikcrECJgW3ADfXX3EsmoD99SYvSVCpDsJrAqxou5tUNbHPvkEFI4bTS image: repository: pionwebrtc/ion-biz pullPolicy: IfNotPresent diff --git a/pkg/conf/biz/conf.go b/pkg/conf/biz/conf.go index e7e6715b9..3a59fee50 100644 --- a/pkg/conf/biz/conf.go +++ b/pkg/conf/biz/conf.go @@ -5,6 +5,7 @@ import ( "fmt" "os" + "github.com/dgrijalva/jwt-go" "github.com/spf13/viper" ) @@ -40,11 +41,29 @@ type etcd struct { } type signal struct { - Host string `mapstructure:"host"` - Port int `mapstructure:"port"` - Cert string `mapstructure:"cert"` - Key string `mapstructure:"key"` - AllowDisconnected bool `mapstructure:"allow_disconnected"` + Host string `mapstructure:"host"` + Port int `mapstructure:"port"` + Cert string `mapstructure:"cert"` + Key string `mapstructure:"key"` + WebSocketPath string `mapstructure:"path"` + AllowDisconnected bool `mapstructure:"allow_disconnected"` + + AuthConnection AuthConfig `mapstructure:"auth_connection"` + AuthRoom AuthConfig `mapstructure:"auth_room"` +} + +type AuthConfig struct { + Enabled bool `mapstructure:"enabled"` + Key string `mapstructure:"key"` + KeyType string `mapstructure:"key_type"` +} + +func (a AuthConfig) KeyFunc(t *jwt.Token) (interface{}, error) { + switch a.KeyType { + //TODO: add more support for keytypes here + default: + return []byte(a.Key), nil + } } type nats struct { diff --git a/pkg/node/biz/client.go b/pkg/node/biz/client.go index 13a58e1f2..55de49f96 100644 --- a/pkg/node/biz/client.go +++ b/pkg/node/biz/client.go @@ -25,43 +25,51 @@ func join(peer *signal.Peer, msg proto.JoinMsg) (interface{}, *nprotoo.Error) { return nil, ridError } + islb, found := getRPCForIslb() + if !found { + return nil, util.NewNpError(500, "Not found any node for islb.") + } + uid := peer.ID() + //already joined this room if signal.HasPeer(rid, peer) { - return emptyMap, nil + log.Infof("biz.join peer.ID()=%s already joined, removing old peer", peer.ID()) + islb.AsyncRequest(proto.IslbOnStreamRemove, util.Map("rid", rid, "uid", uid)) + + _, err := islb.SyncRequest(proto.IslbClientOnLeave, util.Map("rid", rid, "uid", uid)) + if err != nil { + log.Errorf("IslbClientOnLeave failed %v", err.Error()) + } + + oldPeer := signal.GetPeer(rid, peer.ID()) + if oldPeer != nil { + signal.DelPeer(rid, peer.ID()) + oldPeer.Close() + } } + log.Infof("biz.join adding new peer") signal.AddPeer(rid, peer) - islb, found := getRPCForIslb() - if !found { - return nil, util.NewNpError(500, "Not found any node for islb.") - } // Send join => islb info := msg.Info - uid := peer.ID() + _, err := islb.SyncRequest(proto.IslbClientOnJoin, util.Map("rid", rid, "uid", uid, "info", info)) if err != nil { log.Errorf("IslbClientOnJoin failed %v", err.Error()) } - // Send getPubs => islb - islb.AsyncRequest(proto.IslbGetPubs, msg.RoomInfo).Then( - func(result nprotoo.RawMessage) { - var resMsg proto.GetPubResp - if err := result.Unmarshal(&resMsg); err != nil { - log.Errorf("Unmarshal pub response %v", err) - return - } - log.Infof("IslbGetPubs: result=%v", resMsg) - for _, pub := range resMsg.Pubs { - if pub.MID == "" { - continue - } - notif := proto.StreamAddMsg(pub) - peer.Notify(proto.ClientOnStreamAdd, notif) - } - }, - func(err *nprotoo.Error) {}) - return emptyMap, nil + result, err := islb.SyncRequest(proto.IslbGetPubs, msg.RoomInfo) + if err != nil { + log.Errorf("IslbGetPubs failed %v", err.Error()) + return nil, util.NewNpError(500, err.Reason) + } + var resMsg proto.GetPubResp + if err := result.Unmarshal(&resMsg); err != nil { + log.Errorf("Unmarshal pub response %v", err) + return nil, util.NewNpError(500, err.Reason) + } + log.Infof("IslbGetPubs: result=%v", resMsg) + return resMsg, nil } func leave(peer *signal.Peer, msg proto.LeaveMsg) (interface{}, *nprotoo.Error) { @@ -87,6 +95,20 @@ func leave(peer *signal.Peer, msg proto.LeaveMsg) (interface{}, *nprotoo.Error) if err != nil { log.Errorf("IslbOnStreamRemove failed %v", err.Error()) } + + // Unsubscribe from all remote streams + _, sfu, err := getRPCForSFU("", rid) + if err != nil { + log.Warnf("Not found any sfu node, reject: %d => %s", err.Code, err.Reason) + return nil, util.NewNpError(err.Code, err.Reason) + } + + _, err = sfu.SyncRequest(proto.ClientUnSubscribe, util.Map("uid", uid)) + if err != nil { + log.Warnf("unsubscribe: %d => %s", err.Code, err.Reason) + return nil, util.NewNpError(err.Code, err.Reason) + } + signal.DelPeer(rid, peer.ID()) return emptyMap, nil } @@ -240,7 +262,7 @@ func unsubscribe(peer *signal.Peer, msg proto.UnsubscribeMsg) (interface{}, *npr return nil, util.NewNpError(err.Code, err.Reason) } - log.Infof("publish: result => %v", result) + log.Infof("unsubscribe: result => %v", result) return result, nil } diff --git a/pkg/node/biz/dispatch.go b/pkg/node/biz/dispatch.go index 73ea99f14..8e47d980d 100644 --- a/pkg/node/biz/dispatch.go +++ b/pkg/node/biz/dispatch.go @@ -6,6 +6,7 @@ import ( "net/http" nprotoo "github.com/cloudwebrtc/nats-protoo" + "github.com/dgrijalva/jwt-go" "github.com/pion/ion/pkg/discovery" "github.com/pion/ion/pkg/log" "github.com/pion/ion/pkg/proto" @@ -13,17 +14,67 @@ import ( "github.com/pion/ion/pkg/util" ) +var ( + errorTokenRequired = util.NewNpError(http.StatusUnauthorized, "Authorization token required for access") + errorInvalidRoomToken = util.NewNpError(http.StatusUnauthorized, "Invalid room token") + errorUnauthorizedRoomAccess = util.NewNpError(http.StatusForbidden, "Permission not sufficient for room") +) + // ParseProtoo Unmarshals a protoo payload. -func ParseProtoo(msg json.RawMessage, msgType interface{}) *nprotoo.Error { +func ParseProtoo(msg json.RawMessage, connectionClaims *signal.Claims, msgType interface{}) *nprotoo.Error { if err := json.Unmarshal(msg, &msgType); err != nil { log.Errorf("Biz.Entry parse error %v", err.Error()) return util.NewNpError(http.StatusBadRequest, fmt.Sprintf("Error parsing request object %v", err.Error())) } + + authenticatable, ok := msgType.(proto.Authenticatable) + log.Debugf("msgType: %#v \nHasRoomInfo: %#v ok: %v", msgType, authenticatable, ok) + if ok && roomAuth.Enabled { + return authenticateRoom(msgType, connectionClaims, authenticatable) + } + return nil } +// authenticateRoom checks both the connection token AND an optional message token for RID claims +// returns nil for success and returns an error if there are no valid claims for the RID +func authenticateRoom(msgType interface{}, connectionClaims *signal.Claims, authenticatable proto.Authenticatable) *nprotoo.Error { + log.Debugf("authenticateRoom: checking claims on token %v", authenticatable.Token()) + // Connection token has valid claim on this room, succeed early + if connectionClaims != nil && authenticatable.Room().RID == proto.RID(connectionClaims.RID) { + log.Debugf("authenticateRoom: Valid RID in connectionClaims %v", authenticatable.Room().RID) + return nil + } + + // Check for a message level proto.RoomToken + var msgClaims *signal.Claims = nil + if t := authenticatable.Token(); t != "" { + token, err := jwt.ParseWithClaims(t, &signal.Claims{}, roomAuth.KeyFunc) + if err != nil { + log.Debugf("authenticateRoom: Error parsing token: %v", err) + return errorInvalidRoomToken + } + log.Debugf("authenticateRoom: Got Token %#v", token) + msgClaims = token.Claims.(*signal.Claims) + } + + // No tokens were passed in + if connectionClaims == nil && msgClaims == nil { + return errorTokenRequired + } + + // Message token is valid, succeed + if msgClaims != nil && authenticatable.Room().RID == proto.RID(msgClaims.RID) { + log.Debugf("authenticateRoom: Valid RID in msgClaims %v", authenticatable.Room().RID) + return nil + } + + // If this is reached, a token was passed but it did not have a valid RID claim + return errorUnauthorizedRoomAccess +} + // Entry is the biz entry -func Entry(method string, peer *signal.Peer, msg json.RawMessage, accept signal.RespondFunc, reject signal.RejectFunc) { +func Entry(method string, peer *signal.Peer, msg json.RawMessage, claims *signal.Claims, accept signal.RespondFunc, reject signal.RejectFunc) { var result interface{} topErr := util.NewNpError(http.StatusBadRequest, fmt.Sprintf("Unkown method [%s]", method)) @@ -31,42 +82,42 @@ func Entry(method string, peer *signal.Peer, msg json.RawMessage, accept signal. switch method { case proto.ClientJoin: var msgData proto.JoinMsg - if topErr = ParseProtoo(msg, &msgData); topErr == nil { + if topErr = ParseProtoo(msg, claims, &msgData); topErr == nil { result, topErr = join(peer, msgData) } case proto.ClientLeave: var msgData proto.LeaveMsg - if topErr = ParseProtoo(msg, &msgData); topErr == nil { + if topErr = ParseProtoo(msg, claims, &msgData); topErr == nil { result, topErr = leave(peer, msgData) } case proto.ClientPublish: var msgData proto.PublishMsg - if topErr = ParseProtoo(msg, &msgData); topErr == nil { + if topErr = ParseProtoo(msg, claims, &msgData); topErr == nil { result, topErr = publish(peer, msgData) } case proto.ClientUnPublish: var msgData proto.UnpublishMsg - if topErr = ParseProtoo(msg, &msgData); topErr == nil { + if topErr = ParseProtoo(msg, claims, &msgData); topErr == nil { result, topErr = unpublish(peer, msgData) } case proto.ClientSubscribe: var msgData proto.SubscribeMsg - if topErr = ParseProtoo(msg, &msgData); topErr == nil { + if topErr = ParseProtoo(msg, claims, &msgData); topErr == nil { result, topErr = subscribe(peer, msgData) } case proto.ClientUnSubscribe: var msgData proto.UnsubscribeMsg - if topErr = ParseProtoo(msg, &msgData); topErr == nil { + if topErr = ParseProtoo(msg, claims, &msgData); topErr == nil { result, topErr = unsubscribe(peer, msgData) } case proto.ClientBroadcast: var msgData proto.BroadcastMsg - if topErr = ParseProtoo(msg, &msgData); topErr == nil { + if topErr = ParseProtoo(msg, claims, &msgData); topErr == nil { result, topErr = broadcast(peer, msgData) } case proto.ClientTrickleICE: var msgData proto.TrickleMsg - if topErr = ParseProtoo(msg, &msgData); topErr == nil { + if topErr = ParseProtoo(msg, claims, &msgData); topErr == nil { result, topErr = trickle(peer, msgData) } } diff --git a/pkg/node/biz/init.go b/pkg/node/biz/init.go index c332c7555..0bc090f8c 100644 --- a/pkg/node/biz/init.go +++ b/pkg/node/biz/init.go @@ -2,6 +2,7 @@ package biz import ( nprotoo "github.com/cloudwebrtc/nats-protoo" + conf "github.com/pion/ion/pkg/conf/biz" "github.com/pion/ion/pkg/discovery" "github.com/pion/ion/pkg/log" ) @@ -14,15 +15,17 @@ var ( protoo *nprotoo.NatsProtoo rpcs map[string]*nprotoo.Requestor services map[string]discovery.Node + roomAuth conf.AuthConfig ) // Init func -func Init(dcID, nodeID, rpcID, eventID string, natsURL string) { +func Init(dcID, nodeID, rpcID, eventID string, natsURL string, authConf conf.AuthConfig) { dc = dcID nid = nodeID services = make(map[string]discovery.Node) rpcs = make(map[string]*nprotoo.Requestor) protoo = nprotoo.NewNatsProtoo(natsURL) + roomAuth = authConf } // WatchServiceNodes . diff --git a/pkg/node/sfu/internal.go b/pkg/node/sfu/internal.go index 5777732fe..fab5c9676 100644 --- a/pkg/node/sfu/internal.go +++ b/pkg/node/sfu/internal.go @@ -241,7 +241,7 @@ func subscribe(msg proto.SFUSubscribeMsg) (interface{}, *nprotoo.Error) { return nil, util.NewNpError(415, "Unsupported Media Type") } - router.AddSub(subID, sub) + router.AddSub(subID, sub, msg.UID) log.Infof("subscribe mid %s, answer = %v", subID, answer) return util.Map("jsep", answer, "mid", subID), nil @@ -251,15 +251,22 @@ func subscribe(msg proto.SFUSubscribeMsg) (interface{}, *nprotoo.Error) { func unsubscribe(msg proto.UnsubscribeMsg) (interface{}, *nprotoo.Error) { log.Infof("unsubscribe msg=%v", msg) mid := msg.MID + uid := msg.UID found := false rtc.MapRouter(func(id proto.MID, r *rtc.Router) { - subs := r.GetSubs() - for sid := range subs { - if sid == mid { - r.DelSub(mid) - found = true - return + if mid != "" { + subs := r.GetSubs() + for sid := range subs { + if sid == mid { + r.DelSub(mid) + found = true + return + } } + } else if uid != "" { + log.Infof("Remove user sub: %v", uid) + found = true + r.DelUserSub(uid) } }) if found { diff --git a/pkg/proto/biz.go b/pkg/proto/biz.go index 80f4387b2..18ce989ef 100644 --- a/pkg/proto/biz.go +++ b/pkg/proto/biz.go @@ -6,6 +6,11 @@ import ( "github.com/pion/webrtc/v2" ) +type Authenticatable interface { + Room() RoomInfo + Token() string +} + type ClientUserInfo struct { Name string `json:"name"` } @@ -18,6 +23,10 @@ func (m *ClientUserInfo) UnmarshalBinary(data []byte) error { return json.Unmarshal(data, m) } +type RoomToken struct { + Token string `json:"token,omitempty"` +} + type RoomInfo struct { RID RID `json:"rid"` UID UID `json:"uid"` @@ -49,9 +58,17 @@ type TrackMap map[string][]TrackInfo type JoinMsg struct { RoomInfo + RoomToken Info ClientUserInfo `json:"info"` } +func (j *JoinMsg) Token() string { + return j.RoomToken.Token +} +func (j *JoinMsg) Room() RoomInfo { + return j.RoomInfo +} + type LeaveMsg struct { RoomInfo Info ClientUserInfo `json:"info"` @@ -59,10 +76,18 @@ type LeaveMsg struct { type PublishMsg struct { RoomInfo + RoomToken RTCInfo Options PublishOptions `json:"options"` } +func (p *PublishMsg) Token() string { + return p.RoomToken.Token +} +func (p *PublishMsg) Room() RoomInfo { + return p.RoomInfo +} + type PublishResponseMsg struct { MediaInfo RTCInfo diff --git a/pkg/proto/islb.go b/pkg/proto/islb.go index f0b1423b7..88c08edfc 100644 --- a/pkg/proto/islb.go +++ b/pkg/proto/islb.go @@ -9,7 +9,7 @@ type PubInfo struct { type GetPubResp struct { RoomInfo - Pubs []PubInfo + Pubs []PubInfo `json:"pubs,omitempty"` } type GetMediaParams struct { diff --git a/pkg/rtc/router.go b/pkg/rtc/router.go index 64853dde5..7274e0451 100644 --- a/pkg/rtc/router.go +++ b/pkg/rtc/router.go @@ -34,6 +34,7 @@ type RouterConfig struct { type Router struct { pub transport.Transport subs map[proto.MID]transport.Transport + userSubs map[proto.UID]proto.MID subLock sync.RWMutex stop bool liveTime time.Time @@ -48,6 +49,7 @@ func NewRouter(id proto.MID) *Router { log.Infof("NewRouter id=%s", id) return &Router{ subs: make(map[proto.MID]transport.Transport), + userSubs: make(map[proto.UID]proto.MID), liveTime: time.Now().Add(liveCycle), pluginChain: plugins.NewPluginChain(string(id)), subChans: make(map[proto.MID]chan *rtp.Packet), @@ -158,6 +160,7 @@ func (r *Router) subWriteLoop(subID proto.MID, trans transport.Transport) { // log.Errorf("wt.WriteRTP err=%v", err) // del sub when err is increasing if trans.WriteErrTotal() > maxWriteErr { + log.Errorf("Increased number of errors in writing to Sub, deleting sub %v", proto.MID(trans.ID())) r.DelSub(proto.MID(trans.ID())) } } @@ -271,7 +274,7 @@ func (r *Router) subFeedbackLoop(subID proto.MID, trans transport.Transport) { } // AddSub add a sub to router -func (r *Router) AddSub(id proto.MID, t transport.Transport) transport.Transport { +func (r *Router) AddSub(id proto.MID, t transport.Transport, uid proto.UID) transport.Transport { //fix panic: assignment to entry in nil map if r.stop { return nil @@ -279,6 +282,7 @@ func (r *Router) AddSub(id proto.MID, t transport.Transport) transport.Transport r.subLock.Lock() defer r.subLock.Unlock() r.subs[id] = t + r.userSubs[uid] = id r.subChans[id] = make(chan *rtp.Packet, 1000) t.SetShutdownChan(r.subShutdownCh) log.Infof("Router.AddSub id=%s t=%p", id, t) @@ -325,10 +329,27 @@ func (r *Router) DelSub(id proto.MID) { if r.subChans[id] != nil { close(r.subChans[id]) } + for uid, mid := range r.userSubs { + if mid == id { + delete(r.userSubs, uid) + break + } + } delete(r.subs, id) delete(r.subChans, id) } +// DelUserSub del subscription from user uid +func (r *Router) DelUserSub(uid proto.UID) { + log.Infof("Router.DelUserSub uid=%s", uid) + r.subLock.RLock() + mid := r.userSubs[uid] + r.subLock.RUnlock() + if mid != "" { + r.DelSub(mid) + } +} + // DelSubs del all sub func (r *Router) DelSubs() { log.Infof("Router.DelSubs") diff --git a/pkg/rtc/rtc.go b/pkg/rtc/rtc.go index ba4c8850c..cafa78f04 100644 --- a/pkg/rtc/rtc.go +++ b/pkg/rtc/rtc.go @@ -170,14 +170,14 @@ func check() { CleanChannel <- id log.Infof("Stat delete %v", id) } - info += "pub: " + string(id) + "\n" + info += "\npub: " + string(id) + "\n" subs := router.GetSubs() if len(subs) < 6 { for id := range subs { - info += fmt.Sprintf("sub: %s\n\n", id) + info += fmt.Sprintf("sub: %s\n", id) } } else { - info += fmt.Sprintf("subs: %d\n\n", len(subs)) + info += fmt.Sprintf("subs: %d\n", len(subs)) } } routerLock.Unlock() diff --git a/pkg/rtc/transport/webrtctransport.go b/pkg/rtc/transport/webrtctransport.go index 9b6935141..00e64ce49 100644 --- a/pkg/rtc/transport/webrtctransport.go +++ b/pkg/rtc/transport/webrtctransport.go @@ -14,7 +14,7 @@ import ( const ( maxChanSize = 100 - IOSH264Fmtp = "level-asymmetry-allowed=1;packetization-mode=1;profile-level-id=42e01f" + IOSH264Fmtp = "level-asymmetry-allowed=1;packetization-mode=1;profile-level-id=42001f" FireFoxH264Fmtp97 = "profile-level-id=42e01f;level-asymmetry-allowed=1" ) @@ -236,9 +236,11 @@ func NewWebRTCTransport(id string, options RTCOptions) *WebRTCTransport { w.pc.OnICEConnectionStateChange(func(connectionState webrtc.ICEConnectionState) { switch connectionState { case webrtc.ICEConnectionStateDisconnected: - log.Errorf("webrtc ice disconnected") + log.Errorf("webrtc ice disconnected id=%v", id) + w.alive = false + w.shutdownChan <- id case webrtc.ICEConnectionStateFailed: - log.Errorf("webrtc ice failed") + log.Errorf("webrtc ice failed id=%v", id) w.alive = false w.shutdownChan <- id } diff --git a/pkg/signal/handle.go b/pkg/signal/handle.go index e4454ecb7..79a5ca78d 100644 --- a/pkg/signal/handle.go +++ b/pkg/signal/handle.go @@ -7,12 +7,36 @@ import ( "github.com/cloudwebrtc/go-protoo/logger" pr "github.com/cloudwebrtc/go-protoo/peer" "github.com/cloudwebrtc/go-protoo/transport" + "github.com/dgrijalva/jwt-go" + "github.com/pkg/errors" "github.com/pion/ion/pkg/log" "github.com/pion/ion/pkg/proto" "github.com/pion/ion/pkg/util" ) +var ( + errorTokenClaimsInvalid = errors.Errorf("Token claims invalid: must have RID or UID") +) + +// Claims supported in JWT +type Claims struct { + UID string `json:"uid"` + RID string `json:"rid"` + *jwt.StandardClaims +} + +func (c *Claims) Valid() error { + if c.RID == "" && c.UID == "" { + return errorTokenClaimsInvalid + } + + if c.StandardClaims != nil { + return c.StandardClaims.Valid() + } + return nil +} + func in(transport *transport.WebSocketTransport, request *http.Request) { vars := request.URL.Query() peerID := vars["peer"] @@ -23,6 +47,7 @@ func in(transport *transport.WebSocketTransport, request *http.Request) { id := peerID[0] log.Infof("signal.in, id => %s", id) peer := newPeer(id, transport) + connectionClaims := ForContext(request.Context()) handleRequest := func(request pr.Request, accept func(interface{}), reject func(errorCode int, errorReason string)) { defer util.Recover("signal.in handleRequest") @@ -41,7 +66,7 @@ func in(transport *transport.WebSocketTransport, request *http.Request) { } log.Infof("signal.in handleRequest id=%s method => %s", peer.ID(), method) - bizCall(method, peer, data, accept, reject) + bizCall(method, peer, data, connectionClaims, accept, reject) } handleNotification := func(notification pr.Notification) { @@ -62,7 +87,7 @@ func in(transport *transport.WebSocketTransport, request *http.Request) { // msg := data.(map[string]interface{}) log.Infof("signal.in handleNotification id=%s method => %s", peer.ID(), method) - bizCall(method, peer, data, emptyAccept, reject) + bizCall(method, peer, data, connectionClaims, emptyAccept, reject) } handleClose := func(code int, err string) { @@ -77,14 +102,19 @@ func in(transport *transport.WebSocketTransport, request *http.Request) { log.Infof("signal.in handleClose [%d] %s rooms=%v", code, err, rooms) for _, room := range rooms { if room != nil { - if code > 1000 { - msg := proto.LeaveMsg{ - RoomInfo: proto.RoomInfo{RID: room.ID()}, + oldPeer := room.GetPeer(peer.ID()) + // only remove if its the same peer. If newer peer joined before the cleanup, leave it. + if oldPeer == &peer.Peer { + if code > 1000 { + msg := proto.LeaveMsg{ + RoomInfo: proto.RoomInfo{RID: room.ID()}, + } + msgStr, _ := json.Marshal(msg) + bizCall(proto.ClientLeave, peer, msgStr, connectionClaims, emptyAccept, reject) } - msgStr, _ := json.Marshal(msg) - bizCall(proto.ClientLeave, peer, msgStr, emptyAccept, reject) + log.Infof("signal.in handleClose removing peer (%s) from room (%s)", peer.ID(), room.ID()) + room.RemovePeer(peer.ID()) } - room.RemovePeer(peer.ID()) } } log.Infof("signal.in handleClose => peer (%s) ", peer.ID()) diff --git a/pkg/signal/init.go b/pkg/signal/init.go index 78e36d472..b56621036 100644 --- a/pkg/signal/init.go +++ b/pkg/signal/init.go @@ -7,7 +7,6 @@ import ( "time" "github.com/cloudwebrtc/go-protoo/peer" - "github.com/cloudwebrtc/go-protoo/server" "github.com/pion/ion/pkg/log" "github.com/pion/ion/pkg/proto" ) @@ -15,6 +14,7 @@ import ( type AcceptFunc peer.AcceptFunc type RejectFunc peer.RejectFunc type RespondFunc peer.RespondFunc +type BizEntry func(method string, peer *Peer, msg json.RawMessage, claims *Claims, accept RespondFunc, reject RejectFunc) const ( errInvalidMethod = "method not found" @@ -23,25 +23,21 @@ const ( ) var ( - bizCall func(method string, peer *Peer, msg json.RawMessage, accept RespondFunc, reject RejectFunc) - wsServer *server.WebSocketServer + bizCall BizEntry rooms = make(map[proto.RID]*Room) roomLock sync.RWMutex allowClientDisconnect bool ) -func Init(host string, port int, cert, key string, allowDisconnected bool, bizEntry func(method string, peer *Peer, msg json.RawMessage, accept RespondFunc, reject RejectFunc)) { - wsServer = server.NewWebSocketServer(in) - config := server.DefaultConfig() - config.Host = host - config.Port = port - config.CertFile = cert - config.KeyFile = key - config.HTMLRoot = "web" +// Init biz signaling +func Init(conf WebSocketServerConfig, allowDisconnected bool, bizEntry BizEntry) { bizCall = bizEntry allowClientDisconnect = allowDisconnected - go wsServer.Bind(config) go stat() + go func() { + panic(NewWebSocketServer(conf, in)) + }() + } func stat() { diff --git a/pkg/signal/room.go b/pkg/signal/room.go index 280833127..3801c1cfd 100644 --- a/pkg/signal/room.go +++ b/pkg/signal/room.go @@ -104,6 +104,15 @@ func HasPeer(rid proto.RID, peer *Peer) bool { return room.GetPeer(peer.ID()) != nil } +func GetPeer(rid proto.RID, id string) *peer.Peer { + log.Debugf("GetPeer rid=%s peer.ID=%s", rid, id) + room := getRoom(rid) + if room == nil { + return nil + } + return room.GetPeer(id) +} + func NotifyAllWithoutPeer(rid proto.RID, peer *Peer, method string, msg interface{}) { log.Debugf("signal.NotifyAllWithoutPeer rid=%s peer.ID=%s method=%s msg=%v", rid, peer.ID(), method, msg) room := getRoom(rid) diff --git a/pkg/signal/server.go b/pkg/signal/server.go new file mode 100644 index 000000000..67bc693e3 --- /dev/null +++ b/pkg/signal/server.go @@ -0,0 +1,113 @@ +package signal + +import ( + "context" + "errors" + "net/http" + "strconv" + + "github.com/cloudwebrtc/go-protoo/logger" + "github.com/cloudwebrtc/go-protoo/transport" + "github.com/dgrijalva/jwt-go" + "github.com/gorilla/websocket" + conf "github.com/pion/ion/pkg/conf/biz" + "github.com/pion/ion/pkg/log" +) + +type WebSocketServerConfig struct { + Host string + Port int + CertFile string + KeyFile string + WebSocketPath string + + AuthConnection conf.AuthConfig +} + +type MsgHandler func(ws *transport.WebSocketTransport, request *http.Request) + +type contextKey struct { + name string +} + +var claimsCtxKey = &contextKey{"claims"} +var upgrader = websocket.Upgrader{ + CheckOrigin: func(r *http.Request) bool { + return true + }, +} + +func getClaims(connectionAuth conf.AuthConfig, r *http.Request) (*Claims, error) { + vars := r.URL.Query() + + log.Debugf("Authenticating token") + tokenParam := vars["access_token"] + if tokenParam == nil || len(tokenParam) < 1 { + return nil, errors.New("no token") + } + + tokenStr := tokenParam[0] + + log.Debugf("checking claims on token %v", tokenStr) + // Passing nil for keyFunc, since token is expected to be already verified (by a proxy) + token, err := jwt.ParseWithClaims(tokenStr, &Claims{}, connectionAuth.KeyFunc) + if err != nil { + return nil, err + } + return token.Claims.(*Claims), nil +} + +func handler(connectionAuth conf.AuthConfig, msgHandler MsgHandler) func(w http.ResponseWriter, r *http.Request) { + return func(w http.ResponseWriter, r *http.Request) { + if connectionAuth.Enabled { + // extract connection level claims from auth token + claims, err := getClaims(connectionAuth, r) + if err != nil { + log.Errorf("Error authenticating user => %s", err) + http.Error(w, "Invalid token", http.StatusForbidden) + return + } + + log.Debugf("authenticated user with claims %#v", claims) + + // put it in context + ctx := context.WithValue(r.Context(), claimsCtxKey, claims) + // and call the next with our new context + r = r.WithContext(ctx) + } + + responseHeader := http.Header{} + responseHeader.Add("Sec-WebSocket-Protocol", "protoo") + socket, err := upgrader.Upgrade(w, r, responseHeader) + if err != nil { + log.Errorf("Error upgrading => %s", err) + http.Error(w, "Error upgrading socket", http.StatusBadRequest) + return + } + logger.Debugf("Creating new WebSocket") + wsTransport := transport.NewWebSocketTransport(socket) + wsTransport.Start() + + msgHandler(wsTransport, r) + } +} + +// NewWebSocketServer for signaling +func NewWebSocketServer(cfg WebSocketServerConfig, msgHandler MsgHandler) error { + // Websocket handle func + http.HandleFunc(cfg.WebSocketPath, handler(cfg.AuthConnection, msgHandler)) + + if cfg.CertFile == "" || cfg.KeyFile == "" { + logger.Infof("non-TLS WebSocketServer listening on: %s:%d", cfg.Host, cfg.Port) + return http.ListenAndServe(cfg.Host+":"+strconv.Itoa(cfg.Port), nil) + } else { + logger.Infof("TLS WebSocketServer listening on: %s:%d", cfg.Host, cfg.Port) + return http.ListenAndServeTLS(cfg.Host+":"+strconv.Itoa(cfg.Port), cfg.CertFile, cfg.KeyFile, nil) + } +} + +// ForContext finds the request claims from the context. +func ForContext(ctx context.Context) *Claims { + raw, _ := ctx.Value(claimsCtxKey).(*Claims) + return raw +} From e07702dcab6ac4a4432bf062b61a0fcca92b078c Mon Sep 17 00:00:00 2001 From: Leeward Bound Date: Tue, 24 Nov 2020 08:56:21 -0800 Subject: [PATCH 8/9] feat(roadmap): adding ion roadmap --- README.md | 3 ++- docs/imgs/ion-roadmap.png | Bin 0 -> 124740 bytes 2 files changed, 2 insertions(+), 1 deletion(-) create mode 100644 docs/imgs/ion-roadmap.png diff --git a/README.md b/README.md index 3dc287709..20b86d66a 100644 --- a/README.md +++ b/README.md @@ -89,7 +89,8 @@ For dev and more options see the wiki ## Roadmap -[Roadmap to 1.0](https://github.com/pion/ion/projects/2) +![arch](https://github.com/pion/ion/raw/master/docs/imgs/ion-roadmap.png) + ## Maintainers diff --git a/docs/imgs/ion-roadmap.png b/docs/imgs/ion-roadmap.png new file mode 100644 index 0000000000000000000000000000000000000000..a4f2c3480eb33615a079805d9999392b29816758 GIT binary patch literal 124740 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

}o#lq5~?m$xfLwdYI5ba*hSk~}em0$y!% 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%0AnB-ST*D+ZFt$k_^%M#C@@(*UCLdKw1NGDH> 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 literal 0 HcmV?d00001 From cd975eaf10eb802e7044748de0d5d66843a4b1a4 Mon Sep 17 00:00:00 2001 From: Leeward Bound Date: Tue, 24 Nov 2020 09:00:14 -0800 Subject: [PATCH 9/9] feat(readme): tweaking readme --- README.md | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/README.md b/README.md index 20b86d66a..7bdac6822 100644 --- a/README.md +++ b/README.md @@ -23,6 +23,12 @@ + **ION** is a community effort and relies on volunteers like you and me! +## Roadmap + +**NOV24 STATUS UPDATE**: The `ion` project has been undergoing a major restructuring for a few months! If you want to build on top of `ion` today, you should start with `ion-sfu`! You can still deploy `ion:0.4.6` as a ready-to-go conference demonstration. + +![arch](https://github.com/pion/ion/raw/master/docs/imgs/ion-roadmap.png) + ## ❤️Sponsor to help this awsome project go faster!🚀 (https://opencollective.com/pion-ion) @@ -87,11 +93,6 @@ For dev and more options see the wiki ## Architecture ![arch](https://github.com/pion/ion/raw/master/docs/imgs/arch.png) -## Roadmap - -![arch](https://github.com/pion/ion/raw/master/docs/imgs/ion-roadmap.png) - - ## Maintainers