mirror of
https://github.com/libp2p/go-libp2p.git
synced 2025-09-27 04:26:41 +08:00
add WebRTC Direct transport implementation (#2337)
This commit is contained in:
19
go.mod
19
go.mod
@@ -45,6 +45,11 @@ require (
|
||||
github.com/multiformats/go-multistream v0.5.0
|
||||
github.com/multiformats/go-varint v0.0.7
|
||||
github.com/pbnjay/memory v0.0.0-20210728143218-7b4eea64cf58
|
||||
github.com/pion/datachannel v1.5.5
|
||||
github.com/pion/ice/v2 v2.3.6
|
||||
github.com/pion/logging v0.2.2
|
||||
github.com/pion/stun v0.6.0
|
||||
github.com/pion/webrtc/v3 v3.2.9
|
||||
github.com/prometheus/client_golang v1.14.0
|
||||
github.com/prometheus/client_model v0.4.0
|
||||
github.com/quic-go/quic-go v0.38.1
|
||||
@@ -53,6 +58,7 @@ require (
|
||||
github.com/stretchr/testify v1.8.4
|
||||
go.uber.org/fx v1.20.0
|
||||
go.uber.org/goleak v1.2.0
|
||||
go.uber.org/zap v1.25.0
|
||||
golang.org/x/crypto v0.12.0
|
||||
golang.org/x/exp v0.0.0-20230817173708-d852ddb80c63
|
||||
golang.org/x/sync v0.3.0
|
||||
@@ -94,9 +100,19 @@ require (
|
||||
github.com/miekg/dns v1.1.55 // indirect
|
||||
github.com/mikioh/tcpopt v0.0.0-20190314235656-172688c1accc // indirect
|
||||
github.com/multiformats/go-base36 v0.2.0 // indirect
|
||||
github.com/onsi/ginkgo v1.16.5 // indirect
|
||||
github.com/onsi/ginkgo/v2 v2.11.0 // indirect
|
||||
github.com/opencontainers/runtime-spec v1.1.0 // indirect
|
||||
github.com/pion/dtls/v2 v2.2.7 // indirect
|
||||
github.com/pion/interceptor v0.1.17 // indirect
|
||||
github.com/pion/mdns v0.0.7 // indirect
|
||||
github.com/pion/randutil v0.1.0 // indirect
|
||||
github.com/pion/rtcp v1.2.10 // indirect
|
||||
github.com/pion/rtp v1.7.13 // indirect
|
||||
github.com/pion/sctp v1.8.7 // indirect
|
||||
github.com/pion/sdp/v3 v3.0.6 // indirect
|
||||
github.com/pion/srtp/v2 v2.0.15 // indirect
|
||||
github.com/pion/transport/v2 v2.2.1 // indirect
|
||||
github.com/pion/turn/v2 v2.1.0 // indirect
|
||||
github.com/pkg/errors v0.9.1 // indirect
|
||||
github.com/pmezard/go-difflib v1.0.0 // indirect
|
||||
github.com/prometheus/common v0.37.0 // indirect
|
||||
@@ -108,7 +124,6 @@ require (
|
||||
go.uber.org/atomic v1.11.0 // indirect
|
||||
go.uber.org/dig v1.17.0 // indirect
|
||||
go.uber.org/multierr v1.11.0 // indirect
|
||||
go.uber.org/zap v1.25.0 // indirect
|
||||
golang.org/x/mod v0.12.0 // indirect
|
||||
golang.org/x/net v0.14.0 // indirect
|
||||
golang.org/x/text v0.12.0 // indirect
|
||||
|
85
go.sum
85
go.sum
@@ -381,6 +381,7 @@ github.com/nxadm/tail v1.4.8/go.mod h1:+ncqLTQzXmGhMZNUePPaPqPvBxHAIsmXswZKocGu+
|
||||
github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=
|
||||
github.com/onsi/ginkgo v1.7.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=
|
||||
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 h1:8xi0RTUf59SOSfEtZMvwTvXYMzG4gV23XVHOZiXNtnE=
|
||||
github.com/onsi/ginkgo v1.16.5/go.mod h1:+E8gABHa3K6zRBolWtd+ROzc/U5bkGt0FwiG042wbpU=
|
||||
github.com/onsi/ginkgo/v2 v2.11.0 h1:WgqUCUt/lT6yXoQ8Wef0fsNn5cAuMK7+KT9UFRz2tcU=
|
||||
@@ -388,6 +389,7 @@ github.com/onsi/ginkgo/v2 v2.11.0/go.mod h1:ZhrRA5XmEE3x3rhlzamx/JJvujdZoJ2uvgI7
|
||||
github.com/onsi/gomega v1.4.3/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1CpauHY=
|
||||
github.com/onsi/gomega v1.7.1/go.mod h1:XdKZgCCFLUoM/7CFJVPcG8C1xQ1AJ0vpAezJrB7JYyY=
|
||||
github.com/onsi/gomega v1.10.1/go.mod h1:iN09h71vgCQne3DLsj+A5owkum+a2tYe+TOCB1ybHNo=
|
||||
github.com/onsi/gomega v1.17.0/go.mod h1:HnhC7FXeEQY45zxNK3PPoIUhzk/80Xly9PcubAlGdZY=
|
||||
github.com/onsi/gomega v1.27.8 h1:gegWiwZjBsf2DgiSbf5hpokZ98JVDMcWkUiigk6/KXc=
|
||||
github.com/opencontainers/runtime-spec v1.0.2/go.mod h1:jwyrGlmzljRJv/Fgzds9SsS/C5hL+LL3ko9hs6T5lQ0=
|
||||
github.com/opencontainers/runtime-spec v1.1.0 h1:HHUyrt9mwHUjtasSbXSMvs4cyFxh+Bll4AjJ9odEGpg=
|
||||
@@ -396,6 +398,45 @@ github.com/openzipkin/zipkin-go v0.1.1/go.mod h1:NtoC/o8u3JlF1lSlyPNswIbeQH9bJTm
|
||||
github.com/pbnjay/memory v0.0.0-20210728143218-7b4eea64cf58 h1:onHthvaw9LFnH4t2DcNVpwGmV9E1BkGknEliJkfwQj0=
|
||||
github.com/pbnjay/memory v0.0.0-20210728143218-7b4eea64cf58/go.mod h1:DXv8WO4yhMYhSNPKjeNKa5WY9YCIEBRbNzFFPJbWO6Y=
|
||||
github.com/pelletier/go-toml v1.2.0/go.mod h1:5z9KED0ma1S8pY6P1sdut58dfprrGBbd/94hg7ilaic=
|
||||
github.com/pion/datachannel v1.5.5 h1:10ef4kwdjije+M9d7Xm9im2Y3O6A6ccQb0zcqZcJew8=
|
||||
github.com/pion/datachannel v1.5.5/go.mod h1:iMz+lECmfdCMqFRhXhcA/219B0SQlbpoR2V118yimL0=
|
||||
github.com/pion/dtls/v2 v2.2.7 h1:cSUBsETxepsCSFSxC3mc/aDo14qQLMSL+O6IjG28yV8=
|
||||
github.com/pion/dtls/v2 v2.2.7/go.mod h1:8WiMkebSHFD0T+dIU+UeBaoV7kDhOW5oDCzZ7WZ/F9s=
|
||||
github.com/pion/ice/v2 v2.3.6 h1:Jgqw36cAud47iD+N6rNX225uHvrgWtAlHfVyOQc3Heg=
|
||||
github.com/pion/ice/v2 v2.3.6/go.mod h1:9/TzKDRwBVAPsC+YOrKH/e3xDrubeTRACU9/sHQarsU=
|
||||
github.com/pion/interceptor v0.1.17 h1:prJtgwFh/gB8zMqGZoOgJPHivOwVAp61i2aG61Du/1w=
|
||||
github.com/pion/interceptor v0.1.17/go.mod h1:SY8kpmfVBvrbUzvj2bsXz7OJt5JvmVNZ+4Kjq7FcwrI=
|
||||
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.7 h1:P0UB4Sr6xDWEox0kTVxF0LmQihtCbSAdW0H2nEgkA3U=
|
||||
github.com/pion/mdns v0.0.7/go.mod h1:4iP2UbeFhLI/vWju/bw6ZfwjJzk0z8DNValjGxR/dD8=
|
||||
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.10 h1:nkr3uj+8Sp97zyItdN60tE/S6vk4al5CPRR6Gejsdjc=
|
||||
github.com/pion/rtcp v1.2.10/go.mod h1:ztfEwXZNLGyF1oQDttz/ZKIBaeeg/oWbRYqzBM9TL1I=
|
||||
github.com/pion/rtp v1.7.13 h1:qcHwlmtiI50t1XivvoawdCGTP4Uiypzfrsap+bijcoA=
|
||||
github.com/pion/rtp v1.7.13/go.mod h1:bDb5n+BFZxXx0Ea7E5qe+klMuqiBrP+w8XSjiWtCUko=
|
||||
github.com/pion/sctp v1.8.5/go.mod h1:SUFFfDpViyKejTAdwD1d/HQsCu+V/40cCs2nZIvC3s0=
|
||||
github.com/pion/sctp v1.8.7 h1:JnABvFakZueGAn4KU/4PSKg+GWbF6QWbKTWZOSGJjXw=
|
||||
github.com/pion/sctp v1.8.7/go.mod h1:g1Ul+ARqZq5JEmoFy87Q/4CePtKnTJ1QCL9dBBdN6AU=
|
||||
github.com/pion/sdp/v3 v3.0.6 h1:WuDLhtuFUUVpTfus9ILC4HRyHsW6TdugjEX/QY9OiUw=
|
||||
github.com/pion/sdp/v3 v3.0.6/go.mod h1:iiFWFpQO8Fy3S5ldclBkpXqmWy02ns78NOKoLLL0YQw=
|
||||
github.com/pion/srtp/v2 v2.0.15 h1:+tqRtXGsGwHC0G0IUIAzRmdkHvriF79IHVfZGfHrQoA=
|
||||
github.com/pion/srtp/v2 v2.0.15/go.mod h1:b/pQOlDrbB0HEH5EUAQXzSYxikFbNcNuKmF8tM0hCtw=
|
||||
github.com/pion/stun v0.4.0/go.mod h1:QPsh1/SbXASntw3zkkrIk3ZJVKz4saBY2G7S10P3wCw=
|
||||
github.com/pion/stun v0.6.0 h1:JHT/2iyGDPrFWE8NNC15wnddBN8KifsEDw8swQmrEmU=
|
||||
github.com/pion/stun v0.6.0/go.mod h1:HPqcfoeqQn9cuaet7AOmB5e5xkObu9DwBdurwLKO9oA=
|
||||
github.com/pion/transport v0.14.1 h1:XSM6olwW+o8J4SCmOBb/BpwZypkHeyM0PGFCxNQBr40=
|
||||
github.com/pion/transport v0.14.1/go.mod h1:4tGmbk00NeYA3rUa9+n+dzCCoKkcy3YlYb99Jn2fNnI=
|
||||
github.com/pion/transport/v2 v2.0.0/go.mod h1:HS2MEBJTwD+1ZI2eSXSvHJx/HnzQqRy2/LXxt6eVMHc=
|
||||
github.com/pion/transport/v2 v2.1.0/go.mod h1:AdSw4YBZVDkZm8fpoz+fclXyQwANWmZAlDuQdctTThQ=
|
||||
github.com/pion/transport/v2 v2.2.0/go.mod h1:AdSw4YBZVDkZm8fpoz+fclXyQwANWmZAlDuQdctTThQ=
|
||||
github.com/pion/transport/v2 v2.2.1 h1:7qYnCBlpgSJNYMbLCKuSY9KbQdBFoETvPNETv0y4N7c=
|
||||
github.com/pion/transport/v2 v2.2.1/go.mod h1:cXXWavvCnFF6McHTft3DWS9iic2Mftcz1Aq29pGcU5g=
|
||||
github.com/pion/turn/v2 v2.1.0 h1:5wGHSgGhJhP/RpabkUb/T9PdsAjkGLS6toYz5HNzoSI=
|
||||
github.com/pion/turn/v2 v2.1.0/go.mod h1:yrT5XbXSGX1VFSF31A3c1kCNB5bBZgk/uu5LET162qs=
|
||||
github.com/pion/webrtc/v3 v3.2.9 h1:U8NSjQDlZZ+Iy/hg42Q/u6mhEVSXYvKrOIZiZwYTfLc=
|
||||
github.com/pion/webrtc/v3 v3.2.9/go.mod h1:gjQLMZeyN3jXBGdxGmUYCyKjOuYX/c99BDjGqmadq0A=
|
||||
github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
|
||||
github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
|
||||
github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
|
||||
@@ -444,6 +485,7 @@ github.com/raulk/go-watchdog v1.3.0/go.mod h1:fIvOnLbF0b0ZwkB9YU4mOW9Did//4vPZtD
|
||||
github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4=
|
||||
github.com/russross/blackfriday v1.5.2/go.mod h1:JO/DiYxRf+HjHt06OyowR9PTA263kcR/rfWxYHBV53g=
|
||||
github.com/russross/blackfriday/v2 v2.0.1/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
|
||||
github.com/sclevine/agouti v3.0.0+incompatible/go.mod h1:b4WX9W9L1sfQKXeJf1mUTLZKJ48R1S7H23Ji7oFO5Bw=
|
||||
github.com/sergi/go-diff v1.0.0/go.mod h1:0CfEIISq7TuYL3j771MWULgwwjU+GofnZX9QAmXWZgo=
|
||||
github.com/shurcooL/component v0.0.0-20170202220835-f88ec8f54cc4/go.mod h1:XhFIlyj5a1fBNx5aJTbKoIq0mNaPvOagO+HjB3EtxrY=
|
||||
github.com/shurcooL/events v0.0.0-20181021180414-410e4ca65f48/go.mod h1:5u70Mqkb5O5cxEA8nxTsgrgLehJeAw6Oc4Ab1c/P1HM=
|
||||
@@ -485,12 +527,19 @@ github.com/spf13/pflag v1.0.3/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnIn
|
||||
github.com/spf13/viper v1.3.2/go.mod h1:ZiWeW+zYFKm7srdB9IoDzzZXaJaI5eL9QjNiN/DMA2s=
|
||||
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
||||
github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
||||
github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw=
|
||||
github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo=
|
||||
github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
|
||||
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
|
||||
github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
|
||||
github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA=
|
||||
github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
||||
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
||||
github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
||||
github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
|
||||
github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4=
|
||||
github.com/stretchr/testify v1.8.2/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4=
|
||||
github.com/stretchr/testify v1.8.3/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo=
|
||||
github.com/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk=
|
||||
github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo=
|
||||
github.com/syndtr/goleveldb v1.0.0 h1:fBdIW9lB4Iz0n9khmH8w27SJ3QEJ7+IgjPEwGSZiFdE=
|
||||
@@ -506,6 +555,7 @@ github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9de
|
||||
github.com/yuin/goldmark v1.1.32/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
|
||||
github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
|
||||
github.com/yuin/goldmark v1.3.5/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k=
|
||||
github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY=
|
||||
go.opencensus.io v0.18.0/go.mod h1:vKdFvxhtzZ9onBp9VKHK8z/sRpBMnKAsufL7wlDrCOA=
|
||||
go.opencensus.io v0.21.0/go.mod h1:mSImk1erAIZhrmZN+AvHh14ztQfjbGwt4TtuofqLduU=
|
||||
go.opencensus.io v0.22.0/go.mod h1:+kGneAE2xo2IficOXnaByMWTGM9T73dGwxeWcUqIpI8=
|
||||
@@ -546,6 +596,9 @@ golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8U
|
||||
golang.org/x/crypto v0.0.0-20200602180216-279210d13fed/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
|
||||
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
|
||||
golang.org/x/crypto v0.0.0-20210322153248-0c34fe9e7dc2/go.mod h1:T9bdIzuCu7OtxOm1hfPfRQxPLYneinmdGuTeoZ9dtd4=
|
||||
golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
|
||||
golang.org/x/crypto v0.8.0/go.mod h1:mRqEX+O9/h5TFCrQhkgjo2yKi0yYA+9ecGkdQoHrywE=
|
||||
golang.org/x/crypto v0.9.0/go.mod h1:yrmDGqONDYtNj3tH8X9dzUun2m2lzPa9ngI6/RUPGR0=
|
||||
golang.org/x/crypto v0.12.0 h1:tFM/ta59kqch6LlvYnPa0yx5a83cL2nHflFhYKvv9Yk=
|
||||
golang.org/x/crypto v0.12.0/go.mod h1:NF0Gs7EO5K4qLn+Ylc+fih8BSTeIjAP05siRnAh98yw=
|
||||
golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
|
||||
@@ -583,6 +636,8 @@ golang.org/x/mod v0.1.1-0.20191107180719-034126e5016b/go.mod h1:QqPTAvyqsEbceGzB
|
||||
golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
|
||||
golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
|
||||
golang.org/x/mod v0.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
|
||||
golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4=
|
||||
golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=
|
||||
golang.org/x/mod v0.12.0 h1:rmsUpXtvNzj340zd98LZ4KntptpfRHwpFOHG188oHXc=
|
||||
golang.org/x/mod v0.12.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=
|
||||
golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
@@ -623,9 +678,17 @@ golang.org/x/net v0.0.0-20210119194325-5f4716e94777/go.mod h1:m0MpNAwzfU5UDzcl9v
|
||||
golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
|
||||
golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4/go.mod h1:p54w0d4576C0XHj96bSt6lcn1PtDYWL6XObtHCRCNQM=
|
||||
golang.org/x/net v0.0.0-20210423184538-5f58ad60dda6/go.mod h1:OJAsFXCWl8Ukc7SiCT/9KSuxbyM7479/AVlXFRxuMCk=
|
||||
golang.org/x/net v0.0.0-20210428140749-89ef3d95e781/go.mod h1:OJAsFXCWl8Ukc7SiCT/9KSuxbyM7479/AVlXFRxuMCk=
|
||||
golang.org/x/net v0.0.0-20210525063256-abc453219eb5/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
|
||||
golang.org/x/net v0.0.0-20220127200216-cd36cc0744dd/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk=
|
||||
golang.org/x/net v0.0.0-20220225172249-27dd8689420f/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk=
|
||||
golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c=
|
||||
golang.org/x/net v0.1.0/go.mod h1:Cx3nUiGt4eDBEyega/BKRp+/AlGL8hYe7U9odMt2Cco=
|
||||
golang.org/x/net v0.5.0/go.mod h1:DivGGAXEgPSlEBzxGzZI+ZLohi+xUj054jfeKui00ws=
|
||||
golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs=
|
||||
golang.org/x/net v0.8.0/go.mod h1:QVkue5JL9kW//ek3r6jTKnTFis1tRmNAW2P1shuFdJc=
|
||||
golang.org/x/net v0.9.0/go.mod h1:d48xBJpPfHeWQsugry2m+kC02ZBRGRgulfHnEXEuWns=
|
||||
golang.org/x/net v0.10.0/go.mod h1:0qNGK6F8kojg2nk9dLZ2mShWaEBan6FAoqfSigmmuDg=
|
||||
golang.org/x/net v0.14.0 h1:BONx9s002vGdD9umnlX1Po8vOZmrgH34qlHcD1MfK14=
|
||||
golang.org/x/net v0.14.0/go.mod h1:PpSgVXXLK0OxS0F31C1/tv6XNguvCrnXIDrFMspZIUI=
|
||||
golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
|
||||
@@ -649,6 +712,8 @@ golang.org/x/sync v0.0.0-20200625203802-6e8e738ad208/go.mod h1:RxMgew5VJxzue5/jJ
|
||||
golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20201207232520-09787c993a3a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.3.0 h1:ftCYgMx6zT/asHUrPw8BLLscYtGznsLAnjq5RH9P66E=
|
||||
golang.org/x/sync v0.3.0/go.mod h1:FU7BRWz2tNW+3quACPkgCx/L+uEAv1htQ0V83Z9Rj+Y=
|
||||
golang.org/x/sys v0.0.0-20180810173357-98c5dad5d1a0/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
@@ -709,12 +774,25 @@ golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBc
|
||||
golang.org/x/sys v0.0.0-20211216021012-1d35b9e2eb4e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20220114195835-da31bd327af9/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20220412211240-33da011f77ad/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.1.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.2.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.4.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.7.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.11.0 h1:eG7RXZHdqOJ1i+0lgLgCpSXAp6M3LYlAo6osgSi0xOM=
|
||||
golang.org/x/sys v0.11.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
|
||||
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
|
||||
golang.org/x/term v0.1.0/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
|
||||
golang.org/x/term v0.4.0/go.mod h1:9P2UbLfCdcvo3p/nzKvsmas4TnlujnuoV9hGgYzW1lQ=
|
||||
golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k=
|
||||
golang.org/x/term v0.6.0/go.mod h1:m6U89DPEgQRMq3DNkDClhWw02AUbt2daBVO4cn4Hv9U=
|
||||
golang.org/x/term v0.7.0/go.mod h1:P32HKFT3hSsZrRxla30E9HqToFYAQPCMs/zFMBUFqPY=
|
||||
golang.org/x/term v0.8.0/go.mod h1:xPskH00ivmX89bAKVGSKKtLOWNx2+17Eiy94tnKShWo=
|
||||
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=
|
||||
golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||
@@ -722,6 +800,11 @@ golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=
|
||||
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||
golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||
golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
|
||||
golang.org/x/text v0.4.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
|
||||
golang.org/x/text v0.6.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
|
||||
golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
|
||||
golang.org/x/text v0.8.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8=
|
||||
golang.org/x/text v0.9.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8=
|
||||
golang.org/x/text v0.12.0 h1:k+n5B8goJNdU7hSvEtMUz3d1Q6D/XW4COJSJR6fN0mc=
|
||||
golang.org/x/text v0.12.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE=
|
||||
golang.org/x/time v0.0.0-20180412165947-fbb02b2291d2/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
|
||||
@@ -778,6 +861,8 @@ golang.org/x/tools v0.0.0-20201224043029-2b0845dc783e/go.mod h1:emZCQorbCU4vsT4f
|
||||
golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
|
||||
golang.org/x/tools v0.1.1/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk=
|
||||
golang.org/x/tools v0.1.5/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk=
|
||||
golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc=
|
||||
golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU=
|
||||
golang.org/x/tools v0.12.1-0.20230815132531-74c255bcf846 h1:Vve/L0v7CXXuxUmaMGIEK/dEeq7uiqb5qBgQrZzIE7E=
|
||||
golang.org/x/tools v0.12.1-0.20230815132531-74c255bcf846/go.mod h1:Sc0INKfu04TlqNoRA1hgpFZbhYXHPr4V5DzpSBTPqQM=
|
||||
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
|
@@ -2,7 +2,7 @@ package metricshelper
|
||||
|
||||
import ma "github.com/multiformats/go-multiaddr"
|
||||
|
||||
var transports = [...]int{ma.P_CIRCUIT, ma.P_WEBRTC, ma.P_WEBTRANSPORT, ma.P_QUIC, ma.P_QUIC_V1, ma.P_WSS, ma.P_WS, ma.P_TCP}
|
||||
var transports = [...]int{ma.P_CIRCUIT, ma.P_WEBRTC, ma.P_WEBRTC_DIRECT, ma.P_WEBTRANSPORT, ma.P_QUIC, ma.P_QUIC_V1, ma.P_WSS, ma.P_WS, ma.P_TCP}
|
||||
|
||||
func GetTransport(a ma.Multiaddr) string {
|
||||
for _, t := range transports {
|
||||
|
@@ -43,7 +43,9 @@ func TestReadWriteDeadlines(t *testing.T) {
|
||||
buf := make([]byte, 1)
|
||||
_, err = s.Read(buf)
|
||||
require.Error(t, err)
|
||||
require.True(t, err.(net.Error).Timeout())
|
||||
var nerr net.Error
|
||||
require.ErrorAs(t, err, &nerr)
|
||||
require.True(t, nerr.Timeout())
|
||||
require.Less(t, time.Since(start), 1*time.Second)
|
||||
})
|
||||
|
||||
@@ -80,7 +82,9 @@ func TestReadWriteDeadlines(t *testing.T) {
|
||||
_, err = s.Write(sendBuf)
|
||||
}
|
||||
require.Error(t, err)
|
||||
require.True(t, err.(net.Error).Timeout())
|
||||
var nerr net.Error
|
||||
require.ErrorAs(t, err, &nerr)
|
||||
require.True(t, nerr.Timeout())
|
||||
require.Less(t, time.Since(start), 1*time.Second)
|
||||
})
|
||||
}
|
||||
|
@@ -2,6 +2,7 @@ package transport_integration
|
||||
|
||||
import (
|
||||
"context"
|
||||
"strings"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
@@ -88,6 +89,8 @@ func TestInterceptSecuredOutgoing(t *testing.T) {
|
||||
|
||||
h1 := tc.HostGenerator(t, TransportTestCaseOpts{NoListen: true, ConnGater: connGater})
|
||||
h2 := tc.HostGenerator(t, TransportTestCaseOpts{})
|
||||
defer h1.Close()
|
||||
defer h2.Close()
|
||||
require.Len(t, h2.Addrs(), 1)
|
||||
require.Len(t, h2.Addrs(), 1)
|
||||
|
||||
@@ -97,7 +100,7 @@ func TestInterceptSecuredOutgoing(t *testing.T) {
|
||||
connGater.EXPECT().InterceptPeerDial(h2.ID()).Return(true),
|
||||
connGater.EXPECT().InterceptAddrDial(h2.ID(), gomock.Any()).Return(true),
|
||||
connGater.EXPECT().InterceptSecured(network.DirOutbound, h2.ID(), gomock.Any()).Do(func(_ network.Direction, _ peer.ID, addrs network.ConnMultiaddrs) {
|
||||
// remove the certhash component from WebTransport addresses
|
||||
// remove the certhash component from WebTransport and WebRTC addresses
|
||||
require.Equal(t, stripCertHash(h2.Addrs()[0]).String(), addrs.RemoteMultiaddr().String())
|
||||
}),
|
||||
)
|
||||
@@ -120,6 +123,8 @@ func TestInterceptUpgradedOutgoing(t *testing.T) {
|
||||
|
||||
h1 := tc.HostGenerator(t, TransportTestCaseOpts{NoListen: true, ConnGater: connGater})
|
||||
h2 := tc.HostGenerator(t, TransportTestCaseOpts{})
|
||||
defer h1.Close()
|
||||
defer h2.Close()
|
||||
require.Len(t, h2.Addrs(), 1)
|
||||
require.Len(t, h2.Addrs(), 1)
|
||||
|
||||
@@ -154,19 +159,35 @@ func TestInterceptAccept(t *testing.T) {
|
||||
|
||||
h1 := tc.HostGenerator(t, TransportTestCaseOpts{NoListen: true})
|
||||
h2 := tc.HostGenerator(t, TransportTestCaseOpts{ConnGater: connGater})
|
||||
defer h1.Close()
|
||||
defer h2.Close()
|
||||
require.Len(t, h2.Addrs(), 1)
|
||||
|
||||
ctx, cancel := context.WithTimeout(context.Background(), time.Second)
|
||||
defer cancel()
|
||||
// The basic host dials the first connection.
|
||||
connGater.EXPECT().InterceptAccept(gomock.Any()).Do(func(addrs network.ConnMultiaddrs) {
|
||||
// remove the certhash component from WebTransport addresses
|
||||
require.Equal(t, stripCertHash(h2.Addrs()[0]), addrs.LocalMultiaddr())
|
||||
})
|
||||
if strings.Contains(tc.Name, "WebRTC") {
|
||||
// In WebRTC, retransmissions of the STUN packet might cause us to create multiple connections,
|
||||
// if the first connection attempt is rejected.
|
||||
connGater.EXPECT().InterceptAccept(gomock.Any()).Do(func(addrs network.ConnMultiaddrs) {
|
||||
// remove the certhash component from WebTransport addresses
|
||||
require.Equal(t, stripCertHash(h2.Addrs()[0]), addrs.LocalMultiaddr())
|
||||
}).AnyTimes()
|
||||
} else {
|
||||
connGater.EXPECT().InterceptAccept(gomock.Any()).Do(func(addrs network.ConnMultiaddrs) {
|
||||
// remove the certhash component from WebTransport addresses
|
||||
require.Equal(t, stripCertHash(h2.Addrs()[0]), addrs.LocalMultiaddr())
|
||||
})
|
||||
}
|
||||
|
||||
h1.Peerstore().AddAddrs(h2.ID(), h2.Addrs(), time.Hour)
|
||||
_, err := h1.NewStream(ctx, h2.ID(), protocol.TestingID)
|
||||
require.Error(t, err)
|
||||
require.NotErrorIs(t, err, context.DeadlineExceeded)
|
||||
if _, err := h2.Addrs()[0].ValueForProtocol(ma.P_WEBRTC_DIRECT); err != nil {
|
||||
// WebRTC rejects connection attempt before an error can be sent to the client.
|
||||
// This means that the connection attempt will time out.
|
||||
require.NotErrorIs(t, err, context.DeadlineExceeded)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -183,6 +204,8 @@ func TestInterceptSecuredIncoming(t *testing.T) {
|
||||
|
||||
h1 := tc.HostGenerator(t, TransportTestCaseOpts{NoListen: true})
|
||||
h2 := tc.HostGenerator(t, TransportTestCaseOpts{ConnGater: connGater})
|
||||
defer h1.Close()
|
||||
defer h2.Close()
|
||||
require.Len(t, h2.Addrs(), 1)
|
||||
|
||||
ctx, cancel := context.WithTimeout(context.Background(), time.Second)
|
||||
@@ -214,6 +237,8 @@ func TestInterceptUpgradedIncoming(t *testing.T) {
|
||||
|
||||
h1 := tc.HostGenerator(t, TransportTestCaseOpts{NoListen: true})
|
||||
h2 := tc.HostGenerator(t, TransportTestCaseOpts{ConnGater: connGater})
|
||||
defer h1.Close()
|
||||
defer h2.Close()
|
||||
require.Len(t, h2.Addrs(), 1)
|
||||
|
||||
ctx, cancel := context.WithTimeout(context.Background(), time.Second)
|
||||
|
@@ -9,7 +9,7 @@ import (
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
gomock "github.com/golang/mock/gomock"
|
||||
"github.com/golang/mock/gomock"
|
||||
"github.com/libp2p/go-libp2p/core/host"
|
||||
"github.com/libp2p/go-libp2p/core/network"
|
||||
mocknetwork "github.com/libp2p/go-libp2p/core/network/mocks"
|
||||
@@ -55,7 +55,7 @@ func TestResourceManagerIsUsed(t *testing.T) {
|
||||
}
|
||||
|
||||
expectFd := true
|
||||
if strings.Contains(tc.Name, "QUIC") || strings.Contains(tc.Name, "WebTransport") {
|
||||
if strings.Contains(tc.Name, "QUIC") || strings.Contains(tc.Name, "WebTransport") || strings.Contains(tc.Name, "WebRTC") {
|
||||
expectFd = false
|
||||
}
|
||||
|
||||
@@ -86,7 +86,7 @@ func TestResourceManagerIsUsed(t *testing.T) {
|
||||
}
|
||||
return nil
|
||||
})
|
||||
connScope.EXPECT().Done()
|
||||
connScope.EXPECT().Done().MinTimes(1)
|
||||
|
||||
var allStreamsDone sync.WaitGroup
|
||||
|
||||
|
@@ -9,6 +9,7 @@ import (
|
||||
"io"
|
||||
"net"
|
||||
"runtime"
|
||||
"strings"
|
||||
"sync"
|
||||
"sync/atomic"
|
||||
"testing"
|
||||
@@ -27,6 +28,8 @@ import (
|
||||
"github.com/libp2p/go-libp2p/p2p/protocol/ping"
|
||||
"github.com/libp2p/go-libp2p/p2p/security/noise"
|
||||
tls "github.com/libp2p/go-libp2p/p2p/security/tls"
|
||||
libp2pwebrtc "github.com/libp2p/go-libp2p/p2p/transport/webrtc"
|
||||
|
||||
"github.com/multiformats/go-multiaddr"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
@@ -134,6 +137,21 @@ var transportsToTest = []TransportTestCase{
|
||||
return h
|
||||
},
|
||||
},
|
||||
{
|
||||
Name: "WebRTC",
|
||||
HostGenerator: func(t *testing.T, opts TransportTestCaseOpts) host.Host {
|
||||
libp2pOpts := transformOpts(opts)
|
||||
libp2pOpts = append(libp2pOpts, libp2p.Transport(libp2pwebrtc.New))
|
||||
if opts.NoListen {
|
||||
libp2pOpts = append(libp2pOpts, libp2p.NoListenAddrs)
|
||||
} else {
|
||||
libp2pOpts = append(libp2pOpts, libp2p.ListenAddrStrings("/ip4/127.0.0.1/udp/0/webrtc-direct"))
|
||||
}
|
||||
h, err := libp2p.New(libp2pOpts...)
|
||||
require.NoError(t, err)
|
||||
return h
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
func TestPing(t *testing.T) {
|
||||
@@ -229,7 +247,7 @@ func TestLotsOfDataManyStreams(t *testing.T) {
|
||||
// 64k buffer
|
||||
const bufSize = 64 << 10
|
||||
sendBuf := [bufSize]byte{}
|
||||
const totalStreams = 512
|
||||
const totalStreams = 500
|
||||
const parallel = 8
|
||||
// Total sends are > 20MiB
|
||||
require.Greater(t, len(sendBuf)*totalStreams, 20<<20)
|
||||
@@ -296,6 +314,9 @@ func TestManyStreams(t *testing.T) {
|
||||
const streamCount = 128
|
||||
for _, tc := range transportsToTest {
|
||||
t.Run(tc.Name, func(t *testing.T) {
|
||||
if strings.Contains(tc.Name, "WebRTC") {
|
||||
t.Skip("Pion doesn't correctly handle large queues of streams.")
|
||||
}
|
||||
h1 := tc.HostGenerator(t, TransportTestCaseOpts{NoRcmgr: true})
|
||||
h2 := tc.HostGenerator(t, TransportTestCaseOpts{NoListen: true, NoRcmgr: true})
|
||||
defer h1.Close()
|
||||
@@ -361,6 +382,9 @@ func TestMoreStreamsThanOurLimits(t *testing.T) {
|
||||
const streamCount = 1024
|
||||
for _, tc := range transportsToTest {
|
||||
t.Run(tc.Name, func(t *testing.T) {
|
||||
if strings.Contains(tc.Name, "WebRTC") {
|
||||
t.Skip("This test potentially exhausts the uint16 WebRTC stream ID space.")
|
||||
}
|
||||
listenerLimits := rcmgr.PartialLimitConfig{
|
||||
PeerDefault: rcmgr.ResourceLimits{
|
||||
Streams: 32,
|
||||
@@ -455,6 +479,8 @@ func TestMoreStreamsThanOurLimits(t *testing.T) {
|
||||
time.Sleep(50 * time.Millisecond)
|
||||
continue
|
||||
}
|
||||
t.Logf("opening stream failed: %v", err)
|
||||
return
|
||||
}
|
||||
err = func(s network.Stream) error {
|
||||
defer s.Close()
|
||||
@@ -596,8 +622,8 @@ func TestStreamReadDeadline(t *testing.T) {
|
||||
_, err = s.Read([]byte{0})
|
||||
require.Error(t, err)
|
||||
require.Contains(t, err.Error(), "deadline")
|
||||
nerr, ok := err.(net.Error)
|
||||
require.True(t, ok, "expected a net.Error")
|
||||
var nerr net.Error
|
||||
require.True(t, errors.As(err, &nerr), "expected a net.Error")
|
||||
require.True(t, nerr.Timeout(), "expected net.Error.Timeout() == true")
|
||||
// now test that the stream is still usable
|
||||
s.SetReadDeadline(time.Time{})
|
||||
@@ -628,58 +654,41 @@ func TestDiscoverPeerIDFromSecurityNegotiation(t *testing.T) {
|
||||
return "", inputErr
|
||||
}
|
||||
|
||||
// runs a test to verify we can extract the peer ID from a target with just its address
|
||||
runTest := func(t *testing.T, h host.Host) {
|
||||
t.Helper()
|
||||
ctx, cancel := context.WithCancel(context.Background())
|
||||
defer cancel()
|
||||
|
||||
// Use a bogus peer ID so that when we connect to the target we get an error telling
|
||||
// us the targets real peer ID
|
||||
bogusPeerId, err := peer.Decode("QmadAdJ3f63JyNs65X7HHzqDwV53ynvCcKtNFvdNaz3nhk")
|
||||
if err != nil {
|
||||
t.Fatal("the hard coded bogus peerID is invalid")
|
||||
}
|
||||
|
||||
ai := &peer.AddrInfo{
|
||||
ID: bogusPeerId,
|
||||
Addrs: []multiaddr.Multiaddr{h.Addrs()[0]},
|
||||
}
|
||||
|
||||
testHost, err := libp2p.New()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
// Try connecting with the bogus peer ID
|
||||
if err := testHost.Connect(ctx, *ai); err != nil {
|
||||
// Extract the actual peer ID from the error
|
||||
newPeerId, err := extractPeerIDFromError(err)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
ai.ID = newPeerId
|
||||
|
||||
// Make sure the new ID is what we expected
|
||||
if ai.ID != h.ID() {
|
||||
t.Fatalf("peerID mismatch: expected %s, got %s", h.ID(), ai.ID)
|
||||
}
|
||||
|
||||
// and just to double-check try connecting again to make sure it works
|
||||
if err := testHost.Connect(ctx, *ai); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
} else {
|
||||
t.Fatal("somehow we successfully connected to a bogus peerID!")
|
||||
}
|
||||
}
|
||||
|
||||
for _, tc := range transportsToTest {
|
||||
t.Run(tc.Name, func(t *testing.T) {
|
||||
h := tc.HostGenerator(t, TransportTestCaseOpts{})
|
||||
defer h.Close()
|
||||
h1 := tc.HostGenerator(t, TransportTestCaseOpts{})
|
||||
h2 := tc.HostGenerator(t, TransportTestCaseOpts{NoListen: true})
|
||||
defer h1.Close()
|
||||
defer h2.Close()
|
||||
|
||||
runTest(t, h)
|
||||
// runs a test to verify we can extract the peer ID from a target with just its address
|
||||
t.Helper()
|
||||
ctx, cancel := context.WithCancel(context.Background())
|
||||
defer cancel()
|
||||
|
||||
// Use a bogus peer ID so that when we connect to the target we get an error telling
|
||||
// us the targets real peer ID
|
||||
bogusPeerId, err := peer.Decode("QmadAdJ3f63JyNs65X7HHzqDwV53ynvCcKtNFvdNaz3nhk")
|
||||
require.NoError(t, err, "the hard coded bogus peerID is invalid")
|
||||
|
||||
ai := &peer.AddrInfo{
|
||||
ID: bogusPeerId,
|
||||
Addrs: []multiaddr.Multiaddr{h1.Addrs()[0]},
|
||||
}
|
||||
|
||||
// Try connecting with the bogus peer ID
|
||||
err = h2.Connect(ctx, *ai)
|
||||
require.Error(t, err, "somehow we successfully connected to a bogus peerID!")
|
||||
|
||||
// Extract the actual peer ID from the error
|
||||
newPeerId, err := extractPeerIDFromError(err)
|
||||
require.NoError(t, err)
|
||||
ai.ID = newPeerId
|
||||
// Make sure the new ID is what we expected
|
||||
require.Equal(t, h1.ID(), ai.ID)
|
||||
|
||||
// and just to double-check try connecting again to make sure it works
|
||||
require.NoError(t, h2.Connect(ctx, *ai))
|
||||
})
|
||||
}
|
||||
}
|
||||
|
317
p2p/transport/webrtc/connection.go
Normal file
317
p2p/transport/webrtc/connection.go
Normal file
@@ -0,0 +1,317 @@
|
||||
package libp2pwebrtc
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
"math"
|
||||
"net"
|
||||
"sync"
|
||||
"sync/atomic"
|
||||
|
||||
ic "github.com/libp2p/go-libp2p/core/crypto"
|
||||
"github.com/libp2p/go-libp2p/core/network"
|
||||
"github.com/libp2p/go-libp2p/core/peer"
|
||||
tpt "github.com/libp2p/go-libp2p/core/transport"
|
||||
"github.com/libp2p/go-libp2p/p2p/transport/webrtc/pb"
|
||||
|
||||
"github.com/libp2p/go-msgio"
|
||||
|
||||
ma "github.com/multiformats/go-multiaddr"
|
||||
"github.com/pion/datachannel"
|
||||
"github.com/pion/webrtc/v3"
|
||||
"google.golang.org/protobuf/proto"
|
||||
)
|
||||
|
||||
var _ tpt.CapableConn = &connection{}
|
||||
|
||||
const maxAcceptQueueLen = 10
|
||||
|
||||
const maxDataChannelID = 1 << 10
|
||||
|
||||
type errConnectionTimeout struct{}
|
||||
|
||||
var _ net.Error = &errConnectionTimeout{}
|
||||
|
||||
func (errConnectionTimeout) Error() string { return "connection timeout" }
|
||||
func (errConnectionTimeout) Timeout() bool { return true }
|
||||
func (errConnectionTimeout) Temporary() bool { return false }
|
||||
|
||||
type dataChannel struct {
|
||||
stream datachannel.ReadWriteCloser
|
||||
channel *webrtc.DataChannel
|
||||
}
|
||||
|
||||
type connection struct {
|
||||
pc *webrtc.PeerConnection
|
||||
transport *WebRTCTransport
|
||||
scope network.ConnManagementScope
|
||||
|
||||
closeErr error
|
||||
|
||||
localPeer peer.ID
|
||||
localMultiaddr ma.Multiaddr
|
||||
|
||||
remotePeer peer.ID
|
||||
remoteKey ic.PubKey
|
||||
remoteMultiaddr ma.Multiaddr
|
||||
|
||||
m sync.Mutex
|
||||
streams map[uint16]*stream
|
||||
nextStreamID atomic.Int32
|
||||
|
||||
acceptQueue chan dataChannel
|
||||
|
||||
ctx context.Context
|
||||
cancel context.CancelFunc
|
||||
}
|
||||
|
||||
func newConnection(
|
||||
direction network.Direction,
|
||||
pc *webrtc.PeerConnection,
|
||||
transport *WebRTCTransport,
|
||||
scope network.ConnManagementScope,
|
||||
|
||||
localPeer peer.ID,
|
||||
localMultiaddr ma.Multiaddr,
|
||||
|
||||
remotePeer peer.ID,
|
||||
remoteKey ic.PubKey,
|
||||
remoteMultiaddr ma.Multiaddr,
|
||||
) (*connection, error) {
|
||||
ctx, cancel := context.WithCancel(context.Background())
|
||||
c := &connection{
|
||||
pc: pc,
|
||||
transport: transport,
|
||||
scope: scope,
|
||||
|
||||
localPeer: localPeer,
|
||||
localMultiaddr: localMultiaddr,
|
||||
|
||||
remotePeer: remotePeer,
|
||||
remoteKey: remoteKey,
|
||||
remoteMultiaddr: remoteMultiaddr,
|
||||
ctx: ctx,
|
||||
cancel: cancel,
|
||||
streams: make(map[uint16]*stream),
|
||||
|
||||
acceptQueue: make(chan dataChannel, maxAcceptQueueLen),
|
||||
}
|
||||
switch direction {
|
||||
case network.DirInbound:
|
||||
c.nextStreamID.Store(1)
|
||||
case network.DirOutbound:
|
||||
// stream ID 0 is used for the Noise handshake stream
|
||||
c.nextStreamID.Store(2)
|
||||
}
|
||||
|
||||
pc.OnConnectionStateChange(c.onConnectionStateChange)
|
||||
pc.OnDataChannel(func(dc *webrtc.DataChannel) {
|
||||
if c.IsClosed() {
|
||||
return
|
||||
}
|
||||
// Limit the number of streams, since we're not able to actually properly close them.
|
||||
// See https://github.com/libp2p/specs/issues/575 for details.
|
||||
if *dc.ID() > maxDataChannelID {
|
||||
c.Close()
|
||||
return
|
||||
}
|
||||
dc.OnOpen(func() {
|
||||
rwc, err := dc.Detach()
|
||||
if err != nil {
|
||||
log.Warnf("could not detach datachannel: id: %d", *dc.ID())
|
||||
return
|
||||
}
|
||||
select {
|
||||
case c.acceptQueue <- dataChannel{rwc, dc}:
|
||||
default:
|
||||
log.Warnf("connection busy, rejecting stream")
|
||||
b, _ := proto.Marshal(&pb.Message{Flag: pb.Message_RESET.Enum()})
|
||||
w := msgio.NewWriter(rwc)
|
||||
w.WriteMsg(b)
|
||||
rwc.Close()
|
||||
}
|
||||
})
|
||||
})
|
||||
|
||||
return c, nil
|
||||
}
|
||||
|
||||
// ConnState implements transport.CapableConn
|
||||
func (c *connection) ConnState() network.ConnectionState {
|
||||
return network.ConnectionState{Transport: "webrtc-direct"}
|
||||
}
|
||||
|
||||
// Close closes the underlying peerconnection.
|
||||
func (c *connection) Close() error {
|
||||
if c.IsClosed() {
|
||||
return nil
|
||||
}
|
||||
|
||||
c.m.Lock()
|
||||
defer c.m.Unlock()
|
||||
c.scope.Done()
|
||||
c.closeErr = errors.New("connection closed")
|
||||
c.cancel()
|
||||
return c.pc.Close()
|
||||
}
|
||||
|
||||
func (c *connection) IsClosed() bool {
|
||||
select {
|
||||
case <-c.ctx.Done():
|
||||
return true
|
||||
default:
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
func (c *connection) OpenStream(ctx context.Context) (network.MuxedStream, error) {
|
||||
if c.IsClosed() {
|
||||
return nil, c.closeErr
|
||||
}
|
||||
|
||||
id := c.nextStreamID.Add(2) - 2
|
||||
if id > math.MaxUint16 {
|
||||
return nil, errors.New("exhausted stream ID space")
|
||||
}
|
||||
// Limit the number of streams, since we're not able to actually properly close them.
|
||||
// See https://github.com/libp2p/specs/issues/575 for details.
|
||||
if id > maxDataChannelID {
|
||||
c.Close()
|
||||
return c.OpenStream(ctx)
|
||||
}
|
||||
|
||||
streamID := uint16(id)
|
||||
dc, err := c.pc.CreateDataChannel("", &webrtc.DataChannelInit{ID: &streamID})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
rwc, err := c.detachChannel(ctx, dc)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("open stream: %w", err)
|
||||
}
|
||||
str := newStream(dc, rwc, func() { c.removeStream(streamID) })
|
||||
if err := c.addStream(str); err != nil {
|
||||
str.Close()
|
||||
return nil, err
|
||||
}
|
||||
return str, nil
|
||||
}
|
||||
|
||||
func (c *connection) AcceptStream() (network.MuxedStream, error) {
|
||||
select {
|
||||
case <-c.ctx.Done():
|
||||
return nil, c.closeErr
|
||||
case dc := <-c.acceptQueue:
|
||||
str := newStream(dc.channel, dc.stream, func() { c.removeStream(*dc.channel.ID()) })
|
||||
if err := c.addStream(str); err != nil {
|
||||
str.Close()
|
||||
return nil, err
|
||||
}
|
||||
return str, nil
|
||||
}
|
||||
}
|
||||
|
||||
func (c *connection) LocalPeer() peer.ID { return c.localPeer }
|
||||
func (c *connection) RemotePeer() peer.ID { return c.remotePeer }
|
||||
func (c *connection) RemotePublicKey() ic.PubKey { return c.remoteKey }
|
||||
func (c *connection) LocalMultiaddr() ma.Multiaddr { return c.localMultiaddr }
|
||||
func (c *connection) RemoteMultiaddr() ma.Multiaddr { return c.remoteMultiaddr }
|
||||
func (c *connection) Scope() network.ConnScope { return c.scope }
|
||||
func (c *connection) Transport() tpt.Transport { return c.transport }
|
||||
|
||||
func (c *connection) addStream(str *stream) error {
|
||||
c.m.Lock()
|
||||
defer c.m.Unlock()
|
||||
if _, ok := c.streams[str.id]; ok {
|
||||
return errors.New("stream ID already exists")
|
||||
}
|
||||
c.streams[str.id] = str
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c *connection) removeStream(id uint16) {
|
||||
c.m.Lock()
|
||||
defer c.m.Unlock()
|
||||
delete(c.streams, id)
|
||||
}
|
||||
|
||||
func (c *connection) onConnectionStateChange(state webrtc.PeerConnectionState) {
|
||||
if state == webrtc.PeerConnectionStateFailed || state == webrtc.PeerConnectionStateClosed {
|
||||
// reset any streams
|
||||
if c.IsClosed() {
|
||||
return
|
||||
}
|
||||
c.m.Lock()
|
||||
defer c.m.Unlock()
|
||||
c.closeErr = errConnectionTimeout{}
|
||||
for k, str := range c.streams {
|
||||
str.setCloseError(c.closeErr)
|
||||
delete(c.streams, k)
|
||||
}
|
||||
c.cancel()
|
||||
c.scope.Done()
|
||||
c.pc.Close()
|
||||
}
|
||||
}
|
||||
|
||||
// detachChannel detaches an outgoing channel by taking into account the context
|
||||
// passed to `OpenStream` as well the closure of the underlying peerconnection
|
||||
//
|
||||
// The underlying SCTP stream for a datachannel implements a net.Conn interface.
|
||||
// However, the datachannel creates a goroutine which continuously reads from
|
||||
// the SCTP stream and surfaces the data using an OnMessage callback.
|
||||
//
|
||||
// The actual abstractions are as follows: webrtc.DataChannel
|
||||
// wraps pion.DataChannel, which wraps sctp.Stream.
|
||||
//
|
||||
// The goroutine for reading, Detach method,
|
||||
// and the OnMessage callback are present at the webrtc.DataChannel level.
|
||||
// Detach provides us abstracted access to the underlying pion.DataChannel,
|
||||
// which allows us to issue Read calls to the datachannel.
|
||||
// This was desired because it was not feasible to introduce backpressure
|
||||
// with the OnMessage callbacks. The tradeoff is a change in the semantics of
|
||||
// the OnOpen callback, and having to force close Read locally.
|
||||
func (c *connection) detachChannel(ctx context.Context, dc *webrtc.DataChannel) (rwc datachannel.ReadWriteCloser, err error) {
|
||||
done := make(chan struct{})
|
||||
// OnOpen will return immediately for detached datachannels
|
||||
// refer: https://github.com/pion/webrtc/blob/7ab3174640b3ce15abebc2516a2ca3939b5f105f/datachannel.go#L278-L282
|
||||
dc.OnOpen(func() {
|
||||
rwc, err = dc.Detach()
|
||||
// this is safe since the function should return instantly if the peerconnection is closed
|
||||
close(done)
|
||||
})
|
||||
select {
|
||||
case <-c.ctx.Done():
|
||||
return nil, c.closeErr
|
||||
case <-ctx.Done():
|
||||
return nil, ctx.Err()
|
||||
case <-done:
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// A note on these setters and why they are needed:
|
||||
//
|
||||
// The connection object sets up receiving datachannels (streams) from the remote peer.
|
||||
// Please consider the XX noise handshake pattern from a peer A to peer B as described at:
|
||||
// https://noiseexplorer.com/patterns/XX/
|
||||
//
|
||||
// The initiator A completes the noise handshake before B.
|
||||
// This would allow A to create new datachannels before B has set up the callbacks to process incoming datachannels.
|
||||
// This would create a situation where A has successfully created a stream but B is not aware of it.
|
||||
// Moving the construction of the connection object before the noise handshake eliminates this issue,
|
||||
// as callbacks have been set up for both peers.
|
||||
//
|
||||
// This could lead to a case where streams are created during the noise handshake,
|
||||
// and the handshake fails. In this case, we would close the underlying peerconnection.
|
||||
|
||||
// only used during connection setup
|
||||
func (c *connection) setRemotePeer(id peer.ID) {
|
||||
c.remotePeer = id
|
||||
}
|
||||
|
||||
// only used during connection setup
|
||||
func (c *connection) setRemotePublicKey(key ic.PubKey) {
|
||||
c.remoteKey = key
|
||||
}
|
28
p2p/transport/webrtc/datachannel.go
Normal file
28
p2p/transport/webrtc/datachannel.go
Normal file
@@ -0,0 +1,28 @@
|
||||
package libp2pwebrtc
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"github.com/pion/datachannel"
|
||||
"github.com/pion/webrtc/v3"
|
||||
)
|
||||
|
||||
// only use this if the datachannels are detached, since the OnOpen callback
|
||||
// will be called immediately. Only use after the peerconnection is open.
|
||||
// The context should close if the peerconnection underlying the datachannel
|
||||
// is closed.
|
||||
func getDetachedChannel(ctx context.Context, dc *webrtc.DataChannel) (rwc datachannel.ReadWriteCloser, err error) {
|
||||
done := make(chan struct{})
|
||||
dc.OnOpen(func() {
|
||||
defer close(done)
|
||||
rwc, err = dc.Detach()
|
||||
})
|
||||
// this is safe since for detached datachannels, the peerconnection runs the onOpen
|
||||
// callback immediately if the SCTP transport is also connected.
|
||||
select {
|
||||
case <-done:
|
||||
case <-ctx.Done():
|
||||
return nil, ctx.Err()
|
||||
}
|
||||
return
|
||||
}
|
53
p2p/transport/webrtc/fingerprint.go
Normal file
53
p2p/transport/webrtc/fingerprint.go
Normal file
@@ -0,0 +1,53 @@
|
||||
package libp2pwebrtc
|
||||
|
||||
import (
|
||||
"crypto"
|
||||
"crypto/x509"
|
||||
"errors"
|
||||
|
||||
ma "github.com/multiformats/go-multiaddr"
|
||||
"github.com/multiformats/go-multibase"
|
||||
mh "github.com/multiformats/go-multihash"
|
||||
"github.com/pion/webrtc/v3"
|
||||
)
|
||||
|
||||
// parseFingerprint is forked from pion to avoid bytes to string alloc,
|
||||
// and to avoid the entire hex interspersing when we do not need it anyway
|
||||
|
||||
var errHashUnavailable = errors.New("fingerprint: hash algorithm is not linked into the binary")
|
||||
|
||||
// parseFingerprint creates a fingerprint for a certificate using the specified hash algorithm
|
||||
func parseFingerprint(cert *x509.Certificate, algo crypto.Hash) ([]byte, error) {
|
||||
if !algo.Available() {
|
||||
return nil, errHashUnavailable
|
||||
}
|
||||
h := algo.New()
|
||||
// Hash.Writer is specified to be never returning an error.
|
||||
// https://golang.org/pkg/hash/#Hash
|
||||
h.Write(cert.Raw)
|
||||
return h.Sum(nil), nil
|
||||
}
|
||||
|
||||
func decodeRemoteFingerprint(maddr ma.Multiaddr) (*mh.DecodedMultihash, error) {
|
||||
remoteFingerprintMultibase, err := maddr.ValueForProtocol(ma.P_CERTHASH)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
_, data, err := multibase.Decode(remoteFingerprintMultibase)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return mh.Decode(data)
|
||||
}
|
||||
|
||||
func encodeDTLSFingerprint(fp webrtc.DTLSFingerprint) (string, error) {
|
||||
digest, err := decodeInterspersedHexFromASCIIString(fp.Value)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
encoded, err := mh.Encode(digest, mh.SHA2_256)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
return multibase.Encode(multibase.Base64url, encoded)
|
||||
}
|
70
p2p/transport/webrtc/hex.go
Normal file
70
p2p/transport/webrtc/hex.go
Normal file
@@ -0,0 +1,70 @@
|
||||
package libp2pwebrtc
|
||||
|
||||
// The code in this file is adapted from the Go standard library's hex package.
|
||||
// As found in https://cs.opensource.google/go/go/+/refs/tags/go1.20.2:src/encoding/hex/hex.go
|
||||
//
|
||||
// The reason we adapted the original code is to allow us to deal with interspersed requirements
|
||||
// while at the same time hex encoding/decoding, without having to do so in two passes.
|
||||
|
||||
import (
|
||||
"encoding/hex"
|
||||
"errors"
|
||||
)
|
||||
|
||||
// encodeInterspersedHex encodes a byte slice into a string of hex characters,
|
||||
// separating each encoded byte with a colon (':').
|
||||
//
|
||||
// Example: { 0x01, 0x02, 0x03 } -> "01:02:03"
|
||||
func encodeInterspersedHex(src []byte) string {
|
||||
if len(src) == 0 {
|
||||
return ""
|
||||
}
|
||||
s := hex.EncodeToString(src)
|
||||
n := len(s)
|
||||
// Determine number of colons
|
||||
colons := n / 2
|
||||
if n%2 == 0 {
|
||||
colons--
|
||||
}
|
||||
buffer := make([]byte, n+colons)
|
||||
|
||||
for i, j := 0, 0; i < n; i, j = i+2, j+3 {
|
||||
copy(buffer[j:j+2], s[i:i+2])
|
||||
if j+3 < len(buffer) {
|
||||
buffer[j+2] = ':'
|
||||
}
|
||||
}
|
||||
return string(buffer)
|
||||
}
|
||||
|
||||
var errUnexpectedIntersperseHexChar = errors.New("unexpected character in interspersed hex string")
|
||||
|
||||
// decodeInterspersedHexFromASCIIString decodes an ASCII string of hex characters into a byte slice,
|
||||
// where the hex characters are expected to be separated by a colon (':').
|
||||
//
|
||||
// NOTE that this function returns an error in case the input string contains non-ASCII characters.
|
||||
//
|
||||
// Example: "01:02:03" -> { 0x01, 0x02, 0x03 }
|
||||
func decodeInterspersedHexFromASCIIString(s string) ([]byte, error) {
|
||||
n := len(s)
|
||||
buffer := make([]byte, n/3*2+n%3)
|
||||
j := 0
|
||||
for i := 0; i < n; i++ {
|
||||
if i%3 == 2 {
|
||||
if s[i] != ':' {
|
||||
return nil, errUnexpectedIntersperseHexChar
|
||||
}
|
||||
} else {
|
||||
if s[i] == ':' {
|
||||
return nil, errUnexpectedIntersperseHexChar
|
||||
}
|
||||
buffer[j] = s[i]
|
||||
j++
|
||||
}
|
||||
}
|
||||
dst := make([]byte, hex.DecodedLen(len(buffer)))
|
||||
if _, err := hex.Decode(dst, buffer); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return dst, nil
|
||||
}
|
132
p2p/transport/webrtc/hex_test.go
Normal file
132
p2p/transport/webrtc/hex_test.go
Normal file
@@ -0,0 +1,132 @@
|
||||
package libp2pwebrtc
|
||||
|
||||
import (
|
||||
"encoding/hex"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
func TestEncodeInterspersedHex(t *testing.T) {
|
||||
b, err := hex.DecodeString("ba7816bf8f01cfea414140de5dae2223b00361a396177a9cb410ff61f20015ad")
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, "ba:78:16:bf:8f:01:cf:ea:41:41:40:de:5d:ae:22:23:b0:03:61:a3:96:17:7a:9c:b4:10:ff:61:f2:00:15:ad", encodeInterspersedHex(b))
|
||||
}
|
||||
|
||||
func BenchmarkEncodeInterspersedHex(b *testing.B) {
|
||||
data, err := hex.DecodeString("ba7816bf8f01cfea414140de5dae2223b00361a396177a9cb410ff61f20015ad")
|
||||
require.NoError(b, err)
|
||||
|
||||
for i := 0; i < b.N; i++ {
|
||||
encodeInterspersedHex(data)
|
||||
}
|
||||
}
|
||||
|
||||
func TestDecodeInterpersedHexStringLowerCase(t *testing.T) {
|
||||
b, err := decodeInterspersedHexFromASCIIString("ba:78:16:bf:8f:01:cf:ea:41:41:40:de:5d:ae:22:23:b0:03:61:a3:96:17:7a:9c:b4:10:ff:61:f2:00:15:ad")
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, "ba7816bf8f01cfea414140de5dae2223b00361a396177a9cb410ff61f20015ad", hex.EncodeToString(b))
|
||||
}
|
||||
|
||||
func TestDecodeInterpersedHexStringMixedCase(t *testing.T) {
|
||||
b, err := decodeInterspersedHexFromASCIIString("Ba:78:16:BF:8F:01:cf:ea:41:41:40:De:5d:ae:22:23:b0:03:61:a3:96:17:7a:9c:b4:10:FF:61:f2:00:15:ad")
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, "ba7816bf8f01cfea414140de5dae2223b00361a396177a9cb410ff61f20015ad", hex.EncodeToString(b))
|
||||
}
|
||||
|
||||
func TestDecodeInterpersedHexStringOneByte(t *testing.T) {
|
||||
b, err := decodeInterspersedHexFromASCIIString("ba")
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, "ba", hex.EncodeToString(b))
|
||||
}
|
||||
|
||||
func TestDecodeInterpersedHexBytesLowerCase(t *testing.T) {
|
||||
b, err := decodeInterspersedHexFromASCIIString("ba:78:16:bf:8f:01:cf:ea:41:41:40:de:5d:ae:22:23:b0:03:61:a3:96:17:7a:9c:b4:10:ff:61:f2:00:15:ad")
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, "ba7816bf8f01cfea414140de5dae2223b00361a396177a9cb410ff61f20015ad", hex.EncodeToString(b))
|
||||
}
|
||||
|
||||
func BenchmarkDecode(b *testing.B) {
|
||||
for i := 0; i < b.N; i++ {
|
||||
_, err := decodeInterspersedHexFromASCIIString("ba:78:16:bf:8f:01:cf:ea:41:41:40:de:5d:ae:22:23:b0:03:61:a3:96:17:7a:9c:b4:10:ff:61:f2:00:15:ad")
|
||||
require.NoError(b, err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestDecodeInterpersedHexBytesMixedCase(t *testing.T) {
|
||||
b, err := decodeInterspersedHexFromASCIIString("Ba:78:16:BF:8F:01:cf:ea:41:41:40:De:5d:ae:22:23:b0:03:61:a3:96:17:7a:9c:b4:10:FF:61:f2:00:15:ad")
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, "ba7816bf8f01cfea414140de5dae2223b00361a396177a9cb410ff61f20015ad", hex.EncodeToString(b))
|
||||
}
|
||||
|
||||
func TestDecodeInterpersedHexBytesOneByte(t *testing.T) {
|
||||
b, err := decodeInterspersedHexFromASCIIString("ba")
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, "ba", hex.EncodeToString(b))
|
||||
}
|
||||
|
||||
func TestEncodeInterperseHexNilSlice(t *testing.T) {
|
||||
require.Equal(t, "", encodeInterspersedHex(nil))
|
||||
require.Equal(t, "", encodeInterspersedHex([]byte{}))
|
||||
}
|
||||
|
||||
func TestDecodeInterspersedHexEmpty(t *testing.T) {
|
||||
b, err := decodeInterspersedHexFromASCIIString("")
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, []byte{}, b)
|
||||
}
|
||||
|
||||
func TestDecodeInterpersedHexFromASCIIStringEmpty(t *testing.T) {
|
||||
b, err := decodeInterspersedHexFromASCIIString("")
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, []byte{}, b)
|
||||
}
|
||||
|
||||
func TestDecodeInterpersedHexInvalid(t *testing.T) {
|
||||
for _, v := range []string{"0", "0000", "000"} {
|
||||
_, err := decodeInterspersedHexFromASCIIString(v)
|
||||
require.Error(t, err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestDecodeInterpersedHexValid(t *testing.T) {
|
||||
b, err := decodeInterspersedHexFromASCIIString("00")
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, []byte{0}, b)
|
||||
}
|
||||
|
||||
func TestDecodeInterpersedHexFromASCIIStringInvalid(t *testing.T) {
|
||||
for _, v := range []string{"0", "0000", "000"} {
|
||||
_, err := decodeInterspersedHexFromASCIIString(v)
|
||||
require.Error(t, err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestDecodeInterpersedHexFromASCIIStringValid(t *testing.T) {
|
||||
b, err := decodeInterspersedHexFromASCIIString("00")
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, []byte{0}, b)
|
||||
}
|
||||
|
||||
func FuzzInterpersedHex(f *testing.F) {
|
||||
f.Fuzz(func(t *testing.T, b []byte) {
|
||||
decoded, err := decodeInterspersedHexFromASCIIString(string(b))
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
encoded := encodeInterspersedHex(decoded)
|
||||
require.Equal(t, strings.ToLower(string(b)), encoded)
|
||||
})
|
||||
}
|
||||
|
||||
func FuzzInterspersedHexASCII(f *testing.F) {
|
||||
f.Fuzz(func(t *testing.T, s string) {
|
||||
decoded, err := decodeInterspersedHexFromASCIIString(s)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
encoded := encodeInterspersedHex(decoded)
|
||||
require.Equal(t, strings.ToLower(s), encoded)
|
||||
})
|
||||
}
|
366
p2p/transport/webrtc/listener.go
Normal file
366
p2p/transport/webrtc/listener.go
Normal file
@@ -0,0 +1,366 @@
|
||||
package libp2pwebrtc
|
||||
|
||||
import (
|
||||
"context"
|
||||
"crypto"
|
||||
"encoding/hex"
|
||||
"errors"
|
||||
"fmt"
|
||||
"net"
|
||||
"strings"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/libp2p/go-libp2p/core/network"
|
||||
"github.com/libp2p/go-libp2p/core/peer"
|
||||
tpt "github.com/libp2p/go-libp2p/core/transport"
|
||||
"github.com/libp2p/go-libp2p/p2p/transport/webrtc/udpmux"
|
||||
|
||||
ma "github.com/multiformats/go-multiaddr"
|
||||
manet "github.com/multiformats/go-multiaddr/net"
|
||||
"github.com/multiformats/go-multibase"
|
||||
"github.com/multiformats/go-multihash"
|
||||
pionlogger "github.com/pion/logging"
|
||||
"github.com/pion/webrtc/v3"
|
||||
"go.uber.org/zap/zapcore"
|
||||
)
|
||||
|
||||
type connMultiaddrs struct {
|
||||
local, remote ma.Multiaddr
|
||||
}
|
||||
|
||||
var _ network.ConnMultiaddrs = &connMultiaddrs{}
|
||||
|
||||
func (c *connMultiaddrs) LocalMultiaddr() ma.Multiaddr { return c.local }
|
||||
func (c *connMultiaddrs) RemoteMultiaddr() ma.Multiaddr { return c.remote }
|
||||
|
||||
const (
|
||||
candidateSetupTimeout = 20 * time.Second
|
||||
DefaultMaxInFlightConnections = 10
|
||||
)
|
||||
|
||||
type listener struct {
|
||||
transport *WebRTCTransport
|
||||
|
||||
mux *udpmux.UDPMux
|
||||
|
||||
config webrtc.Configuration
|
||||
localFingerprint webrtc.DTLSFingerprint
|
||||
localFingerprintMultibase string
|
||||
|
||||
localAddr net.Addr
|
||||
localMultiaddr ma.Multiaddr
|
||||
|
||||
// buffered incoming connections
|
||||
acceptQueue chan tpt.CapableConn
|
||||
|
||||
// used to control the lifecycle of the listener
|
||||
ctx context.Context
|
||||
cancel context.CancelFunc
|
||||
}
|
||||
|
||||
var _ tpt.Listener = &listener{}
|
||||
|
||||
func newListener(transport *WebRTCTransport, laddr ma.Multiaddr, socket net.PacketConn, config webrtc.Configuration) (*listener, error) {
|
||||
localFingerprints, err := config.Certificates[0].GetFingerprints()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
localMh, err := hex.DecodeString(strings.ReplaceAll(localFingerprints[0].Value, ":", ""))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
localMhBuf, err := multihash.Encode(localMh, multihash.SHA2_256)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
localFpMultibase, err := multibase.Encode(multibase.Base64url, localMhBuf)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
l := &listener{
|
||||
transport: transport,
|
||||
config: config,
|
||||
localFingerprint: localFingerprints[0],
|
||||
localFingerprintMultibase: localFpMultibase,
|
||||
localMultiaddr: laddr,
|
||||
localAddr: socket.LocalAddr(),
|
||||
acceptQueue: make(chan tpt.CapableConn),
|
||||
}
|
||||
|
||||
l.ctx, l.cancel = context.WithCancel(context.Background())
|
||||
mux := udpmux.NewUDPMux(socket)
|
||||
l.mux = mux
|
||||
mux.Start()
|
||||
|
||||
go l.listen()
|
||||
|
||||
return l, err
|
||||
}
|
||||
|
||||
func (l *listener) listen() {
|
||||
// Accepting a connection requires instantiating a peerconnection
|
||||
// and a noise connection which is expensive. We therefore limit
|
||||
// the number of in-flight connection requests. A connection
|
||||
// is considered to be in flight from the instant it is handled
|
||||
// until it is dequeued by a call to Accept, or errors out in some
|
||||
// way.
|
||||
inFlightQueueCh := make(chan struct{}, l.transport.maxInFlightConnections)
|
||||
for i := uint32(0); i < l.transport.maxInFlightConnections; i++ {
|
||||
inFlightQueueCh <- struct{}{}
|
||||
}
|
||||
|
||||
for {
|
||||
select {
|
||||
case <-inFlightQueueCh:
|
||||
case <-l.ctx.Done():
|
||||
return
|
||||
}
|
||||
|
||||
candidate, err := l.mux.Accept(l.ctx)
|
||||
if err != nil {
|
||||
if l.ctx.Err() == nil {
|
||||
log.Debugf("accepting candidate failed: %s", err)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
go func() {
|
||||
defer func() { inFlightQueueCh <- struct{}{} }() // free this spot once again
|
||||
|
||||
ctx, cancel := context.WithTimeout(l.ctx, candidateSetupTimeout)
|
||||
defer cancel()
|
||||
|
||||
conn, err := l.handleCandidate(ctx, candidate)
|
||||
if err != nil {
|
||||
l.mux.RemoveConnByUfrag(candidate.Ufrag)
|
||||
log.Debugf("could not accept connection: %s: %v", candidate.Ufrag, err)
|
||||
return
|
||||
}
|
||||
|
||||
select {
|
||||
case <-ctx.Done():
|
||||
log.Warn("could not push connection: ctx done")
|
||||
conn.Close()
|
||||
case l.acceptQueue <- conn:
|
||||
// acceptQueue is an unbuffered channel, so this block until the connection is accepted.
|
||||
}
|
||||
}()
|
||||
}
|
||||
}
|
||||
|
||||
func (l *listener) handleCandidate(ctx context.Context, candidate udpmux.Candidate) (tpt.CapableConn, error) {
|
||||
remoteMultiaddr, err := manet.FromNetAddr(candidate.Addr)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if l.transport.gater != nil {
|
||||
localAddr, _ := ma.SplitFunc(l.localMultiaddr, func(c ma.Component) bool { return c.Protocol().Code == ma.P_CERTHASH })
|
||||
if !l.transport.gater.InterceptAccept(&connMultiaddrs{local: localAddr, remote: remoteMultiaddr}) {
|
||||
// The connection attempt is rejected before we can send the client an error.
|
||||
// This means that the connection attempt will time out.
|
||||
return nil, errors.New("connection gated")
|
||||
}
|
||||
}
|
||||
scope, err := l.transport.rcmgr.OpenConnection(network.DirInbound, false, remoteMultiaddr)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
conn, err := l.setupConnection(ctx, scope, remoteMultiaddr, candidate)
|
||||
if err != nil {
|
||||
scope.Done()
|
||||
return nil, err
|
||||
}
|
||||
if l.transport.gater != nil && !l.transport.gater.InterceptSecured(network.DirInbound, conn.RemotePeer(), conn) {
|
||||
conn.Close()
|
||||
return nil, errors.New("connection gated")
|
||||
}
|
||||
return conn, nil
|
||||
}
|
||||
|
||||
func (l *listener) setupConnection(
|
||||
ctx context.Context, scope network.ConnManagementScope,
|
||||
remoteMultiaddr ma.Multiaddr, candidate udpmux.Candidate,
|
||||
) (tConn tpt.CapableConn, err error) {
|
||||
var pc *webrtc.PeerConnection
|
||||
defer func() {
|
||||
if err != nil {
|
||||
if pc != nil {
|
||||
_ = pc.Close()
|
||||
}
|
||||
if tConn != nil {
|
||||
_ = tConn.Close()
|
||||
}
|
||||
}
|
||||
}()
|
||||
|
||||
loggerFactory := pionlogger.NewDefaultLoggerFactory()
|
||||
pionLogLevel := pionlogger.LogLevelDisabled
|
||||
switch log.Level() {
|
||||
case zapcore.DebugLevel:
|
||||
pionLogLevel = pionlogger.LogLevelDebug
|
||||
case zapcore.InfoLevel:
|
||||
pionLogLevel = pionlogger.LogLevelInfo
|
||||
case zapcore.WarnLevel:
|
||||
pionLogLevel = pionlogger.LogLevelWarn
|
||||
case zapcore.ErrorLevel:
|
||||
pionLogLevel = pionlogger.LogLevelError
|
||||
}
|
||||
loggerFactory.DefaultLogLevel = pionLogLevel
|
||||
|
||||
settingEngine := webrtc.SettingEngine{LoggerFactory: loggerFactory}
|
||||
settingEngine.SetAnsweringDTLSRole(webrtc.DTLSRoleServer)
|
||||
settingEngine.SetICECredentials(candidate.Ufrag, candidate.Ufrag)
|
||||
settingEngine.SetLite(true)
|
||||
settingEngine.SetICEUDPMux(l.mux)
|
||||
settingEngine.SetIncludeLoopbackCandidate(true)
|
||||
settingEngine.DisableCertificateFingerprintVerification(true)
|
||||
settingEngine.SetICETimeouts(
|
||||
l.transport.peerConnectionTimeouts.Disconnect,
|
||||
l.transport.peerConnectionTimeouts.Failed,
|
||||
l.transport.peerConnectionTimeouts.Keepalive,
|
||||
)
|
||||
settingEngine.DetachDataChannels()
|
||||
|
||||
api := webrtc.NewAPI(webrtc.WithSettingEngine(settingEngine))
|
||||
pc, err = api.NewPeerConnection(l.config)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
negotiated, id := handshakeChannelNegotiated, handshakeChannelID
|
||||
rawDatachannel, err := pc.CreateDataChannel("", &webrtc.DataChannelInit{
|
||||
Negotiated: &negotiated,
|
||||
ID: &id,
|
||||
})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
errC := addOnConnectionStateChangeCallback(pc)
|
||||
// Infer the client SDP from the incoming STUN message by setting the ice-ufrag.
|
||||
if err := pc.SetRemoteDescription(webrtc.SessionDescription{
|
||||
SDP: createClientSDP(candidate.Addr, candidate.Ufrag),
|
||||
Type: webrtc.SDPTypeOffer,
|
||||
}); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
answer, err := pc.CreateAnswer(nil)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if err := pc.SetLocalDescription(answer); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
select {
|
||||
case <-ctx.Done():
|
||||
return nil, ctx.Err()
|
||||
case err := <-errC:
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("peer connection failed for ufrag: %s", candidate.Ufrag)
|
||||
}
|
||||
}
|
||||
|
||||
rwc, err := getDetachedChannel(ctx, rawDatachannel)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
localMultiaddrWithoutCerthash, _ := ma.SplitFunc(l.localMultiaddr, func(c ma.Component) bool { return c.Protocol().Code == ma.P_CERTHASH })
|
||||
|
||||
handshakeChannel := newStream(rawDatachannel, rwc, func() {})
|
||||
// The connection is instantiated before performing the Noise handshake. This is
|
||||
// to handle the case where the remote is faster and attempts to initiate a stream
|
||||
// before the ondatachannel callback can be set.
|
||||
conn, err := newConnection(
|
||||
network.DirInbound,
|
||||
pc,
|
||||
l.transport,
|
||||
scope,
|
||||
l.transport.localPeerId,
|
||||
localMultiaddrWithoutCerthash,
|
||||
"", // remotePeer
|
||||
nil, // remoteKey
|
||||
remoteMultiaddr,
|
||||
)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// we do not yet know A's peer ID so accept any inbound
|
||||
remotePubKey, err := l.transport.noiseHandshake(ctx, pc, handshakeChannel, "", crypto.SHA256, true)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
remotePeer, err := peer.IDFromPublicKey(remotePubKey)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// earliest point where we know the remote's peerID
|
||||
if err := scope.SetPeer(remotePeer); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
conn.setRemotePeer(remotePeer)
|
||||
conn.setRemotePublicKey(remotePubKey)
|
||||
|
||||
return conn, err
|
||||
}
|
||||
|
||||
func (l *listener) Accept() (tpt.CapableConn, error) {
|
||||
select {
|
||||
case <-l.ctx.Done():
|
||||
return nil, tpt.ErrListenerClosed
|
||||
case conn := <-l.acceptQueue:
|
||||
return conn, nil
|
||||
}
|
||||
}
|
||||
|
||||
func (l *listener) Close() error {
|
||||
select {
|
||||
case <-l.ctx.Done():
|
||||
default:
|
||||
l.cancel()
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (l *listener) Addr() net.Addr {
|
||||
return l.localAddr
|
||||
}
|
||||
|
||||
func (l *listener) Multiaddr() ma.Multiaddr {
|
||||
return l.localMultiaddr
|
||||
}
|
||||
|
||||
// addOnConnectionStateChangeCallback adds the OnConnectionStateChange to the PeerConnection.
|
||||
// The channel returned here:
|
||||
// * is closed when the state changes to Connection
|
||||
// * receives an error when the state changes to Failed
|
||||
// * doesn't receive anything (nor is closed) when the state changes to Disconnected
|
||||
func addOnConnectionStateChangeCallback(pc *webrtc.PeerConnection) <-chan error {
|
||||
errC := make(chan error, 1)
|
||||
var once sync.Once
|
||||
pc.OnConnectionStateChange(func(state webrtc.PeerConnectionState) {
|
||||
switch state {
|
||||
case webrtc.PeerConnectionStateConnected:
|
||||
once.Do(func() { close(errC) })
|
||||
case webrtc.PeerConnectionStateFailed:
|
||||
once.Do(func() {
|
||||
errC <- errors.New("peerconnection failed")
|
||||
close(errC)
|
||||
})
|
||||
case webrtc.PeerConnectionStateDisconnected:
|
||||
// the connection can move to a disconnected state and back to a connected state without ICE renegotiation.
|
||||
// This could happen when underlying UDP packets are lost, and therefore the connection moves to the disconnected state.
|
||||
// If the connection then receives packets on the connection, it can move back to the connected state.
|
||||
// If no packets are received until the failed timeout is triggered, the connection moves to the failed state.
|
||||
log.Warn("peerconnection disconnected")
|
||||
}
|
||||
})
|
||||
return errC
|
||||
}
|
3
p2p/transport/webrtc/message.go
Normal file
3
p2p/transport/webrtc/message.go
Normal file
@@ -0,0 +1,3 @@
|
||||
package libp2pwebrtc
|
||||
|
||||
//go:generate protoc --go_out=. --go_opt=Mpb/message.proto=./pb pb/message.proto
|
3
p2p/transport/webrtc/pb/generate.go
Normal file
3
p2p/transport/webrtc/pb/generate.go
Normal file
@@ -0,0 +1,3 @@
|
||||
package pb
|
||||
|
||||
//go:generate protoc --go_out=. --go_opt=paths=source_relative -I . message.proto
|
224
p2p/transport/webrtc/pb/message.pb.go
Normal file
224
p2p/transport/webrtc/pb/message.pb.go
Normal file
@@ -0,0 +1,224 @@
|
||||
// Code generated by protoc-gen-go. DO NOT EDIT.
|
||||
// versions:
|
||||
// protoc-gen-go v1.30.0
|
||||
// protoc v3.21.12
|
||||
// source: message.proto
|
||||
|
||||
package pb
|
||||
|
||||
import (
|
||||
protoreflect "google.golang.org/protobuf/reflect/protoreflect"
|
||||
protoimpl "google.golang.org/protobuf/runtime/protoimpl"
|
||||
reflect "reflect"
|
||||
sync "sync"
|
||||
)
|
||||
|
||||
const (
|
||||
// Verify that this generated code is sufficiently up-to-date.
|
||||
_ = protoimpl.EnforceVersion(20 - protoimpl.MinVersion)
|
||||
// Verify that runtime/protoimpl is sufficiently up-to-date.
|
||||
_ = protoimpl.EnforceVersion(protoimpl.MaxVersion - 20)
|
||||
)
|
||||
|
||||
type Message_Flag int32
|
||||
|
||||
const (
|
||||
// The sender will no longer send messages on the stream.
|
||||
Message_FIN Message_Flag = 0
|
||||
// The sender will no longer read messages on the stream. Incoming data is
|
||||
// being discarded on receipt.
|
||||
Message_STOP_SENDING Message_Flag = 1
|
||||
// The sender abruptly terminates the sending part of the stream. The
|
||||
// receiver can discard any data that it already received on that stream.
|
||||
Message_RESET Message_Flag = 2
|
||||
)
|
||||
|
||||
// Enum value maps for Message_Flag.
|
||||
var (
|
||||
Message_Flag_name = map[int32]string{
|
||||
0: "FIN",
|
||||
1: "STOP_SENDING",
|
||||
2: "RESET",
|
||||
}
|
||||
Message_Flag_value = map[string]int32{
|
||||
"FIN": 0,
|
||||
"STOP_SENDING": 1,
|
||||
"RESET": 2,
|
||||
}
|
||||
)
|
||||
|
||||
func (x Message_Flag) Enum() *Message_Flag {
|
||||
p := new(Message_Flag)
|
||||
*p = x
|
||||
return p
|
||||
}
|
||||
|
||||
func (x Message_Flag) String() string {
|
||||
return protoimpl.X.EnumStringOf(x.Descriptor(), protoreflect.EnumNumber(x))
|
||||
}
|
||||
|
||||
func (Message_Flag) Descriptor() protoreflect.EnumDescriptor {
|
||||
return file_message_proto_enumTypes[0].Descriptor()
|
||||
}
|
||||
|
||||
func (Message_Flag) Type() protoreflect.EnumType {
|
||||
return &file_message_proto_enumTypes[0]
|
||||
}
|
||||
|
||||
func (x Message_Flag) Number() protoreflect.EnumNumber {
|
||||
return protoreflect.EnumNumber(x)
|
||||
}
|
||||
|
||||
// Deprecated: Do not use.
|
||||
func (x *Message_Flag) UnmarshalJSON(b []byte) error {
|
||||
num, err := protoimpl.X.UnmarshalJSONEnum(x.Descriptor(), b)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
*x = Message_Flag(num)
|
||||
return nil
|
||||
}
|
||||
|
||||
// Deprecated: Use Message_Flag.Descriptor instead.
|
||||
func (Message_Flag) EnumDescriptor() ([]byte, []int) {
|
||||
return file_message_proto_rawDescGZIP(), []int{0, 0}
|
||||
}
|
||||
|
||||
type Message struct {
|
||||
state protoimpl.MessageState
|
||||
sizeCache protoimpl.SizeCache
|
||||
unknownFields protoimpl.UnknownFields
|
||||
|
||||
Flag *Message_Flag `protobuf:"varint,1,opt,name=flag,enum=Message_Flag" json:"flag,omitempty"`
|
||||
Message []byte `protobuf:"bytes,2,opt,name=message" json:"message,omitempty"`
|
||||
}
|
||||
|
||||
func (x *Message) Reset() {
|
||||
*x = Message{}
|
||||
if protoimpl.UnsafeEnabled {
|
||||
mi := &file_message_proto_msgTypes[0]
|
||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||
ms.StoreMessageInfo(mi)
|
||||
}
|
||||
}
|
||||
|
||||
func (x *Message) String() string {
|
||||
return protoimpl.X.MessageStringOf(x)
|
||||
}
|
||||
|
||||
func (*Message) ProtoMessage() {}
|
||||
|
||||
func (x *Message) ProtoReflect() protoreflect.Message {
|
||||
mi := &file_message_proto_msgTypes[0]
|
||||
if protoimpl.UnsafeEnabled && x != nil {
|
||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||
if ms.LoadMessageInfo() == nil {
|
||||
ms.StoreMessageInfo(mi)
|
||||
}
|
||||
return ms
|
||||
}
|
||||
return mi.MessageOf(x)
|
||||
}
|
||||
|
||||
// Deprecated: Use Message.ProtoReflect.Descriptor instead.
|
||||
func (*Message) Descriptor() ([]byte, []int) {
|
||||
return file_message_proto_rawDescGZIP(), []int{0}
|
||||
}
|
||||
|
||||
func (x *Message) GetFlag() Message_Flag {
|
||||
if x != nil && x.Flag != nil {
|
||||
return *x.Flag
|
||||
}
|
||||
return Message_FIN
|
||||
}
|
||||
|
||||
func (x *Message) GetMessage() []byte {
|
||||
if x != nil {
|
||||
return x.Message
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
var File_message_proto protoreflect.FileDescriptor
|
||||
|
||||
var file_message_proto_rawDesc = []byte{
|
||||
0x0a, 0x0d, 0x6d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x22,
|
||||
0x74, 0x0a, 0x07, 0x4d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x12, 0x21, 0x0a, 0x04, 0x66, 0x6c,
|
||||
0x61, 0x67, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0e, 0x32, 0x0d, 0x2e, 0x4d, 0x65, 0x73, 0x73, 0x61,
|
||||
0x67, 0x65, 0x2e, 0x46, 0x6c, 0x61, 0x67, 0x52, 0x04, 0x66, 0x6c, 0x61, 0x67, 0x12, 0x18, 0x0a,
|
||||
0x07, 0x6d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x07,
|
||||
0x6d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x22, 0x2c, 0x0a, 0x04, 0x46, 0x6c, 0x61, 0x67, 0x12,
|
||||
0x07, 0x0a, 0x03, 0x46, 0x49, 0x4e, 0x10, 0x00, 0x12, 0x10, 0x0a, 0x0c, 0x53, 0x54, 0x4f, 0x50,
|
||||
0x5f, 0x53, 0x45, 0x4e, 0x44, 0x49, 0x4e, 0x47, 0x10, 0x01, 0x12, 0x09, 0x0a, 0x05, 0x52, 0x45,
|
||||
0x53, 0x45, 0x54, 0x10, 0x02, 0x42, 0x35, 0x5a, 0x33, 0x67, 0x69, 0x74, 0x68, 0x75, 0x62, 0x2e,
|
||||
0x63, 0x6f, 0x6d, 0x2f, 0x6c, 0x69, 0x62, 0x70, 0x32, 0x70, 0x2f, 0x67, 0x6f, 0x2d, 0x6c, 0x69,
|
||||
0x62, 0x70, 0x32, 0x70, 0x2f, 0x70, 0x32, 0x70, 0x2f, 0x74, 0x72, 0x61, 0x6e, 0x73, 0x70, 0x6f,
|
||||
0x72, 0x74, 0x2f, 0x77, 0x65, 0x62, 0x72, 0x74, 0x63, 0x2f, 0x70, 0x62,
|
||||
}
|
||||
|
||||
var (
|
||||
file_message_proto_rawDescOnce sync.Once
|
||||
file_message_proto_rawDescData = file_message_proto_rawDesc
|
||||
)
|
||||
|
||||
func file_message_proto_rawDescGZIP() []byte {
|
||||
file_message_proto_rawDescOnce.Do(func() {
|
||||
file_message_proto_rawDescData = protoimpl.X.CompressGZIP(file_message_proto_rawDescData)
|
||||
})
|
||||
return file_message_proto_rawDescData
|
||||
}
|
||||
|
||||
var file_message_proto_enumTypes = make([]protoimpl.EnumInfo, 1)
|
||||
var file_message_proto_msgTypes = make([]protoimpl.MessageInfo, 1)
|
||||
var file_message_proto_goTypes = []interface{}{
|
||||
(Message_Flag)(0), // 0: Message.Flag
|
||||
(*Message)(nil), // 1: Message
|
||||
}
|
||||
var file_message_proto_depIdxs = []int32{
|
||||
0, // 0: Message.flag:type_name -> Message.Flag
|
||||
1, // [1:1] is the sub-list for method output_type
|
||||
1, // [1:1] is the sub-list for method input_type
|
||||
1, // [1:1] is the sub-list for extension type_name
|
||||
1, // [1:1] is the sub-list for extension extendee
|
||||
0, // [0:1] is the sub-list for field type_name
|
||||
}
|
||||
|
||||
func init() { file_message_proto_init() }
|
||||
func file_message_proto_init() {
|
||||
if File_message_proto != nil {
|
||||
return
|
||||
}
|
||||
if !protoimpl.UnsafeEnabled {
|
||||
file_message_proto_msgTypes[0].Exporter = func(v interface{}, i int) interface{} {
|
||||
switch v := v.(*Message); i {
|
||||
case 0:
|
||||
return &v.state
|
||||
case 1:
|
||||
return &v.sizeCache
|
||||
case 2:
|
||||
return &v.unknownFields
|
||||
default:
|
||||
return nil
|
||||
}
|
||||
}
|
||||
}
|
||||
type x struct{}
|
||||
out := protoimpl.TypeBuilder{
|
||||
File: protoimpl.DescBuilder{
|
||||
GoPackagePath: reflect.TypeOf(x{}).PkgPath(),
|
||||
RawDescriptor: file_message_proto_rawDesc,
|
||||
NumEnums: 1,
|
||||
NumMessages: 1,
|
||||
NumExtensions: 0,
|
||||
NumServices: 0,
|
||||
},
|
||||
GoTypes: file_message_proto_goTypes,
|
||||
DependencyIndexes: file_message_proto_depIdxs,
|
||||
EnumInfos: file_message_proto_enumTypes,
|
||||
MessageInfos: file_message_proto_msgTypes,
|
||||
}.Build()
|
||||
File_message_proto = out.File
|
||||
file_message_proto_rawDesc = nil
|
||||
file_message_proto_goTypes = nil
|
||||
file_message_proto_depIdxs = nil
|
||||
}
|
20
p2p/transport/webrtc/pb/message.proto
Normal file
20
p2p/transport/webrtc/pb/message.proto
Normal file
@@ -0,0 +1,20 @@
|
||||
syntax = "proto2";
|
||||
|
||||
option go_package = "github.com/libp2p/go-libp2p/p2p/transport/webrtc/pb";
|
||||
|
||||
message Message {
|
||||
enum Flag {
|
||||
// The sender will no longer send messages on the stream.
|
||||
FIN = 0;
|
||||
// The sender will no longer read messages on the stream. Incoming data is
|
||||
// being discarded on receipt.
|
||||
STOP_SENDING = 1;
|
||||
// The sender abruptly terminates the sending part of the stream. The
|
||||
// receiver can discard any data that it already received on that stream.
|
||||
RESET = 2;
|
||||
}
|
||||
|
||||
optional Flag flag=1;
|
||||
|
||||
optional bytes message = 2;
|
||||
}
|
143
p2p/transport/webrtc/sdp.go
Normal file
143
p2p/transport/webrtc/sdp.go
Normal file
@@ -0,0 +1,143 @@
|
||||
package libp2pwebrtc
|
||||
|
||||
import (
|
||||
"crypto"
|
||||
"fmt"
|
||||
"net"
|
||||
"strings"
|
||||
|
||||
"github.com/multiformats/go-multihash"
|
||||
)
|
||||
|
||||
// clientSDP describes an SDP format string which can be used
|
||||
// to infer a client's SDP offer from the incoming STUN message.
|
||||
// The fingerprint used to render a client SDP is arbitrary since
|
||||
// it fingerprint verification is disabled in favour of a noise
|
||||
// handshake. The max message size is fixed to 16384 bytes.
|
||||
const clientSDP = `v=0
|
||||
o=- 0 0 IN %[1]s %[2]s
|
||||
s=-
|
||||
c=IN %[1]s %[2]s
|
||||
t=0 0
|
||||
|
||||
m=application %[3]d UDP/DTLS/SCTP webrtc-datachannel
|
||||
a=mid:0
|
||||
a=ice-options:ice2
|
||||
a=ice-ufrag:%[4]s
|
||||
a=ice-pwd:%[4]s
|
||||
a=fingerprint:sha-256 ba:78:16:bf:8f:01:cf:ea:41:41:40:de:5d:ae:22:23:b0:03:61:a3:96:17:7a:9c:b4:10:ff:61:f2:00:15:ad
|
||||
a=setup:actpass
|
||||
a=sctp-port:5000
|
||||
a=max-message-size:16384
|
||||
`
|
||||
|
||||
func createClientSDP(addr *net.UDPAddr, ufrag string) string {
|
||||
ipVersion := "IP4"
|
||||
if addr.IP.To4() == nil {
|
||||
ipVersion = "IP6"
|
||||
}
|
||||
return fmt.Sprintf(
|
||||
clientSDP,
|
||||
ipVersion,
|
||||
addr.IP,
|
||||
addr.Port,
|
||||
ufrag,
|
||||
)
|
||||
}
|
||||
|
||||
// serverSDP defines an SDP format string used by a dialer
|
||||
// to infer the SDP answer of a server based on the provided
|
||||
// multiaddr, and the locally set ICE credentials. The max
|
||||
// message size is fixed to 16384 bytes.
|
||||
const serverSDP = `v=0
|
||||
o=- 0 0 IN %[1]s %[2]s
|
||||
s=-
|
||||
t=0 0
|
||||
a=ice-lite
|
||||
m=application %[3]d UDP/DTLS/SCTP webrtc-datachannel
|
||||
c=IN %[1]s %[2]s
|
||||
a=mid:0
|
||||
a=ice-options:ice2
|
||||
a=ice-ufrag:%[4]s
|
||||
a=ice-pwd:%[4]s
|
||||
a=fingerprint:%[5]s
|
||||
|
||||
a=setup:passive
|
||||
a=sctp-port:5000
|
||||
a=max-message-size:16384
|
||||
a=candidate:1 1 UDP 1 %[2]s %[3]d typ host
|
||||
a=end-of-candidates
|
||||
`
|
||||
|
||||
func createServerSDP(addr *net.UDPAddr, ufrag string, fingerprint multihash.DecodedMultihash) (string, error) {
|
||||
ipVersion := "IP4"
|
||||
if addr.IP.To4() == nil {
|
||||
ipVersion = "IP6"
|
||||
}
|
||||
|
||||
sdpString, err := getSupportedSDPString(fingerprint.Code)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
var builder strings.Builder
|
||||
builder.Grow(len(fingerprint.Digest)*3 + 8)
|
||||
builder.WriteString(sdpString)
|
||||
builder.WriteByte(' ')
|
||||
builder.WriteString(encodeInterspersedHex(fingerprint.Digest))
|
||||
fp := builder.String()
|
||||
|
||||
return fmt.Sprintf(
|
||||
serverSDP,
|
||||
ipVersion,
|
||||
addr.IP,
|
||||
addr.Port,
|
||||
ufrag,
|
||||
fp,
|
||||
), nil
|
||||
}
|
||||
|
||||
// getSupportedSDPHash converts a multihash code to the
|
||||
// corresponding crypto.Hash for supported protocols. If a
|
||||
// crypto.Hash cannot be found, it returns `(0, false)`
|
||||
func getSupportedSDPHash(code uint64) (crypto.Hash, bool) {
|
||||
switch code {
|
||||
case multihash.MD5:
|
||||
return crypto.MD5, true
|
||||
case multihash.SHA1:
|
||||
return crypto.SHA1, true
|
||||
case multihash.SHA3_224:
|
||||
return crypto.SHA3_224, true
|
||||
case multihash.SHA2_256:
|
||||
return crypto.SHA256, true
|
||||
case multihash.SHA3_384:
|
||||
return crypto.SHA3_384, true
|
||||
case multihash.SHA2_512:
|
||||
return crypto.SHA512, true
|
||||
default:
|
||||
return 0, false
|
||||
}
|
||||
}
|
||||
|
||||
// getSupportedSDPString converts a multihash code
|
||||
// to a string format recognised by pion for fingerprint
|
||||
// algorithms
|
||||
func getSupportedSDPString(code uint64) (string, error) {
|
||||
// values based on (cryto.Hash).String()
|
||||
switch code {
|
||||
case multihash.MD5:
|
||||
return "md5", nil
|
||||
case multihash.SHA1:
|
||||
return "sha-1", nil
|
||||
case multihash.SHA3_224:
|
||||
return "sha3-224", nil
|
||||
case multihash.SHA2_256:
|
||||
return "sha-256", nil
|
||||
case multihash.SHA3_384:
|
||||
return "sha3-384", nil
|
||||
case multihash.SHA2_512:
|
||||
return "sha-512", nil
|
||||
default:
|
||||
return "", fmt.Errorf("unsupported hash code (%d)", code)
|
||||
}
|
||||
}
|
101
p2p/transport/webrtc/sdp_test.go
Normal file
101
p2p/transport/webrtc/sdp_test.go
Normal file
@@ -0,0 +1,101 @@
|
||||
package libp2pwebrtc
|
||||
|
||||
import (
|
||||
"encoding/hex"
|
||||
"net"
|
||||
"testing"
|
||||
|
||||
"github.com/multiformats/go-multihash"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
const expectedServerSDP = `v=0
|
||||
o=- 0 0 IN IP4 0.0.0.0
|
||||
s=-
|
||||
t=0 0
|
||||
a=ice-lite
|
||||
m=application 37826 UDP/DTLS/SCTP webrtc-datachannel
|
||||
c=IN IP4 0.0.0.0
|
||||
a=mid:0
|
||||
a=ice-options:ice2
|
||||
a=ice-ufrag:d2c0fc07-8bb3-42ae-bae2-a6fce8a0b581
|
||||
a=ice-pwd:d2c0fc07-8bb3-42ae-bae2-a6fce8a0b581
|
||||
a=fingerprint:sha-256 ba:78:16:bf:8f:01:cf:ea:41:41:40:de:5d:ae:22:23:b0:03:61:a3:96:17:7a:9c:b4:10:ff:61:f2:00:15:ad
|
||||
|
||||
a=setup:passive
|
||||
a=sctp-port:5000
|
||||
a=max-message-size:16384
|
||||
a=candidate:1 1 UDP 1 0.0.0.0 37826 typ host
|
||||
a=end-of-candidates
|
||||
`
|
||||
|
||||
func TestRenderServerSDP(t *testing.T) {
|
||||
encoded, err := hex.DecodeString("ba7816bf8f01cfea414140de5dae2223b00361a396177a9cb410ff61f20015ad")
|
||||
require.NoError(t, err)
|
||||
|
||||
testMultihash := multihash.DecodedMultihash{
|
||||
Code: multihash.SHA2_256,
|
||||
Name: multihash.Codes[multihash.SHA2_256],
|
||||
Digest: encoded,
|
||||
Length: len(encoded),
|
||||
}
|
||||
addr := &net.UDPAddr{IP: net.IPv4(0, 0, 0, 0), Port: 37826}
|
||||
ufrag := "d2c0fc07-8bb3-42ae-bae2-a6fce8a0b581"
|
||||
fingerprint := testMultihash
|
||||
|
||||
sdp, err := createServerSDP(addr, ufrag, fingerprint)
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, expectedServerSDP, sdp)
|
||||
}
|
||||
|
||||
const expectedClientSDP = `v=0
|
||||
o=- 0 0 IN IP4 0.0.0.0
|
||||
s=-
|
||||
c=IN IP4 0.0.0.0
|
||||
t=0 0
|
||||
|
||||
m=application 37826 UDP/DTLS/SCTP webrtc-datachannel
|
||||
a=mid:0
|
||||
a=ice-options:ice2
|
||||
a=ice-ufrag:d2c0fc07-8bb3-42ae-bae2-a6fce8a0b581
|
||||
a=ice-pwd:d2c0fc07-8bb3-42ae-bae2-a6fce8a0b581
|
||||
a=fingerprint:sha-256 ba:78:16:bf:8f:01:cf:ea:41:41:40:de:5d:ae:22:23:b0:03:61:a3:96:17:7a:9c:b4:10:ff:61:f2:00:15:ad
|
||||
a=setup:actpass
|
||||
a=sctp-port:5000
|
||||
a=max-message-size:16384
|
||||
`
|
||||
|
||||
func TestRenderClientSDP(t *testing.T) {
|
||||
addr := &net.UDPAddr{IP: net.IPv4(0, 0, 0, 0), Port: 37826}
|
||||
ufrag := "d2c0fc07-8bb3-42ae-bae2-a6fce8a0b581"
|
||||
sdp := createClientSDP(addr, ufrag)
|
||||
require.Equal(t, expectedClientSDP, sdp)
|
||||
}
|
||||
|
||||
func BenchmarkRenderClientSDP(b *testing.B) {
|
||||
addr := &net.UDPAddr{IP: net.IPv4(0, 0, 0, 0), Port: 37826}
|
||||
ufrag := "d2c0fc07-8bb3-42ae-bae2-a6fce8a0b581"
|
||||
|
||||
for i := 0; i < b.N; i++ {
|
||||
createClientSDP(addr, ufrag)
|
||||
}
|
||||
}
|
||||
|
||||
func BenchmarkRenderServerSDP(b *testing.B) {
|
||||
encoded, _ := hex.DecodeString("ba7816bf8f01cfea414140de5dae2223b00361a396177a9cb410ff61f20015ad")
|
||||
|
||||
testMultihash := multihash.DecodedMultihash{
|
||||
Code: multihash.SHA2_256,
|
||||
Name: multihash.Codes[multihash.SHA2_256],
|
||||
Digest: encoded,
|
||||
Length: len(encoded),
|
||||
}
|
||||
addr := &net.UDPAddr{IP: net.IPv4(0, 0, 0, 0), Port: 37826}
|
||||
ufrag := "d2c0fc07-8bb3-42ae-bae2-a6fce8a0b581"
|
||||
fingerprint := testMultihash
|
||||
|
||||
for i := 0; i < b.N; i++ {
|
||||
createServerSDP(addr, ufrag, fingerprint)
|
||||
}
|
||||
|
||||
}
|
208
p2p/transport/webrtc/stream.go
Normal file
208
p2p/transport/webrtc/stream.go
Normal file
@@ -0,0 +1,208 @@
|
||||
package libp2pwebrtc
|
||||
|
||||
import (
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/libp2p/go-libp2p/core/network"
|
||||
"github.com/libp2p/go-libp2p/p2p/transport/webrtc/pb"
|
||||
"github.com/libp2p/go-msgio/pbio"
|
||||
|
||||
"github.com/pion/datachannel"
|
||||
"github.com/pion/webrtc/v3"
|
||||
)
|
||||
|
||||
const (
|
||||
// maxMessageSize is the maximum message size of the Protobuf message we send / receive.
|
||||
maxMessageSize = 16384
|
||||
// Pion SCTP association has an internal receive buffer of 1MB (roughly, 1MB per connection).
|
||||
// We can change this value in the SettingEngine before creating the peerconnection.
|
||||
// https://github.com/pion/webrtc/blob/v3.1.49/sctptransport.go#L341
|
||||
maxBufferedAmount = 2 * maxMessageSize
|
||||
// bufferedAmountLowThreshold and maxBufferedAmount are bound
|
||||
// to a stream but congestion control is done on the whole
|
||||
// SCTP association. This means that a single stream can monopolize
|
||||
// the complete congestion control window (cwnd) if it does not
|
||||
// read stream data and it's remote continues to send. We can
|
||||
// add messages to the send buffer once there is space for 1 full
|
||||
// sized message.
|
||||
bufferedAmountLowThreshold = maxBufferedAmount / 2
|
||||
|
||||
// Proto overhead assumption is 5 bytes
|
||||
protoOverhead = 5
|
||||
// Varint overhead is assumed to be 2 bytes. This is safe since
|
||||
// 1. This is only used and when writing message, and
|
||||
// 2. We only send messages in chunks of `maxMessageSize - varintOverhead`
|
||||
// which includes the data and the protobuf header. Since `maxMessageSize`
|
||||
// is less than or equal to 2 ^ 14, the varint will not be more than
|
||||
// 2 bytes in length.
|
||||
varintOverhead = 2
|
||||
)
|
||||
|
||||
type receiveState uint8
|
||||
|
||||
const (
|
||||
receiveStateReceiving receiveState = iota
|
||||
receiveStateDataRead // received and read the FIN
|
||||
receiveStateReset // either by calling CloseRead locally, or by receiving
|
||||
)
|
||||
|
||||
type sendState uint8
|
||||
|
||||
const (
|
||||
sendStateSending sendState = iota
|
||||
sendStateDataSent
|
||||
sendStateReset
|
||||
)
|
||||
|
||||
// Package pion detached data channel into a net.Conn
|
||||
// and then a network.MuxedStream
|
||||
type stream struct {
|
||||
mx sync.Mutex
|
||||
// pbio.Reader is not thread safe,
|
||||
// and while our Read is not promised to be thread safe,
|
||||
// we ourselves internally read from multiple routines...
|
||||
reader pbio.Reader
|
||||
// this buffer is limited up to a single message. Reason we need it
|
||||
// is because a reader might read a message midway, and so we need a
|
||||
// wait to buffer that for as long as the remaining part is not (yet) read
|
||||
nextMessage *pb.Message
|
||||
receiveState receiveState
|
||||
|
||||
// The public Write API is not promised to be thread safe,
|
||||
// but we need to be able to write control messages.
|
||||
writer pbio.Writer
|
||||
sendStateChanged chan struct{}
|
||||
sendState sendState
|
||||
controlMsgQueue []*pb.Message
|
||||
writeDeadline time.Time
|
||||
writeDeadlineUpdated chan struct{}
|
||||
writeAvailable chan struct{}
|
||||
|
||||
readLoopOnce sync.Once
|
||||
|
||||
onDone func()
|
||||
id uint16 // for logging purposes
|
||||
dataChannel *datachannel.DataChannel
|
||||
closeErr error
|
||||
}
|
||||
|
||||
var _ network.MuxedStream = &stream{}
|
||||
|
||||
func newStream(
|
||||
channel *webrtc.DataChannel,
|
||||
rwc datachannel.ReadWriteCloser,
|
||||
onDone func(),
|
||||
) *stream {
|
||||
s := &stream{
|
||||
reader: pbio.NewDelimitedReader(rwc, maxMessageSize),
|
||||
writer: pbio.NewDelimitedWriter(rwc),
|
||||
|
||||
sendStateChanged: make(chan struct{}, 1),
|
||||
writeDeadlineUpdated: make(chan struct{}, 1),
|
||||
writeAvailable: make(chan struct{}, 1),
|
||||
|
||||
id: *channel.ID(),
|
||||
dataChannel: rwc.(*datachannel.DataChannel),
|
||||
onDone: onDone,
|
||||
}
|
||||
|
||||
channel.SetBufferedAmountLowThreshold(bufferedAmountLowThreshold)
|
||||
channel.OnBufferedAmountLow(func() {
|
||||
s.mx.Lock()
|
||||
defer s.mx.Unlock()
|
||||
// first send out queued control messages
|
||||
for len(s.controlMsgQueue) > 0 {
|
||||
msg := s.controlMsgQueue[0]
|
||||
available := s.availableSendSpace()
|
||||
if controlMsgSize < available {
|
||||
s.writer.WriteMsg(msg) // TODO: handle error
|
||||
s.controlMsgQueue = s.controlMsgQueue[1:]
|
||||
} else {
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
s.maybeDeclareStreamDone()
|
||||
|
||||
select {
|
||||
case s.writeAvailable <- struct{}{}:
|
||||
default:
|
||||
}
|
||||
})
|
||||
return s
|
||||
}
|
||||
|
||||
func (s *stream) Close() error {
|
||||
closeWriteErr := s.CloseWrite()
|
||||
closeReadErr := s.CloseRead()
|
||||
if closeWriteErr != nil {
|
||||
return closeWriteErr
|
||||
}
|
||||
return closeReadErr
|
||||
}
|
||||
|
||||
func (s *stream) Reset() error {
|
||||
cancelWriteErr := s.cancelWrite()
|
||||
closeReadErr := s.CloseRead()
|
||||
if cancelWriteErr != nil {
|
||||
return cancelWriteErr
|
||||
}
|
||||
return closeReadErr
|
||||
}
|
||||
|
||||
func (s *stream) SetDeadline(t time.Time) error {
|
||||
_ = s.SetReadDeadline(t)
|
||||
return s.SetWriteDeadline(t)
|
||||
}
|
||||
|
||||
// processIncomingFlag process the flag on an incoming message
|
||||
// It needs to be called with msg.Flag, not msg.GetFlag(),
|
||||
// otherwise we'd misinterpret the default value.
|
||||
// It needs to be called while the mutex is locked.
|
||||
func (s *stream) processIncomingFlag(flag *pb.Message_Flag) {
|
||||
if flag == nil {
|
||||
return
|
||||
}
|
||||
|
||||
switch *flag {
|
||||
case pb.Message_FIN:
|
||||
if s.receiveState == receiveStateReceiving {
|
||||
s.receiveState = receiveStateDataRead
|
||||
}
|
||||
case pb.Message_STOP_SENDING:
|
||||
if s.sendState == sendStateSending {
|
||||
s.sendState = sendStateReset
|
||||
}
|
||||
select {
|
||||
case s.sendStateChanged <- struct{}{}:
|
||||
default:
|
||||
}
|
||||
case pb.Message_RESET:
|
||||
if s.receiveState == receiveStateReceiving {
|
||||
s.receiveState = receiveStateReset
|
||||
}
|
||||
}
|
||||
s.maybeDeclareStreamDone()
|
||||
}
|
||||
|
||||
// this is used to force reset a stream
|
||||
func (s *stream) maybeDeclareStreamDone() {
|
||||
if (s.sendState == sendStateReset || s.sendState == sendStateDataSent) &&
|
||||
(s.receiveState == receiveStateReset || s.receiveState == receiveStateDataRead) &&
|
||||
len(s.controlMsgQueue) == 0 {
|
||||
_ = s.SetReadDeadline(time.Now().Add(-1 * time.Hour)) // pion ignores zero times
|
||||
// TODO: we should be closing the underlying datachannel, but this resets the stream
|
||||
// See https://github.com/libp2p/specs/issues/575 for details.
|
||||
// _ = s.dataChannel.Close()
|
||||
// TODO: write for the spawned reader to return
|
||||
s.onDone()
|
||||
}
|
||||
}
|
||||
|
||||
func (s *stream) setCloseError(e error) {
|
||||
s.mx.Lock()
|
||||
defer s.mx.Unlock()
|
||||
|
||||
s.closeErr = e
|
||||
}
|
100
p2p/transport/webrtc/stream_read.go
Normal file
100
p2p/transport/webrtc/stream_read.go
Normal file
@@ -0,0 +1,100 @@
|
||||
package libp2pwebrtc
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"io"
|
||||
"time"
|
||||
|
||||
"github.com/libp2p/go-libp2p/core/network"
|
||||
"github.com/libp2p/go-libp2p/p2p/transport/webrtc/pb"
|
||||
)
|
||||
|
||||
func (s *stream) Read(b []byte) (int, error) {
|
||||
if len(b) == 0 {
|
||||
return 0, nil
|
||||
}
|
||||
|
||||
s.mx.Lock()
|
||||
defer s.mx.Unlock()
|
||||
|
||||
if s.closeErr != nil {
|
||||
return 0, s.closeErr
|
||||
}
|
||||
switch s.receiveState {
|
||||
case receiveStateDataRead:
|
||||
return 0, io.EOF
|
||||
case receiveStateReset:
|
||||
return 0, network.ErrReset
|
||||
}
|
||||
|
||||
var read int
|
||||
for {
|
||||
if s.nextMessage == nil {
|
||||
// load the next message
|
||||
s.mx.Unlock()
|
||||
var msg pb.Message
|
||||
if err := s.reader.ReadMsg(&msg); err != nil {
|
||||
s.mx.Lock()
|
||||
if err == io.EOF {
|
||||
// if the channel was properly closed, return EOF
|
||||
if s.receiveState == receiveStateDataRead {
|
||||
return 0, io.EOF
|
||||
}
|
||||
// This case occurs when the remote node closes the stream without writing a FIN message
|
||||
// There's little we can do here
|
||||
return 0, errors.New("didn't receive final state for stream")
|
||||
}
|
||||
if s.receiveState == receiveStateReset {
|
||||
return 0, network.ErrReset
|
||||
}
|
||||
return 0, err
|
||||
}
|
||||
s.mx.Lock()
|
||||
s.nextMessage = &msg
|
||||
}
|
||||
|
||||
if len(s.nextMessage.Message) > 0 {
|
||||
n := copy(b, s.nextMessage.Message)
|
||||
read += n
|
||||
s.nextMessage.Message = s.nextMessage.Message[n:]
|
||||
return read, nil
|
||||
}
|
||||
|
||||
// process flags on the message after reading all the data
|
||||
s.processIncomingFlag(s.nextMessage.Flag)
|
||||
s.nextMessage = nil
|
||||
if s.closeErr != nil {
|
||||
return read, s.closeErr
|
||||
}
|
||||
switch s.receiveState {
|
||||
case receiveStateDataRead:
|
||||
return read, io.EOF
|
||||
case receiveStateReset:
|
||||
s.dataChannel.SetReadDeadline(time.Time{})
|
||||
return read, network.ErrReset
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (s *stream) SetReadDeadline(t time.Time) error { return s.dataChannel.SetReadDeadline(t) }
|
||||
|
||||
func (s *stream) CloseRead() error {
|
||||
s.mx.Lock()
|
||||
defer s.mx.Unlock()
|
||||
|
||||
if s.nextMessage != nil {
|
||||
s.processIncomingFlag(s.nextMessage.Flag)
|
||||
s.nextMessage = nil
|
||||
}
|
||||
var err error
|
||||
if s.receiveState == receiveStateReceiving && s.closeErr == nil {
|
||||
err = s.sendControlMessage(&pb.Message{Flag: pb.Message_STOP_SENDING.Enum()})
|
||||
}
|
||||
s.receiveState = receiveStateReset
|
||||
s.maybeDeclareStreamDone()
|
||||
|
||||
// make any calls to Read blocking on ReadMsg return immediately
|
||||
s.dataChannel.SetReadDeadline(time.Now())
|
||||
|
||||
return err
|
||||
}
|
307
p2p/transport/webrtc/stream_test.go
Normal file
307
p2p/transport/webrtc/stream_test.go
Normal file
@@ -0,0 +1,307 @@
|
||||
package libp2pwebrtc
|
||||
|
||||
import (
|
||||
"crypto/rand"
|
||||
"errors"
|
||||
"io"
|
||||
"os"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/libp2p/go-libp2p/p2p/transport/webrtc/pb"
|
||||
|
||||
"github.com/libp2p/go-libp2p/core/network"
|
||||
|
||||
"github.com/pion/datachannel"
|
||||
"github.com/pion/webrtc/v3"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
type detachedChan struct {
|
||||
rwc datachannel.ReadWriteCloser
|
||||
dc *webrtc.DataChannel
|
||||
}
|
||||
|
||||
func getDetachedDataChannels(t *testing.T) (detachedChan, detachedChan) {
|
||||
s := webrtc.SettingEngine{}
|
||||
s.DetachDataChannels()
|
||||
api := webrtc.NewAPI(webrtc.WithSettingEngine(s))
|
||||
|
||||
offerPC, err := api.NewPeerConnection(webrtc.Configuration{})
|
||||
require.NoError(t, err)
|
||||
t.Cleanup(func() { offerPC.Close() })
|
||||
offerRWCChan := make(chan datachannel.ReadWriteCloser, 1)
|
||||
offerDC, err := offerPC.CreateDataChannel("data", nil)
|
||||
require.NoError(t, err)
|
||||
offerDC.OnOpen(func() {
|
||||
rwc, err := offerDC.Detach()
|
||||
require.NoError(t, err)
|
||||
offerRWCChan <- rwc
|
||||
})
|
||||
|
||||
answerPC, err := api.NewPeerConnection(webrtc.Configuration{})
|
||||
require.NoError(t, err)
|
||||
|
||||
answerChan := make(chan detachedChan, 1)
|
||||
answerPC.OnDataChannel(func(dc *webrtc.DataChannel) {
|
||||
dc.OnOpen(func() {
|
||||
rwc, err := dc.Detach()
|
||||
require.NoError(t, err)
|
||||
answerChan <- detachedChan{rwc: rwc, dc: dc}
|
||||
})
|
||||
})
|
||||
t.Cleanup(func() { answerPC.Close() })
|
||||
|
||||
// Set ICE Candidate handlers. As soon as a PeerConnection has gathered a candidate send it to the other peer
|
||||
answerPC.OnICECandidate(func(candidate *webrtc.ICECandidate) {
|
||||
if candidate != nil {
|
||||
require.NoError(t, offerPC.AddICECandidate(candidate.ToJSON()))
|
||||
}
|
||||
})
|
||||
offerPC.OnICECandidate(func(candidate *webrtc.ICECandidate) {
|
||||
if candidate != nil {
|
||||
require.NoError(t, answerPC.AddICECandidate(candidate.ToJSON()))
|
||||
}
|
||||
})
|
||||
|
||||
// Set the handler for Peer connection state
|
||||
// This will notify you when the peer has connected/disconnected
|
||||
offerPC.OnConnectionStateChange(func(s webrtc.PeerConnectionState) {
|
||||
if s == webrtc.PeerConnectionStateFailed {
|
||||
t.Log("peer connection failed on offerer")
|
||||
}
|
||||
})
|
||||
|
||||
// Set the handler for Peer connection state
|
||||
// This will notify you when the peer has connected/disconnected
|
||||
answerPC.OnConnectionStateChange(func(s webrtc.PeerConnectionState) {
|
||||
if s == webrtc.PeerConnectionStateFailed {
|
||||
t.Log("peer connection failed on answerer")
|
||||
}
|
||||
})
|
||||
|
||||
// Now, create an offer
|
||||
offer, err := offerPC.CreateOffer(nil)
|
||||
require.NoError(t, err)
|
||||
require.NoError(t, answerPC.SetRemoteDescription(offer))
|
||||
require.NoError(t, offerPC.SetLocalDescription(offer))
|
||||
|
||||
answer, err := answerPC.CreateAnswer(nil)
|
||||
require.NoError(t, err)
|
||||
require.NoError(t, offerPC.SetRemoteDescription(answer))
|
||||
require.NoError(t, answerPC.SetLocalDescription(answer))
|
||||
|
||||
return <-answerChan, detachedChan{rwc: <-offerRWCChan, dc: offerDC}
|
||||
}
|
||||
|
||||
func TestStreamSimpleReadWriteClose(t *testing.T) {
|
||||
client, server := getDetachedDataChannels(t)
|
||||
|
||||
var clientDone, serverDone bool
|
||||
clientStr := newStream(client.dc, client.rwc, func() { clientDone = true })
|
||||
serverStr := newStream(server.dc, server.rwc, func() { serverDone = true })
|
||||
|
||||
// send a foobar from the client
|
||||
n, err := clientStr.Write([]byte("foobar"))
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, 6, n)
|
||||
require.NoError(t, clientStr.CloseWrite())
|
||||
// writing after closing should error
|
||||
_, err = clientStr.Write([]byte("foobar"))
|
||||
require.Error(t, err)
|
||||
require.False(t, clientDone)
|
||||
|
||||
// now read all the data on the server side
|
||||
b, err := io.ReadAll(serverStr)
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, []byte("foobar"), b)
|
||||
// reading again should give another io.EOF
|
||||
n, err = serverStr.Read(make([]byte, 10))
|
||||
require.Zero(t, n)
|
||||
require.ErrorIs(t, err, io.EOF)
|
||||
require.False(t, serverDone)
|
||||
|
||||
// send something back
|
||||
_, err = serverStr.Write([]byte("lorem ipsum"))
|
||||
require.NoError(t, err)
|
||||
require.NoError(t, serverStr.CloseWrite())
|
||||
require.True(t, serverDone)
|
||||
// and read it at the client
|
||||
require.False(t, clientDone)
|
||||
b, err = io.ReadAll(clientStr)
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, []byte("lorem ipsum"), b)
|
||||
require.True(t, clientDone)
|
||||
}
|
||||
|
||||
func TestStreamPartialReads(t *testing.T) {
|
||||
client, server := getDetachedDataChannels(t)
|
||||
|
||||
clientStr := newStream(client.dc, client.rwc, func() {})
|
||||
serverStr := newStream(server.dc, server.rwc, func() {})
|
||||
|
||||
_, err := serverStr.Write([]byte("foobar"))
|
||||
require.NoError(t, err)
|
||||
require.NoError(t, serverStr.CloseWrite())
|
||||
|
||||
n, err := clientStr.Read([]byte{}) // empty read
|
||||
require.NoError(t, err)
|
||||
require.Zero(t, n)
|
||||
b := make([]byte, 3)
|
||||
n, err = clientStr.Read(b)
|
||||
require.Equal(t, 3, n)
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, []byte("foo"), b)
|
||||
b, err = io.ReadAll(clientStr)
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, []byte("bar"), b)
|
||||
}
|
||||
|
||||
func TestStreamSkipEmptyFrames(t *testing.T) {
|
||||
client, server := getDetachedDataChannels(t)
|
||||
|
||||
clientStr := newStream(client.dc, client.rwc, func() {})
|
||||
serverStr := newStream(server.dc, server.rwc, func() {})
|
||||
|
||||
for i := 0; i < 10; i++ {
|
||||
require.NoError(t, serverStr.writer.WriteMsg(&pb.Message{}))
|
||||
}
|
||||
require.NoError(t, serverStr.writer.WriteMsg(&pb.Message{Message: []byte("foo")}))
|
||||
for i := 0; i < 10; i++ {
|
||||
require.NoError(t, serverStr.writer.WriteMsg(&pb.Message{}))
|
||||
}
|
||||
require.NoError(t, serverStr.writer.WriteMsg(&pb.Message{Message: []byte("bar")}))
|
||||
for i := 0; i < 10; i++ {
|
||||
require.NoError(t, serverStr.writer.WriteMsg(&pb.Message{}))
|
||||
}
|
||||
require.NoError(t, serverStr.writer.WriteMsg(&pb.Message{Flag: pb.Message_FIN.Enum()}))
|
||||
|
||||
var read []byte
|
||||
var count int
|
||||
for i := 0; i < 100; i++ {
|
||||
b := make([]byte, 10)
|
||||
count++
|
||||
n, err := clientStr.Read(b)
|
||||
read = append(read, b[:n]...)
|
||||
if err == io.EOF {
|
||||
break
|
||||
}
|
||||
require.NoError(t, err)
|
||||
}
|
||||
require.LessOrEqual(t, count, 3, "should've taken a maximum of 3 reads")
|
||||
require.Equal(t, []byte("foobar"), read)
|
||||
}
|
||||
|
||||
func TestStreamReadReturnsOnClose(t *testing.T) {
|
||||
client, _ := getDetachedDataChannels(t)
|
||||
|
||||
clientStr := newStream(client.dc, client.rwc, func() {})
|
||||
errChan := make(chan error, 1)
|
||||
go func() {
|
||||
_, err := clientStr.Read([]byte{0})
|
||||
errChan <- err
|
||||
}()
|
||||
time.Sleep(50 * time.Millisecond) // give the Read call some time to hit the loop
|
||||
require.NoError(t, clientStr.Close())
|
||||
select {
|
||||
case err := <-errChan:
|
||||
require.ErrorIs(t, err, network.ErrReset)
|
||||
case <-time.After(500 * time.Millisecond):
|
||||
t.Fatal("timeout")
|
||||
}
|
||||
}
|
||||
|
||||
func TestStreamResets(t *testing.T) {
|
||||
client, server := getDetachedDataChannels(t)
|
||||
|
||||
var clientDone, serverDone bool
|
||||
clientStr := newStream(client.dc, client.rwc, func() { clientDone = true })
|
||||
serverStr := newStream(server.dc, server.rwc, func() { serverDone = true })
|
||||
|
||||
// send a foobar from the client
|
||||
_, err := clientStr.Write([]byte("foobar"))
|
||||
require.NoError(t, err)
|
||||
_, err = serverStr.Write([]byte("lorem ipsum"))
|
||||
require.NoError(t, err)
|
||||
require.NoError(t, clientStr.Reset()) // resetting resets both directions
|
||||
require.True(t, clientDone)
|
||||
// attempting to write more data should result in a reset error
|
||||
_, err = clientStr.Write([]byte("foobar"))
|
||||
require.ErrorIs(t, err, network.ErrReset)
|
||||
// read what the server sent
|
||||
b, err := io.ReadAll(clientStr)
|
||||
require.Empty(t, b)
|
||||
require.ErrorIs(t, err, network.ErrReset)
|
||||
|
||||
// read the data on the server side
|
||||
require.False(t, serverDone)
|
||||
b, err = io.ReadAll(serverStr)
|
||||
require.Equal(t, []byte("foobar"), b)
|
||||
require.ErrorIs(t, err, network.ErrReset)
|
||||
require.Eventually(t, func() bool {
|
||||
_, err := serverStr.Write([]byte("foobar"))
|
||||
return errors.Is(err, network.ErrReset)
|
||||
}, time.Second, 50*time.Millisecond)
|
||||
require.True(t, serverDone)
|
||||
}
|
||||
|
||||
func TestStreamReadDeadlineAsync(t *testing.T) {
|
||||
client, server := getDetachedDataChannels(t)
|
||||
|
||||
clientStr := newStream(client.dc, client.rwc, func() {})
|
||||
serverStr := newStream(server.dc, server.rwc, func() {})
|
||||
|
||||
timeout := 100 * time.Millisecond
|
||||
if os.Getenv("CI") != "" {
|
||||
timeout *= 5
|
||||
}
|
||||
start := time.Now()
|
||||
clientStr.SetReadDeadline(start.Add(timeout))
|
||||
_, err := clientStr.Read([]byte{0})
|
||||
require.ErrorIs(t, err, os.ErrDeadlineExceeded)
|
||||
took := time.Since(start)
|
||||
require.GreaterOrEqual(t, took, timeout)
|
||||
require.LessOrEqual(t, took, timeout*3/2)
|
||||
// repeated calls should return immediately
|
||||
start = time.Now()
|
||||
_, err = clientStr.Read([]byte{0})
|
||||
require.ErrorIs(t, err, os.ErrDeadlineExceeded)
|
||||
require.LessOrEqual(t, time.Since(start), timeout/3)
|
||||
// clear the deadline
|
||||
clientStr.SetReadDeadline(time.Time{})
|
||||
_, err = serverStr.Write([]byte("foobar"))
|
||||
require.NoError(t, err)
|
||||
_, err = clientStr.Read([]byte{0})
|
||||
require.NoError(t, err)
|
||||
require.LessOrEqual(t, time.Since(start), timeout/3)
|
||||
}
|
||||
|
||||
func TestStreamWriteDeadlineAsync(t *testing.T) {
|
||||
client, server := getDetachedDataChannels(t)
|
||||
|
||||
clientStr := newStream(client.dc, client.rwc, func() {})
|
||||
serverStr := newStream(server.dc, server.rwc, func() {})
|
||||
_ = serverStr
|
||||
|
||||
b := make([]byte, 1024)
|
||||
rand.Read(b)
|
||||
start := time.Now()
|
||||
timeout := 100 * time.Millisecond
|
||||
if os.Getenv("CI") != "" {
|
||||
timeout *= 5
|
||||
}
|
||||
clientStr.SetWriteDeadline(start.Add(timeout))
|
||||
var hitDeadline bool
|
||||
for i := 0; i < 2000; i++ {
|
||||
if _, err := clientStr.Write(b); err != nil {
|
||||
t.Logf("wrote %d kB", i)
|
||||
require.ErrorIs(t, err, os.ErrDeadlineExceeded)
|
||||
hitDeadline = true
|
||||
break
|
||||
}
|
||||
}
|
||||
require.True(t, hitDeadline)
|
||||
took := time.Since(start)
|
||||
require.GreaterOrEqual(t, took, timeout)
|
||||
require.LessOrEqual(t, took, timeout*3/2)
|
||||
}
|
197
p2p/transport/webrtc/stream_write.go
Normal file
197
p2p/transport/webrtc/stream_write.go
Normal file
@@ -0,0 +1,197 @@
|
||||
package libp2pwebrtc
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"os"
|
||||
"time"
|
||||
|
||||
"github.com/libp2p/go-libp2p/core/network"
|
||||
"github.com/libp2p/go-libp2p/p2p/transport/webrtc/pb"
|
||||
)
|
||||
|
||||
var errWriteAfterClose = errors.New("write after close")
|
||||
|
||||
// If we have less space than minMessageSize, we don't put a new message on the data channel.
|
||||
// Instead, we wait until more space opens up.
|
||||
const minMessageSize = 1 << 10
|
||||
|
||||
func (s *stream) Write(b []byte) (int, error) {
|
||||
s.mx.Lock()
|
||||
defer s.mx.Unlock()
|
||||
|
||||
if s.closeErr != nil {
|
||||
return 0, s.closeErr
|
||||
}
|
||||
switch s.sendState {
|
||||
case sendStateReset:
|
||||
return 0, network.ErrReset
|
||||
case sendStateDataSent:
|
||||
return 0, errWriteAfterClose
|
||||
}
|
||||
|
||||
// Check if there is any message on the wire. This is used for control
|
||||
// messages only when the read side of the stream is closed
|
||||
if s.receiveState != receiveStateReceiving {
|
||||
s.readLoopOnce.Do(s.spawnControlMessageReader)
|
||||
}
|
||||
|
||||
var writeDeadlineTimer *time.Timer
|
||||
defer func() {
|
||||
if writeDeadlineTimer != nil {
|
||||
writeDeadlineTimer.Stop()
|
||||
}
|
||||
}()
|
||||
|
||||
var n int
|
||||
for len(b) > 0 {
|
||||
if s.closeErr != nil {
|
||||
return n, s.closeErr
|
||||
}
|
||||
switch s.sendState {
|
||||
case sendStateReset:
|
||||
return n, network.ErrReset
|
||||
case sendStateDataSent:
|
||||
return n, errWriteAfterClose
|
||||
}
|
||||
|
||||
writeDeadline := s.writeDeadline
|
||||
// deadline deleted, stop and remove the timer
|
||||
if writeDeadline.IsZero() && writeDeadlineTimer != nil {
|
||||
writeDeadlineTimer.Stop()
|
||||
writeDeadlineTimer = nil
|
||||
}
|
||||
var writeDeadlineChan <-chan time.Time
|
||||
if !writeDeadline.IsZero() {
|
||||
if writeDeadlineTimer == nil {
|
||||
writeDeadlineTimer = time.NewTimer(time.Until(writeDeadline))
|
||||
} else {
|
||||
if !writeDeadlineTimer.Stop() {
|
||||
<-writeDeadlineTimer.C
|
||||
}
|
||||
writeDeadlineTimer.Reset(time.Until(writeDeadline))
|
||||
}
|
||||
writeDeadlineChan = writeDeadlineTimer.C
|
||||
}
|
||||
|
||||
availableSpace := s.availableSendSpace()
|
||||
if availableSpace < minMessageSize {
|
||||
s.mx.Unlock()
|
||||
select {
|
||||
case <-s.writeAvailable:
|
||||
case <-writeDeadlineChan:
|
||||
s.mx.Lock()
|
||||
return n, os.ErrDeadlineExceeded
|
||||
case <-s.sendStateChanged:
|
||||
case <-s.writeDeadlineUpdated:
|
||||
}
|
||||
s.mx.Lock()
|
||||
continue
|
||||
}
|
||||
end := maxMessageSize
|
||||
if end > availableSpace {
|
||||
end = availableSpace
|
||||
}
|
||||
end -= protoOverhead + varintOverhead
|
||||
if end > len(b) {
|
||||
end = len(b)
|
||||
}
|
||||
msg := &pb.Message{Message: b[:end]}
|
||||
if err := s.writer.WriteMsg(msg); err != nil {
|
||||
return n, err
|
||||
}
|
||||
n += end
|
||||
b = b[end:]
|
||||
}
|
||||
return n, nil
|
||||
}
|
||||
|
||||
// used for reading control messages while writing, in case the reader is closed,
|
||||
// as to ensure we do still get control messages. This is important as according to the spec
|
||||
// our data and control channels are intermixed on the same conn.
|
||||
func (s *stream) spawnControlMessageReader() {
|
||||
if s.nextMessage != nil {
|
||||
s.processIncomingFlag(s.nextMessage.Flag)
|
||||
s.nextMessage = nil
|
||||
}
|
||||
|
||||
go func() {
|
||||
// no deadline needed, Read will return once there's a new message, or an error occurred
|
||||
_ = s.dataChannel.SetReadDeadline(time.Time{})
|
||||
for {
|
||||
var msg pb.Message
|
||||
if err := s.reader.ReadMsg(&msg); err != nil {
|
||||
return
|
||||
}
|
||||
s.mx.Lock()
|
||||
s.processIncomingFlag(msg.Flag)
|
||||
s.mx.Unlock()
|
||||
}
|
||||
}()
|
||||
}
|
||||
|
||||
func (s *stream) SetWriteDeadline(t time.Time) error {
|
||||
s.mx.Lock()
|
||||
defer s.mx.Unlock()
|
||||
s.writeDeadline = t
|
||||
select {
|
||||
case s.writeDeadlineUpdated <- struct{}{}:
|
||||
default:
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *stream) availableSendSpace() int {
|
||||
buffered := int(s.dataChannel.BufferedAmount())
|
||||
availableSpace := maxBufferedAmount - buffered
|
||||
if availableSpace < 0 { // this should never happen, but better check
|
||||
log.Errorw("data channel buffered more data than the maximum amount", "max", maxBufferedAmount, "buffered", buffered)
|
||||
}
|
||||
return availableSpace
|
||||
}
|
||||
|
||||
// There's no way to determine the size of a Protobuf message in the pbio package.
|
||||
// Setting the size to 100 works as long as the control messages (incl. the varint prefix) are smaller than that value.
|
||||
const controlMsgSize = 100
|
||||
|
||||
func (s *stream) sendControlMessage(msg *pb.Message) error {
|
||||
available := s.availableSendSpace()
|
||||
if controlMsgSize < available {
|
||||
return s.writer.WriteMsg(msg)
|
||||
}
|
||||
s.controlMsgQueue = append(s.controlMsgQueue, msg)
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *stream) cancelWrite() error {
|
||||
s.mx.Lock()
|
||||
defer s.mx.Unlock()
|
||||
|
||||
if s.sendState != sendStateSending {
|
||||
return nil
|
||||
}
|
||||
s.sendState = sendStateReset
|
||||
select {
|
||||
case s.sendStateChanged <- struct{}{}:
|
||||
default:
|
||||
}
|
||||
if err := s.sendControlMessage(&pb.Message{Flag: pb.Message_RESET.Enum()}); err != nil {
|
||||
return err
|
||||
}
|
||||
s.maybeDeclareStreamDone()
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *stream) CloseWrite() error {
|
||||
s.mx.Lock()
|
||||
defer s.mx.Unlock()
|
||||
|
||||
if s.sendState != sendStateSending {
|
||||
return nil
|
||||
}
|
||||
s.sendState = sendStateDataSent
|
||||
if err := s.sendControlMessage(&pb.Message{Flag: pb.Message_FIN.Enum()}); err != nil {
|
||||
return err
|
||||
}
|
||||
s.maybeDeclareStreamDone()
|
||||
return nil
|
||||
}
|
527
p2p/transport/webrtc/transport.go
Normal file
527
p2p/transport/webrtc/transport.go
Normal file
@@ -0,0 +1,527 @@
|
||||
// Package libp2pwebrtc implements the WebRTC transport for go-libp2p,
|
||||
// as described in https://github.com/libp2p/specs/tree/master/webrtc.
|
||||
//
|
||||
// At this point, this package is EXPERIMENTAL, and the WebRTC transport is not enabled by default.
|
||||
// While we're fairly confident that the implementation correctly implements the specification,
|
||||
// we're not making any guarantees regarding its security (especially regarding resource exhaustion attacks).
|
||||
// Fixes, even for security-related issues, will be conducted in the open.
|
||||
//
|
||||
// Experimentation is encouraged. Please open an issue if you encounter any problems with this transport.
|
||||
//
|
||||
// The udpmux subpackage contains the logic for multiplexing multiple WebRTC (ICE)
|
||||
// connections over a single UDP socket.
|
||||
package libp2pwebrtc
|
||||
|
||||
import (
|
||||
"context"
|
||||
"crypto"
|
||||
"crypto/ecdsa"
|
||||
"crypto/elliptic"
|
||||
"crypto/rand"
|
||||
"crypto/x509"
|
||||
"encoding/binary"
|
||||
"errors"
|
||||
"fmt"
|
||||
"net"
|
||||
"time"
|
||||
|
||||
mrand "golang.org/x/exp/rand"
|
||||
|
||||
"github.com/libp2p/go-libp2p/core/connmgr"
|
||||
ic "github.com/libp2p/go-libp2p/core/crypto"
|
||||
"github.com/libp2p/go-libp2p/core/network"
|
||||
"github.com/libp2p/go-libp2p/core/peer"
|
||||
"github.com/libp2p/go-libp2p/core/pnet"
|
||||
"github.com/libp2p/go-libp2p/core/sec"
|
||||
tpt "github.com/libp2p/go-libp2p/core/transport"
|
||||
"github.com/libp2p/go-libp2p/p2p/security/noise"
|
||||
|
||||
logging "github.com/ipfs/go-log/v2"
|
||||
ma "github.com/multiformats/go-multiaddr"
|
||||
mafmt "github.com/multiformats/go-multiaddr-fmt"
|
||||
manet "github.com/multiformats/go-multiaddr/net"
|
||||
"github.com/multiformats/go-multihash"
|
||||
|
||||
pionlogger "github.com/pion/logging"
|
||||
"github.com/pion/webrtc/v3"
|
||||
)
|
||||
|
||||
var log = logging.Logger("webrtc-transport")
|
||||
|
||||
var dialMatcher = mafmt.And(mafmt.UDP, mafmt.Base(ma.P_WEBRTC_DIRECT), mafmt.Base(ma.P_CERTHASH))
|
||||
|
||||
var webrtcComponent *ma.Component
|
||||
|
||||
func init() {
|
||||
var err error
|
||||
webrtcComponent, err = ma.NewComponent(ma.ProtocolWithCode(ma.P_WEBRTC_DIRECT).Name, "")
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
}
|
||||
|
||||
const (
|
||||
// handshakeChannelNegotiated is used to specify that the
|
||||
// handshake data channel does not need negotiation via DCEP.
|
||||
// A constant is used since the `DataChannelInit` struct takes
|
||||
// references instead of values.
|
||||
handshakeChannelNegotiated = true
|
||||
// handshakeChannelID is the agreed ID for the handshake data
|
||||
// channel. A constant is used since the `DataChannelInit` struct takes
|
||||
// references instead of values. We specify the type here as this
|
||||
// value is only ever copied and passed by reference
|
||||
handshakeChannelID = uint16(0)
|
||||
)
|
||||
|
||||
// timeout values for the peerconnection
|
||||
// https://github.com/pion/webrtc/blob/v3.1.50/settingengine.go#L102-L109
|
||||
const (
|
||||
DefaultDisconnectedTimeout = 20 * time.Second
|
||||
DefaultFailedTimeout = 30 * time.Second
|
||||
DefaultKeepaliveTimeout = 15 * time.Second
|
||||
)
|
||||
|
||||
type WebRTCTransport struct {
|
||||
webrtcConfig webrtc.Configuration
|
||||
rcmgr network.ResourceManager
|
||||
gater connmgr.ConnectionGater
|
||||
privKey ic.PrivKey
|
||||
noiseTpt *noise.Transport
|
||||
localPeerId peer.ID
|
||||
|
||||
// timeouts
|
||||
peerConnectionTimeouts iceTimeouts
|
||||
|
||||
// in-flight connections
|
||||
maxInFlightConnections uint32
|
||||
}
|
||||
|
||||
var _ tpt.Transport = &WebRTCTransport{}
|
||||
|
||||
type Option func(*WebRTCTransport) error
|
||||
|
||||
type iceTimeouts struct {
|
||||
Disconnect time.Duration
|
||||
Failed time.Duration
|
||||
Keepalive time.Duration
|
||||
}
|
||||
|
||||
func New(privKey ic.PrivKey, psk pnet.PSK, gater connmgr.ConnectionGater, rcmgr network.ResourceManager, opts ...Option) (*WebRTCTransport, error) {
|
||||
if psk != nil {
|
||||
log.Error("WebRTC doesn't support private networks yet.")
|
||||
return nil, fmt.Errorf("WebRTC doesn't support private networks yet")
|
||||
}
|
||||
localPeerID, err := peer.IDFromPrivateKey(privKey)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("get local peer ID: %w", err)
|
||||
}
|
||||
// We use elliptic P-256 since it is widely supported by browsers.
|
||||
//
|
||||
// Implementation note: Testing with the browser,
|
||||
// it seems like Chromium only supports ECDSA P-256 or RSA key signatures in the webrtc TLS certificate.
|
||||
// We tried using P-228 and P-384 which caused the DTLS handshake to fail with Illegal Parameter
|
||||
//
|
||||
// Please refer to this is a list of suggested algorithms for the WebCrypto API.
|
||||
// The algorithm for generating a certificate for an RTCPeerConnection
|
||||
// must adhere to the WebCrpyto API. From my observation,
|
||||
// RSA and ECDSA P-256 is supported on almost all browsers.
|
||||
// Ed25519 is not present on the list.
|
||||
pk, err := ecdsa.GenerateKey(elliptic.P256(), rand.Reader)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("generate key for cert: %w", err)
|
||||
}
|
||||
cert, err := webrtc.GenerateCertificate(pk)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("generate certificate: %w", err)
|
||||
}
|
||||
config := webrtc.Configuration{
|
||||
Certificates: []webrtc.Certificate{*cert},
|
||||
}
|
||||
noiseTpt, err := noise.New(noise.ID, privKey, nil)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("unable to create noise transport: %w", err)
|
||||
}
|
||||
transport := &WebRTCTransport{
|
||||
rcmgr: rcmgr,
|
||||
gater: gater,
|
||||
webrtcConfig: config,
|
||||
privKey: privKey,
|
||||
noiseTpt: noiseTpt,
|
||||
localPeerId: localPeerID,
|
||||
|
||||
peerConnectionTimeouts: iceTimeouts{
|
||||
Disconnect: DefaultDisconnectedTimeout,
|
||||
Failed: DefaultFailedTimeout,
|
||||
Keepalive: DefaultKeepaliveTimeout,
|
||||
},
|
||||
|
||||
maxInFlightConnections: DefaultMaxInFlightConnections,
|
||||
}
|
||||
for _, opt := range opts {
|
||||
if err := opt(transport); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
return transport, nil
|
||||
}
|
||||
|
||||
func (t *WebRTCTransport) Protocols() []int {
|
||||
return []int{ma.P_WEBRTC_DIRECT}
|
||||
}
|
||||
|
||||
func (t *WebRTCTransport) Proxy() bool {
|
||||
return false
|
||||
}
|
||||
|
||||
func (t *WebRTCTransport) CanDial(addr ma.Multiaddr) bool {
|
||||
return dialMatcher.Matches(addr)
|
||||
}
|
||||
|
||||
func (t *WebRTCTransport) Listen(addr ma.Multiaddr) (tpt.Listener, error) {
|
||||
addr, wrtcComponent := ma.SplitLast(addr)
|
||||
isWebrtc := wrtcComponent.Equal(webrtcComponent)
|
||||
if !isWebrtc {
|
||||
return nil, fmt.Errorf("must listen on webrtc multiaddr")
|
||||
}
|
||||
nw, host, err := manet.DialArgs(addr)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("listener could not fetch dialargs: %w", err)
|
||||
}
|
||||
udpAddr, err := net.ResolveUDPAddr(nw, host)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("listener could not resolve udp address: %w", err)
|
||||
}
|
||||
|
||||
socket, err := net.ListenUDP(nw, udpAddr)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("listen on udp: %w", err)
|
||||
}
|
||||
|
||||
listener, err := t.listenSocket(socket)
|
||||
if err != nil {
|
||||
socket.Close()
|
||||
return nil, err
|
||||
}
|
||||
return listener, nil
|
||||
}
|
||||
|
||||
func (t *WebRTCTransport) listenSocket(socket *net.UDPConn) (tpt.Listener, error) {
|
||||
listenerMultiaddr, err := manet.FromNetAddr(socket.LocalAddr())
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
listenerFingerprint, err := t.getCertificateFingerprint()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
encodedLocalFingerprint, err := encodeDTLSFingerprint(listenerFingerprint)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
certComp, err := ma.NewComponent(ma.ProtocolWithCode(ma.P_CERTHASH).Name, encodedLocalFingerprint)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
listenerMultiaddr = listenerMultiaddr.Encapsulate(webrtcComponent).Encapsulate(certComp)
|
||||
|
||||
return newListener(
|
||||
t,
|
||||
listenerMultiaddr,
|
||||
socket,
|
||||
t.webrtcConfig,
|
||||
)
|
||||
}
|
||||
|
||||
func (t *WebRTCTransport) Dial(ctx context.Context, remoteMultiaddr ma.Multiaddr, p peer.ID) (tpt.CapableConn, error) {
|
||||
scope, err := t.rcmgr.OpenConnection(network.DirOutbound, false, remoteMultiaddr)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if err := scope.SetPeer(p); err != nil {
|
||||
scope.Done()
|
||||
return nil, err
|
||||
}
|
||||
conn, err := t.dial(ctx, scope, remoteMultiaddr, p)
|
||||
if err != nil {
|
||||
scope.Done()
|
||||
return nil, err
|
||||
}
|
||||
return conn, nil
|
||||
}
|
||||
|
||||
func (t *WebRTCTransport) dial(ctx context.Context, scope network.ConnManagementScope, remoteMultiaddr ma.Multiaddr, p peer.ID) (tConn tpt.CapableConn, err error) {
|
||||
var pc *webrtc.PeerConnection
|
||||
defer func() {
|
||||
if err != nil {
|
||||
if pc != nil {
|
||||
_ = pc.Close()
|
||||
}
|
||||
if tConn != nil {
|
||||
_ = tConn.Close()
|
||||
}
|
||||
}
|
||||
}()
|
||||
|
||||
remoteMultihash, err := decodeRemoteFingerprint(remoteMultiaddr)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("decode fingerprint: %w", err)
|
||||
}
|
||||
remoteHashFunction, ok := getSupportedSDPHash(remoteMultihash.Code)
|
||||
if !ok {
|
||||
return nil, fmt.Errorf("unsupported hash function: %w", nil)
|
||||
}
|
||||
|
||||
rnw, rhost, err := manet.DialArgs(remoteMultiaddr)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("generate dial args: %w", err)
|
||||
}
|
||||
|
||||
raddr, err := net.ResolveUDPAddr(rnw, rhost)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("resolve udp address: %w", err)
|
||||
}
|
||||
|
||||
// Instead of encoding the local fingerprint we
|
||||
// generate a random UUID as the connection ufrag.
|
||||
// The only requirement here is that the ufrag and password
|
||||
// must be equal, which will allow the server to determine
|
||||
// the password using the STUN message.
|
||||
ufrag := genUfrag()
|
||||
|
||||
settingEngine := webrtc.SettingEngine{}
|
||||
// suppress pion logs
|
||||
loggerFactory := pionlogger.NewDefaultLoggerFactory()
|
||||
loggerFactory.DefaultLogLevel = pionlogger.LogLevelDisabled
|
||||
settingEngine.LoggerFactory = loggerFactory
|
||||
|
||||
settingEngine.SetICECredentials(ufrag, ufrag)
|
||||
settingEngine.DetachDataChannels()
|
||||
// use the first best address candidate
|
||||
settingEngine.SetPrflxAcceptanceMinWait(0)
|
||||
settingEngine.SetICETimeouts(
|
||||
t.peerConnectionTimeouts.Disconnect,
|
||||
t.peerConnectionTimeouts.Failed,
|
||||
t.peerConnectionTimeouts.Keepalive,
|
||||
)
|
||||
// By default, webrtc will not collect candidates on the loopback address.
|
||||
// This is disallowed in the ICE specification. However, implementations
|
||||
// do not strictly follow this, for eg. Chrome gathers TCP loopback candidates.
|
||||
// If you run pion on a system with only the loopback interface UP,
|
||||
// it will not connect to anything. However, if it has any other interface
|
||||
// (even a private one, eg. 192.168.0.0/16), it will gather candidates on it and
|
||||
// will be able to connect to other pion instances on the same interface.
|
||||
settingEngine.SetIncludeLoopbackCandidate(true)
|
||||
|
||||
api := webrtc.NewAPI(webrtc.WithSettingEngine(settingEngine))
|
||||
|
||||
pc, err = api.NewPeerConnection(t.webrtcConfig)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("instantiate peerconnection: %w", err)
|
||||
}
|
||||
|
||||
errC := addOnConnectionStateChangeCallback(pc)
|
||||
// We need to set negotiated = true for this channel on both
|
||||
// the client and server to avoid DCEP errors.
|
||||
negotiated, id := handshakeChannelNegotiated, handshakeChannelID
|
||||
rawHandshakeChannel, err := pc.CreateDataChannel("", &webrtc.DataChannelInit{
|
||||
Negotiated: &negotiated,
|
||||
ID: &id,
|
||||
})
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("create datachannel: %w", err)
|
||||
}
|
||||
|
||||
// do offer-answer exchange
|
||||
offer, err := pc.CreateOffer(nil)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("create offer: %w", err)
|
||||
}
|
||||
|
||||
err = pc.SetLocalDescription(offer)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("set local description: %w", err)
|
||||
}
|
||||
|
||||
answerSDPString, err := createServerSDP(raddr, ufrag, *remoteMultihash)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("render server SDP: %w", err)
|
||||
}
|
||||
|
||||
answer := webrtc.SessionDescription{SDP: answerSDPString, Type: webrtc.SDPTypeAnswer}
|
||||
err = pc.SetRemoteDescription(answer)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("set remote description: %w", err)
|
||||
}
|
||||
|
||||
// await peerconnection opening
|
||||
select {
|
||||
case err := <-errC:
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
case <-ctx.Done():
|
||||
return nil, errors.New("peerconnection opening timed out")
|
||||
}
|
||||
|
||||
detached, err := getDetachedChannel(ctx, rawHandshakeChannel)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
// set the local address from the candidate pair
|
||||
cp, err := rawHandshakeChannel.Transport().Transport().ICETransport().GetSelectedCandidatePair()
|
||||
if cp == nil {
|
||||
return nil, errors.New("ice connection did not have selected candidate pair: nil result")
|
||||
}
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("ice connection did not have selected candidate pair: error: %w", err)
|
||||
}
|
||||
|
||||
channel := newStream(rawHandshakeChannel, detached, func() {})
|
||||
// the local address of the selected candidate pair should be the
|
||||
// local address for the connection, since different datachannels
|
||||
// are multiplexed over the same SCTP connection
|
||||
localAddr, err := manet.FromNetAddr(&net.UDPAddr{IP: net.ParseIP(cp.Local.Address), Port: int(cp.Local.Port)})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
remoteMultiaddrWithoutCerthash, _ := ma.SplitFunc(remoteMultiaddr, func(c ma.Component) bool { return c.Protocol().Code == ma.P_CERTHASH })
|
||||
|
||||
// we can only know the remote public key after the noise handshake,
|
||||
// but need to set up the callbacks on the peerconnection
|
||||
conn, err := newConnection(
|
||||
network.DirOutbound,
|
||||
pc,
|
||||
t,
|
||||
scope,
|
||||
t.localPeerId,
|
||||
localAddr,
|
||||
p,
|
||||
nil,
|
||||
remoteMultiaddrWithoutCerthash,
|
||||
)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
remotePubKey, err := t.noiseHandshake(ctx, pc, channel, p, remoteHashFunction, false)
|
||||
if err != nil {
|
||||
return conn, err
|
||||
}
|
||||
if t.gater != nil && !t.gater.InterceptSecured(network.DirOutbound, p, conn) {
|
||||
conn.Close()
|
||||
return nil, fmt.Errorf("secured connection gated")
|
||||
}
|
||||
conn.setRemotePublicKey(remotePubKey)
|
||||
return conn, nil
|
||||
}
|
||||
|
||||
func genUfrag() string {
|
||||
const (
|
||||
uFragAlphabet = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890"
|
||||
uFragPrefix = "libp2p+webrtc+v1/"
|
||||
uFragIdLength = 32
|
||||
uFragIdOffset = len(uFragPrefix)
|
||||
uFragLength = uFragIdOffset + uFragIdLength
|
||||
)
|
||||
|
||||
seed := [8]byte{}
|
||||
rand.Read(seed[:])
|
||||
r := mrand.New(mrand.NewSource(binary.BigEndian.Uint64(seed[:])))
|
||||
b := make([]byte, uFragLength)
|
||||
for i := uFragIdOffset; i < uFragLength; i++ {
|
||||
b[i] = uFragAlphabet[r.Intn(len(uFragAlphabet))]
|
||||
}
|
||||
return string(b)
|
||||
}
|
||||
|
||||
func (t *WebRTCTransport) getCertificateFingerprint() (webrtc.DTLSFingerprint, error) {
|
||||
fps, err := t.webrtcConfig.Certificates[0].GetFingerprints()
|
||||
if err != nil {
|
||||
return webrtc.DTLSFingerprint{}, err
|
||||
}
|
||||
return fps[0], nil
|
||||
}
|
||||
|
||||
func (t *WebRTCTransport) generateNoisePrologue(pc *webrtc.PeerConnection, hash crypto.Hash, inbound bool) ([]byte, error) {
|
||||
raw := pc.SCTP().Transport().GetRemoteCertificate()
|
||||
cert, err := x509.ParseCertificate(raw)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// NOTE: should we want we can fork the cert code as well to avoid
|
||||
// all the extra allocations due to unneeded string interspersing (hex)
|
||||
localFp, err := t.getCertificateFingerprint()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
remoteFpBytes, err := parseFingerprint(cert, hash)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
localFpBytes, err := decodeInterspersedHexFromASCIIString(localFp.Value)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
localEncoded, err := multihash.Encode(localFpBytes, multihash.SHA2_256)
|
||||
if err != nil {
|
||||
log.Debugf("could not encode multihash for local fingerprint")
|
||||
return nil, err
|
||||
}
|
||||
remoteEncoded, err := multihash.Encode(remoteFpBytes, multihash.SHA2_256)
|
||||
if err != nil {
|
||||
log.Debugf("could not encode multihash for remote fingerprint")
|
||||
return nil, err
|
||||
}
|
||||
|
||||
result := []byte("libp2p-webrtc-noise:")
|
||||
if inbound {
|
||||
result = append(result, remoteEncoded...)
|
||||
result = append(result, localEncoded...)
|
||||
} else {
|
||||
result = append(result, localEncoded...)
|
||||
result = append(result, remoteEncoded...)
|
||||
}
|
||||
return result, nil
|
||||
}
|
||||
|
||||
func (t *WebRTCTransport) noiseHandshake(ctx context.Context, pc *webrtc.PeerConnection, datachannel *stream, peer peer.ID, hash crypto.Hash, inbound bool) (ic.PubKey, error) {
|
||||
prologue, err := t.generateNoisePrologue(pc, hash, inbound)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("generate prologue: %w", err)
|
||||
}
|
||||
opts := make([]noise.SessionOption, 0, 2)
|
||||
opts = append(opts, noise.Prologue(prologue))
|
||||
if peer == "" {
|
||||
opts = append(opts, noise.DisablePeerIDCheck())
|
||||
}
|
||||
sessionTransport, err := t.noiseTpt.WithSessionOptions(opts...)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to instantiate Noise transport: %w", err)
|
||||
}
|
||||
var secureConn sec.SecureConn
|
||||
if inbound {
|
||||
secureConn, err = sessionTransport.SecureOutbound(ctx, fakeStreamConn{datachannel}, peer)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to secure inbound connection: %w", err)
|
||||
}
|
||||
} else {
|
||||
secureConn, err = sessionTransport.SecureInbound(ctx, fakeStreamConn{datachannel}, peer)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to secure outbound connection: %w", err)
|
||||
}
|
||||
}
|
||||
return secureConn.RemotePublicKey(), nil
|
||||
}
|
||||
|
||||
type fakeStreamConn struct{ *stream }
|
||||
|
||||
func (fakeStreamConn) LocalAddr() net.Addr { return nil }
|
||||
func (fakeStreamConn) RemoteAddr() net.Addr { return nil }
|
707
p2p/transport/webrtc/transport_test.go
Normal file
707
p2p/transport/webrtc/transport_test.go
Normal file
@@ -0,0 +1,707 @@
|
||||
package libp2pwebrtc
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/hex"
|
||||
"fmt"
|
||||
"io"
|
||||
"net"
|
||||
"os"
|
||||
"sync"
|
||||
"sync/atomic"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
manet "github.com/multiformats/go-multiaddr/net"
|
||||
|
||||
quicproxy "github.com/quic-go/quic-go/integrationtests/tools/proxy"
|
||||
|
||||
"golang.org/x/crypto/sha3"
|
||||
|
||||
"github.com/libp2p/go-libp2p/core/crypto"
|
||||
"github.com/libp2p/go-libp2p/core/network"
|
||||
"github.com/libp2p/go-libp2p/core/peer"
|
||||
|
||||
ma "github.com/multiformats/go-multiaddr"
|
||||
"github.com/multiformats/go-multibase"
|
||||
"github.com/multiformats/go-multihash"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
func getTransport(t *testing.T, opts ...Option) (*WebRTCTransport, peer.ID) {
|
||||
t.Helper()
|
||||
privKey, _, err := crypto.GenerateKeyPair(crypto.Ed25519, -1)
|
||||
require.NoError(t, err)
|
||||
rcmgr := &network.NullResourceManager{}
|
||||
transport, err := New(privKey, nil, nil, rcmgr, opts...)
|
||||
require.NoError(t, err)
|
||||
peerID, err := peer.IDFromPrivateKey(privKey)
|
||||
require.NoError(t, err)
|
||||
t.Cleanup(func() { rcmgr.Close() })
|
||||
return transport, peerID
|
||||
}
|
||||
|
||||
func TestTransportWebRTC_CanDial(t *testing.T) {
|
||||
tr, _ := getTransport(t)
|
||||
invalid := []string{
|
||||
"/ip4/1.2.3.4/udp/1234/webrtc-direct",
|
||||
"/dns/test.test/udp/1234/webrtc-direct",
|
||||
}
|
||||
|
||||
valid := []string{
|
||||
"/ip4/1.2.3.4/udp/1234/webrtc-direct/certhash/uEiAsGPzpiPGQzSlVHRXrUCT5EkTV7YFrV4VZ3hpEKTd_zg",
|
||||
"/ip6/0:0:0:0:0:0:0:1/udp/1234/webrtc-direct/certhash/uEiAsGPzpiPGQzSlVHRXrUCT5EkTV7YFrV4VZ3hpEKTd_zg",
|
||||
"/ip6/::1/udp/1234/webrtc-direct/certhash/uEiAsGPzpiPGQzSlVHRXrUCT5EkTV7YFrV4VZ3hpEKTd_zg",
|
||||
"/dns/test.test/udp/1234/webrtc-direct/certhash/uEiAsGPzpiPGQzSlVHRXrUCT5EkTV7YFrV4VZ3hpEKTd_zg",
|
||||
}
|
||||
|
||||
for _, addr := range invalid {
|
||||
a := ma.StringCast(addr)
|
||||
require.Equal(t, false, tr.CanDial(a))
|
||||
}
|
||||
|
||||
for _, addr := range valid {
|
||||
a := ma.StringCast(addr)
|
||||
require.Equal(t, true, tr.CanDial(a), addr)
|
||||
}
|
||||
}
|
||||
|
||||
func TestTransportWebRTC_ListenFailsOnNonWebRTCMultiaddr(t *testing.T) {
|
||||
tr, _ := getTransport(t)
|
||||
testAddrs := []string{
|
||||
"/ip4/0.0.0.0/udp/0",
|
||||
"/ip4/0.0.0.0/tcp/0/wss",
|
||||
}
|
||||
for _, addr := range testAddrs {
|
||||
listenMultiaddr, err := ma.NewMultiaddr(addr)
|
||||
require.NoError(t, err)
|
||||
listener, err := tr.Listen(listenMultiaddr)
|
||||
require.Error(t, err)
|
||||
require.Nil(t, listener)
|
||||
}
|
||||
}
|
||||
|
||||
// using assert inside goroutines, refer: https://github.com/stretchr/testify/issues/772#issuecomment-945166599
|
||||
func TestTransportWebRTC_DialFailsOnUnsupportedHashFunction(t *testing.T) {
|
||||
tr, _ := getTransport(t)
|
||||
hash := sha3.New512()
|
||||
certhash := func() string {
|
||||
_, err := hash.Write([]byte("test-data"))
|
||||
require.NoError(t, err)
|
||||
mh, err := multihash.Encode(hash.Sum([]byte{}), multihash.SHA3_512)
|
||||
require.NoError(t, err)
|
||||
certhash, err := multibase.Encode(multibase.Base58BTC, mh)
|
||||
require.NoError(t, err)
|
||||
return certhash
|
||||
}()
|
||||
testaddr, err := ma.NewMultiaddr("/ip4/1.2.3.4/udp/1234/webrtc-direct/certhash/" + certhash)
|
||||
require.NoError(t, err)
|
||||
_, err = tr.Dial(context.Background(), testaddr, "")
|
||||
require.ErrorContains(t, err, "unsupported hash function")
|
||||
}
|
||||
|
||||
func TestTransportWebRTC_CanListenSingle(t *testing.T) {
|
||||
tr, listeningPeer := getTransport(t)
|
||||
tr1, connectingPeer := getTransport(t)
|
||||
listenMultiaddr := ma.StringCast("/ip4/127.0.0.1/udp/0/webrtc-direct")
|
||||
|
||||
listener, err := tr.Listen(listenMultiaddr)
|
||||
require.NoError(t, err)
|
||||
|
||||
done := make(chan struct{})
|
||||
go func() {
|
||||
_, err := tr1.Dial(context.Background(), listener.Multiaddr(), listeningPeer)
|
||||
assert.NoError(t, err)
|
||||
close(done)
|
||||
}()
|
||||
|
||||
conn, err := listener.Accept()
|
||||
require.NoError(t, err)
|
||||
require.NotNil(t, conn)
|
||||
|
||||
require.Equal(t, connectingPeer, conn.RemotePeer())
|
||||
select {
|
||||
case <-done:
|
||||
case <-time.After(10 * time.Second):
|
||||
t.FailNow()
|
||||
}
|
||||
}
|
||||
|
||||
// WithListenerMaxInFlightConnections sets the maximum number of connections that are in-flight, i.e
|
||||
// they are being negotiated, or are waiting to be accepted.
|
||||
func WithListenerMaxInFlightConnections(m uint32) Option {
|
||||
return func(t *WebRTCTransport) error {
|
||||
if m == 0 {
|
||||
t.maxInFlightConnections = DefaultMaxInFlightConnections
|
||||
} else {
|
||||
t.maxInFlightConnections = m
|
||||
}
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
func TestTransportWebRTC_CanListenMultiple(t *testing.T) {
|
||||
count := 3
|
||||
tr, listeningPeer := getTransport(t, WithListenerMaxInFlightConnections(uint32(count)))
|
||||
|
||||
listenMultiaddr := ma.StringCast("/ip4/127.0.0.1/udp/0/webrtc-direct")
|
||||
listener, err := tr.Listen(listenMultiaddr)
|
||||
require.NoError(t, err)
|
||||
|
||||
ctx, cancel := context.WithCancel(context.Background())
|
||||
defer cancel()
|
||||
|
||||
var wg sync.WaitGroup
|
||||
go func() {
|
||||
for i := 0; i < count; i++ {
|
||||
conn, err := listener.Accept()
|
||||
assert.NoError(t, err)
|
||||
assert.NotNil(t, conn)
|
||||
}
|
||||
wg.Wait()
|
||||
cancel()
|
||||
}()
|
||||
|
||||
for i := 0; i < count; i++ {
|
||||
wg.Add(1)
|
||||
go func() {
|
||||
defer wg.Done()
|
||||
ctr, _ := getTransport(t)
|
||||
conn, err := ctr.Dial(ctx, listener.Multiaddr(), listeningPeer)
|
||||
select {
|
||||
case <-ctx.Done():
|
||||
default:
|
||||
assert.NoError(t, err)
|
||||
assert.NotNil(t, conn)
|
||||
}
|
||||
}()
|
||||
}
|
||||
|
||||
select {
|
||||
case <-ctx.Done():
|
||||
case <-time.After(30 * time.Second):
|
||||
t.Fatalf("timed out")
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
func TestTransportWebRTC_CanCreateSuccessiveConnections(t *testing.T) {
|
||||
tr, listeningPeer := getTransport(t)
|
||||
listenMultiaddr := ma.StringCast("/ip4/127.0.0.1/udp/0/webrtc-direct")
|
||||
listener, err := tr.Listen(listenMultiaddr)
|
||||
require.NoError(t, err)
|
||||
count := 2
|
||||
|
||||
go func() {
|
||||
for i := 0; i < count; i++ {
|
||||
ctr, _ := getTransport(t)
|
||||
conn, err := ctr.Dial(context.Background(), listener.Multiaddr(), listeningPeer)
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, conn.RemotePeer(), listeningPeer)
|
||||
}
|
||||
}()
|
||||
|
||||
for i := 0; i < count; i++ {
|
||||
_, err := listener.Accept()
|
||||
require.NoError(t, err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestTransportWebRTC_ListenerCanCreateStreams(t *testing.T) {
|
||||
tr, listeningPeer := getTransport(t)
|
||||
tr1, connectingPeer := getTransport(t)
|
||||
listenMultiaddr := ma.StringCast("/ip4/127.0.0.1/udp/0/webrtc-direct")
|
||||
listener, err := tr.Listen(listenMultiaddr)
|
||||
require.NoError(t, err)
|
||||
|
||||
streamChan := make(chan network.MuxedStream)
|
||||
go func() {
|
||||
conn, err := tr1.Dial(context.Background(), listener.Multiaddr(), listeningPeer)
|
||||
require.NoError(t, err)
|
||||
t.Logf("connection opened by dialer")
|
||||
stream, err := conn.AcceptStream()
|
||||
require.NoError(t, err)
|
||||
t.Logf("dialer accepted stream")
|
||||
streamChan <- stream
|
||||
}()
|
||||
|
||||
conn, err := listener.Accept()
|
||||
require.NoError(t, err)
|
||||
t.Logf("listener accepted connection")
|
||||
require.Equal(t, connectingPeer, conn.RemotePeer())
|
||||
|
||||
stream, err := conn.OpenStream(context.Background())
|
||||
require.NoError(t, err)
|
||||
t.Logf("listener opened stream")
|
||||
_, err = stream.Write([]byte("test"))
|
||||
require.NoError(t, err)
|
||||
|
||||
var str network.MuxedStream
|
||||
select {
|
||||
case str = <-streamChan:
|
||||
case <-time.After(3 * time.Second):
|
||||
t.Fatal("stream opening timed out")
|
||||
}
|
||||
buf := make([]byte, 100)
|
||||
stream.SetReadDeadline(time.Now().Add(3 * time.Second))
|
||||
n, err := str.Read(buf)
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, "test", string(buf[:n]))
|
||||
|
||||
}
|
||||
|
||||
func TestTransportWebRTC_DialerCanCreateStreams(t *testing.T) {
|
||||
tr, listeningPeer := getTransport(t)
|
||||
listenMultiaddr := ma.StringCast("/ip4/127.0.0.1/udp/0/webrtc-direct")
|
||||
listener, err := tr.Listen(listenMultiaddr)
|
||||
require.NoError(t, err)
|
||||
|
||||
tr1, connectingPeer := getTransport(t)
|
||||
done := make(chan struct{})
|
||||
|
||||
go func() {
|
||||
lconn, err := listener.Accept()
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, connectingPeer, lconn.RemotePeer())
|
||||
|
||||
stream, err := lconn.AcceptStream()
|
||||
require.NoError(t, err)
|
||||
buf := make([]byte, 100)
|
||||
n, err := stream.Read(buf)
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, "test", string(buf[:n]))
|
||||
|
||||
done <- struct{}{}
|
||||
}()
|
||||
|
||||
go func() {
|
||||
conn, err := tr1.Dial(context.Background(), listener.Multiaddr(), listeningPeer)
|
||||
require.NoError(t, err)
|
||||
t.Logf("dialer opened connection")
|
||||
stream, err := conn.OpenStream(context.Background())
|
||||
require.NoError(t, err)
|
||||
t.Logf("dialer opened stream")
|
||||
_, err = stream.Write([]byte("test"))
|
||||
require.NoError(t, err)
|
||||
}()
|
||||
select {
|
||||
case <-done:
|
||||
case <-time.After(10 * time.Second):
|
||||
t.Fatal("timed out")
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
func TestTransportWebRTC_DialerCanCreateStreamsMultiple(t *testing.T) {
|
||||
count := 5
|
||||
tr, listeningPeer := getTransport(t)
|
||||
listenMultiaddr := ma.StringCast("/ip4/127.0.0.1/udp/0/webrtc-direct")
|
||||
listener, err := tr.Listen(listenMultiaddr)
|
||||
require.NoError(t, err)
|
||||
|
||||
tr1, connectingPeer := getTransport(t)
|
||||
done := make(chan struct{})
|
||||
|
||||
go func() {
|
||||
lconn, err := listener.Accept()
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, connectingPeer, lconn.RemotePeer())
|
||||
var wg sync.WaitGroup
|
||||
|
||||
for i := 0; i < count; i++ {
|
||||
stream, err := lconn.AcceptStream()
|
||||
require.NoError(t, err)
|
||||
wg.Add(1)
|
||||
go func() {
|
||||
defer wg.Done()
|
||||
buf := make([]byte, 100)
|
||||
n, err := stream.Read(buf)
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, "test", string(buf[:n]))
|
||||
_, err = stream.Write([]byte("test"))
|
||||
require.NoError(t, err)
|
||||
}()
|
||||
}
|
||||
|
||||
wg.Wait()
|
||||
done <- struct{}{}
|
||||
}()
|
||||
|
||||
conn, err := tr1.Dial(context.Background(), listener.Multiaddr(), listeningPeer)
|
||||
require.NoError(t, err)
|
||||
t.Logf("dialer opened connection")
|
||||
|
||||
for i := 0; i < count; i++ {
|
||||
idx := i
|
||||
go func() {
|
||||
stream, err := conn.OpenStream(context.Background())
|
||||
require.NoError(t, err)
|
||||
t.Logf("dialer opened stream: %d", idx)
|
||||
buf := make([]byte, 100)
|
||||
_, err = stream.Write([]byte("test"))
|
||||
require.NoError(t, err)
|
||||
n, err := stream.Read(buf)
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, "test", string(buf[:n]))
|
||||
}()
|
||||
if i%10 == 0 && i > 0 {
|
||||
time.Sleep(100 * time.Millisecond)
|
||||
}
|
||||
}
|
||||
select {
|
||||
case <-done:
|
||||
case <-time.After(100 * time.Second):
|
||||
t.Fatal("timed out")
|
||||
}
|
||||
}
|
||||
|
||||
func TestTransportWebRTC_Deadline(t *testing.T) {
|
||||
tr, listeningPeer := getTransport(t)
|
||||
listenMultiaddr := ma.StringCast("/ip4/127.0.0.1/udp/0/webrtc-direct")
|
||||
listener, err := tr.Listen(listenMultiaddr)
|
||||
require.NoError(t, err)
|
||||
tr1, connectingPeer := getTransport(t)
|
||||
|
||||
t.Run("SetReadDeadline", func(t *testing.T) {
|
||||
go func() {
|
||||
lconn, err := listener.Accept()
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, connectingPeer, lconn.RemotePeer())
|
||||
_, err = lconn.AcceptStream()
|
||||
require.NoError(t, err)
|
||||
}()
|
||||
|
||||
conn, err := tr1.Dial(context.Background(), listener.Multiaddr(), listeningPeer)
|
||||
require.NoError(t, err)
|
||||
stream, err := conn.OpenStream(context.Background())
|
||||
require.NoError(t, err)
|
||||
|
||||
// deadline set to the past
|
||||
stream.SetReadDeadline(time.Now().Add(-200 * time.Millisecond))
|
||||
_, err = stream.Read([]byte{0, 0})
|
||||
require.ErrorIs(t, err, os.ErrDeadlineExceeded)
|
||||
|
||||
// future deadline exceeded
|
||||
stream.SetReadDeadline(time.Now().Add(100 * time.Millisecond))
|
||||
_, err = stream.Read([]byte{0, 0})
|
||||
require.ErrorIs(t, err, os.ErrDeadlineExceeded)
|
||||
})
|
||||
|
||||
t.Run("SetWriteDeadline", func(t *testing.T) {
|
||||
go func() {
|
||||
lconn, err := listener.Accept()
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, connectingPeer, lconn.RemotePeer())
|
||||
_, err = lconn.AcceptStream()
|
||||
require.NoError(t, err)
|
||||
}()
|
||||
|
||||
conn, err := tr1.Dial(context.Background(), listener.Multiaddr(), listeningPeer)
|
||||
require.NoError(t, err)
|
||||
stream, err := conn.OpenStream(context.Background())
|
||||
require.NoError(t, err)
|
||||
|
||||
stream.SetWriteDeadline(time.Now().Add(200 * time.Millisecond))
|
||||
largeBuffer := make([]byte, 2*1024*1024)
|
||||
_, err = stream.Write(largeBuffer)
|
||||
require.ErrorIs(t, err, os.ErrDeadlineExceeded)
|
||||
})
|
||||
}
|
||||
|
||||
func TestTransportWebRTC_StreamWriteBufferContention(t *testing.T) {
|
||||
tr, listeningPeer := getTransport(t)
|
||||
listenMultiaddr := ma.StringCast("/ip4/127.0.0.1/udp/0/webrtc-direct")
|
||||
listener, err := tr.Listen(listenMultiaddr)
|
||||
require.NoError(t, err)
|
||||
|
||||
tr1, connectingPeer := getTransport(t)
|
||||
|
||||
for i := 0; i < 2; i++ {
|
||||
go func() {
|
||||
lconn, err := listener.Accept()
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, connectingPeer, lconn.RemotePeer())
|
||||
_, err = lconn.AcceptStream()
|
||||
require.NoError(t, err)
|
||||
}()
|
||||
|
||||
}
|
||||
|
||||
conn, err := tr1.Dial(context.Background(), listener.Multiaddr(), listeningPeer)
|
||||
require.NoError(t, err)
|
||||
|
||||
errC := make(chan error)
|
||||
// writers
|
||||
for i := 0; i < 2; i++ {
|
||||
go func() {
|
||||
stream, err := conn.OpenStream(context.Background())
|
||||
require.NoError(t, err)
|
||||
|
||||
stream.SetWriteDeadline(time.Now().Add(200 * time.Millisecond))
|
||||
largeBuffer := make([]byte, 2*1024*1024)
|
||||
_, err = stream.Write(largeBuffer)
|
||||
errC <- err
|
||||
}()
|
||||
}
|
||||
|
||||
require.ErrorIs(t, <-errC, os.ErrDeadlineExceeded)
|
||||
require.ErrorIs(t, <-errC, os.ErrDeadlineExceeded)
|
||||
}
|
||||
|
||||
func TestTransportWebRTC_RemoteReadsAfterClose(t *testing.T) {
|
||||
tr, listeningPeer := getTransport(t)
|
||||
listenMultiaddr := ma.StringCast("/ip4/127.0.0.1/udp/0/webrtc-direct")
|
||||
listener, err := tr.Listen(listenMultiaddr)
|
||||
require.NoError(t, err)
|
||||
|
||||
tr1, _ := getTransport(t)
|
||||
|
||||
done := make(chan error)
|
||||
go func() {
|
||||
lconn, err := listener.Accept()
|
||||
if err != nil {
|
||||
done <- err
|
||||
return
|
||||
}
|
||||
stream, err := lconn.AcceptStream()
|
||||
if err != nil {
|
||||
done <- err
|
||||
return
|
||||
}
|
||||
_, err = stream.Write([]byte{1, 2, 3, 4})
|
||||
if err != nil {
|
||||
done <- err
|
||||
return
|
||||
}
|
||||
err = stream.Close()
|
||||
if err != nil {
|
||||
done <- err
|
||||
return
|
||||
}
|
||||
close(done)
|
||||
}()
|
||||
|
||||
conn, err := tr1.Dial(context.Background(), listener.Multiaddr(), listeningPeer)
|
||||
require.NoError(t, err)
|
||||
// create a stream
|
||||
stream, err := conn.OpenStream(context.Background())
|
||||
|
||||
require.NoError(t, err)
|
||||
// require write and close to complete
|
||||
require.NoError(t, <-done)
|
||||
|
||||
stream.SetReadDeadline(time.Now().Add(5 * time.Second))
|
||||
|
||||
buf := make([]byte, 10)
|
||||
n, err := stream.Read(buf)
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, n, 4)
|
||||
}
|
||||
|
||||
func TestTransportWebRTC_RemoteReadsAfterClose2(t *testing.T) {
|
||||
tr, listeningPeer := getTransport(t)
|
||||
listenMultiaddr := ma.StringCast("/ip4/127.0.0.1/udp/0/webrtc-direct")
|
||||
listener, err := tr.Listen(listenMultiaddr)
|
||||
require.NoError(t, err)
|
||||
|
||||
tr1, _ := getTransport(t)
|
||||
|
||||
awaitStreamClosure := make(chan struct{})
|
||||
readBytesResult := make(chan int)
|
||||
done := make(chan error)
|
||||
go func() {
|
||||
lconn, err := listener.Accept()
|
||||
if err != nil {
|
||||
done <- err
|
||||
return
|
||||
}
|
||||
stream, err := lconn.AcceptStream()
|
||||
if err != nil {
|
||||
done <- err
|
||||
return
|
||||
}
|
||||
|
||||
<-awaitStreamClosure
|
||||
buf := make([]byte, 16)
|
||||
n, err := stream.Read(buf)
|
||||
if err != nil {
|
||||
done <- err
|
||||
return
|
||||
}
|
||||
readBytesResult <- n
|
||||
close(done)
|
||||
}()
|
||||
|
||||
conn, err := tr1.Dial(context.Background(), listener.Multiaddr(), listeningPeer)
|
||||
require.NoError(t, err)
|
||||
// create a stream
|
||||
stream, err := conn.OpenStream(context.Background())
|
||||
require.NoError(t, err)
|
||||
_, err = stream.Write([]byte{1, 2, 3, 4})
|
||||
require.NoError(t, err)
|
||||
err = stream.Close()
|
||||
require.NoError(t, err)
|
||||
// signal stream closure
|
||||
close(awaitStreamClosure)
|
||||
require.Equal(t, <-readBytesResult, 4)
|
||||
}
|
||||
|
||||
func TestTransportWebRTC_Close(t *testing.T) {
|
||||
tr, listeningPeer := getTransport(t)
|
||||
listenMultiaddr := ma.StringCast("/ip4/127.0.0.1/udp/0/webrtc-direct")
|
||||
listener, err := tr.Listen(listenMultiaddr)
|
||||
require.NoError(t, err)
|
||||
|
||||
tr1, connectingPeer := getTransport(t)
|
||||
|
||||
t.Run("RemoteClosesStream", func(t *testing.T) {
|
||||
var wg sync.WaitGroup
|
||||
wg.Add(1)
|
||||
go func() {
|
||||
defer wg.Done()
|
||||
lconn, err := listener.Accept()
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, connectingPeer, lconn.RemotePeer())
|
||||
stream, err := lconn.AcceptStream()
|
||||
require.NoError(t, err)
|
||||
time.Sleep(100 * time.Millisecond)
|
||||
_ = stream.Close()
|
||||
|
||||
}()
|
||||
|
||||
buf := make([]byte, 2)
|
||||
|
||||
conn, err := tr1.Dial(context.Background(), listener.Multiaddr(), listeningPeer)
|
||||
require.NoError(t, err)
|
||||
stream, err := conn.OpenStream(context.Background())
|
||||
require.NoError(t, err)
|
||||
|
||||
err = stream.SetReadDeadline(time.Now().Add(2 * time.Second))
|
||||
require.NoError(t, err)
|
||||
_, err = stream.Read(buf)
|
||||
require.ErrorIs(t, err, io.EOF)
|
||||
|
||||
wg.Wait()
|
||||
})
|
||||
}
|
||||
|
||||
func TestTransportWebRTC_PeerConnectionDTLSFailed(t *testing.T) {
|
||||
tr, listeningPeer := getTransport(t)
|
||||
listenMultiaddr := ma.StringCast("/ip4/127.0.0.1/udp/0/webrtc-direct")
|
||||
ln, err := tr.Listen(listenMultiaddr)
|
||||
require.NoError(t, err)
|
||||
defer ln.Close()
|
||||
|
||||
encoded, err := hex.DecodeString("ba7816bf8f01cfea414140de5dae2223b00361a396177a9cb410ff61f20015ad")
|
||||
require.NoError(t, err)
|
||||
encodedCerthash, err := multihash.Encode(encoded, multihash.SHA2_256)
|
||||
require.NoError(t, err)
|
||||
badEncodedCerthash, err := multibase.Encode(multibase.Base58BTC, encodedCerthash)
|
||||
require.NoError(t, err)
|
||||
badCerthash, err := ma.NewMultiaddr(fmt.Sprintf("/certhash/%s", badEncodedCerthash))
|
||||
require.NoError(t, err)
|
||||
badMultiaddr, _ := ma.SplitFunc(ln.Multiaddr(), func(c ma.Component) bool { return c.Protocol().Code == ma.P_CERTHASH })
|
||||
badMultiaddr = badMultiaddr.Encapsulate(badCerthash)
|
||||
|
||||
tr1, _ := getTransport(t)
|
||||
conn, err := tr1.Dial(context.Background(), badMultiaddr, listeningPeer)
|
||||
require.Error(t, err)
|
||||
require.ErrorContains(t, err, "failed")
|
||||
require.Nil(t, conn)
|
||||
}
|
||||
|
||||
func TestConnectionTimeoutOnListener(t *testing.T) {
|
||||
tr, listeningPeer := getTransport(t)
|
||||
tr.peerConnectionTimeouts.Disconnect = 100 * time.Millisecond
|
||||
tr.peerConnectionTimeouts.Failed = 150 * time.Millisecond
|
||||
tr.peerConnectionTimeouts.Keepalive = 50 * time.Millisecond
|
||||
|
||||
listenMultiaddr := ma.StringCast("/ip4/127.0.0.1/udp/0/webrtc-direct")
|
||||
ln, err := tr.Listen(listenMultiaddr)
|
||||
require.NoError(t, err)
|
||||
defer ln.Close()
|
||||
|
||||
var drop atomic.Bool
|
||||
proxy, err := quicproxy.NewQuicProxy("127.0.0.1:0", &quicproxy.Opts{
|
||||
RemoteAddr: fmt.Sprintf("127.0.0.1:%d", ln.Addr().(*net.UDPAddr).Port),
|
||||
DropPacket: func(quicproxy.Direction, []byte) bool { return drop.Load() },
|
||||
})
|
||||
require.NoError(t, err)
|
||||
defer proxy.Close()
|
||||
|
||||
tr1, connectingPeer := getTransport(t)
|
||||
go func() {
|
||||
ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
|
||||
defer cancel()
|
||||
addr, err := manet.FromNetAddr(proxy.LocalAddr())
|
||||
require.NoError(t, err)
|
||||
_, webrtcComponent := ma.SplitFunc(ln.Multiaddr(), func(c ma.Component) bool { return c.Protocol().Code == ma.P_WEBRTC_DIRECT })
|
||||
addr = addr.Encapsulate(webrtcComponent)
|
||||
conn, err := tr1.Dial(ctx, addr, listeningPeer)
|
||||
require.NoError(t, err)
|
||||
str, err := conn.OpenStream(ctx)
|
||||
require.NoError(t, err)
|
||||
str.Write([]byte("foobar"))
|
||||
}()
|
||||
|
||||
conn, err := ln.Accept()
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, connectingPeer, conn.RemotePeer())
|
||||
|
||||
str, err := conn.AcceptStream()
|
||||
require.NoError(t, err)
|
||||
_, err = str.Write([]byte("test"))
|
||||
require.NoError(t, err)
|
||||
// start dropping all packets
|
||||
drop.Store(true)
|
||||
start := time.Now()
|
||||
// TODO: return timeout errors here
|
||||
for {
|
||||
if _, err := str.Write([]byte("test")); err != nil {
|
||||
require.True(t, os.IsTimeout(err))
|
||||
break
|
||||
}
|
||||
if time.Since(start) > 5*time.Second {
|
||||
t.Fatal("timeout")
|
||||
}
|
||||
// make sure to not write too often, we don't want to fill the flow control window
|
||||
time.Sleep(5 * time.Millisecond)
|
||||
}
|
||||
// make sure that accepting a stream also returns an error...
|
||||
_, err = conn.AcceptStream()
|
||||
require.True(t, os.IsTimeout(err))
|
||||
// ... as well as opening a new stream
|
||||
_, err = conn.OpenStream(context.Background())
|
||||
require.True(t, os.IsTimeout(err))
|
||||
}
|
||||
|
||||
func TestMaxInFlightRequests(t *testing.T) {
|
||||
const count = 3
|
||||
tr, listeningPeer := getTransport(t,
|
||||
WithListenerMaxInFlightConnections(count),
|
||||
)
|
||||
ln, err := tr.Listen(ma.StringCast("/ip4/127.0.0.1/udp/0/webrtc-direct"))
|
||||
require.NoError(t, err)
|
||||
defer ln.Close()
|
||||
|
||||
var wg sync.WaitGroup
|
||||
var success, fails atomic.Int32
|
||||
for i := 0; i < count+1; i++ {
|
||||
wg.Add(1)
|
||||
go func() {
|
||||
defer wg.Done()
|
||||
dialer, _ := getTransport(t)
|
||||
ctx, cancel := context.WithTimeout(context.Background(), 500*time.Millisecond)
|
||||
defer cancel()
|
||||
if _, err := dialer.Dial(ctx, ln.Multiaddr(), listeningPeer); err == nil {
|
||||
success.Add(1)
|
||||
} else {
|
||||
fails.Add(1)
|
||||
}
|
||||
}()
|
||||
}
|
||||
wg.Wait()
|
||||
require.Equal(t, count, int(success.Load()), "expected exactly 3 dial successes")
|
||||
require.Equal(t, 1, int(fails.Load()), "expected exactly 1 dial failure")
|
||||
}
|
275
p2p/transport/webrtc/udpmux/mux.go
Normal file
275
p2p/transport/webrtc/udpmux/mux.go
Normal file
@@ -0,0 +1,275 @@
|
||||
package udpmux
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"fmt"
|
||||
"io"
|
||||
"net"
|
||||
"sync"
|
||||
|
||||
logging "github.com/ipfs/go-log/v2"
|
||||
pool "github.com/libp2p/go-buffer-pool"
|
||||
"github.com/pion/ice/v2"
|
||||
"github.com/pion/stun"
|
||||
)
|
||||
|
||||
var log = logging.Logger("webrtc-udpmux")
|
||||
|
||||
const ReceiveMTU = 1500
|
||||
|
||||
type Candidate struct {
|
||||
Ufrag string
|
||||
Addr *net.UDPAddr
|
||||
}
|
||||
|
||||
// UDPMux multiplexes multiple ICE connections over a single net.PacketConn,
|
||||
// generally a UDP socket.
|
||||
//
|
||||
// The connections are indexed by (ufrag, IP address family) and by remote
|
||||
// address from which the connection has received valid STUN/RTC packets.
|
||||
//
|
||||
// When a new packet is received on the underlying net.PacketConn, we
|
||||
// first check the address map to see if there is a connection associated with the
|
||||
// remote address:
|
||||
// If found, we pass the packet to that connection.
|
||||
// Otherwise, we check to see if the packet is a STUN packet.
|
||||
// If it is, we read the ufrag from the STUN packet and use it to check if there
|
||||
// is a connection associated with the (ufrag, IP address family) pair.
|
||||
// If found we add the association to the address map.
|
||||
type UDPMux struct {
|
||||
socket net.PacketConn
|
||||
|
||||
queue chan Candidate
|
||||
|
||||
mx sync.Mutex
|
||||
ufragMap map[ufragConnKey]*muxedConnection
|
||||
addrMap map[string]*muxedConnection
|
||||
|
||||
// the context controls the lifecycle of the mux
|
||||
wg sync.WaitGroup
|
||||
ctx context.Context
|
||||
cancel context.CancelFunc
|
||||
}
|
||||
|
||||
var _ ice.UDPMux = &UDPMux{}
|
||||
|
||||
func NewUDPMux(socket net.PacketConn) *UDPMux {
|
||||
ctx, cancel := context.WithCancel(context.Background())
|
||||
mux := &UDPMux{
|
||||
ctx: ctx,
|
||||
cancel: cancel,
|
||||
socket: socket,
|
||||
ufragMap: make(map[ufragConnKey]*muxedConnection),
|
||||
addrMap: make(map[string]*muxedConnection),
|
||||
queue: make(chan Candidate, 32),
|
||||
}
|
||||
|
||||
return mux
|
||||
}
|
||||
|
||||
func (mux *UDPMux) Start() {
|
||||
mux.wg.Add(1)
|
||||
go func() {
|
||||
defer mux.wg.Done()
|
||||
mux.readLoop()
|
||||
}()
|
||||
}
|
||||
|
||||
// GetListenAddresses implements ice.UDPMux
|
||||
func (mux *UDPMux) GetListenAddresses() []net.Addr {
|
||||
return []net.Addr{mux.socket.LocalAddr()}
|
||||
}
|
||||
|
||||
// GetConn implements ice.UDPMux
|
||||
// It creates a net.PacketConn for a given ufrag if an existing one cannot be found.
|
||||
// We differentiate IPv4 and IPv6 addresses, since a remote is can be reachable at multiple different
|
||||
// UDP addresses of the same IP address family (eg. server-reflexive addresses and peer-reflexive addresses).
|
||||
func (mux *UDPMux) GetConn(ufrag string, addr net.Addr) (net.PacketConn, error) {
|
||||
a, ok := addr.(*net.UDPAddr)
|
||||
if !ok {
|
||||
return nil, fmt.Errorf("unexpected address type: %T", addr)
|
||||
}
|
||||
select {
|
||||
case <-mux.ctx.Done():
|
||||
return nil, io.ErrClosedPipe
|
||||
default:
|
||||
isIPv6 := ok && a.IP.To4() == nil
|
||||
_, conn := mux.getOrCreateConn(ufrag, isIPv6, mux, addr)
|
||||
return conn, nil
|
||||
}
|
||||
}
|
||||
|
||||
// Close implements ice.UDPMux
|
||||
func (mux *UDPMux) Close() error {
|
||||
select {
|
||||
case <-mux.ctx.Done():
|
||||
return nil
|
||||
default:
|
||||
}
|
||||
mux.cancel()
|
||||
mux.socket.Close()
|
||||
mux.wg.Wait()
|
||||
return nil
|
||||
}
|
||||
|
||||
// writeTo writes a packet to the underlying net.PacketConn
|
||||
func (mux *UDPMux) writeTo(buf []byte, addr net.Addr) (int, error) {
|
||||
return mux.socket.WriteTo(buf, addr)
|
||||
}
|
||||
|
||||
func (mux *UDPMux) readLoop() {
|
||||
for {
|
||||
select {
|
||||
case <-mux.ctx.Done():
|
||||
return
|
||||
default:
|
||||
}
|
||||
|
||||
buf := pool.Get(ReceiveMTU)
|
||||
|
||||
n, addr, err := mux.socket.ReadFrom(buf)
|
||||
if err != nil {
|
||||
log.Errorf("error reading from socket: %v", err)
|
||||
pool.Put(buf)
|
||||
return
|
||||
}
|
||||
buf = buf[:n]
|
||||
|
||||
if processed := mux.processPacket(buf, addr); !processed {
|
||||
pool.Put(buf)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (mux *UDPMux) processPacket(buf []byte, addr net.Addr) (processed bool) {
|
||||
udpAddr, ok := addr.(*net.UDPAddr)
|
||||
if !ok {
|
||||
log.Errorf("received a non-UDP address: %s", addr)
|
||||
return false
|
||||
}
|
||||
isIPv6 := udpAddr.IP.To4() == nil
|
||||
|
||||
// Connections are indexed by remote address. We first
|
||||
// check if the remote address has a connection associated
|
||||
// with it. If yes, we push the received packet to the connection
|
||||
mux.mx.Lock()
|
||||
conn, ok := mux.addrMap[addr.String()]
|
||||
mux.mx.Unlock()
|
||||
if ok {
|
||||
if err := conn.Push(buf); err != nil {
|
||||
log.Debugf("could not push packet: %v", err)
|
||||
return false
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
if !stun.IsMessage(buf) {
|
||||
log.Debug("incoming message is not a STUN message")
|
||||
return false
|
||||
}
|
||||
|
||||
msg := &stun.Message{Raw: buf}
|
||||
if err := msg.Decode(); err != nil {
|
||||
log.Debugf("failed to decode STUN message: %s", err)
|
||||
return false
|
||||
}
|
||||
if msg.Type != stun.BindingRequest {
|
||||
log.Debugf("incoming message should be a STUN binding request, got %s", msg.Type)
|
||||
return false
|
||||
}
|
||||
|
||||
ufrag, err := ufragFromSTUNMessage(msg)
|
||||
if err != nil {
|
||||
log.Debugf("could not find STUN username: %s", err)
|
||||
return false
|
||||
}
|
||||
|
||||
connCreated, conn := mux.getOrCreateConn(ufrag, isIPv6, mux, udpAddr)
|
||||
if connCreated {
|
||||
select {
|
||||
case mux.queue <- Candidate{Addr: udpAddr, Ufrag: ufrag}:
|
||||
default:
|
||||
log.Debugw("queue full, dropping incoming candidate", "ufrag", ufrag, "addr", udpAddr)
|
||||
conn.Close()
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
if err := conn.Push(buf); err != nil {
|
||||
log.Debugf("could not push packet: %v", err)
|
||||
return false
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
func (mux *UDPMux) Accept(ctx context.Context) (Candidate, error) {
|
||||
select {
|
||||
case c := <-mux.queue:
|
||||
return c, nil
|
||||
case <-ctx.Done():
|
||||
return Candidate{}, ctx.Err()
|
||||
case <-mux.ctx.Done():
|
||||
return Candidate{}, mux.ctx.Err()
|
||||
}
|
||||
}
|
||||
|
||||
type ufragConnKey struct {
|
||||
ufrag string
|
||||
isIPv6 bool
|
||||
}
|
||||
|
||||
// ufragFromSTUNMessage returns the local or ufrag
|
||||
// from the STUN username attribute. Local ufrag is the ufrag of the
|
||||
// peer which initiated the connectivity check, e.g in a connectivity
|
||||
// check from A to B, the username attribute will be B_ufrag:A_ufrag
|
||||
// with the local ufrag value being A_ufrag. In case of ice-lite, the
|
||||
// localUfrag value will always be the remote peer's ufrag since ICE-lite
|
||||
// implementations do not generate connectivity checks. In our specific
|
||||
// case, since the local and remote ufrag is equal, we can return
|
||||
// either value.
|
||||
func ufragFromSTUNMessage(msg *stun.Message) (string, error) {
|
||||
attr, err := msg.Get(stun.AttrUsername)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
index := bytes.Index(attr, []byte{':'})
|
||||
if index == -1 {
|
||||
return "", fmt.Errorf("invalid STUN username attribute")
|
||||
}
|
||||
return string(attr[index+1:]), nil
|
||||
}
|
||||
|
||||
func (mux *UDPMux) RemoveConnByUfrag(ufrag string) {
|
||||
if ufrag == "" {
|
||||
return
|
||||
}
|
||||
|
||||
mux.mx.Lock()
|
||||
defer mux.mx.Unlock()
|
||||
|
||||
for _, isIPv6 := range [...]bool{true, false} {
|
||||
key := ufragConnKey{ufrag: ufrag, isIPv6: isIPv6}
|
||||
if conn, ok := mux.ufragMap[key]; ok {
|
||||
delete(mux.ufragMap, key)
|
||||
delete(mux.addrMap, conn.RemoteAddr().String())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (mux *UDPMux) getOrCreateConn(ufrag string, isIPv6 bool, _ *UDPMux, addr net.Addr) (created bool, _ *muxedConnection) {
|
||||
key := ufragConnKey{ufrag: ufrag, isIPv6: isIPv6}
|
||||
|
||||
mux.mx.Lock()
|
||||
defer mux.mx.Unlock()
|
||||
|
||||
if conn, ok := mux.ufragMap[key]; ok {
|
||||
return false, conn
|
||||
}
|
||||
|
||||
conn := newMuxedConnection(mux, func() { mux.RemoveConnByUfrag(ufrag) }, addr)
|
||||
mux.ufragMap[key] = conn
|
||||
mux.addrMap[addr.String()] = conn
|
||||
|
||||
return true, conn
|
||||
}
|
89
p2p/transport/webrtc/udpmux/mux_test.go
Normal file
89
p2p/transport/webrtc/udpmux/mux_test.go
Normal file
@@ -0,0 +1,89 @@
|
||||
package udpmux
|
||||
|
||||
import (
|
||||
"net"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
var _ net.PacketConn = dummyPacketConn{}
|
||||
|
||||
type dummyPacketConn struct{}
|
||||
|
||||
// Close implements net.PacketConn
|
||||
func (dummyPacketConn) Close() error {
|
||||
return nil
|
||||
}
|
||||
|
||||
// LocalAddr implements net.PacketConn
|
||||
func (dummyPacketConn) LocalAddr() net.Addr {
|
||||
return nil
|
||||
}
|
||||
|
||||
// ReadFrom implements net.PacketConn
|
||||
func (dummyPacketConn) ReadFrom(p []byte) (n int, addr net.Addr, err error) {
|
||||
return 0, &net.UDPAddr{}, nil
|
||||
}
|
||||
|
||||
// SetDeadline implements net.PacketConn
|
||||
func (dummyPacketConn) SetDeadline(t time.Time) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
// SetReadDeadline implements net.PacketConn
|
||||
func (dummyPacketConn) SetReadDeadline(t time.Time) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
// SetWriteDeadline implements net.PacketConn
|
||||
func (dummyPacketConn) SetWriteDeadline(t time.Time) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
// WriteTo implements net.PacketConn
|
||||
func (dummyPacketConn) WriteTo(p []byte, addr net.Addr) (n int, err error) {
|
||||
return 0, nil
|
||||
}
|
||||
|
||||
func hasConn(m *UDPMux, ufrag string, isIPv6 bool) bool {
|
||||
m.mx.Lock()
|
||||
_, ok := m.ufragMap[ufragConnKey{ufrag: ufrag, isIPv6: isIPv6}]
|
||||
m.mx.Unlock()
|
||||
return ok
|
||||
}
|
||||
|
||||
var (
|
||||
addrV4 = net.UDPAddr{IP: net.IPv4zero, Port: 1234}
|
||||
addrV6 = net.UDPAddr{IP: net.IPv6zero, Port: 1234}
|
||||
)
|
||||
|
||||
func TestUDPMux_GetConn(t *testing.T) {
|
||||
m := NewUDPMux(dummyPacketConn{})
|
||||
require.False(t, hasConn(m, "test", false))
|
||||
conn, err := m.GetConn("test", &addrV4)
|
||||
require.NoError(t, err)
|
||||
require.NotNil(t, conn)
|
||||
|
||||
require.False(t, hasConn(m, "test", true))
|
||||
connv6, err := m.GetConn("test", &addrV6)
|
||||
require.NoError(t, err)
|
||||
require.NotNil(t, connv6)
|
||||
|
||||
require.NotEqual(t, conn, connv6)
|
||||
}
|
||||
|
||||
func TestUDPMux_RemoveConnectionOnClose(t *testing.T) {
|
||||
mux := NewUDPMux(dummyPacketConn{})
|
||||
conn, err := mux.GetConn("test", &addrV4)
|
||||
require.NoError(t, err)
|
||||
require.NotNil(t, conn)
|
||||
|
||||
require.True(t, hasConn(mux, "test", false))
|
||||
|
||||
err = conn.Close()
|
||||
require.NoError(t, err)
|
||||
|
||||
require.False(t, hasConn(mux, "test", false))
|
||||
}
|
106
p2p/transport/webrtc/udpmux/muxed_connection.go
Normal file
106
p2p/transport/webrtc/udpmux/muxed_connection.go
Normal file
@@ -0,0 +1,106 @@
|
||||
package udpmux
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"net"
|
||||
"time"
|
||||
)
|
||||
|
||||
var _ net.PacketConn = &muxedConnection{}
|
||||
|
||||
const queueLen = 128
|
||||
|
||||
// muxedConnection provides a net.PacketConn abstraction
|
||||
// over packetQueue and adds the ability to store addresses
|
||||
// from which this connection (indexed by ufrag) received
|
||||
// data.
|
||||
type muxedConnection struct {
|
||||
ctx context.Context
|
||||
cancel context.CancelFunc
|
||||
onClose func()
|
||||
queue chan []byte
|
||||
remote net.Addr
|
||||
mux *UDPMux
|
||||
}
|
||||
|
||||
var _ net.PacketConn = &muxedConnection{}
|
||||
|
||||
func newMuxedConnection(mux *UDPMux, onClose func(), remote net.Addr) *muxedConnection {
|
||||
ctx, cancel := context.WithCancel(mux.ctx)
|
||||
return &muxedConnection{
|
||||
ctx: ctx,
|
||||
cancel: cancel,
|
||||
queue: make(chan []byte, queueLen),
|
||||
onClose: onClose,
|
||||
remote: remote,
|
||||
mux: mux,
|
||||
}
|
||||
}
|
||||
|
||||
func (c *muxedConnection) Push(buf []byte) error {
|
||||
select {
|
||||
case <-c.ctx.Done():
|
||||
return errors.New("closed")
|
||||
default:
|
||||
}
|
||||
select {
|
||||
case c.queue <- buf:
|
||||
return nil
|
||||
default:
|
||||
return errors.New("queue full")
|
||||
}
|
||||
}
|
||||
|
||||
func (c *muxedConnection) ReadFrom(p []byte) (int, net.Addr, error) {
|
||||
select {
|
||||
case buf := <-c.queue:
|
||||
n := copy(p, buf) // This might discard parts of the packet, if p is too short
|
||||
if n < len(buf) {
|
||||
log.Debugf("short read, had %d, read %d", len(buf), n)
|
||||
}
|
||||
return n, c.remote, nil
|
||||
case <-c.ctx.Done():
|
||||
return 0, nil, c.ctx.Err()
|
||||
}
|
||||
}
|
||||
|
||||
func (c *muxedConnection) WriteTo(p []byte, addr net.Addr) (n int, err error) {
|
||||
return c.mux.writeTo(p, addr)
|
||||
}
|
||||
|
||||
func (c *muxedConnection) Close() error {
|
||||
select {
|
||||
case <-c.ctx.Done():
|
||||
return nil
|
||||
default:
|
||||
}
|
||||
c.onClose()
|
||||
c.cancel()
|
||||
// drain the packet queue
|
||||
for {
|
||||
select {
|
||||
case <-c.queue:
|
||||
default:
|
||||
return nil
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (c *muxedConnection) LocalAddr() net.Addr { return c.mux.socket.LocalAddr() }
|
||||
func (c *muxedConnection) RemoteAddr() net.Addr { return c.remote }
|
||||
|
||||
func (*muxedConnection) SetDeadline(t time.Time) error {
|
||||
// no deadline is desired here
|
||||
return nil
|
||||
}
|
||||
|
||||
func (*muxedConnection) SetReadDeadline(t time.Time) error {
|
||||
// no read deadline is desired here
|
||||
return nil
|
||||
}
|
||||
|
||||
func (*muxedConnection) SetWriteDeadline(t time.Time) error {
|
||||
// no write deadline is desired here
|
||||
return nil
|
||||
}
|
@@ -15,6 +15,8 @@ import (
|
||||
"strconv"
|
||||
"time"
|
||||
|
||||
libp2pwebrtc "github.com/libp2p/go-libp2p/p2p/transport/webrtc"
|
||||
|
||||
"github.com/go-redis/redis/v8"
|
||||
"github.com/libp2p/go-libp2p"
|
||||
"github.com/libp2p/go-libp2p/core/peer"
|
||||
@@ -103,6 +105,9 @@ func main() {
|
||||
case "webtransport":
|
||||
options = append(options, libp2p.Transport(libp2pwebtransport.New))
|
||||
listenAddr = fmt.Sprintf("/ip4/%s/udp/0/quic-v1/webtransport", ip)
|
||||
case "webrtc-direct":
|
||||
options = append(options, libp2p.Transport(libp2pwebrtc.New))
|
||||
listenAddr = fmt.Sprintf("/ip4/%s/udp/0/webrtc-direct", ip)
|
||||
default:
|
||||
log.Fatalf("Unsupported transport: %s", transport)
|
||||
}
|
||||
@@ -112,13 +117,11 @@ func main() {
|
||||
var skipMuxer bool
|
||||
var skipSecureChannel bool
|
||||
switch transport {
|
||||
case "quic":
|
||||
fallthrough
|
||||
case "quic-v1":
|
||||
fallthrough
|
||||
case "webtransport":
|
||||
fallthrough
|
||||
case "webrtc":
|
||||
case "webrtc-direct":
|
||||
skipMuxer = true
|
||||
skipSecureChannel = true
|
||||
}
|
||||
|
@@ -8,12 +8,25 @@ require (
|
||||
github.com/multiformats/go-multiaddr v0.11.0
|
||||
)
|
||||
|
||||
require (
|
||||
golang.org/x/crypto v0.12.0 // indirect
|
||||
golang.org/x/exp v0.0.0-20230817173708-d852ddb80c63 // indirect
|
||||
golang.org/x/mod v0.12.0 // indirect
|
||||
golang.org/x/net v0.14.0 // indirect
|
||||
golang.org/x/sync v0.3.0 // indirect
|
||||
golang.org/x/sys v0.11.0 // indirect
|
||||
golang.org/x/text v0.12.0 // indirect
|
||||
golang.org/x/tools v0.12.1-0.20230815132531-74c255bcf846 // indirect
|
||||
google.golang.org/protobuf v1.31.0 // indirect
|
||||
)
|
||||
|
||||
require (
|
||||
github.com/benbjohnson/clock v1.3.5 // indirect
|
||||
github.com/beorn7/perks v1.0.1 // indirect
|
||||
github.com/cespare/xxhash/v2 v2.2.0 // indirect
|
||||
github.com/containerd/cgroups v1.1.0 // indirect
|
||||
github.com/coreos/go-systemd/v22 v22.5.0 // indirect
|
||||
github.com/davecgh/go-spew v1.1.1 // indirect
|
||||
github.com/davidlazar/go-crypto v0.0.0-20200604182044-b73af7476f6c // indirect
|
||||
github.com/decred/dcrd/dcrec/secp256k1/v4 v4.2.0 // indirect
|
||||
github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f // indirect
|
||||
@@ -28,6 +41,7 @@ require (
|
||||
github.com/golang/protobuf v1.5.3 // indirect
|
||||
github.com/google/gopacket v1.1.19 // indirect
|
||||
github.com/google/pprof v0.0.0-20230817174616-7a8ec2ada47b // indirect
|
||||
github.com/google/uuid v1.3.0 // indirect
|
||||
github.com/gorilla/websocket v1.5.0 // indirect
|
||||
github.com/huin/goupnp v1.2.0 // indirect
|
||||
github.com/ipfs/go-cid v0.4.1 // indirect
|
||||
@@ -66,7 +80,24 @@ require (
|
||||
github.com/onsi/ginkgo/v2 v2.11.0 // indirect
|
||||
github.com/opencontainers/runtime-spec v1.1.0 // indirect
|
||||
github.com/pbnjay/memory v0.0.0-20210728143218-7b4eea64cf58 // indirect
|
||||
github.com/pion/datachannel v1.5.5 // indirect
|
||||
github.com/pion/dtls/v2 v2.2.7 // indirect
|
||||
github.com/pion/ice/v2 v2.3.6 // indirect
|
||||
github.com/pion/interceptor v0.1.17 // indirect
|
||||
github.com/pion/logging v0.2.2 // indirect
|
||||
github.com/pion/mdns v0.0.7 // indirect
|
||||
github.com/pion/randutil v0.1.0 // indirect
|
||||
github.com/pion/rtcp v1.2.10 // indirect
|
||||
github.com/pion/rtp v1.7.13 // indirect
|
||||
github.com/pion/sctp v1.8.7 // indirect
|
||||
github.com/pion/sdp/v3 v3.0.6 // indirect
|
||||
github.com/pion/srtp/v2 v2.0.15 // indirect
|
||||
github.com/pion/stun v0.6.0 // indirect
|
||||
github.com/pion/transport/v2 v2.2.1 // indirect
|
||||
github.com/pion/turn/v2 v2.1.0 // indirect
|
||||
github.com/pion/webrtc/v3 v3.2.9 // indirect
|
||||
github.com/pkg/errors v0.9.1 // indirect
|
||||
github.com/pmezard/go-difflib v1.0.0 // indirect
|
||||
github.com/prometheus/client_golang v1.16.0 // indirect
|
||||
github.com/prometheus/client_model v0.4.0 // indirect
|
||||
github.com/prometheus/common v0.44.0 // indirect
|
||||
@@ -77,19 +108,12 @@ require (
|
||||
github.com/quic-go/webtransport-go v0.5.3 // indirect
|
||||
github.com/raulk/go-watchdog v1.3.0 // indirect
|
||||
github.com/spaolacci/murmur3 v1.1.0 // indirect
|
||||
github.com/stretchr/testify v1.8.4 // indirect
|
||||
go.uber.org/dig v1.17.0 // indirect
|
||||
go.uber.org/fx v1.20.0 // indirect
|
||||
go.uber.org/multierr v1.11.0 // indirect
|
||||
go.uber.org/zap v1.25.0 // indirect
|
||||
golang.org/x/crypto v0.12.0 // indirect
|
||||
golang.org/x/exp v0.0.0-20230817173708-d852ddb80c63 // indirect
|
||||
golang.org/x/mod v0.12.0 // indirect
|
||||
golang.org/x/net v0.14.0 // indirect
|
||||
golang.org/x/sync v0.3.0 // indirect
|
||||
golang.org/x/sys v0.11.0 // indirect
|
||||
golang.org/x/text v0.12.0 // indirect
|
||||
golang.org/x/tools v0.12.1-0.20230815132531-74c255bcf846 // indirect
|
||||
google.golang.org/protobuf v1.31.0 // indirect
|
||||
gopkg.in/yaml.v3 v3.0.1 // indirect
|
||||
lukechampine.com/blake3 v1.2.1 // indirect
|
||||
)
|
||||
|
||||
|
@@ -54,6 +54,7 @@ github.com/flynn/noise v1.0.0/go.mod h1:xbMo+0i6+IGbYdJhF31t2eR1BIU0CYc12+BNAKwU
|
||||
github.com/francoispqt/gojay v1.2.13 h1:d2m3sFjloqoIUQU3TsHBgj6qg/BVGlTBeHDUmyJnXKk=
|
||||
github.com/francoispqt/gojay v1.2.13/go.mod h1:ehT5mTG4ua4581f1++1WLG0vPdaA9HaiDsoyrBGkyDY=
|
||||
github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo=
|
||||
github.com/fsnotify/fsnotify v1.4.9/go.mod h1:znqG4EE+3YCdAaPaxE2ZRY/06pZUdp0tY4IgpuI1SZQ=
|
||||
github.com/fsnotify/fsnotify v1.5.4 h1:jRbGcIw6P2Meqdwuo0H1p6JVLbL5DHKAKlYndzMwVZI=
|
||||
github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04=
|
||||
github.com/gliderlabs/ssh v0.1.1/go.mod h1:U7qILu1NlMHj9FlMhZLlkCdDnU1DBEAqr0aevW3Awn0=
|
||||
@@ -61,6 +62,7 @@ github.com/go-errors/errors v1.0.1/go.mod h1:f4zRHt4oKfwPJE5k8C9vpYG+aDHdBFUsgrm
|
||||
github.com/go-logr/logr v1.2.4 h1:g01GSCwiDw2xSZfjJ2/T9M+S6pFdcNtFYsp+Y43HYDQ=
|
||||
github.com/go-redis/redis/v8 v8.11.5 h1:AcZZR7igkdvfVmQTPnu9WE37LRrO/YrBH5zWyjDC0oI=
|
||||
github.com/go-redis/redis/v8 v8.11.5/go.mod h1:gREzHqY1hg6oD9ngVRbLStwAWKhA0FEgq8Jd4h5lpwo=
|
||||
github.com/go-task/slim-sprig v0.0.0-20210107165309-348f09dbbbc0/go.mod h1:fyg7847qk6SyHyPtNmDHnmrv/HOrqktSC+C9fM+CJOE=
|
||||
github.com/go-task/slim-sprig v0.0.0-20230315185526-52ccab3ef572 h1:tfuBGBXKqDEevZMzYi5KSi8KkcZtzBcTgAUUtapy0OI=
|
||||
github.com/go-task/slim-sprig v0.0.0-20230315185526-52ccab3ef572/go.mod h1:9Pwr4B2jHnOSGXyyzV8ROjYa2ojvAY6HCGYYfMoC3Ls=
|
||||
github.com/godbus/dbus/v5 v5.0.3/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA=
|
||||
@@ -79,11 +81,21 @@ github.com/golang/mock v1.6.0 h1:ErTB+efbowRARo13NNdxyJji2egdxLGQhRaY+DUumQc=
|
||||
github.com/golang/mock v1.6.0/go.mod h1:p6yTPP+5HYm5mzsMV8JkE6ZKdX+/wYM6Hr+LicevLPs=
|
||||
github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
|
||||
github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
|
||||
github.com/golang/protobuf v1.4.0-rc.1/go.mod h1:ceaxUfeHdC40wWswd/P6IGgMaK3YpKi5j83Wpe3EHw8=
|
||||
github.com/golang/protobuf v1.4.0-rc.1.0.20200221234624-67d41d38c208/go.mod h1:xKAWHe0F5eneWXFV3EuXVDTCmh+JuBKY0li0aMyXATA=
|
||||
github.com/golang/protobuf v1.4.0-rc.2/go.mod h1:LlEzMj4AhA7rCAGe4KMBDvJI+AwstrUpVNzEA03Pprs=
|
||||
github.com/golang/protobuf v1.4.0-rc.4.0.20200313231945-b860323f09d0/go.mod h1:WU3c8KckQ9AFe+yFwt9sWVRKCVIyN9cPHBJSNnbL67w=
|
||||
github.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvqG2KuDX0=
|
||||
github.com/golang/protobuf v1.4.2/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI=
|
||||
github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk=
|
||||
github.com/golang/protobuf v1.5.2/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY=
|
||||
github.com/golang/protobuf v1.5.3 h1:KhyjKVUg7Usr/dYsdSqoFveMYd5ko72D+zANwlG1mmg=
|
||||
github.com/golang/protobuf v1.5.3/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY=
|
||||
github.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ=
|
||||
github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M=
|
||||
github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
|
||||
github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
|
||||
github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
||||
github.com/google/go-cmp v0.5.2/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
||||
github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
||||
github.com/google/go-cmp v0.5.9 h1:O2Tfq5qg4qc4AmwVlvv0oLiVAGB7enBSJ2x2DqQFi38=
|
||||
@@ -95,6 +107,8 @@ github.com/google/martian v2.1.0+incompatible/go.mod h1:9I4somxYTbIHy5NJKHRl3wXi
|
||||
github.com/google/pprof v0.0.0-20181206194817-3ea8567a2e57/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc=
|
||||
github.com/google/pprof v0.0.0-20230817174616-7a8ec2ada47b h1:h9U78+dx9a4BKdQkBBos92HalKpaGKHrp+3Uo6yTodo=
|
||||
github.com/google/pprof v0.0.0-20230817174616-7a8ec2ada47b/go.mod h1:czg5+yv1E0ZGTi6S6vVK1mke0fV+FaUhNGcd6VRS9Ik=
|
||||
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.0.0+incompatible/go.mod h1:SFVmujtThgffbyetf+mdk2eWhX2bMyUtNHzFKcPA9HY=
|
||||
github.com/googleapis/gax-go/v2 v2.0.3/go.mod h1:LLvjysVCY1JZeum8Z6l8qUty8fiNwE08qbEPm1M08qg=
|
||||
github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY=
|
||||
@@ -102,6 +116,7 @@ github.com/gorilla/websocket v1.5.0 h1:PPwGk2jz7EePpoHN/+ClbZu8SPxiqlu12wZP/3sWm
|
||||
github.com/gorilla/websocket v1.5.0/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE=
|
||||
github.com/gregjones/httpcache v0.0.0-20180305231024-9cad4c3443a7/go.mod h1:FecbI9+v66THATjSRHfNgh1IVFe/9kFxbXtjV0ctIMA=
|
||||
github.com/grpc-ecosystem/grpc-gateway v1.5.0/go.mod h1:RSKVYQBd5MCa4OVpNdGskqpgL2+G+NZTnrVHpWWfpdw=
|
||||
github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU=
|
||||
github.com/huin/goupnp v1.2.0 h1:uOKW26NG1hsSSbXIZ1IR7XP9Gjd1U8pnLaCMgntmkmY=
|
||||
github.com/huin/goupnp v1.2.0/go.mod h1:gnGPsThkYa7bFi/KWmEysQRf48l2dvR5bxr2OFckNX8=
|
||||
github.com/ipfs/go-cid v0.4.1 h1:A/T3qGvxi4kpKWWcPC/PgbvDA2bjVLO7n4UeVwnbs/s=
|
||||
@@ -207,10 +222,19 @@ github.com/multiformats/go-varint v0.0.7 h1:sWSGR+f/eu5ABZA2ZpYKBILXTTs9JWpdEM/n
|
||||
github.com/multiformats/go-varint v0.0.7/go.mod h1:r8PUYw/fD/SjBCiKOoDlGF6QawOELpZAu9eioSos/OU=
|
||||
github.com/neelance/astrewrite v0.0.0-20160511093645-99348263ae86/go.mod h1:kHJEU3ofeGjhHklVoIGuVj85JJwZ6kWPaJwCIxgnFmo=
|
||||
github.com/neelance/sourcemap v0.0.0-20151028013722-8c68805598ab/go.mod h1:Qr6/a/Q4r9LP1IltGz7tA7iOK1WonHEYhu1HRBA7ZiM=
|
||||
github.com/nxadm/tail v1.4.4/go.mod h1:kenIhsEOeOJmVchQTgglprH7qJGnHDVpk1VPCcaMI8A=
|
||||
github.com/nxadm/tail v1.4.8 h1:nPr65rt6Y5JFSKQO7qToXr7pePgD6Gwiw05lkbyAQTE=
|
||||
github.com/nxadm/tail v1.4.8/go.mod h1:+ncqLTQzXmGhMZNUePPaPqPvBxHAIsmXswZKocGu+AU=
|
||||
github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=
|
||||
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 h1:8xi0RTUf59SOSfEtZMvwTvXYMzG4gV23XVHOZiXNtnE=
|
||||
github.com/onsi/ginkgo v1.16.5/go.mod h1:+E8gABHa3K6zRBolWtd+ROzc/U5bkGt0FwiG042wbpU=
|
||||
github.com/onsi/ginkgo/v2 v2.11.0 h1:WgqUCUt/lT6yXoQ8Wef0fsNn5cAuMK7+KT9UFRz2tcU=
|
||||
github.com/onsi/ginkgo/v2 v2.11.0/go.mod h1:ZhrRA5XmEE3x3rhlzamx/JJvujdZoJ2uvgI7kR0iZvM=
|
||||
github.com/onsi/gomega v1.7.1/go.mod h1:XdKZgCCFLUoM/7CFJVPcG8C1xQ1AJ0vpAezJrB7JYyY=
|
||||
github.com/onsi/gomega v1.10.1/go.mod h1:iN09h71vgCQne3DLsj+A5owkum+a2tYe+TOCB1ybHNo=
|
||||
github.com/onsi/gomega v1.17.0/go.mod h1:HnhC7FXeEQY45zxNK3PPoIUhzk/80Xly9PcubAlGdZY=
|
||||
github.com/onsi/gomega v1.27.8 h1:gegWiwZjBsf2DgiSbf5hpokZ98JVDMcWkUiigk6/KXc=
|
||||
github.com/opencontainers/runtime-spec v1.0.2/go.mod h1:jwyrGlmzljRJv/Fgzds9SsS/C5hL+LL3ko9hs6T5lQ0=
|
||||
github.com/opencontainers/runtime-spec v1.1.0 h1:HHUyrt9mwHUjtasSbXSMvs4cyFxh+Bll4AjJ9odEGpg=
|
||||
@@ -218,6 +242,45 @@ github.com/opencontainers/runtime-spec v1.1.0/go.mod h1:jwyrGlmzljRJv/Fgzds9SsS/
|
||||
github.com/openzipkin/zipkin-go v0.1.1/go.mod h1:NtoC/o8u3JlF1lSlyPNswIbeQH9bJTmOf0Erfk+hxe8=
|
||||
github.com/pbnjay/memory v0.0.0-20210728143218-7b4eea64cf58 h1:onHthvaw9LFnH4t2DcNVpwGmV9E1BkGknEliJkfwQj0=
|
||||
github.com/pbnjay/memory v0.0.0-20210728143218-7b4eea64cf58/go.mod h1:DXv8WO4yhMYhSNPKjeNKa5WY9YCIEBRbNzFFPJbWO6Y=
|
||||
github.com/pion/datachannel v1.5.5 h1:10ef4kwdjije+M9d7Xm9im2Y3O6A6ccQb0zcqZcJew8=
|
||||
github.com/pion/datachannel v1.5.5/go.mod h1:iMz+lECmfdCMqFRhXhcA/219B0SQlbpoR2V118yimL0=
|
||||
github.com/pion/dtls/v2 v2.2.7 h1:cSUBsETxepsCSFSxC3mc/aDo14qQLMSL+O6IjG28yV8=
|
||||
github.com/pion/dtls/v2 v2.2.7/go.mod h1:8WiMkebSHFD0T+dIU+UeBaoV7kDhOW5oDCzZ7WZ/F9s=
|
||||
github.com/pion/ice/v2 v2.3.6 h1:Jgqw36cAud47iD+N6rNX225uHvrgWtAlHfVyOQc3Heg=
|
||||
github.com/pion/ice/v2 v2.3.6/go.mod h1:9/TzKDRwBVAPsC+YOrKH/e3xDrubeTRACU9/sHQarsU=
|
||||
github.com/pion/interceptor v0.1.17 h1:prJtgwFh/gB8zMqGZoOgJPHivOwVAp61i2aG61Du/1w=
|
||||
github.com/pion/interceptor v0.1.17/go.mod h1:SY8kpmfVBvrbUzvj2bsXz7OJt5JvmVNZ+4Kjq7FcwrI=
|
||||
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.7 h1:P0UB4Sr6xDWEox0kTVxF0LmQihtCbSAdW0H2nEgkA3U=
|
||||
github.com/pion/mdns v0.0.7/go.mod h1:4iP2UbeFhLI/vWju/bw6ZfwjJzk0z8DNValjGxR/dD8=
|
||||
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.10 h1:nkr3uj+8Sp97zyItdN60tE/S6vk4al5CPRR6Gejsdjc=
|
||||
github.com/pion/rtcp v1.2.10/go.mod h1:ztfEwXZNLGyF1oQDttz/ZKIBaeeg/oWbRYqzBM9TL1I=
|
||||
github.com/pion/rtp v1.7.13 h1:qcHwlmtiI50t1XivvoawdCGTP4Uiypzfrsap+bijcoA=
|
||||
github.com/pion/rtp v1.7.13/go.mod h1:bDb5n+BFZxXx0Ea7E5qe+klMuqiBrP+w8XSjiWtCUko=
|
||||
github.com/pion/sctp v1.8.5/go.mod h1:SUFFfDpViyKejTAdwD1d/HQsCu+V/40cCs2nZIvC3s0=
|
||||
github.com/pion/sctp v1.8.7 h1:JnABvFakZueGAn4KU/4PSKg+GWbF6QWbKTWZOSGJjXw=
|
||||
github.com/pion/sctp v1.8.7/go.mod h1:g1Ul+ARqZq5JEmoFy87Q/4CePtKnTJ1QCL9dBBdN6AU=
|
||||
github.com/pion/sdp/v3 v3.0.6 h1:WuDLhtuFUUVpTfus9ILC4HRyHsW6TdugjEX/QY9OiUw=
|
||||
github.com/pion/sdp/v3 v3.0.6/go.mod h1:iiFWFpQO8Fy3S5ldclBkpXqmWy02ns78NOKoLLL0YQw=
|
||||
github.com/pion/srtp/v2 v2.0.15 h1:+tqRtXGsGwHC0G0IUIAzRmdkHvriF79IHVfZGfHrQoA=
|
||||
github.com/pion/srtp/v2 v2.0.15/go.mod h1:b/pQOlDrbB0HEH5EUAQXzSYxikFbNcNuKmF8tM0hCtw=
|
||||
github.com/pion/stun v0.4.0/go.mod h1:QPsh1/SbXASntw3zkkrIk3ZJVKz4saBY2G7S10P3wCw=
|
||||
github.com/pion/stun v0.6.0 h1:JHT/2iyGDPrFWE8NNC15wnddBN8KifsEDw8swQmrEmU=
|
||||
github.com/pion/stun v0.6.0/go.mod h1:HPqcfoeqQn9cuaet7AOmB5e5xkObu9DwBdurwLKO9oA=
|
||||
github.com/pion/transport v0.14.1 h1:XSM6olwW+o8J4SCmOBb/BpwZypkHeyM0PGFCxNQBr40=
|
||||
github.com/pion/transport v0.14.1/go.mod h1:4tGmbk00NeYA3rUa9+n+dzCCoKkcy3YlYb99Jn2fNnI=
|
||||
github.com/pion/transport/v2 v2.0.0/go.mod h1:HS2MEBJTwD+1ZI2eSXSvHJx/HnzQqRy2/LXxt6eVMHc=
|
||||
github.com/pion/transport/v2 v2.1.0/go.mod h1:AdSw4YBZVDkZm8fpoz+fclXyQwANWmZAlDuQdctTThQ=
|
||||
github.com/pion/transport/v2 v2.2.0/go.mod h1:AdSw4YBZVDkZm8fpoz+fclXyQwANWmZAlDuQdctTThQ=
|
||||
github.com/pion/transport/v2 v2.2.1 h1:7qYnCBlpgSJNYMbLCKuSY9KbQdBFoETvPNETv0y4N7c=
|
||||
github.com/pion/transport/v2 v2.2.1/go.mod h1:cXXWavvCnFF6McHTft3DWS9iic2Mftcz1Aq29pGcU5g=
|
||||
github.com/pion/turn/v2 v2.1.0 h1:5wGHSgGhJhP/RpabkUb/T9PdsAjkGLS6toYz5HNzoSI=
|
||||
github.com/pion/turn/v2 v2.1.0/go.mod h1:yrT5XbXSGX1VFSF31A3c1kCNB5bBZgk/uu5LET162qs=
|
||||
github.com/pion/webrtc/v3 v3.2.9 h1:U8NSjQDlZZ+Iy/hg42Q/u6mhEVSXYvKrOIZiZwYTfLc=
|
||||
github.com/pion/webrtc/v3 v3.2.9/go.mod h1:gjQLMZeyN3jXBGdxGmUYCyKjOuYX/c99BDjGqmadq0A=
|
||||
github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
|
||||
github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
|
||||
github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
|
||||
@@ -248,6 +311,7 @@ github.com/raulk/go-watchdog v1.3.0/go.mod h1:fIvOnLbF0b0ZwkB9YU4mOW9Did//4vPZtD
|
||||
github.com/rogpeppe/go-internal v1.10.0 h1:TMyTOH3F/DB16zRVcYyreMH6GnZZrwQVAoYjRBZyWFQ=
|
||||
github.com/russross/blackfriday v1.5.2/go.mod h1:JO/DiYxRf+HjHt06OyowR9PTA263kcR/rfWxYHBV53g=
|
||||
github.com/russross/blackfriday/v2 v2.0.1/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
|
||||
github.com/sclevine/agouti v3.0.0+incompatible/go.mod h1:b4WX9W9L1sfQKXeJf1mUTLZKJ48R1S7H23Ji7oFO5Bw=
|
||||
github.com/sergi/go-diff v1.0.0/go.mod h1:0CfEIISq7TuYL3j771MWULgwwjU+GofnZX9QAmXWZgo=
|
||||
github.com/shurcooL/component v0.0.0-20170202220835-f88ec8f54cc4/go.mod h1:XhFIlyj5a1fBNx5aJTbKoIq0mNaPvOagO+HjB3EtxrY=
|
||||
github.com/shurcooL/events v0.0.0-20181021180414-410e4ca65f48/go.mod h1:5u70Mqkb5O5cxEA8nxTsgrgLehJeAw6Oc4Ab1c/P1HM=
|
||||
@@ -278,12 +342,21 @@ github.com/sourcegraph/syntaxhighlight v0.0.0-20170531221838-bd320f5d308e/go.mod
|
||||
github.com/spaolacci/murmur3 v1.1.0 h1:7c1g84S4BPRrfL5Xrdp6fOJ206sU9y293DDHaoy0bLI=
|
||||
github.com/spaolacci/murmur3 v1.1.0/go.mod h1:JwIasOWyU6f++ZhiEuf87xNszmSA2myDM2Kzu9HwQUA=
|
||||
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
||||
github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw=
|
||||
github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo=
|
||||
github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
|
||||
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
|
||||
github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
|
||||
github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA=
|
||||
github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
||||
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
||||
github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
||||
github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
|
||||
github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4=
|
||||
github.com/stretchr/testify v1.8.2/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4=
|
||||
github.com/stretchr/testify v1.8.3/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo=
|
||||
github.com/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk=
|
||||
github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo=
|
||||
github.com/tarm/serial v0.0.0-20180830185346-98f6abe2eb07/go.mod h1:kDXzergiv9cbyO7IOYJZWg1U88JhDg3PB6klq9Hg2pA=
|
||||
github.com/urfave/cli v1.22.2/go.mod h1:Gos4lmkARVdJ6EkW0WaNv/tZAAMe9V7XWyB60NtXRu0=
|
||||
github.com/viant/assertly v0.4.8/go.mod h1:aGifi++jvCrUaklKEKT0BU95igDNaqkvz+49uaYMPRU=
|
||||
@@ -291,6 +364,7 @@ github.com/viant/toolbox v0.24.0/go.mod h1:OxMCG57V0PXuIP2HNQrtJf2CjqdmbrOx5EkMI
|
||||
github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
|
||||
github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
|
||||
github.com/yuin/goldmark v1.3.5/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k=
|
||||
github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY=
|
||||
go.opencensus.io v0.18.0/go.mod h1:vKdFvxhtzZ9onBp9VKHK8z/sRpBMnKAsufL7wlDrCOA=
|
||||
go.uber.org/atomic v1.7.0/go.mod h1:fEN4uk6kAWBTFdckzkM89CLk9XfWZrxpCo0nPH17wJc=
|
||||
go.uber.org/atomic v1.11.0 h1:ZvwS0R+56ePWxUNi+Atn9dWONBPp/AUETXlHW0DxSjE=
|
||||
@@ -316,6 +390,9 @@ golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8U
|
||||
golang.org/x/crypto v0.0.0-20200602180216-279210d13fed/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
|
||||
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
|
||||
golang.org/x/crypto v0.0.0-20210322153248-0c34fe9e7dc2/go.mod h1:T9bdIzuCu7OtxOm1hfPfRQxPLYneinmdGuTeoZ9dtd4=
|
||||
golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
|
||||
golang.org/x/crypto v0.8.0/go.mod h1:mRqEX+O9/h5TFCrQhkgjo2yKi0yYA+9ecGkdQoHrywE=
|
||||
golang.org/x/crypto v0.9.0/go.mod h1:yrmDGqONDYtNj3tH8X9dzUun2m2lzPa9ngI6/RUPGR0=
|
||||
golang.org/x/crypto v0.12.0 h1:tFM/ta59kqch6LlvYnPa0yx5a83cL2nHflFhYKvv9Yk=
|
||||
golang.org/x/crypto v0.12.0/go.mod h1:NF0Gs7EO5K4qLn+Ylc+fih8BSTeIjAP05siRnAh98yw=
|
||||
golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
|
||||
@@ -330,6 +407,8 @@ golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzB
|
||||
golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
|
||||
golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
|
||||
golang.org/x/mod v0.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
|
||||
golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4=
|
||||
golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=
|
||||
golang.org/x/mod v0.12.0 h1:rmsUpXtvNzj340zd98LZ4KntptpfRHwpFOHG188oHXc=
|
||||
golang.org/x/mod v0.12.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=
|
||||
golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
@@ -344,10 +423,19 @@ golang.org/x/net v0.0.0-20190313220215-9f648a60d977/go.mod h1:t9HGtf8HONx5eT2rtn
|
||||
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
|
||||
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||
golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||
golang.org/x/net v0.0.0-20200520004742-59133d7f0dd7/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
|
||||
golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
|
||||
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-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-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c=
|
||||
golang.org/x/net v0.1.0/go.mod h1:Cx3nUiGt4eDBEyega/BKRp+/AlGL8hYe7U9odMt2Cco=
|
||||
golang.org/x/net v0.5.0/go.mod h1:DivGGAXEgPSlEBzxGzZI+ZLohi+xUj054jfeKui00ws=
|
||||
golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs=
|
||||
golang.org/x/net v0.8.0/go.mod h1:QVkue5JL9kW//ek3r6jTKnTFis1tRmNAW2P1shuFdJc=
|
||||
golang.org/x/net v0.9.0/go.mod h1:d48xBJpPfHeWQsugry2m+kC02ZBRGRgulfHnEXEuWns=
|
||||
golang.org/x/net v0.10.0/go.mod h1:0qNGK6F8kojg2nk9dLZ2mShWaEBan6FAoqfSigmmuDg=
|
||||
golang.org/x/net v0.14.0 h1:BONx9s002vGdD9umnlX1Po8vOZmrgH34qlHcD1MfK14=
|
||||
golang.org/x/net v0.14.0/go.mod h1:PpSgVXXLK0OxS0F31C1/tv6XNguvCrnXIDrFMspZIUI=
|
||||
golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
|
||||
@@ -363,6 +451,8 @@ golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJ
|
||||
golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.3.0 h1:ftCYgMx6zT/asHUrPw8BLLscYtGznsLAnjq5RH9P66E=
|
||||
golang.org/x/sync v0.3.0/go.mod h1:FU7BRWz2tNW+3quACPkgCx/L+uEAv1htQ0V83Z9Rj+Y=
|
||||
golang.org/x/sys v0.0.0-20180810173357-98c5dad5d1a0/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
@@ -372,23 +462,51 @@ golang.org/x/sys v0.0.0-20181029174526-d69651ed3497/go.mod h1:STP8DvDyc/dI5b8T5h
|
||||
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20190316082340-a2f829d7f35f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20190904154756-749cb33beabd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20191005200804-aed5e4c7ecf9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20191120155948-bd437916bb0e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20200124204421-9fbb57f87de9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20200602225109-6fdc65e7d980/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20210112080510-489259a85091/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20210303074136-134d130e1a04/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20210330210617-4fbd30eecc44/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20210510120138-977fb7262007/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.1.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.2.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.4.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.7.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.11.0 h1:eG7RXZHdqOJ1i+0lgLgCpSXAp6M3LYlAo6osgSi0xOM=
|
||||
golang.org/x/sys v0.11.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
|
||||
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
|
||||
golang.org/x/term v0.1.0/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
|
||||
golang.org/x/term v0.4.0/go.mod h1:9P2UbLfCdcvo3p/nzKvsmas4TnlujnuoV9hGgYzW1lQ=
|
||||
golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k=
|
||||
golang.org/x/term v0.6.0/go.mod h1:m6U89DPEgQRMq3DNkDClhWw02AUbt2daBVO4cn4Hv9U=
|
||||
golang.org/x/term v0.7.0/go.mod h1:P32HKFT3hSsZrRxla30E9HqToFYAQPCMs/zFMBUFqPY=
|
||||
golang.org/x/term v0.8.0/go.mod h1:xPskH00ivmX89bAKVGSKKtLOWNx2+17Eiy94tnKShWo=
|
||||
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||
golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||
golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||
golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
|
||||
golang.org/x/text v0.4.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
|
||||
golang.org/x/text v0.6.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
|
||||
golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
|
||||
golang.org/x/text v0.8.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8=
|
||||
golang.org/x/text v0.9.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8=
|
||||
golang.org/x/text v0.12.0 h1:k+n5B8goJNdU7hSvEtMUz3d1Q6D/XW4COJSJR6fN0mc=
|
||||
golang.org/x/text v0.12.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE=
|
||||
golang.org/x/time v0.0.0-20180412165947-fbb02b2291d2/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
|
||||
@@ -403,9 +521,12 @@ golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3
|
||||
golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
||||
golang.org/x/tools v0.0.0-20200130002326-2f3ba24bd6e7/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
|
||||
golang.org/x/tools v0.0.0-20200619180055-7c47624df98f/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=
|
||||
golang.org/x/tools v0.0.0-20201224043029-2b0845dc783e/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
|
||||
golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
|
||||
golang.org/x/tools v0.1.1/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk=
|
||||
golang.org/x/tools v0.1.5/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk=
|
||||
golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc=
|
||||
golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU=
|
||||
golang.org/x/tools v0.12.1-0.20230815132531-74c255bcf846 h1:Vve/L0v7CXXuxUmaMGIEK/dEeq7uiqb5qBgQrZzIE7E=
|
||||
golang.org/x/tools v0.12.1-0.20230815132531-74c255bcf846/go.mod h1:Sc0INKfu04TlqNoRA1hgpFZbhYXHPr4V5DzpSBTPqQM=
|
||||
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
@@ -428,22 +549,35 @@ google.golang.org/grpc v1.14.0/go.mod h1:yo6s7OP7yaDglbqo1J04qKzAhqBH6lvTonzMVmE
|
||||
google.golang.org/grpc v1.16.0/go.mod h1:0JHn/cJsOMiMfNA9+DeHDlAU7KAAB5GDlYFpa9MZMio=
|
||||
google.golang.org/grpc v1.17.0/go.mod h1:6QZJwpn2B+Zp71q/5VxRsJ6NXXVCE5NRUHRo+f3cWCs=
|
||||
google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c=
|
||||
google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8=
|
||||
google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0=
|
||||
google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM=
|
||||
google.golang.org/protobuf v1.20.1-0.20200309200217-e05f789c0967/go.mod h1:A+miEFZTKqfCUM6K7xSMQL9OKL/b6hQv+e19PK+JZNE=
|
||||
google.golang.org/protobuf v1.21.0/go.mod h1:47Nbq4nVaFHyn7ilMalzfO3qCViNmqZ2kzikPIcrTAo=
|
||||
google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=
|
||||
google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw=
|
||||
google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc=
|
||||
google.golang.org/protobuf v1.31.0 h1:g0LDEJHgrBl9N9r17Ru3sqWhkIx2NB67okBHPwC7hs8=
|
||||
google.golang.org/protobuf v1.31.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I=
|
||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk=
|
||||
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q=
|
||||
gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys=
|
||||
gopkg.in/inf.v0 v0.9.1/go.mod h1:cWUDdTG/fYaXco+Dcufb5Vnc6Gp2YChqWtbxRZE0mXw=
|
||||
gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 h1:uRGJdciOHaEIrze2W8Q3AKkepLTh2hOroT7a+7czfdQ=
|
||||
gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw=
|
||||
gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||
gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||
gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||
gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||
gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ=
|
||||
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||
gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
|
||||
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||
grpc.go4.org v0.0.0-20170609214715-11d0a25b4919/go.mod h1:77eQGdRu53HpSqPFJFmuJdjuHRquDANNeA4x7B8WQ9o=
|
||||
honnef.co/go/tools v0.0.0-20180728063816-88497007e858/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
|
||||
honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
|
||||
|
@@ -6,7 +6,8 @@
|
||||
"ws",
|
||||
"wss",
|
||||
"quic-v1",
|
||||
"webtransport"
|
||||
"webtransport",
|
||||
"webrtc-direct"
|
||||
],
|
||||
"secureChannels": [
|
||||
"tls",
|
||||
|
Reference in New Issue
Block a user