From 8accb815e1c2134e08c129753c530c460b93503c Mon Sep 17 00:00:00 2001 From: Kyle Carberry Date: Wed, 5 Jan 2022 11:18:29 -0600 Subject: [PATCH] chore: Add peer package for networking (#6) This package was pulled straight from github.com/coder/m. Nothing has been changed. It will be used for networking clients<->workspaces, and coderd<->provisionerd. --- go.mod | 33 +++- go.sum | 87 ++++++++- peer/channel.go | 308 +++++++++++++++++++++++++++++ peer/conn.go | 482 ++++++++++++++++++++++++++++++++++++++++++++++ peer/conn_test.go | 294 ++++++++++++++++++++++++++++ peer/netconn.go | 59 ++++++ 6 files changed, 1260 insertions(+), 3 deletions(-) create mode 100644 peer/channel.go create mode 100644 peer/conn.go create mode 100644 peer/conn_test.go create mode 100644 peer/netconn.go diff --git a/go.mod b/go.mod index 0f74f5584c..aff08106f6 100644 --- a/go.mod +++ b/go.mod @@ -3,10 +3,16 @@ module github.com/coder/coder go 1.17 require ( + cdr.dev/slog v1.4.1 github.com/golang-migrate/migrate/v4 v4.15.1 github.com/lib/pq v1.10.4 github.com/ory/dockertest/v3 v3.8.1 + github.com/pion/datachannel v1.5.2 + github.com/pion/logging v0.2.2 + github.com/pion/transport v0.13.0 + github.com/pion/webrtc/v3 v3.1.13 github.com/stretchr/testify v1.7.0 + go.uber.org/atomic v1.7.0 go.uber.org/goleak v1.1.12 golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 ) @@ -15,33 +21,56 @@ require ( github.com/Azure/go-ansiterm v0.0.0-20210617225240-d185dfc1b5a1 // indirect github.com/Microsoft/go-winio v0.5.1 // indirect github.com/Nvveen/Gotty v0.0.0-20120604004816-cd527374f1e5 // indirect + github.com/alecthomas/chroma v0.9.1 // indirect github.com/cenkalti/backoff/v4 v4.1.2 // indirect github.com/containerd/continuity v0.1.0 // indirect + github.com/danwakefield/fnmatch v0.0.0-20160403171240-cbb64ac3d964 // indirect github.com/davecgh/go-spew v1.1.1 // indirect github.com/dhui/dktest v0.3.8 // indirect + github.com/dlclark/regexp2 v1.4.0 // indirect github.com/docker/cli v20.10.11+incompatible // indirect github.com/docker/docker v20.10.12+incompatible // indirect github.com/docker/go-connections v0.4.0 // indirect github.com/docker/go-units v0.4.0 // indirect + github.com/fatih/color v1.12.0 // indirect github.com/gogo/protobuf v1.3.2 // indirect + github.com/golang/groupcache v0.0.0-20200121045136-8c9f03a8e57e // indirect github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510 // indirect + github.com/google/uuid v1.3.0 // indirect github.com/hashicorp/errwrap v1.0.0 // indirect github.com/hashicorp/go-multierror v1.1.0 // indirect github.com/imdario/mergo v0.3.12 // indirect + github.com/mattn/go-colorable v0.1.8 // indirect + github.com/mattn/go-isatty v0.0.12 // indirect github.com/mitchellh/mapstructure v1.4.1 // indirect github.com/moby/term v0.0.0-20210619224110-3f7ff695adc6 // indirect github.com/opencontainers/go-digest v1.0.0 // indirect github.com/opencontainers/image-spec v1.0.2 // indirect github.com/opencontainers/runc v1.0.2 // indirect + github.com/pion/dtls/v2 v2.0.13 // indirect + github.com/pion/ice/v2 v2.1.18 // indirect + github.com/pion/interceptor v0.1.4 // indirect + github.com/pion/mdns v0.0.5 // indirect + github.com/pion/randutil v0.1.0 // indirect + github.com/pion/rtcp v1.2.9 // indirect + github.com/pion/rtp v1.7.4 // indirect + github.com/pion/sctp v1.8.2 // indirect + github.com/pion/sdp/v3 v3.0.4 // indirect + github.com/pion/srtp/v2 v2.0.5 // indirect + github.com/pion/stun v0.3.5 // indirect + github.com/pion/turn/v2 v2.0.6 // indirect + github.com/pion/udp v0.1.1 // indirect github.com/pkg/errors v0.9.1 // indirect github.com/pmezard/go-difflib v1.0.0 // indirect github.com/sirupsen/logrus v1.8.1 // indirect github.com/xeipuuv/gojsonpointer v0.0.0-20180127040702-4e3ac2762d5f // indirect github.com/xeipuuv/gojsonreference v0.0.0-20180127040603-bd5ef7bd5415 // indirect github.com/xeipuuv/gojsonschema v1.2.0 // indirect - go.uber.org/atomic v1.7.0 // indirect - golang.org/x/net v0.0.0-20211013171255-e13a2654a71e // indirect + go.opencensus.io v0.23.0 // indirect + golang.org/x/crypto v0.0.0-20211117183948-ae814b36b871 // indirect + golang.org/x/net v0.0.0-20211215060638-4ddde0e984e9 // indirect golang.org/x/sys v0.0.0-20211013075003-97ac67df715c // indirect + golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1 // indirect gopkg.in/yaml.v2 v2.4.0 // indirect gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b // indirect ) diff --git a/go.sum b/go.sum index 8ea9068592..c3670e723f 100644 --- a/go.sum +++ b/go.sum @@ -1,4 +1,6 @@ bazil.org/fuse v0.0.0-20160811212531-371fbbdaa898/go.mod h1:Xbm+BRKSBEpa4q4hTSxohYNQpsxXPbPry4JJWOB3LB8= +cdr.dev/slog v1.4.1 h1:Q8+X63m8/WB4geelMTDO8t4CTwVh1f7+5Cxi7kS/SZg= +cdr.dev/slog v1.4.1/go.mod h1:O76C6gZJxa5HK1SXMrjd48V2kJxYZKFRTcFfn/V9OhA= cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= cloud.google.com/go v0.34.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= cloud.google.com/go v0.38.0/go.mod h1:990N+gfupTy94rShfmMCWGDn0LpTmnzTp2qbd1dvSRU= @@ -22,6 +24,7 @@ cloud.google.com/go v0.81.0/go.mod h1:mk/AM35KwGk/Nm2YSeZbxXdrNK3KZOYHmLkOqC2V6E cloud.google.com/go v0.83.0/go.mod h1:Z7MJUsANfY0pYPdw0lbnivPx4/vhy/e2FEkSkF7vAVY= cloud.google.com/go v0.84.0/go.mod h1:RazrYuxIK6Kb7YrzzhPoLmCVzl7Sup4NrbKPg8KHSUM= cloud.google.com/go v0.87.0/go.mod h1:TpDYlFy7vuLzZMMZ+B6iRiELaY7z/gJPaqbMx6mlWcY= +cloud.google.com/go v0.88.0 h1:MZ2cf9Elnv1wqccq8ooKO2MqHQLc+ChCp/+QWObCpxg= cloud.google.com/go v0.88.0/go.mod h1:dnKwfYbP9hQhefiUvpbcAyoGSHUrOxR20JVElLiUvEY= cloud.google.com/go/bigquery v1.0.1/go.mod h1:i/xbL2UlR5RvWAURpBYZTtm/cXjCha9lbfbpx4poX+o= cloud.google.com/go/bigquery v1.3.0/go.mod h1:PjpwJnslEMmckchkHFfq+HTD2DmtT67aNFKH1/VBDHE= @@ -95,6 +98,15 @@ github.com/PuerkitoBio/purell v1.1.1/go.mod h1:c11w/QuzBsJSee3cPx9rAFu61PvFxuPbt github.com/PuerkitoBio/urlesc v0.0.0-20170810143723-de5bf2ad4578/go.mod h1:uGdkoq3SwY9Y+13GIhn11/XLaGBb4BfwItxLd5jeuXE= github.com/Shopify/logrus-bugsnag v0.0.0-20171204204709-577dee27f20d/go.mod h1:HI8ITrYtUY+O+ZhtlqUnD8+KwNPOyugEhfP9fdUIaEQ= github.com/ajstarks/svgo v0.0.0-20180226025133-644b8db467af/go.mod h1:K08gAheRH3/J6wwsYMMT4xOr94bZjxIelGM0+d/wbFw= +github.com/alecthomas/assert v0.0.0-20170929043011-405dbfeb8e38 h1:smF2tmSOzy2Mm+0dGI2AIUHY+w0BUc+4tn40djz7+6U= +github.com/alecthomas/assert v0.0.0-20170929043011-405dbfeb8e38/go.mod h1:r7bzyVFMNntcxPZXK3/+KdruV1H5KSlyVY0gc+NgInI= +github.com/alecthomas/chroma v0.9.1 h1:cBmvQqRImzR5aWqdMxYZByND4S7BCS/g0svZb28h0Dc= +github.com/alecthomas/chroma v0.9.1/go.mod h1:eMuEnpA18XbG/WhOWtCzJHS7WqEtDAI+HxdwoW0nVSk= +github.com/alecthomas/colour v0.0.0-20160524082231-60882d9e2721 h1:JHZL0hZKJ1VENNfmXvHbgYlbUOvpzYzvy2aZU5gXVeo= +github.com/alecthomas/colour v0.0.0-20160524082231-60882d9e2721/go.mod h1:QO9JBoKquHd+jz9nshCh40fOfO+JzsoXy8qTHF68zU0= +github.com/alecthomas/kong v0.2.4/go.mod h1:kQOmtJgV+Lb4aj+I2LEn40cbtawdWJ9Y8QLq+lElKxE= +github.com/alecthomas/repr v0.0.0-20180818092828-117648cd9897 h1:p9Sln00KOTlrYkxI1zYWl1QLnEqAqEARBEYa8FQnQcY= +github.com/alecthomas/repr v0.0.0-20180818092828-117648cd9897/go.mod h1:xTS7Pm1pD1mvyM075QCDSRqH6qRLXylzS24ZTpRiSzQ= github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc= github.com/alecthomas/template v0.0.0-20190718012654-fb15b899a751/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc= github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0= @@ -293,6 +305,8 @@ github.com/d2g/dhcp4 v0.0.0-20170904100407-a1d1b6c41b1c/go.mod h1:Ct2BUK8SB0YC1S github.com/d2g/dhcp4client v1.0.0/go.mod h1:j0hNfjhrt2SxUOw55nL0ATM/z4Yt3t2Kd1mW34z5W5s= github.com/d2g/dhcp4server v0.0.0-20181031114812-7d4a0a7f59a5/go.mod h1:Eo87+Kg/IX2hfWJfwxMzLyuSZyxSoAug2nGa1G2QAi8= github.com/d2g/hardwareaddr v0.0.0-20190221164911-e7d9fbe030e4/go.mod h1:bMl4RjIciD2oAxI7DmWRx6gbeqrkoLqv3MV0vzNad+I= +github.com/danwakefield/fnmatch v0.0.0-20160403171240-cbb64ac3d964 h1:y5HC9v93H5EPKqaS1UYVg1uYah5Xf51mBfIoWehClUQ= +github.com/danwakefield/fnmatch v0.0.0-20160403171240-cbb64ac3d964/go.mod h1:Xd9hchkHSWYkEqJwUGisez3G1QY8Ryz0sdWrLPMGjLk= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= @@ -304,6 +318,8 @@ github.com/dgryski/go-sip13 v0.0.0-20181026042036-e10d5fee7954/go.mod h1:vAd38F8 github.com/dhui/dktest v0.3.7/go.mod h1:nYMOkafiA07WchSwKnKFUSbGMb2hMm5DrCGiXYG6gwM= github.com/dhui/dktest v0.3.8 h1:X6HaIx14Gkuo2d9/k4o6RmeZE/QVE+fvst1mRz+Uj7c= github.com/dhui/dktest v0.3.8/go.mod h1:UpB3XEKHXfO6xR9XktKvxDpo6h0hyOuoDK2KoNK04UI= +github.com/dlclark/regexp2 v1.4.0 h1:F1rxgk7p4uKjwIQxBs9oAXe5CqrXlCduYEJvrF4u93E= +github.com/dlclark/regexp2 v1.4.0/go.mod h1:2pZnwuY/m+8K6iRw6wQdMtk+rH5tNGR1i55kozfMjCc= github.com/dnaeon/go-vcr v1.0.1/go.mod h1:aBB1+wY4s93YsC3HHjMBMrwTj2R9FHDzUr9KyGc8n1E= github.com/docker/cli v20.10.11+incompatible h1:tXU1ezXcruZQRrMP8RN2z9N91h+6egZTS1gsPsKantc= github.com/docker/cli v20.10.11+incompatible/go.mod h1:JLrzqnKDaYBop7H2jaqPtU4hHvMKP+vjCwu2uszcLI8= @@ -343,6 +359,8 @@ github.com/envoyproxy/go-control-plane v0.9.10-0.20210907150352-cf90f659a021/go. github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c= github.com/evanphx/json-patch v4.9.0+incompatible/go.mod h1:50XU6AFN0ol/bzJsmQLiYLvXMP4fmwYFNcr97nuDLSk= github.com/fatih/color v1.7.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5KwzbycvMj4= +github.com/fatih/color v1.12.0 h1:mRhaKNwANqRgUBGKmnI5ZxEk7QXmjQeCcuYFMX2bfcc= +github.com/fatih/color v1.12.0/go.mod h1:ELkj/draVOlAH/xkhN6mQ50Qd0MPOk5AAr3maGEBuJM= github.com/fogleman/gg v1.2.1-0.20190220221249-0403632d5b90/go.mod h1:R/bRT+9gY/C5z7JzPU0zXsXHKM4/ayA+zqcVNZzPa1k= github.com/fogleman/gg v1.3.0/go.mod h1:R/bRT+9gY/C5z7JzPU0zXsXHKM4/ayA+zqcVNZzPa1k= github.com/form3tech-oss/jwt-go v3.2.2+incompatible/go.mod h1:pbq4aXjuKjdthFRnoDwaVPLA+WlJuPGy+QneDUgJi2k= @@ -382,6 +400,7 @@ github.com/go-openapi/swag v0.19.5/go.mod h1:POnQmlKehdgb5mhVOsnJFsivZCEZ/vjK9gh github.com/go-sql-driver/mysql v1.4.0/go.mod h1:zAC/RDZ24gD3HViQzih4MyKcchzm+sOG5ZlKdlhCg5w= github.com/go-sql-driver/mysql v1.5.0/go.mod h1:DCzpHaOWr8IXmIStZouvnhqoel9Qv2LBy8hT2VhHyBg= github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY= +github.com/go-task/slim-sprig v0.0.0-20210107165309-348f09dbbbc0/go.mod h1:fyg7847qk6SyHyPtNmDHnmrv/HOrqktSC+C9fM+CJOE= github.com/gobuffalo/attrs v0.0.0-20190224210810-a9411de4debd/go.mod h1:4duuawTqi2wkkpB4ePgWMaai6/Kc6WEz83bhFwpHzj0= github.com/gobuffalo/depgen v0.0.0-20190329151759-d478694a28d3/go.mod h1:3STtPUQYuzV0gBVOY3vy6CfMm/ljR4pABfrTeHNLHUY= github.com/gobuffalo/depgen v0.1.0/go.mod h1:+ifsuy7fhi15RWncXQQKjWS9JPkdah5sZvtHc2RXGlg= @@ -433,6 +452,7 @@ github.com/golang/groupcache v0.0.0-20160516000752-02826c3e7903/go.mod h1:cIg4er github.com/golang/groupcache v0.0.0-20190129154638-5b532d6fd5ef/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= github.com/golang/groupcache v0.0.0-20190702054246-869f871628b6/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= github.com/golang/groupcache v0.0.0-20191227052852-215e87163ea7/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= +github.com/golang/groupcache v0.0.0-20200121045136-8c9f03a8e57e h1:1r7pUrabqp18hOBcwBwiTsbnFeTZHV9eER/QT5JVZxY= github.com/golang/groupcache v0.0.0-20200121045136-8c9f03a8e57e/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= github.com/golang/mock v1.2.0/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= @@ -511,6 +531,7 @@ github.com/google/uuid v1.0.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+ github.com/google/uuid v1.1.1/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/google/uuid v1.1.2/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/google/uuid v1.2.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= +github.com/google/uuid v1.3.0 h1:t6JiXgmwXMjEs8VusXIJk2BXHsn+wx8BZdTaoZ5fu7I= github.com/google/uuid v1.3.0/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= @@ -667,6 +688,8 @@ github.com/mattn/go-colorable v0.0.9/go.mod h1:9vuHe8Xs5qXnSaW/c/ABM9alt+Vo+STaO github.com/mattn/go-colorable v0.1.1/go.mod h1:FuOcm+DKB9mbwrcAfNl7/TZVBZ6rcnceauSikq3lYCQ= github.com/mattn/go-colorable v0.1.2/go.mod h1:U0ppj6V5qS13XJ6of8GYAs25YV2eR4EVcfRqFIhoBtE= github.com/mattn/go-colorable v0.1.6/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc= +github.com/mattn/go-colorable v0.1.8 h1:c1ghPdyEDarC70ftn0y+A/Ee++9zz8ljHG1b13eJ0s8= +github.com/mattn/go-colorable v0.1.8/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc= github.com/mattn/go-ieproxy v0.0.1/go.mod h1:pYabZ6IHcRpFh7vIaLfK7rdcWgFEb3SFJ6/gNWuh88E= github.com/mattn/go-isatty v0.0.3/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNxMWT7Zi4= github.com/mattn/go-isatty v0.0.4/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNxMWT7Zi4= @@ -674,6 +697,7 @@ github.com/mattn/go-isatty v0.0.5/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hd github.com/mattn/go-isatty v0.0.7/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s= github.com/mattn/go-isatty v0.0.8/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s= github.com/mattn/go-isatty v0.0.9/go.mod h1:YNRxwqDuOph6SZLI9vUUz6OYw3QyUt7WiY2yME+cCiQ= +github.com/mattn/go-isatty v0.0.12 h1:wuysRhFDzyxgEmMf5xjvJ2M9dZoWAXNNr5LSBS7uHXY= github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU= github.com/mattn/go-runewidth v0.0.2/go.mod h1:LwmH8dsx7+W8Uxz3IHJYH5QSwggIsqBzpuz5H//U1FU= github.com/mattn/go-shellwords v1.0.3/go.mod h1:3xCvwCdWdlDJUrvuMn7Wuy9eWs4pE8vqg+NOMyg4B2o= @@ -714,6 +738,7 @@ github.com/nakagami/firebirdsql v0.0.0-20190310045651-3c02a58cfed8/go.mod h1:86w github.com/ncw/swift v1.0.47/go.mod h1:23YIA4yWVnGwv2dQlN4bB7egfYX6YLn0Yo/S6zZO/ZM= github.com/neo4j/neo4j-go-driver v1.8.1-0.20200803113522-b626aa943eba/go.mod h1:ncO5VaFWh0Nrt+4KT4mOZboaczBZcLuHrG+/sUeP8gI= github.com/nxadm/tail v1.4.4/go.mod h1:kenIhsEOeOJmVchQTgglprH7qJGnHDVpk1VPCcaMI8A= +github.com/nxadm/tail v1.4.8/go.mod h1:+ncqLTQzXmGhMZNUePPaPqPvBxHAIsmXswZKocGu+AU= github.com/oklog/ulid v1.3.1/go.mod h1:CirwcVhetQ6Lv90oh/F+FBtV6XMibvdAFo93nm5qn4U= github.com/olekukonko/tablewriter v0.0.0-20170122224234-a0225b3f23b5/go.mod h1:vsDQFd/mU46D+Z4whnwzcISnGGzXWMclvtLoiIKAKIo= github.com/onsi/ginkgo v0.0.0-20151202141238-7f8ab55aaf3b/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= @@ -724,12 +749,16 @@ github.com/onsi/ginkgo v1.10.3/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+ github.com/onsi/ginkgo v1.11.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= github.com/onsi/ginkgo v1.12.0/go.mod h1:oUhWkIvk5aDxtKvDDuw8gItl8pKl42LzjC9KZE0HfGg= github.com/onsi/ginkgo v1.12.1/go.mod h1:zj2OWP4+oCPe1qIXoGWkgMRwljMUYCdkwsT2108oapk= +github.com/onsi/ginkgo v1.16.4/go.mod h1:dX+/inL/fNMqNlz0e9LfyB9TswhZpCVdJM/Z6Vvnwo0= +github.com/onsi/ginkgo v1.16.5/go.mod h1:+E8gABHa3K6zRBolWtd+ROzc/U5bkGt0FwiG042wbpU= github.com/onsi/gomega v0.0.0-20151007035656-2152b45fa28a/go.mod h1:C1qb7wdrVGGVU+Z6iS04AVkA3Q65CEZX59MT0QO5uiA= github.com/onsi/gomega v0.0.0-20170829124025-dcabb60a477c/go.mod h1:C1qb7wdrVGGVU+Z6iS04AVkA3Q65CEZX59MT0QO5uiA= github.com/onsi/gomega v1.7.0/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1CpauHY= github.com/onsi/gomega v1.7.1/go.mod h1:XdKZgCCFLUoM/7CFJVPcG8C1xQ1AJ0vpAezJrB7JYyY= github.com/onsi/gomega v1.9.0/go.mod h1:Ho0h+IUsWyvy1OpqCwxlQ/21gkhVunqlU8fDGcoTdcA= +github.com/onsi/gomega v1.10.1/go.mod h1:iN09h71vgCQne3DLsj+A5owkum+a2tYe+TOCB1ybHNo= github.com/onsi/gomega v1.10.3/go.mod h1:V9xEwhxec5O8UDM77eCW8vLymOMltsqPVYWrpDsH8xc= +github.com/onsi/gomega v1.17.0/go.mod h1:HnhC7FXeEQY45zxNK3PPoIUhzk/80Xly9PcubAlGdZY= github.com/opencontainers/go-digest v0.0.0-20170106003457-a6d0ee40d420/go.mod h1:cMLVZDEM3+U2I4VmLI6N8jQYUd2OVphdqWwCJHrFt2s= github.com/opencontainers/go-digest v0.0.0-20180430190053-c9281466c8b2/go.mod h1:cMLVZDEM3+U2I4VmLI6N8jQYUd2OVphdqWwCJHrFt2s= github.com/opencontainers/go-digest v1.0.0-rc1/go.mod h1:cMLVZDEM3+U2I4VmLI6N8jQYUd2OVphdqWwCJHrFt2s= @@ -767,6 +796,45 @@ github.com/phpdave11/gofpdf v1.4.2/go.mod h1:zpO6xFn9yxo3YLyMvW8HcKWVdbNqgIfOOp2 github.com/phpdave11/gofpdi v1.0.12/go.mod h1:vBmVV0Do6hSBHC8uKUQ71JGW+ZGQq74llk/7bXwjDoI= github.com/pierrec/lz4 v2.0.5+incompatible/go.mod h1:pdkljMzZIN41W+lC3N2tnIh5sFi+IEE17M5jbnwPHcY= github.com/pierrec/lz4/v4 v4.1.8/go.mod h1:gZWDp/Ze/IJXGXf23ltt2EXimqmTUXEy0GFuRQyBid4= +github.com/pion/datachannel v1.5.2 h1:piB93s8LGmbECrpO84DnkIVWasRMk3IimbcXkTQLE6E= +github.com/pion/datachannel v1.5.2/go.mod h1:FTGQWaHrdCwIJ1rw6xBIfZVkslikjShim5yr05XFuCQ= +github.com/pion/dtls/v2 v2.0.13 h1:toLgXzq42/MEmfgkXDfzdnwLHMi4tfycaQPGkv9tzRE= +github.com/pion/dtls/v2 v2.0.13/go.mod h1:OaE7eTM+ppaUhJ99OTO4aHl9uY6vPrT1gPY27uNTxRY= +github.com/pion/ice/v2 v2.1.18 h1:mDzd+iPKJmU30p4Kb+RPjK9olORLqJmQdiTUnVba50g= +github.com/pion/ice/v2 v2.1.18/go.mod h1:9jDr0iIUg8P6+0Jq8QJ/eFSkX3JnsPd293TjCdkfpTs= +github.com/pion/interceptor v0.1.4 h1:qL2xrdR6taLkVxEQj39btwEPRO3i9yd/olEw6+20dag= +github.com/pion/interceptor v0.1.4/go.mod h1:Lh3JSl/cbJ2wP8I3ccrjh1K/deRGRn3UlSPuOTiHb6U= +github.com/pion/logging v0.2.2 h1:M9+AIj/+pxNsDfAT64+MAVgJO0rsyLnoJKCqf//DoeY= +github.com/pion/logging v0.2.2/go.mod h1:k0/tDVsRCX2Mb2ZEmTqNa7CWsQPc+YYCB7Q+5pahoms= +github.com/pion/mdns v0.0.5 h1:Q2oj/JB3NqfzY9xGZ1fPzZzK7sDSD8rZPOvcIQ10BCw= +github.com/pion/mdns v0.0.5/go.mod h1:UgssrvdD3mxpi8tMxAXbsppL3vJ4Jipw1mTCW+al01g= +github.com/pion/randutil v0.1.0 h1:CFG1UdESneORglEsnimhUjf33Rwjubwj6xfiOXBa3mA= +github.com/pion/randutil v0.1.0/go.mod h1:XcJrSMMbbMRhASFVOlj/5hQial/Y8oH/HVo7TBZq+j8= +github.com/pion/rtcp v1.2.6/go.mod h1:52rMNPWFsjr39z9B9MhnkqhPLoeHTv1aN63o/42bWE0= +github.com/pion/rtcp v1.2.9 h1:1ujStwg++IOLIEoOiIQ2s+qBuJ1VN81KW+9pMPsif+U= +github.com/pion/rtcp v1.2.9/go.mod h1:qVPhiCzAm4D/rxb6XzKeyZiQK69yJpbUDJSF7TgrqNo= +github.com/pion/rtp v1.7.0/go.mod h1:bDb5n+BFZxXx0Ea7E5qe+klMuqiBrP+w8XSjiWtCUko= +github.com/pion/rtp v1.7.4 h1:4dMbjb1SuynU5OpA3kz1zHK+u+eOCQjW3MAeVHf1ODA= +github.com/pion/rtp v1.7.4/go.mod h1:bDb5n+BFZxXx0Ea7E5qe+klMuqiBrP+w8XSjiWtCUko= +github.com/pion/sctp v1.8.0/go.mod h1:xFe9cLMZ5Vj6eOzpyiKjT9SwGM4KpK/8Jbw5//jc+0s= +github.com/pion/sctp v1.8.2 h1:yBBCIrUMJ4yFICL3RIvR4eh/H2BTTvlligmSTy+3kiA= +github.com/pion/sctp v1.8.2/go.mod h1:xFe9cLMZ5Vj6eOzpyiKjT9SwGM4KpK/8Jbw5//jc+0s= +github.com/pion/sdp/v3 v3.0.4 h1:2Kf+dgrzJflNCSw3TV5v2VLeI0s/qkzy2r5jlR0wzf8= +github.com/pion/sdp/v3 v3.0.4/go.mod h1:bNiSknmJE0HYBprTHXKPQ3+JjacTv5uap92ueJZKsRk= +github.com/pion/srtp/v2 v2.0.5 h1:ks3wcTvIUE/GHndO3FAvROQ9opy0uLELpwHJaQ1yqhQ= +github.com/pion/srtp/v2 v2.0.5/go.mod h1:8k6AJlal740mrZ6WYxc4Dg6qDqqhxoRG2GSjlUhDF0A= +github.com/pion/stun v0.3.5 h1:uLUCBCkQby4S1cf6CGuR9QrVOKcvUwFeemaC865QHDg= +github.com/pion/stun v0.3.5/go.mod h1:gDMim+47EeEtfWogA37n6qXZS88L5V6LqFcf+DZA2UA= +github.com/pion/transport v0.12.2/go.mod h1:N3+vZQD9HlDP5GWkZ85LohxNsDcNgofQmyL6ojX5d8Q= +github.com/pion/transport v0.12.3/go.mod h1:OViWW9SP2peE/HbwBvARicmAVnesphkNkCVZIWJ6q9A= +github.com/pion/transport v0.13.0 h1:KWTA5ZrQogizzYwPEciGtHPLwpAjE91FgXnyu+Hv2uY= +github.com/pion/transport v0.13.0/go.mod h1:yxm9uXpK9bpBBWkITk13cLo1y5/ur5VQpG22ny6EP7g= +github.com/pion/turn/v2 v2.0.6 h1:AsXjSPR6Im15DMTB39NlfdTY9BQfieANPBjdg/aVNwY= +github.com/pion/turn/v2 v2.0.6/go.mod h1:+y7xl719J8bAEVpSXBXvTxStjJv3hbz9YFflvkpcGPw= +github.com/pion/udp v0.1.1 h1:8UAPvyqmsxK8oOjloDk4wUt63TzFe9WEJkg5lChlj7o= +github.com/pion/udp v0.1.1/go.mod h1:6AFo+CMdKQm7UiA0eUPA8/eVCTx8jBIITLZHc9DWX5M= +github.com/pion/webrtc/v3 v3.1.13 h1:2XxgGstOqt03ba8QD5+m9S8DCA3Ez53mULT4If8onOg= +github.com/pion/webrtc/v3 v3.1.13/go.mod h1:RACpyE1EDYlzonfbdPvXkIGDaqD8+NsHqZJN0yEbRbA= github.com/pkg/browser v0.0.0-20210706143420-7d21f8c997e2/go.mod h1:HKlIX3XHQyzLZPlr7++PzdhaXEj94dEiJgZDTsxEqUI= github.com/pkg/browser v0.0.0-20210911075715-681adbf594b8/go.mod h1:HKlIX3XHQyzLZPlr7++PzdhaXEj94dEiJgZDTsxEqUI= github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= @@ -820,7 +888,10 @@ github.com/russross/blackfriday/v2 v2.0.1/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQD github.com/ruudk/golang-pdf417 v0.0.0-20181029194003-1af4ab5afa58/go.mod h1:6lfFZQK844Gfx8o5WFuvpxWRwnSoipWe/p622j1v06w= github.com/safchain/ethtool v0.0.0-20190326074333-42ed695e3de8/go.mod h1:Z0q5wiBQGYcxhMZ6gUqHn6pYNLypFAvaL3UvgZLR0U4= github.com/satori/go.uuid v1.2.0/go.mod h1:dA0hQrYB0VpLJoorglMZABFdXlWrHn1NEOzdhQKdks0= +github.com/sclevine/agouti v3.0.0+incompatible/go.mod h1:b4WX9W9L1sfQKXeJf1mUTLZKJ48R1S7H23Ji7oFO5Bw= github.com/seccomp/libseccomp-golang v0.9.1/go.mod h1:GbW5+tmTXfcxTToHLXlScSlAvWlF4P2Ca7zGrPiEpWo= +github.com/sergi/go-diff v1.0.0 h1:Kpca3qRNrduNnOQeazBd0ysaKrUJiIuISHxogkT9RPQ= +github.com/sergi/go-diff v1.0.0/go.mod h1:0CfEIISq7TuYL3j771MWULgwwjU+GofnZX9QAmXWZgo= github.com/shopspring/decimal v0.0.0-20180709203117-cd690d0c9e24/go.mod h1:M+9NzErvs504Cn4c5DxATwIqPbtswREoFCre64PpcG4= github.com/shopspring/decimal v0.0.0-20200227202807-02e2044944cc/go.mod h1:DKyhrW/HYNuLGql+MJL6WCR6knT2jwCFRcu2hWCYk4o= github.com/shopspring/decimal v1.2.0/go.mod h1:DKyhrW/HYNuLGql+MJL6WCR6knT2jwCFRcu2hWCYk4o= @@ -922,6 +993,7 @@ go.opencensus.io v0.22.2/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= go.opencensus.io v0.22.3/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= go.opencensus.io v0.22.4/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= go.opencensus.io v0.22.5/go.mod h1:5pWMHQbX5EPX2/62yrJeAkowc+lfs/XD7Uxpq3pI6kk= +go.opencensus.io v0.23.0 h1:gqCw0LfLxScz8irSi8exQc7fyQ0fKQU/qnC/X8+V/1M= go.opencensus.io v0.23.0/go.mod h1:XItmlyltB5F7CS4xOC1DcqMoFqwtC6OG2xF7mCv7P7E= go.opentelemetry.io/proto/otlp v0.7.0/go.mod h1:PqfVotwruBrMGOCsRd/89rSnXhoiJIqeYNgFYFoEGnI= go.uber.org/atomic v1.3.2/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE= @@ -958,6 +1030,8 @@ golang.org/x/crypto v0.0.0-20201002170205-7f63de1d35b0/go.mod h1:LzIPMQfyMNhhGPh golang.org/x/crypto v0.0.0-20210322153248-0c34fe9e7dc2/go.mod h1:T9bdIzuCu7OtxOm1hfPfRQxPLYneinmdGuTeoZ9dtd4= golang.org/x/crypto v0.0.0-20210817164053-32db794688a5/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= +golang.org/x/crypto v0.0.0-20211117183948-ae814b36b871 h1:/pEO3GD/ABYAjuakUS6xSEmmlyVS4kxBNkeA9tLJiTI= +golang.org/x/crypto v0.0.0-20211117183948-ae814b36b871/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= golang.org/x/exp v0.0.0-20180321215751-8460e604b9de/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/exp v0.0.0-20180807140117-3d87b88a115f/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= @@ -1041,6 +1115,7 @@ golang.org/x/net v0.0.0-20200324143707-d3edc9973b7e/go.mod h1:qpuaurCH72eLCgpAm/ golang.org/x/net v0.0.0-20200501053045-e0ff5e5a1de5/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= golang.org/x/net v0.0.0-20200506145744-7e3656a0809f/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= golang.org/x/net v0.0.0-20200513185701-a91f0712d120/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= +golang.org/x/net v0.0.0-20200520004742-59133d7f0dd7/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= golang.org/x/net v0.0.0-20200520182314-0ba52f642ac2/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= golang.org/x/net v0.0.0-20200625001655-4c5254603344/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= golang.org/x/net v0.0.0-20200707034311-ab3426394381/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= @@ -1049,18 +1124,23 @@ golang.org/x/net v0.0.0-20201006153459-a7d1128ccaa0/go.mod h1:sp8m0HH+o8qH0wwXwY golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= golang.org/x/net v0.0.0-20201031054903-ff519b6c9102/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= golang.org/x/net v0.0.0-20201110031124-69a78807bb2b/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= +golang.org/x/net v0.0.0-20201201195509-5d6afe98e0b7/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= golang.org/x/net v0.0.0-20201209123823-ac852fbbde11/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= golang.org/x/net v0.0.0-20201224014010-6772e930b67b/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= golang.org/x/net v0.0.0-20210119194325-5f4716e94777/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= golang.org/x/net v0.0.0-20210316092652-d523dce5a7f4/go.mod h1:RBQZq4jEuRlivfhVLdyRGr576XBO4/greRjx4P4O3yc= golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4/go.mod h1:p54w0d4576C0XHj96bSt6lcn1PtDYWL6XObtHCRCNQM= +golang.org/x/net v0.0.0-20210428140749-89ef3d95e781/go.mod h1:OJAsFXCWl8Ukc7SiCT/9KSuxbyM7479/AVlXFRxuMCk= golang.org/x/net v0.0.0-20210503060351-7fd8e65b6420/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= golang.org/x/net v0.0.0-20210505024714-0287a6fb4125/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= golang.org/x/net v0.0.0-20210614182718-04defd469f4e/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= golang.org/x/net v0.0.0-20210813160813-60bc85c4be6d/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= -golang.org/x/net v0.0.0-20211013171255-e13a2654a71e h1:Xj+JO91noE97IN6F/7WZxzC5QE6yENAQPrwIYhW3bsA= golang.org/x/net v0.0.0-20211013171255-e13a2654a71e/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= +golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= +golang.org/x/net v0.0.0-20211201190559-0a0e4e1bb54c/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= +golang.org/x/net v0.0.0-20211215060638-4ddde0e984e9 h1:kmreh1vGI63l2FxOAYS3Yv6ATsi7lSTuwNSVbGfJV9I= +golang.org/x/net v0.0.0-20211215060638-4ddde0e984e9/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= golang.org/x/oauth2 v0.0.0-20180227000427-d7d64896b5ff/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= golang.org/x/oauth2 v0.0.0-20181106182150-f42d05182288/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= @@ -1141,6 +1221,7 @@ golang.org/x/sys v0.0.0-20200223170610-d5e6a3e2c0ae/go.mod h1:h1NjWce9XRLGQEsW7w golang.org/x/sys v0.0.0-20200302150141-5c8b2ff67527/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200331124033-c3d80250170d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200413165638-669c56c373c4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200501052902-10377860bb8e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200511232937-7e40ca221e25/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200515095857-1151b9dac4a9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= @@ -1164,6 +1245,7 @@ golang.org/x/sys v0.0.0-20201126233918-771906719818/go.mod h1:h1NjWce9XRLGQEsW7w golang.org/x/sys v0.0.0-20201201145000-ef89a241ccb3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20201202213521-69691e467435/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210104204734-6f8348627aad/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210112080510-489259a85091/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210119212857-b64e53b001e4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210124154548-22da62e12c0c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210220050731-9a76102bfb43/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= @@ -1185,6 +1267,7 @@ golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBc golang.org/x/sys v0.0.0-20210818153620-00dd8d7831e7/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20211013075003-97ac67df715c h1:taxlMj0D/1sOAuv/CbSD+MMDof2vbyPTqz5FNYKpXt8= golang.org/x/sys v0.0.0-20211013075003-97ac67df715c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1 h1:v+OssWQX+hTHEmOBgwxdZxK4zHq3yOs8F9J7mk0PY8E= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= @@ -1194,6 +1277,7 @@ golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.4/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.5/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= +golang.org/x/text v0.3.7 h1:olpwvP2KacW1ZWvsR7uQhoyTYvKAupfQrRGBFM352Gk= golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= golang.org/x/time v0.0.0-20180412165947-fbb02b2291d2/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= @@ -1262,6 +1346,7 @@ golang.org/x/tools v0.0.0-20201110124207-079ba7bd75cd/go.mod h1:emZCQorbCU4vsT4f golang.org/x/tools v0.0.0-20201124115921-2c860bdd6e78/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= golang.org/x/tools v0.0.0-20201201161351-ac6f37ff4c2a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= golang.org/x/tools v0.0.0-20201208233053-a543418bbed2/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= +golang.org/x/tools v0.0.0-20201224043029-2b0845dc783e/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= golang.org/x/tools v0.0.0-20210105154028-b0ab187a4818/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= golang.org/x/tools v0.1.0/go.mod h1:xkSsbof2nBLbhDlRMhhhyNLN/zl3eTqcnHD5viDpcZ0= diff --git a/peer/channel.go b/peer/channel.go new file mode 100644 index 0000000000..f61f35590c --- /dev/null +++ b/peer/channel.go @@ -0,0 +1,308 @@ +package peer + +import ( + "context" + "io" + "net" + "sync" + "time" + + "github.com/pion/datachannel" + "github.com/pion/webrtc/v3" + "golang.org/x/xerrors" + + "cdr.dev/slog" +) + +const ( + bufferedAmountLowThreshold uint64 = 512 * 1024 // 512 KB + maxBufferedAmount uint64 = 1024 * 1024 // 1 MB + // For some reason messages larger just don't work... + // This shouldn't be a huge deal for real-world usage. + // See: https://github.com/pion/datachannel/issues/59 + maxMessageLength = 32 * 1024 // 32 KB +) + +// newChannel creates a new channel and initializes it. +// The initialization overrides listener handles, and detaches +// the channel on open. The datachannel should not be manually +// mutated after being passed to this function. +func newChannel(conn *Conn, dc *webrtc.DataChannel, opts *ChannelOpts) *Channel { + c := &Channel{ + opts: opts, + conn: conn, + dc: dc, + + opened: make(chan struct{}), + closed: make(chan struct{}), + sendMore: make(chan struct{}, 1), + } + c.init() + return c +} + +type ChannelOpts struct { + // ID is a channel ID that should be used when `Negotiated` + // is true. + ID uint16 + + // Negotiated returns whether the data channel will already + // be active on the other end. Defaults to false. + Negotiated bool + + // Arbitrary string that can be parsed on `Accept`. + Protocol string + + // Ordered determines whether the channel acts like + // a TCP connection. Defaults to false. + Unordered bool + + // Whether the channel will be left open on disconnect or not. + // If true, data will be buffered on either end to be sent + // once reconnected. Defaults to false. + OpenOnDisconnect bool +} + +// Channel represents a WebRTC DataChannel. +// +// This struct wraps webrtc.DataChannel to add concurrent-safe usage, +// data bufferring, and standardized errors for connection state. +// +// It modifies the default behavior of a DataChannel by closing on +// WebRTC PeerConnection failure. This is done to emulate TCP connections. +// This option can be changed in the options when creating a Channel. +type Channel struct { + opts *ChannelOpts + + conn *Conn + dc *webrtc.DataChannel + // This field can be nil. It becomes set after the DataChannel + // has been opened and is detached. + rwc datachannel.ReadWriteCloser + + closed chan struct{} + closeMutex sync.Mutex + closeError error + + opened chan struct{} + + // sendMore is used to block Write operations on a full buffer. + // It's signaled when the buffer can accept more data. + sendMore chan struct{} + writeMutex sync.Mutex +} + +// init attaches listeners to the DataChannel to detect opening, +// closing, and when the channel is ready to transmit data. +// +// This should only be called once on creation. +func (c *Channel) init() { + // WebRTC connections maintain an internal buffer that can fill when: + // 1. Data is being sent faster than it can flush. + // 2. The connection is disconnected, but data is still being sent. + // + // This applies a maximum in-memory buffer for data, and will cause + // write operations to block once the threshold is set. + c.dc.SetBufferedAmountLowThreshold(bufferedAmountLowThreshold) + c.dc.OnBufferedAmountLow(func() { + if c.isClosed() { + return + } + select { + case c.sendMore <- struct{}{}: + default: + } + }) + c.dc.OnClose(func() { + c.conn.opts.Logger.Debug(context.Background(), "datachannel closing from OnClose", slog.F("id", c.dc.ID()), slog.F("label", c.dc.Label())) + _ = c.closeWithError(ErrClosed) + }) + c.dc.OnOpen(func() { + c.closeMutex.Lock() + defer c.closeMutex.Unlock() + + c.conn.opts.Logger.Debug(context.Background(), "datachannel opening", slog.F("id", c.dc.ID()), slog.F("label", c.dc.Label())) + var err error + c.rwc, err = c.dc.Detach() + if err != nil { + _ = c.closeWithError(xerrors.Errorf("detach: %w", err)) + return + } + close(c.opened) + }) + + c.conn.dcDisconnectListeners.Add(1) + c.conn.dcFailedListeners.Add(1) + go func() { + var err error + // A DataChannel can disconnect multiple times, so this needs to loop. + for { + select { + case <-c.closed: + // If this channel was closed, there's no need to close again. + return + case <-c.conn.Closed(): + // If the RTC connection closed with an error, this channel + // should end with the same one. + err = c.conn.closeError + case <-c.conn.dcDisconnectChannel: + // If the RTC connection is disconnected, we need to check if + // the DataChannel is supposed to end on disconnect. + if c.opts.OpenOnDisconnect { + continue + } + err = xerrors.Errorf("rtc disconnected. closing: %w", ErrClosed) + case <-c.conn.dcFailedChannel: + // If the RTC connection failed, close the Channel. + err = ErrFailed + } + if err != nil { + break + } + } + _ = c.closeWithError(err) + }() +} + +// Read blocks until data is received. +// +// This will block until the underlying DataChannel has been opened. +func (c *Channel) Read(b []byte) (n int, err error) { + if c.isClosed() { + return 0, c.closeError + } + if !c.isOpened() { + err := c.waitOpened() + if err != nil { + return 0, err + } + } + + n, err = c.rwc.Read(b) + if err != nil { + if c.isClosed() { + return 0, c.closeError + } + // An EOF always occurs when the connection is closed. + // Alternative close errors will occur first if an unexpected + // close has occurred. + if xerrors.Is(err, io.EOF) { + err = c.closeWithError(ErrClosed) + } + return + } + return +} + +// Write sends data to the underlying DataChannel. +// +// This function will block if too much data is being sent. +// Data will buffer if the connection is temporarily disconnected, +// and will be flushed upon reconnection. +// +// If the Channel is setup to close on disconnect, any buffered +// data will be lost. +func (c *Channel) Write(b []byte) (n int, err error) { + if len(b) > maxMessageLength { + return 0, xerrors.Errorf("outbound packet larger than maximum message size: %d", maxMessageLength) + } + + c.writeMutex.Lock() + defer c.writeMutex.Unlock() + + if c.isClosed() { + return 0, c.closeWithError(nil) + } + if !c.isOpened() { + err := c.waitOpened() + if err != nil { + return 0, err + } + } + + if c.dc.BufferedAmount()+uint64(len(b)) >= maxBufferedAmount { + <-c.sendMore + } + // TODO (@kyle): There's an obvious race-condition here. + // This is an edge-case, as most-frequently data won't + // be pooled so synchronously, but is definitely possible. + // + // See: https://github.com/pion/sctp/issues/181 + time.Sleep(time.Microsecond) + + return c.rwc.Write(b) +} + +// Close gracefully closes the DataChannel. +func (c *Channel) Close() error { + return c.closeWithError(nil) +} + +// Label returns the label of the underlying DataChannel. +func (c *Channel) Label() string { + return c.dc.Label() +} + +// NetConn wraps the DataChannel in a struct fulfilling net.Conn. +// Read, Write, and Close operations can still be used on the *Channel struct. +func (c *Channel) NetConn() net.Conn { + return &fakeNetConn{ + c: c, + addr: &peerAddr{}, + } +} + +// closeWithError closes the Channel with the error provided. +// If a graceful close occurs, the error will be nil. +func (c *Channel) closeWithError(err error) error { + c.closeMutex.Lock() + defer c.closeMutex.Unlock() + + if c.isClosed() { + return c.closeError + } + + c.conn.opts.Logger.Debug(context.Background(), "datachannel closing with error", slog.F("id", c.dc.ID()), slog.F("label", c.dc.Label()), slog.Error(err)) + if err == nil { + c.closeError = ErrClosed + } else { + c.closeError = err + } + close(c.closed) + close(c.sendMore) + c.conn.dcDisconnectListeners.Sub(1) + c.conn.dcFailedListeners.Sub(1) + + if c.rwc != nil { + _ = c.rwc.Close() + } + _ = c.dc.Close() + return err +} + +func (c *Channel) isClosed() bool { + select { + case <-c.closed: + return true + default: + return false + } +} + +func (c *Channel) isOpened() bool { + select { + case <-c.opened: + return true + default: + return false + } +} + +func (c *Channel) waitOpened() error { + select { + case <-c.opened: + return nil + case <-c.closed: + return c.closeError + } +} diff --git a/peer/conn.go b/peer/conn.go new file mode 100644 index 0000000000..00f340f5c4 --- /dev/null +++ b/peer/conn.go @@ -0,0 +1,482 @@ +package peer + +import ( + "bytes" + "context" + "crypto/rand" + "io" + "sync" + "time" + + "github.com/pion/logging" + "github.com/pion/webrtc/v3" + "go.uber.org/atomic" + "golang.org/x/xerrors" + + "cdr.dev/slog" +) + +var ( + // ErrDisconnected occurs when the connection has disconnected. + // The connection will be attempting to reconnect at this point. + ErrDisconnected = xerrors.New("connection is disconnected") + // ErrFailed occurs when the connection has failed. + // The connection will not retry after this point. + ErrFailed = xerrors.New("connection has failed") + // ErrClosed occurs when the connection was closed. It wraps io.EOF + // to fulfill expected read errors from closed pipes. + ErrClosed = xerrors.Errorf("connection was closed: %w", io.EOF) + + // The amount of random bytes sent in a ping. + pingDataLength = 64 +) + +// Client creates a new client connection. +func Client(servers []webrtc.ICEServer, opts *ConnOpts) (*Conn, error) { + return newWithClientOrServer(servers, true, opts) +} + +// Server creates a new server connection. +func Server(servers []webrtc.ICEServer, opts *ConnOpts) (*Conn, error) { + return newWithClientOrServer(servers, false, opts) +} + +// newWithClientOrServer constructs a new connection with the client option. +func newWithClientOrServer(servers []webrtc.ICEServer, client bool, opts *ConnOpts) (*Conn, error) { + if opts == nil { + opts = &ConnOpts{} + } + + // Enables preference to STUN. + opts.SettingEngine.SetSrflxAcceptanceMinWait(0) + opts.SettingEngine.DetachDataChannels() + lf := logging.NewDefaultLoggerFactory() + lf.DefaultLogLevel = logging.LogLevelDisabled + opts.SettingEngine.LoggerFactory = lf + api := webrtc.NewAPI(webrtc.WithSettingEngine(opts.SettingEngine)) + rtc, err := api.NewPeerConnection(webrtc.Configuration{ + ICEServers: servers, + }) + if err != nil { + return nil, xerrors.Errorf("create peer connection: %w", err) + } + c := &Conn{ + pingChannelID: 1, + pingEchoChannelID: 2, + opts: opts, + rtc: rtc, + offerrer: client, + closed: make(chan struct{}), + dcOpenChannel: make(chan *webrtc.DataChannel), + dcDisconnectChannel: make(chan struct{}), + dcFailedChannel: make(chan struct{}), + localCandidateChannel: make(chan webrtc.ICECandidateInit), + localSessionDescriptionChannel: make(chan webrtc.SessionDescription), + remoteSessionDescriptionChannel: make(chan webrtc.SessionDescription), + } + if client { + // If we're the client, we want to flip the echo and + // ping channel IDs so pings don't accidentally hit each other. + c.pingChannelID, c.pingEchoChannelID = c.pingEchoChannelID, c.pingChannelID + } + err = c.init() + if err != nil { + return nil, xerrors.Errorf("init: %w", err) + } + return c, nil +} + +type ConnOpts struct { + Logger slog.Logger + + // Enables customization on the underlying WebRTC connection. + SettingEngine webrtc.SettingEngine +} + +// Conn represents a WebRTC peer connection. +// +// This struct wraps webrtc.PeerConnection to add bidirectional pings, +// concurrent-safe webrtc.DataChannel, and standardized errors for connection state. +type Conn struct { + rtc *webrtc.PeerConnection + opts *ConnOpts + // Determines whether this connection will send the offer or the answer. + offerrer bool + + closed chan struct{} + closeMutex sync.Mutex + closeError error + + dcOpenChannel chan *webrtc.DataChannel + dcDisconnectChannel chan struct{} + dcDisconnectListeners atomic.Uint32 + dcFailedChannel chan struct{} + dcFailedListeners atomic.Uint32 + + localCandidateChannel chan webrtc.ICECandidateInit + localSessionDescriptionChannel chan webrtc.SessionDescription + remoteSessionDescriptionChannel chan webrtc.SessionDescription + + pingChannelID uint16 + pingEchoChannelID uint16 + + pingEchoChan *Channel + pingEchoOnce sync.Once + pingEchoError error + + pingMutex sync.Mutex + pingOnce sync.Once + pingChan *Channel + pingError error +} + +func (c *Conn) init() error { + c.rtc.OnNegotiationNeeded(c.negotiate) + c.rtc.OnDataChannel(func(dc *webrtc.DataChannel) { + select { + case <-c.closed: + return + case c.dcOpenChannel <- dc: + default: + } + }) + c.rtc.OnConnectionStateChange(func(pcs webrtc.PeerConnectionState) { + if c.isClosed() { + return + } + + c.opts.Logger.Debug(context.Background(), "rtc connection updated", + slog.F("state", pcs), + slog.F("ice", c.rtc.ICEConnectionState())) + + switch pcs { + case webrtc.PeerConnectionStateDisconnected: + for i := 0; i < int(c.dcDisconnectListeners.Load()); i++ { + select { + case c.dcDisconnectChannel <- struct{}{}: + default: + } + } + case webrtc.PeerConnectionStateFailed: + for i := 0; i < int(c.dcFailedListeners.Load()); i++ { + select { + case c.dcFailedChannel <- struct{}{}: + default: + } + } + } + }) + _, err := c.pingChannel() + if err != nil { + return err + } + _, err = c.pingEchoChannel() + if err != nil { + return err + } + + return nil +} + +func (c *Conn) pingChannel() (*Channel, error) { + c.pingOnce.Do(func() { + c.pingChan, c.pingError = c.dialChannel(context.Background(), "ping", &ChannelOpts{ + ID: c.pingChannelID, + Negotiated: true, + OpenOnDisconnect: true, + }) + if c.pingError != nil { + return + } + }) + return c.pingChan, c.pingError +} + +func (c *Conn) pingEchoChannel() (*Channel, error) { + c.pingEchoOnce.Do(func() { + c.pingEchoChan, c.pingEchoError = c.dialChannel(context.Background(), "echo", &ChannelOpts{ + ID: c.pingEchoChannelID, + Negotiated: true, + OpenOnDisconnect: true, + }) + if c.pingEchoError != nil { + return + } + go func() { + for { + data := make([]byte, pingDataLength) + bytesRead, err := c.pingEchoChan.Read(data) + if err != nil { + if c.isClosed() { + return + } + _ = c.closeWithError(xerrors.Errorf("read ping echo channel: %w", err)) + return + } + _, err = c.pingEchoChan.Write(data[:bytesRead]) + if err != nil { + _ = c.closeWithError(xerrors.Errorf("write ping echo channel: %w", err)) + return + } + } + }() + }) + return c.pingEchoChan, c.pingEchoError +} + +func (c *Conn) negotiate() { + c.opts.Logger.Debug(context.Background(), "negotiating") + flushCandidates := c.proxyICECandidates() + + if c.offerrer { + offer, err := c.rtc.CreateOffer(&webrtc.OfferOptions{}) + if err != nil { + _ = c.closeWithError(xerrors.Errorf("create offer: %w", err)) + return + } + err = c.rtc.SetLocalDescription(offer) + if err != nil { + _ = c.closeWithError(xerrors.Errorf("set local description: %w", err)) + return + } + select { + case <-c.closed: + return + case c.localSessionDescriptionChannel <- offer: + } + } + + var remoteDescription webrtc.SessionDescription + select { + case <-c.closed: + return + case remoteDescription = <-c.remoteSessionDescriptionChannel: + } + + err := c.rtc.SetRemoteDescription(remoteDescription) + if err != nil { + _ = c.closeWithError(xerrors.Errorf("set remote description (closed %v): %w", c.isClosed(), err)) + return + } + + if !c.offerrer { + answer, err := c.rtc.CreateAnswer(&webrtc.AnswerOptions{}) + if err != nil { + _ = c.closeWithError(xerrors.Errorf("create answer: %w", err)) + return + } + err = c.rtc.SetLocalDescription(answer) + if err != nil { + _ = c.closeWithError(xerrors.Errorf("set local description: %w", err)) + return + } + if c.isClosed() { + return + } + select { + case <-c.closed: + return + case c.localSessionDescriptionChannel <- answer: + } + } + + flushCandidates() + c.opts.Logger.Debug(context.Background(), "flushed candidates") +} + +func (c *Conn) proxyICECandidates() func() { + var ( + mut sync.Mutex + queue = []webrtc.ICECandidateInit{} + flushed = false + ) + c.rtc.OnICECandidate(func(i *webrtc.ICECandidate) { + if i == nil { + return + } + mut.Lock() + defer mut.Unlock() + if !flushed { + queue = append(queue, i.ToJSON()) + return + } + select { + case <-c.closed: + return + case c.localCandidateChannel <- i.ToJSON(): + } + }) + return func() { + mut.Lock() + defer mut.Unlock() + for _, q := range queue { + select { + case <-c.closed: + break + case c.localCandidateChannel <- q: + } + } + flushed = true + } +} + +// LocalCandidate returns a channel that emits when a local candidate +// needs to be exchanged with a remote connection. +func (c *Conn) LocalCandidate() <-chan webrtc.ICECandidateInit { + return c.localCandidateChannel +} + +// AddRemoteCandidate adds a remote candidate to the RTC connection. +func (c *Conn) AddRemoteCandidate(i webrtc.ICECandidateInit) error { + return c.rtc.AddICECandidate(i) +} + +// LocalSessionDescription returns a channel that emits a session description +// when one is required to be exchanged. +func (c *Conn) LocalSessionDescription() <-chan webrtc.SessionDescription { + return c.localSessionDescriptionChannel +} + +// SetRemoteSessionDescription sets the remote description for the WebRTC connection. +func (c *Conn) SetRemoteSessionDescription(s webrtc.SessionDescription) { + if c.isClosed() { + return + } + c.closeMutex.Lock() + defer c.closeMutex.Unlock() + select { + case <-c.closed: + case c.remoteSessionDescriptionChannel <- s: + } +} + +// Accept blocks waiting for a channel to be opened. +func (c *Conn) Accept(ctx context.Context) (*Channel, error) { + var dataChannel *webrtc.DataChannel + select { + case <-ctx.Done(): + return nil, ctx.Err() + case <-c.closed: + return nil, c.closeError + case dataChannel = <-c.dcOpenChannel: + } + + return newChannel(c, dataChannel, &ChannelOpts{}), nil +} + +// Dial creates a new DataChannel. +func (c *Conn) Dial(ctx context.Context, label string, opts *ChannelOpts) (*Channel, error) { + if opts == nil { + opts = &ChannelOpts{} + } + if opts.ID == c.pingChannelID || opts.ID == c.pingEchoChannelID { + return nil, xerrors.Errorf("datachannel id %d and %d are reserved for ping", c.pingChannelID, c.pingEchoChannelID) + } + return c.dialChannel(ctx, label, opts) +} + +func (c *Conn) dialChannel(ctx context.Context, label string, opts *ChannelOpts) (*Channel, error) { + c.opts.Logger.Debug(ctx, "creating data channel", slog.F("label", label), slog.F("opts", opts)) + var id *uint16 + if opts.ID != 0 { + id = &opts.ID + } + ordered := true + if opts.Unordered { + ordered = false + } + if opts.OpenOnDisconnect && !opts.Negotiated { + return nil, xerrors.New("OpenOnDisconnect is only allowed for Negotiated channels") + } + + dc, err := c.rtc.CreateDataChannel(label, &webrtc.DataChannelInit{ + ID: id, + Negotiated: &opts.Negotiated, + Ordered: &ordered, + Protocol: &opts.Protocol, + }) + if err != nil { + return nil, xerrors.Errorf("create data channel: %w", err) + } + return newChannel(c, dc, opts), nil +} + +// Ping returns the duration it took to round-trip data. +// Multiple pings cannot occur at the same time, so this function will block. +func (c *Conn) Ping() (time.Duration, error) { + // Pings are not async, so we need a mutex. + c.pingMutex.Lock() + defer c.pingMutex.Unlock() + + ping, err := c.pingChannel() + if err != nil { + return 0, xerrors.Errorf("get ping channel: %w", err) + } + pingDataSent := make([]byte, pingDataLength) + _, err = rand.Read(pingDataSent) + if err != nil { + return 0, xerrors.Errorf("read random ping data: %w", err) + } + start := time.Now() + _, err = ping.Write(pingDataSent) + if err != nil { + return 0, xerrors.Errorf("send ping: %w", err) + } + c.opts.Logger.Debug(context.Background(), "wrote ping", + slog.F("connection_state", c.rtc.ConnectionState())) + + pingDataReceived := make([]byte, pingDataLength) + _, err = ping.Read(pingDataReceived) + if err != nil { + return 0, xerrors.Errorf("read ping: %w", err) + } + end := time.Now() + if !bytes.Equal(pingDataSent, pingDataReceived) { + return 0, xerrors.Errorf("ping data inconsistency sent != received") + } + return end.Sub(start), nil +} + +func (c *Conn) Closed() <-chan struct{} { + return c.closed +} + +// Close closes the connection and frees all associated resources. +func (c *Conn) Close() error { + return c.closeWithError(nil) +} + +func (c *Conn) isClosed() bool { + select { + case <-c.closed: + return true + default: + return false + } +} + +func (c *Conn) closeWithError(err error) error { + c.closeMutex.Lock() + defer c.closeMutex.Unlock() + + if c.isClosed() { + return c.closeError + } + + c.opts.Logger.Debug(context.Background(), "closing conn with error", slog.Error(err)) + if err == nil { + c.closeError = ErrClosed + } else { + c.closeError = err + } + close(c.closed) + + if ch, _ := c.pingChannel(); ch != nil { + _ = ch.closeWithError(c.closeError) + } + // If the WebRTC connection has already been closed (due to failure or disconnect), + // this call will return an error that isn't typed. We don't check the error because + // closing an already closed connection isn't an issue for us. + _ = c.rtc.Close() + return err +} diff --git a/peer/conn_test.go b/peer/conn_test.go new file mode 100644 index 0000000000..e9b8732ad4 --- /dev/null +++ b/peer/conn_test.go @@ -0,0 +1,294 @@ +package peer_test + +import ( + "context" + "io" + "net" + "net/http" + "sync" + "testing" + "time" + + "github.com/pion/logging" + "github.com/pion/transport/vnet" + "github.com/pion/webrtc/v3" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + "go.uber.org/goleak" + + "cdr.dev/slog" + "cdr.dev/slog/sloggers/slogtest" + "github.com/coder/coder/peer" +) + +const ( + disconnectedTimeout = time.Millisecond * 200 + failedTimeout = disconnectedTimeout * 5 + keepAliveInterval = time.Millisecond * 2 +) + +var ( + // There's a global race in the vnet library allocation code. + // This mutex locks around the creation of the vnet. + vnetMutex = sync.Mutex{} +) + +func TestMain(m *testing.M) { + goleak.VerifyTestMain(m) +} + +func TestConn(t *testing.T) { + t.Parallel() + + t.Run("Ping", func(t *testing.T) { + t.Parallel() + client, server, _ := createPair(t) + _, err := client.Ping() + require.NoError(t, err) + _, err = server.Ping() + require.NoError(t, err) + }) + + t.Run("PingNetworkOffline", func(t *testing.T) { + t.Parallel() + _, server, wan := createPair(t) + _, err := server.Ping() + require.NoError(t, err) + err = wan.Stop() + require.NoError(t, err) + _, err = server.Ping() + require.ErrorIs(t, err, peer.ErrFailed) + }) + + t.Run("PingReconnect", func(t *testing.T) { + t.Parallel() + _, server, wan := createPair(t) + _, err := server.Ping() + require.NoError(t, err) + // Create a channel that closes on disconnect. + ch, err := server.Dial(context.Background(), "wow", nil) + assert.NoError(t, err) + err = wan.Stop() + require.NoError(t, err) + // Once the connection is marked as disconnected, this + // channel will be closed. + _, err = ch.Read(make([]byte, 4)) + assert.ErrorIs(t, err, peer.ErrClosed) + err = wan.Start() + require.NoError(t, err) + _, err = server.Ping() + require.NoError(t, err) + }) + + t.Run("Accept", func(t *testing.T) { + t.Parallel() + client, server, _ := createPair(t) + cch, err := client.Dial(context.Background(), "hello", &peer.ChannelOpts{}) + require.NoError(t, err) + + sch, err := server.Accept(context.Background()) + require.NoError(t, err) + + _ = cch.Close() + _, err = sch.Read(make([]byte, 4)) + require.ErrorIs(t, err, peer.ErrClosed) + }) + + t.Run("AcceptNetworkOffline", func(t *testing.T) { + t.Parallel() + client, server, wan := createPair(t) + cch, err := client.Dial(context.Background(), "hello", &peer.ChannelOpts{}) + require.NoError(t, err) + sch, err := server.Accept(context.Background()) + require.NoError(t, err) + + err = wan.Stop() + require.NoError(t, err) + _ = cch.Close() + _, err = sch.Read(make([]byte, 4)) + require.ErrorIs(t, err, peer.ErrClosed) + }) + + t.Run("Buffering", func(t *testing.T) { + t.Parallel() + client, server, _ := createPair(t) + cch, err := client.Dial(context.Background(), "hello", &peer.ChannelOpts{}) + require.NoError(t, err) + sch, err := server.Accept(context.Background()) + require.NoError(t, err) + go func() { + for i := 0; i < 1024; i++ { + _, err := cch.Write(make([]byte, 4096)) + require.NoError(t, err) + } + _ = cch.Close() + }() + for { + _, err = sch.Read(make([]byte, 4096)) + if err != nil { + require.ErrorIs(t, err, peer.ErrClosed) + break + } + } + }) + + t.Run("NetConn", func(t *testing.T) { + t.Parallel() + client, server, _ := createPair(t) + srv, err := net.Listen("tcp", "127.0.0.1:0") + require.NoError(t, err) + defer srv.Close() + go func() { + sch, err := server.Accept(context.Background()) + require.NoError(t, err) + nc2 := sch.NetConn() + nc1, err := net.Dial("tcp", srv.Addr().String()) + require.NoError(t, err) + go func() { + _, _ = io.Copy(nc1, nc2) + }() + _, _ = io.Copy(nc2, nc1) + }() + go func() { + s := http.Server{ + Handler: http.HandlerFunc(func(rw http.ResponseWriter, r *http.Request) { + rw.WriteHeader(200) + }), + } + defer s.Close() + _ = s.Serve(srv) + }() + + dt := http.DefaultTransport.(*http.Transport).Clone() + var cch *peer.Channel + dt.DialContext = func(ctx context.Context, network, addr string) (net.Conn, error) { + cch, err = client.Dial(context.Background(), "hello", &peer.ChannelOpts{}) + if err != nil { + return nil, err + } + return cch.NetConn(), nil + } + c := http.Client{ + Transport: dt, + } + req, err := http.NewRequestWithContext(context.Background(), "GET", "http://localhost/", nil) + require.NoError(t, err) + resp, err := c.Do(req) + require.NoError(t, err) + defer resp.Body.Close() + require.Equal(t, resp.StatusCode, 200) + // Triggers any connections to close. + // This test below ensures the DataChannel actually closes. + dt.CloseIdleConnections() + err = cch.Close() + require.ErrorIs(t, err, peer.ErrClosed) + }) + + t.Run("CloseBeforeNegotiate", func(t *testing.T) { + t.Parallel() + client, server, _ := createPair(t) + err := client.Close() + require.NoError(t, err) + err = server.Close() + require.NoError(t, err) + }) + + t.Run("PingConcurrent", func(t *testing.T) { + t.Parallel() + client, server, _ := createPair(t) + var wg sync.WaitGroup + wg.Add(2) + go func() { + defer wg.Done() + _, err := client.Ping() + require.NoError(t, err) + }() + go func() { + defer wg.Done() + _, err := server.Ping() + require.NoError(t, err) + }() + wg.Wait() + }) +} + +func createPair(t *testing.T) (client *peer.Conn, server *peer.Conn, wan *vnet.Router) { + lf := logging.NewDefaultLoggerFactory() + lf.DefaultLogLevel = logging.LogLevelDisabled + vnetMutex.Lock() + defer vnetMutex.Unlock() + wan, err := vnet.NewRouter(&vnet.RouterConfig{ + CIDR: "1.2.3.0/24", + LoggerFactory: lf, + }) + require.NoError(t, err) + c1Net := vnet.NewNet(&vnet.NetConfig{ + StaticIPs: []string{"1.2.3.4"}, + }) + err = wan.AddNet(c1Net) + require.NoError(t, err) + c2Net := vnet.NewNet(&vnet.NetConfig{ + StaticIPs: []string{"1.2.3.5"}, + }) + err = wan.AddNet(c2Net) + require.NoError(t, err) + + c1SettingEngine := webrtc.SettingEngine{} + c1SettingEngine.SetVNet(c1Net) + c1SettingEngine.SetPrflxAcceptanceMinWait(0) + c1SettingEngine.SetICETimeouts(disconnectedTimeout, failedTimeout, keepAliveInterval) + c1, err := peer.Client([]webrtc.ICEServer{}, &peer.ConnOpts{ + SettingEngine: c1SettingEngine, + Logger: slogtest.Make(t, nil).Named("client").Leveled(slog.LevelDebug), + }) + require.NoError(t, err) + t.Cleanup(func() { + c1.Close() + }) + c2SettingEngine := webrtc.SettingEngine{} + c2SettingEngine.SetVNet(c2Net) + c2SettingEngine.SetPrflxAcceptanceMinWait(0) + c2SettingEngine.SetICETimeouts(disconnectedTimeout, failedTimeout, keepAliveInterval) + c2, err := peer.Server([]webrtc.ICEServer{}, &peer.ConnOpts{ + SettingEngine: c2SettingEngine, + Logger: slogtest.Make(t, nil).Named("server").Leveled(slog.LevelDebug), + }) + require.NoError(t, err) + t.Cleanup(func() { + c2.Close() + }) + + err = wan.Start() + require.NoError(t, err) + t.Cleanup(func() { + _ = wan.Stop() + }) + + go func() { + for { + select { + case c := <-c2.LocalCandidate(): + _ = c1.AddRemoteCandidate(c) + case c := <-c2.LocalSessionDescription(): + c1.SetRemoteSessionDescription(c) + case <-c2.Closed(): + return + } + } + }() + + go func() { + for { + select { + case c := <-c1.LocalCandidate(): + _ = c2.AddRemoteCandidate(c) + case c := <-c1.LocalSessionDescription(): + c2.SetRemoteSessionDescription(c) + case <-c1.Closed(): + return + } + } + }() + + return c1, c2, wan +} diff --git a/peer/netconn.go b/peer/netconn.go new file mode 100644 index 0000000000..67c3fb55e5 --- /dev/null +++ b/peer/netconn.go @@ -0,0 +1,59 @@ +package peer + +import ( + "net" + "time" +) + +type peerAddr struct{} + +// Statically checks if we properly implement net.Addr. +var _ net.Addr = &peerAddr{} + +func (a *peerAddr) Network() string { + return "peer" +} + +func (a *peerAddr) String() string { + return "peer/unknown-addr" +} + +type fakeNetConn struct { + c *Channel + addr *peerAddr +} + +// Statically checks if we properly implement net.Conn. +var _ net.Conn = &fakeNetConn{} + +func (c *fakeNetConn) Read(b []byte) (n int, err error) { + return c.c.Read(b) +} + +func (c *fakeNetConn) Write(b []byte) (n int, err error) { + return c.c.Write(b) +} + +func (c *fakeNetConn) Close() error { + return c.c.Close() +} + +func (c *fakeNetConn) LocalAddr() net.Addr { + return c.addr +} + +func (c *fakeNetConn) RemoteAddr() net.Addr { + return c.addr +} + +func (c *fakeNetConn) SetDeadline(_ time.Time) error { + return nil +} + +func (c *fakeNetConn) SetReadDeadline(_ time.Time) error { + return nil +} + +func (c *fakeNetConn) SetWriteDeadline(_ time.Time) error { + return nil +}