So much I dislike that kind of technologies.
"github.com/davecgh/go-spew/spew"
"github.com/dustin/go-humanize"
gbtree "github.com/google/btree"
- "github.com/pion/datachannel"
"golang.org/x/time/rate"
"github.com/anacrolix/torrent/bencode"
pp "github.com/anacrolix/torrent/peer_protocol"
request_strategy "github.com/anacrolix/torrent/request-strategy"
"github.com/anacrolix/torrent/storage"
- "github.com/anacrolix/torrent/tracker"
- "github.com/anacrolix/torrent/webtorrent"
)
// Clients contain zero or more Torrents. A Client manages a blocklist, the
dialRateLimiter *rate.Limiter
numHalfOpen int
- websocketTrackers websocketTrackers
-
activeAnnounceLimiter limiter.Instance
httpClient *http.Client
}
}
}
- cl.websocketTrackers = websocketTrackers{
- PeerId: cl.peerID,
- Logger: cl.logger,
- GetAnnounceRequest: func(event tracker.AnnounceEvent, infoHash [20]byte) (tracker.AnnounceRequest, error) {
- cl.lock()
- defer cl.unlock()
- t, ok := cl.torrents[infoHash]
- if !ok {
- return tracker.AnnounceRequest{}, errors.New("torrent not tracked by client")
- }
- return t.announceRequest(event), nil
- },
- Proxy: cl.config.HTTPProxy,
- WebsocketTrackerHttpHeader: cl.config.WebsocketTrackerHttpHeader,
- DialContext: cl.config.TrackerDialContext,
- OnConn: func(dc datachannel.ReadWriteCloser, dcc webtorrent.DataChannelContext) {
- cl.lock()
- defer cl.unlock()
- t, ok := cl.torrents[dcc.InfoHash]
- if !ok {
- cl.logger.WithDefaultLevel(log.Warning).Printf(
- "got webrtc conn for unloaded torrent with infohash %x",
- dcc.InfoHash,
- )
- dc.Close()
- return
- }
- go t.onWebRtcConn(dc, dcc)
- },
- }
-
return
}
github.com/fsnotify/fsnotify v1.5.4
github.com/google/btree v1.1.2
github.com/google/go-cmp v0.5.9
- github.com/gorilla/websocket v1.5.0
github.com/jessevdk/go-flags v1.5.0
github.com/lispad/go-generics-tools v1.1.0
- github.com/pion/datachannel v1.5.2
- github.com/pion/logging v0.2.2
- github.com/pion/webrtc/v3 v3.1.42
github.com/pkg/errors v0.9.1
github.com/prometheus/client_golang v1.12.2
github.com/stretchr/testify v1.8.0
go.opentelemetry.io/otel v1.8.0
go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.8.0
go.opentelemetry.io/otel/sdk v1.8.0
- go.opentelemetry.io/otel/trace v1.8.0
golang.org/x/time v0.0.0-20220609170525-579cf78fd858
)
github.com/go-logr/logr v1.2.3 // indirect
github.com/go-logr/stdr v1.2.2 // indirect
github.com/golang/protobuf v1.5.2 // indirect
- github.com/google/uuid v1.3.0 // indirect
github.com/grpc-ecosystem/grpc-gateway/v2 v2.7.0 // indirect
github.com/huandu/xstrings v1.3.2 // indirect
github.com/kr/pretty v0.3.1 // indirect
github.com/kr/text v0.2.0 // indirect
github.com/matttproud/golang_protobuf_extensions v1.0.1 // indirect
github.com/mschoch/smat v0.2.0 // indirect
- github.com/pion/dtls/v2 v2.1.5 // indirect
- github.com/pion/ice/v2 v2.2.6 // indirect
- github.com/pion/interceptor v0.1.11 // indirect
- github.com/pion/mdns v0.0.5 // indirect
- github.com/pion/randutil v0.1.0 // indirect
- github.com/pion/rtcp v1.2.9 // indirect
- github.com/pion/rtp v1.7.13 // indirect
- github.com/pion/sctp v1.8.2 // indirect
- github.com/pion/sdp/v3 v3.0.5 // indirect
- github.com/pion/srtp/v2 v2.0.9 // indirect
- github.com/pion/stun v0.3.5 // indirect
- github.com/pion/transport v0.13.1 // indirect
- github.com/pion/turn/v2 v2.0.8 // indirect
- github.com/pion/udp v0.1.1 // indirect
github.com/pmezard/go-difflib v1.0.0 // indirect
github.com/prometheus/client_model v0.2.0 // indirect
github.com/prometheus/common v0.35.0 // indirect
github.com/ryszard/goskiplist v0.0.0-20150312221310-2dfbae5fcf46 // indirect
go.opentelemetry.io/otel/exporters/otlp/internal/retry v1.8.0 // indirect
go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.8.0 // indirect
+ go.opentelemetry.io/otel/trace v1.8.0 // indirect
go.opentelemetry.io/proto/otlp v0.18.0 // indirect
- golang.org/x/crypto v0.0.0-20220622213112-05595931fe9d // indirect
golang.org/x/exp v0.0.0-20220613132600-b0d781184e0d // indirect
golang.org/x/net v0.0.0-20220630215102-69896b714898 // indirect
golang.org/x/sync v0.0.0-20220601150217-0de741cfad7f // indirect
github.com/frankban/quicktest v1.14.4 h1:g2rn0vABPOOXmZUj+vbmUp0lPoXEMuhTpIluN0XL9UY=
github.com/frankban/quicktest v1.14.4/go.mod h1:4ptaffx2x8+WTWXmUCuVU6aPUX1/Mz7zb5vbUoiM6w0=
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/fsnotify/fsnotify v1.5.4/go.mod h1:OVB6XrOHzAwXMpEM7uPOzcehqUV2UqJxmVXmkdnm1bU=
github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04=
github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag=
github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE=
github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY=
-github.com/go-task/slim-sprig v0.0.0-20210107165309-348f09dbbbc0/go.mod h1:fyg7847qk6SyHyPtNmDHnmrv/HOrqktSC+C9fM+CJOE=
github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ=
github.com/gogo/protobuf v1.2.0/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ=
github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q=
github.com/google/pprof v0.0.0-20200708004538-1a94d8640e99/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM=
github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI=
github.com/google/uuid v1.1.2/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
-github.com/google/uuid v1.3.0 h1:t6JiXgmwXMjEs8VusXIJk2BXHsn+wx8BZdTaoZ5fu7I=
-github.com/google/uuid v1.3.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
github.com/googleapis/gax-go/v2 v2.0.4/go.mod h1:0Wqv26UfaUD9n4G6kQubkQ+KchISgw+vpHVxEJEs9eg=
github.com/googleapis/gax-go/v2 v2.0.5/go.mod h1:DWXyrwAJ9X0FpwwEdw+IPEYBICEFu5mhpdKc/us6bOk=
github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY=
github.com/gopherjs/gopherjs v0.0.0-20190910122728-9d188e94fb99/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY=
github.com/gorilla/context v1.1.1/go.mod h1:kBGZzfjB9CEq2AlWe17Uuf7NDRt0dE0s8S51q0aT7Yg=
github.com/gorilla/mux v1.6.2/go.mod h1:1lud6UwP+6orDFRuTfBEV8e9/aOM/c4fVVCaMa2zaAs=
-github.com/gorilla/websocket v1.5.0 h1:PPwGk2jz7EePpoHN/+ClbZu8SPxiqlu12wZP/3sWmnc=
-github.com/gorilla/websocket v1.5.0/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE=
github.com/grpc-ecosystem/grpc-gateway v1.16.0/go.mod h1:BDjrQk3hbvj6Nolgz8mAMFbcEtjT1g+wF4CSlocrBnw=
github.com/grpc-ecosystem/grpc-gateway/v2 v2.7.0 h1:BZHcxBETFHIdVyhyEfOvn/RdU/QGdLI4y34qQGjGWO0=
github.com/grpc-ecosystem/grpc-gateway/v2 v2.7.0/go.mod h1:hgWBS7lorOAVIJEQMi4ZsPv9hVvWI6+ch50m39Pf2Ks=
github.com/mschoch/smat v0.2.0/go.mod h1:kc9mz7DoBKqDyiRL7VZN8KvXQMWeTaVnttLRXOlotKw=
github.com/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U=
github.com/mwitkow/go-conntrack v0.0.0-20190716064945-2f068394615f/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U=
-github.com/nxadm/tail v1.4.4/go.mod h1:kenIhsEOeOJmVchQTgglprH7qJGnHDVpk1VPCcaMI8A=
-github.com/nxadm/tail v1.4.8/go.mod h1:+ncqLTQzXmGhMZNUePPaPqPvBxHAIsmXswZKocGu+AU=
github.com/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/go.mod h1:+E8gABHa3K6zRBolWtd+ROzc/U5bkGt0FwiG042wbpU=
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/openzipkin/zipkin-go v0.1.6/go.mod h1:QgAqvLzwWbR/WpD4A3cGpPtJrZXNIiJc5AZX7/PBEpw=
github.com/philhofer/fwd v1.0.0/go.mod h1:gk3iGcWd9+svBvR0sR+KPcfE+RNWozjowpeBVG3ZVNU=
github.com/pierrec/lz4 v2.0.5+incompatible/go.mod h1:pdkljMzZIN41W+lC3N2tnIh5sFi+IEE17M5jbnwPHcY=
-github.com/pion/datachannel v1.5.2 h1:piB93s8LGmbECrpO84DnkIVWasRMk3IimbcXkTQLE6E=
-github.com/pion/datachannel v1.5.2/go.mod h1:FTGQWaHrdCwIJ1rw6xBIfZVkslikjShim5yr05XFuCQ=
-github.com/pion/dtls/v2 v2.1.3/go.mod h1:o6+WvyLDAlXF7YiPB/RlskRoeK+/JtuaZa5emwQcWus=
-github.com/pion/dtls/v2 v2.1.5 h1:jlh2vtIyUBShchoTDqpCCqiYCyRFJ/lvf/gQ8TALs+c=
-github.com/pion/dtls/v2 v2.1.5/go.mod h1:BqCE7xPZbPSubGasRoDFJeTsyJtdD1FanJYL0JGheqY=
-github.com/pion/ice/v2 v2.2.6 h1:R/vaLlI1J2gCx141L5PEwtuGAGcyS6e7E0hDeJFq5Ig=
-github.com/pion/ice/v2 v2.2.6/go.mod h1:SWuHiOGP17lGromHTFadUe1EuPgFh/oCU6FCMZHooVE=
-github.com/pion/interceptor v0.1.11 h1:00U6OlqxA3FFB50HSg25J/8cWi7P6FbSzw4eFn24Bvs=
-github.com/pion/interceptor v0.1.11/go.mod h1:tbtKjZY14awXd7Bq0mmWvgtHB5MDaRN7HV3OZ/uy7s8=
-github.com/pion/logging v0.2.2 h1:M9+AIj/+pxNsDfAT64+MAVgJO0rsyLnoJKCqf//DoeY=
-github.com/pion/logging v0.2.2/go.mod h1:k0/tDVsRCX2Mb2ZEmTqNa7CWsQPc+YYCB7Q+5pahoms=
-github.com/pion/mdns v0.0.5 h1:Q2oj/JB3NqfzY9xGZ1fPzZzK7sDSD8rZPOvcIQ10BCw=
-github.com/pion/mdns v0.0.5/go.mod h1:UgssrvdD3mxpi8tMxAXbsppL3vJ4Jipw1mTCW+al01g=
-github.com/pion/randutil v0.1.0 h1:CFG1UdESneORglEsnimhUjf33Rwjubwj6xfiOXBa3mA=
-github.com/pion/randutil v0.1.0/go.mod h1:XcJrSMMbbMRhASFVOlj/5hQial/Y8oH/HVo7TBZq+j8=
-github.com/pion/rtcp v1.2.9 h1:1ujStwg++IOLIEoOiIQ2s+qBuJ1VN81KW+9pMPsif+U=
-github.com/pion/rtcp v1.2.9/go.mod h1:qVPhiCzAm4D/rxb6XzKeyZiQK69yJpbUDJSF7TgrqNo=
-github.com/pion/rtp v1.7.13 h1:qcHwlmtiI50t1XivvoawdCGTP4Uiypzfrsap+bijcoA=
-github.com/pion/rtp v1.7.13/go.mod h1:bDb5n+BFZxXx0Ea7E5qe+klMuqiBrP+w8XSjiWtCUko=
-github.com/pion/sctp v1.8.0/go.mod h1:xFe9cLMZ5Vj6eOzpyiKjT9SwGM4KpK/8Jbw5//jc+0s=
-github.com/pion/sctp v1.8.2 h1:yBBCIrUMJ4yFICL3RIvR4eh/H2BTTvlligmSTy+3kiA=
-github.com/pion/sctp v1.8.2/go.mod h1:xFe9cLMZ5Vj6eOzpyiKjT9SwGM4KpK/8Jbw5//jc+0s=
-github.com/pion/sdp/v3 v3.0.5 h1:ouvI7IgGl+V4CrqskVtr3AaTrPvPisEOxwgpdktctkU=
-github.com/pion/sdp/v3 v3.0.5/go.mod h1:iiFWFpQO8Fy3S5ldclBkpXqmWy02ns78NOKoLLL0YQw=
-github.com/pion/srtp/v2 v2.0.9 h1:JJq3jClmDFBPX/F5roEb0U19jSU7eUhyDqR/NZ34EKQ=
-github.com/pion/srtp/v2 v2.0.9/go.mod h1:5TtM9yw6lsH0ppNCehB/EjEUli7VkUgKSPJqWVqbhQ4=
-github.com/pion/stun v0.3.5 h1:uLUCBCkQby4S1cf6CGuR9QrVOKcvUwFeemaC865QHDg=
-github.com/pion/stun v0.3.5/go.mod h1:gDMim+47EeEtfWogA37n6qXZS88L5V6LqFcf+DZA2UA=
-github.com/pion/transport v0.12.2/go.mod h1:N3+vZQD9HlDP5GWkZ85LohxNsDcNgofQmyL6ojX5d8Q=
-github.com/pion/transport v0.12.3/go.mod h1:OViWW9SP2peE/HbwBvARicmAVnesphkNkCVZIWJ6q9A=
-github.com/pion/transport v0.13.0/go.mod h1:yxm9uXpK9bpBBWkITk13cLo1y5/ur5VQpG22ny6EP7g=
-github.com/pion/transport v0.13.1 h1:/UH5yLeQtwm2VZIPjxwnNFxjS4DFhyLfS4GlfuKUzfA=
-github.com/pion/transport v0.13.1/go.mod h1:EBxbqzyv+ZrmDb82XswEE0BjfQFtuw1Nu6sjnjWCsGg=
-github.com/pion/turn/v2 v2.0.8 h1:KEstL92OUN3k5k8qxsXHpr7WWfrdp7iJZHx99ud8muw=
-github.com/pion/turn/v2 v2.0.8/go.mod h1:+y7xl719J8bAEVpSXBXvTxStjJv3hbz9YFflvkpcGPw=
-github.com/pion/udp v0.1.1 h1:8UAPvyqmsxK8oOjloDk4wUt63TzFe9WEJkg5lChlj7o=
-github.com/pion/udp v0.1.1/go.mod h1:6AFo+CMdKQm7UiA0eUPA8/eVCTx8jBIITLZHc9DWX5M=
-github.com/pion/webrtc/v3 v3.1.42 h1:wJEQFIXVanptnQcHOLTuIo4AtGB2+mG2x4OhIhnITOA=
-github.com/pion/webrtc/v3 v3.1.42/go.mod h1:ffD9DulDrPxyWvDPUIPAOSAWx9GUlOExiJPf7cCcMLA=
github.com/pkg/diff v0.0.0-20210226163009-20ebb0f2a09e/go.mod h1:pJLUxLENpZxwdsKMEsNbx1VGcRFpLqf3715MtcvvzbA=
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/rs/dnscache v0.0.0-20211102005908-e0241e321417/go.mod h1:qe5TWALJ8/a1Lqznoc5BDHpYX/8HU60Hm2AwRmqzxqA=
github.com/ryszard/goskiplist v0.0.0-20150312221310-2dfbae5fcf46 h1:GHRpF1pTW19a8tTFrMLUcfWwyC0pnifVo2ClaLq+hP8=
github.com/ryszard/goskiplist v0.0.0-20150312221310-2dfbae5fcf46/go.mod h1:uAQ5PCi+MFsC7HjREoAz1BU+Mq60+05gifQSsHSDG/8=
-github.com/sclevine/agouti v3.0.0+incompatible/go.mod h1:b4WX9W9L1sfQKXeJf1mUTLZKJ48R1S7H23Ji7oFO5Bw=
github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo=
github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE=
github.com/sirupsen/logrus v1.6.0/go.mod h1:7uNnSEd1DgxDLC74fIahvMZmmYsHGZGEOFrfsX/uA88=
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 h1:pSgiaMZlXftHpm5L7V1+rVB+AZJydKsMxsQBIJw4PKk=
github.com/yuin/goldmark v1.1.25/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
github.com/yuin/goldmark v1.1.32/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
-github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
go.etcd.io/bbolt v1.3.6 h1:/ecaJf0sk1l4l6V4awd65v2C3ILy7MSj+s/x1ADCIMU=
go.etcd.io/bbolt v1.3.6/go.mod h1:qXsaaIqmgQH0T+OPdb99Bf+PKfBBQVAdyD6TY9G8XM4=
go.opencensus.io v0.20.1/go.mod h1:6WKK9ahsWS3RSO+PY9ZHZUfv2irvY6gN279GOPZjmmk=
golang.org/x/crypto v0.0.0-20190605123033-f99c8df09eb5/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
-golang.org/x/crypto v0.0.0-20220131195533-30dcbda58838/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=
-golang.org/x/crypto v0.0.0-20220427172511-eb4f295cb31f/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=
-golang.org/x/crypto v0.0.0-20220516162934-403b01795ae8/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=
-golang.org/x/crypto v0.0.0-20220622213112-05595931fe9d h1:sK3txAijHtOK88l68nt020reeT1ZdKLIYetKl95FzVY=
-golang.org/x/crypto v0.0.0-20220622213112-05595931fe9d/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=
golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
golang.org/x/exp v0.0.0-20190510132918-efd6b22b2522/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8=
golang.org/x/net v0.0.0-20200501053045-e0ff5e5a1de5/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
golang.org/x/net v0.0.0-20200506145744-7e3656a0809f/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
golang.org/x/net v0.0.0-20200513185701-a91f0712d120/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
-golang.org/x/net v0.0.0-20200520004742-59133d7f0dd7/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
golang.org/x/net v0.0.0-20200520182314-0ba52f642ac2/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
golang.org/x/net v0.0.0-20200625001655-4c5254603344/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA=
golang.org/x/net v0.0.0-20200707034311-ab3426394381/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA=
golang.org/x/net v0.0.0-20200822124328-c89045814202/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA=
golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
-golang.org/x/net v0.0.0-20201201195509-5d6afe98e0b7/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
-golang.org/x/net v0.0.0-20210119194325-5f4716e94777/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-20210525063256-abc453219eb5/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
-golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
-golang.org/x/net v0.0.0-20211201190559-0a0e4e1bb54c/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
golang.org/x/net v0.0.0-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-20220401154927-543a649e0bdd/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk=
-golang.org/x/net v0.0.0-20220425223048-2871e0cb64e4/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk=
-golang.org/x/net v0.0.0-20220531201128-c960675eff93/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c=
golang.org/x/net v0.0.0-20220630215102-69896b714898 h1:K7wO6V1IrczY9QOQ2WkVpw4JQSwCd52UsxVEirZUfiw=
golang.org/x/net v0.0.0-20220630215102-69896b714898/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c=
golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20200317015054-43a5402ce75a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20200625203802-6e8e738ad208/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-20201207232520-09787c993a3a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20220601150217-0de741cfad7f h1:Ax0t5p6N38Ga0dThY21weqDEyz2oklo4IvDkpigvkD8=
golang.org/x/sync v0.0.0-20220601150217-0de741cfad7f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sys v0.0.0-20190606165138-5da285871e9c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190624142023-c5567b49c5d0/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190726091711-fc99dfbffb4e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
-golang.org/x/sys v0.0.0-20190904154756-749cb33beabd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20191001151750-bb3f8db39f24/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
-golang.org/x/sys v0.0.0-20191005200804-aed5e4c7ecf9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
-golang.org/x/sys v0.0.0-20191120155948-bd437916bb0e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20191204072324-ce4227a45e2e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20191210023423-ac6580df4449/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20191228213918-04cbcbbfeed8/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200923182605-d9f96fdee20d/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-20210119212857-b64e53b001e4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210124154548-22da62e12c0c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210320140829-1e4c9ba3b0c4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
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-20220608164250-635b8c9b7f68/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220702020025-31831981b65f h1:xdsejrW/0Wf2diT5CPp3XmKUNbr7Xvw8kYilQ+6qjRY=
golang.org/x/sys v0.0.0-20220702020025-31831981b65f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
golang.org/x/tools v0.0.0-20200729194436-6467de6f59a7/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA=
golang.org/x/tools v0.0.0-20200804011535-6c149bb5ef0d/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA=
golang.org/x/tools v0.0.0-20200825202427-b303f430e36d/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA=
-golang.org/x/tools v0.0.0-20201224043029-2b0845dc783e/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
import (
"bytes"
"container/heap"
- "context"
"crypto/sha1"
"errors"
"fmt"
"github.com/anacrolix/multiless"
"github.com/anacrolix/sync"
"github.com/davecgh/go-spew/spew"
- "github.com/pion/datachannel"
"github.com/anacrolix/torrent/bencode"
"github.com/anacrolix/torrent/common"
"github.com/anacrolix/torrent/tracker"
typedRoaring "github.com/anacrolix/torrent/typed-roaring"
"github.com/anacrolix/torrent/webseed"
- "github.com/anacrolix/torrent/webtorrent"
)
// Maintains state of torrent within a Client. Many methods should not be called before the info is
return true
}
-func (t *Torrent) onWebRtcConn(
- c datachannel.ReadWriteCloser,
- dcc webtorrent.DataChannelContext,
-) {
- defer c.Close()
- netConn := webrtcNetConn{
- ReadWriteCloser: c,
- DataChannelContext: dcc,
- }
- peerRemoteAddr := netConn.RemoteAddr()
- //t.logger.Levelf(log.Critical, "onWebRtcConn remote addr: %v", peerRemoteAddr)
- if t.cl.badPeerAddr(peerRemoteAddr) {
- return
- }
- localAddrIpPort := missinggo.IpPortFromNetAddr(netConn.LocalAddr())
- pc, err := t.cl.initiateProtocolHandshakes(
- context.Background(),
- netConn,
- t,
- false,
- newConnectionOpts{
- outgoing: dcc.LocalOffered,
- remoteAddr: peerRemoteAddr,
- localPublicAddr: localAddrIpPort,
- network: webrtcNetwork,
- connString: fmt.Sprintf("webrtc offer_id %x: %v", dcc.OfferId, regularNetConnPeerConnConnString(netConn)),
- },
- )
- if err != nil {
- t.logger.WithDefaultLevel(log.Error).Printf("error in handshaking webrtc connection: %v", err)
- return
- }
- if dcc.LocalOffered {
- pc.Discovery = PeerSourceTracker
- } else {
- pc.Discovery = PeerSourceIncoming
- }
- pc.conn.SetWriteDeadline(time.Time{})
- t.cl.lock()
- defer t.cl.unlock()
- err = t.cl.runHandshookConn(pc, t)
- if err != nil {
- t.logger.WithDefaultLevel(log.Debug).Printf("error running handshook webrtc conn: %v", err)
- }
-}
-
func (t *Torrent) logRunHandshookConn(pc *PeerConn, logAll bool, level log.Level) {
err := t.cl.runHandshookConn(pc, t)
if err != nil || logAll {
t.logRunHandshookConn(pc, false, log.Debug)
}
-func (t *Torrent) startWebsocketAnnouncer(u url.URL) torrentTrackerAnnouncer {
- wtc, release := t.cl.websocketTrackers.Get(u.String(), t.infoHash)
- // This needs to run before the Torrent is dropped from the Client, to prevent a new webtorrent.TrackerClient for
- // the same info hash before the old one is cleaned up.
- t.onClose = append(t.onClose, release)
- wst := websocketTrackerStatus{u, wtc}
- go func() {
- err := wtc.Announce(tracker.Started, t.infoHash)
- if err != nil {
- t.logger.WithDefaultLevel(log.Warning).Printf(
- "error in initial announce to %q: %v",
- u.String(), err,
- )
- }
- }()
- return wst
-}
-
func (t *Torrent) startScrapingTracker(_url string) {
if _url == "" {
return
}
sl := func() torrentTrackerAnnouncer {
switch u.Scheme {
- case "ws", "wss":
- if t.cl.config.DisableWebtorrent {
- return nil
- }
- return t.startWebsocketAnnouncer(*u)
case "udp4":
if t.cl.config.DisableIPv4Peers || t.cl.config.DisableIPv4 {
return nil
+++ /dev/null
-package torrent
-
-import (
- "net"
- "strconv"
- "time"
-
- "github.com/pion/datachannel"
- "github.com/pion/webrtc/v3"
- "go.opentelemetry.io/otel"
- "go.opentelemetry.io/otel/attribute"
- "go.opentelemetry.io/otel/trace"
-
- "github.com/anacrolix/torrent/webtorrent"
-)
-
-const webrtcNetwork = "webrtc"
-
-type webrtcNetConn struct {
- datachannel.ReadWriteCloser
- webtorrent.DataChannelContext
-}
-
-type webrtcNetAddr struct {
- *webrtc.ICECandidate
-}
-
-var _ net.Addr = webrtcNetAddr{}
-
-func (webrtcNetAddr) Network() string {
- // Now that we have the ICE candidate, we can tell if it's over udp or tcp. But should we use
- // that for the network?
- return webrtcNetwork
-}
-
-func (me webrtcNetAddr) String() string {
- return net.JoinHostPort(me.Address, strconv.FormatUint(uint64(me.Port), 10))
-}
-
-func (me webrtcNetConn) LocalAddr() net.Addr {
- // I'm not sure if this evolves over time. It might also be unavailable if the PeerConnection is
- // closed or closes itself. The same concern applies to RemoteAddr.
- pair, err := me.DataChannelContext.GetSelectedIceCandidatePair()
- if err != nil {
- panic(err)
- }
- return webrtcNetAddr{pair.Local}
-}
-
-func (me webrtcNetConn) RemoteAddr() net.Addr {
- // See comments on LocalAddr.
- pair, err := me.DataChannelContext.GetSelectedIceCandidatePair()
- if err != nil {
- panic(err)
- }
- return webrtcNetAddr{pair.Remote}
-}
-
-// Do we need these for WebRTC connections exposed as net.Conns? Can we set them somewhere inside
-// PeerConnection or on the channel or some transport?
-
-func (w webrtcNetConn) SetDeadline(t time.Time) error {
- w.Span.AddEvent("SetDeadline", trace.WithAttributes(attribute.String("time", t.String())))
- return nil
-}
-
-func (w webrtcNetConn) SetReadDeadline(t time.Time) error {
- w.Span.AddEvent("SetReadDeadline", trace.WithAttributes(attribute.String("time", t.String())))
- return nil
-}
-
-func (w webrtcNetConn) SetWriteDeadline(t time.Time) error {
- w.Span.AddEvent("SetWriteDeadline", trace.WithAttributes(attribute.String("time", t.String())))
- return nil
-}
-
-func (w webrtcNetConn) Read(b []byte) (n int, err error) {
- _, span := otel.Tracer(tracerName).Start(w.Context, "Read")
- defer span.End()
- span.SetAttributes(attribute.Int("buf_len", len(b)))
- n, err = w.ReadWriteCloser.Read(b)
- span.RecordError(err)
- span.SetAttributes(attribute.Int("bytes_read", n))
- return
-}
+++ /dev/null
-MIT License
-
-Copyright (c) 2019 Michiel De Backker
-
-Permission is hereby granted, free of charge, to any person obtaining a copy
-of this software and associated documentation files (the "Software"), to deal
-in the Software without restriction, including without limitation the rights
-to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
-copies of the Software, and to permit persons to whom the Software is
-furnished to do so, subject to the following conditions:
-
-The above copyright notice and this permission notice shall be included in all
-copies or substantial portions of the Software.
-
-THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
-IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
-FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
-AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
-LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
-OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
-SOFTWARE.
+++ /dev/null
-//go:build go1.18
-// +build go1.18
-
-package webtorrent
-
-import (
- "encoding/json"
- "testing"
-
- qt "github.com/frankban/quicktest"
-)
-
-func FuzzJsonBinaryStrings(f *testing.F) {
- f.Fuzz(func(t *testing.T, in []byte) {
- jsonBytes, err := json.Marshal(binaryToJsonString(in))
- if err != nil {
- t.Fatal(err)
- }
- // t.Logf("%q", jsonBytes)
- var jsonStr string
- err = json.Unmarshal(jsonBytes, &jsonStr)
- if err != nil {
- t.Fatal(err)
- }
- // t.Logf("%q", jsonStr)
- c := qt.New(t)
- out, err := decodeJsonByteString(jsonStr, []byte{})
- c.Assert(err, qt.IsNil)
- c.Assert(out, qt.DeepEquals, in)
- })
-}
+++ /dev/null
-package webtorrent
-
-const (
- tracerName = "anacrolix.torrent.webtorrent"
- webrtcConnTypeKey = "webtorrent.webrtc.conn.type"
-)
+++ /dev/null
-// These build constraints are copied from webrtc's settingengine.go.
-//go:build !js
-// +build !js
-
-package webtorrent
-
-import (
- "io"
-
- "github.com/pion/logging"
- "github.com/pion/webrtc/v3"
-)
-
-var s = webrtc.SettingEngine{
- // This could probably be done with better integration into anacrolix/log, but I'm not sure if
- // it's worth the effort.
- LoggerFactory: discardLoggerFactory{},
-}
-
-type discardLoggerFactory struct{}
-
-func (discardLoggerFactory) NewLogger(scope string) logging.LeveledLogger {
- return logging.NewDefaultLeveledLoggerForScope(scope, logging.LogLevelInfo, io.Discard)
-}
+++ /dev/null
-// These build constraints are copied from webrtc's settingengine_js.go.
-//go:build js && wasm
-// +build js,wasm
-
-package webtorrent
-
-import (
- "github.com/pion/webrtc/v3"
-)
-
-// I'm not sure what to do for logging for JS. See
-// https://gophers.slack.com/archives/CAK2124AG/p1649651943947579.
-var s = webrtc.SettingEngine{}
+++ /dev/null
-go test fuzz v1
-[]byte("\x93")
+++ /dev/null
-go test fuzz v1
-[]byte("0")
+++ /dev/null
-go test fuzz v1
-[]byte("")
+++ /dev/null
-package webtorrent
-
-import (
- "context"
- "crypto/rand"
- "encoding/json"
- "fmt"
- "net/http"
- "sync"
- "time"
-
- "github.com/anacrolix/generics"
- "github.com/anacrolix/log"
- "github.com/gorilla/websocket"
- "github.com/pion/datachannel"
- "github.com/pion/webrtc/v3"
- "go.opentelemetry.io/otel/trace"
-
- "github.com/anacrolix/torrent/tracker"
-)
-
-type TrackerClientStats struct {
- Dials int64
- ConvertedInboundConns int64
- ConvertedOutboundConns int64
-}
-
-// Client represents the webtorrent client
-type TrackerClient struct {
- Url string
- GetAnnounceRequest func(_ tracker.AnnounceEvent, infoHash [20]byte) (tracker.AnnounceRequest, error)
- PeerId [20]byte
- OnConn onDataChannelOpen
- Logger log.Logger
- Dialer *websocket.Dialer
-
- mu sync.Mutex
- cond sync.Cond
- outboundOffers map[string]outboundOfferValue // OfferID to outboundOfferValue
- wsConn *websocket.Conn
- closed bool
- stats TrackerClientStats
- pingTicker *time.Ticker
-
- WebsocketTrackerHttpHeader func() http.Header
-}
-
-func (me *TrackerClient) Stats() TrackerClientStats {
- me.mu.Lock()
- defer me.mu.Unlock()
- return me.stats
-}
-
-func (me *TrackerClient) peerIdBinary() string {
- return binaryToJsonString(me.PeerId[:])
-}
-
-type outboundOffer struct {
- offerId string
- outboundOfferValue
-}
-
-// outboundOfferValue represents an outstanding offer.
-type outboundOfferValue struct {
- originalOffer webrtc.SessionDescription
- peerConnection *wrappedPeerConnection
- infoHash [20]byte
- dataChannel *webrtc.DataChannel
-}
-
-type DataChannelContext struct {
- OfferId string
- LocalOffered bool
- InfoHash [20]byte
- // This is private as some methods might not be appropriate with data channel context.
- peerConnection *wrappedPeerConnection
- Span trace.Span
- Context context.Context
-}
-
-func (me *DataChannelContext) GetSelectedIceCandidatePair() (*webrtc.ICECandidatePair, error) {
- return me.peerConnection.SCTP().Transport().ICETransport().GetSelectedCandidatePair()
-}
-
-type onDataChannelOpen func(_ datachannel.ReadWriteCloser, dcc DataChannelContext)
-
-func (tc *TrackerClient) doWebsocket() error {
- metrics.Add("websocket dials", 1)
- tc.mu.Lock()
- tc.stats.Dials++
- tc.mu.Unlock()
-
- var header http.Header
- if tc.WebsocketTrackerHttpHeader != nil {
- header = tc.WebsocketTrackerHttpHeader()
- }
-
- c, _, err := tc.Dialer.Dial(tc.Url, header)
- if err != nil {
- return fmt.Errorf("dialing tracker: %w", err)
- }
- defer c.Close()
- tc.Logger.WithDefaultLevel(log.Info).Printf("connected")
- tc.mu.Lock()
- tc.wsConn = c
- tc.cond.Broadcast()
- tc.mu.Unlock()
- tc.announceOffers()
- closeChan := make(chan struct{})
- go func() {
- for {
- select {
- case <-tc.pingTicker.C:
- tc.mu.Lock()
- err := c.WriteMessage(websocket.PingMessage, []byte{})
- tc.mu.Unlock()
- if err != nil {
- return
- }
- case <-closeChan:
- return
-
- }
- }
- }()
- err = tc.trackerReadLoop(tc.wsConn)
- close(closeChan)
- tc.mu.Lock()
- c.Close()
- tc.mu.Unlock()
- return err
-}
-
-// Finishes initialization and spawns the run routine, calling onStop when it completes with the
-// result. We don't let the caller just spawn the runner directly, since then we can race against
-// .Close to finish initialization.
-func (tc *TrackerClient) Start(onStop func(error)) {
- tc.pingTicker = time.NewTicker(60 * time.Second)
- tc.cond.L = &tc.mu
- go func() {
- onStop(tc.run())
- }()
-}
-
-func (tc *TrackerClient) run() error {
- tc.mu.Lock()
- for !tc.closed {
- tc.mu.Unlock()
- err := tc.doWebsocket()
- level := log.Info
- tc.mu.Lock()
- if tc.closed {
- level = log.Debug
- }
- tc.mu.Unlock()
- tc.Logger.WithDefaultLevel(level).Printf("websocket instance ended: %v", err)
- time.Sleep(time.Minute)
- tc.mu.Lock()
- }
- tc.mu.Unlock()
- return nil
-}
-
-func (tc *TrackerClient) Close() error {
- tc.mu.Lock()
- tc.closed = true
- if tc.wsConn != nil {
- tc.wsConn.Close()
- }
- tc.closeUnusedOffers()
- tc.pingTicker.Stop()
- tc.mu.Unlock()
- tc.cond.Broadcast()
- return nil
-}
-
-func (tc *TrackerClient) announceOffers() {
- // tc.Announce grabs a lock on tc.outboundOffers. It also handles the case where outboundOffers
- // is nil. Take ownership of outboundOffers here.
- tc.mu.Lock()
- offers := tc.outboundOffers
- tc.outboundOffers = nil
- tc.mu.Unlock()
-
- if offers == nil {
- return
- }
-
- // Iterate over our locally-owned offers, close any existing "invalid" ones from before the
- // socket reconnected, reannounce the infohash, adding it back into the tc.outboundOffers.
- tc.Logger.WithDefaultLevel(log.Info).Printf("reannouncing %d infohashes after restart", len(offers))
- for _, offer := range offers {
- // TODO: Capture the errors? Are we even in a position to do anything with them?
- offer.peerConnection.Close()
- // Use goroutine here to allow read loop to start and ensure the buffer drains.
- go tc.Announce(tracker.Started, offer.infoHash)
- }
-}
-
-func (tc *TrackerClient) closeUnusedOffers() {
- for _, offer := range tc.outboundOffers {
- offer.peerConnection.Close()
- offer.dataChannel.Close()
- }
- tc.outboundOffers = nil
-}
-
-func (tc *TrackerClient) CloseOffersForInfohash(infoHash [20]byte) {
- tc.mu.Lock()
- defer tc.mu.Unlock()
- for key, offer := range tc.outboundOffers {
- if offer.infoHash == infoHash {
- offer.peerConnection.Close()
- delete(tc.outboundOffers, key)
- }
- }
-}
-
-func (tc *TrackerClient) Announce(event tracker.AnnounceEvent, infoHash [20]byte) error {
- metrics.Add("outbound announces", 1)
- if event == tracker.Stopped {
- return tc.announce(event, infoHash, nil)
- }
- var randOfferId [20]byte
- _, err := rand.Read(randOfferId[:])
- if err != nil {
- return fmt.Errorf("generating offer_id bytes: %w", err)
- }
- offerIDBinary := binaryToJsonString(randOfferId[:])
-
- pc, dc, offer, err := tc.newOffer(tc.Logger, offerIDBinary, infoHash)
- if err != nil {
- return fmt.Errorf("creating offer: %w", err)
- }
-
- err = tc.announce(event, infoHash, []outboundOffer{{
- offerId: offerIDBinary,
- outboundOfferValue: outboundOfferValue{
- originalOffer: offer,
- peerConnection: pc,
- infoHash: infoHash,
- dataChannel: dc,
- }},
- })
- if err != nil {
- dc.Close()
- pc.Close()
- }
- return err
-}
-
-func (tc *TrackerClient) announce(event tracker.AnnounceEvent, infoHash [20]byte, offers []outboundOffer) error {
- request, err := tc.GetAnnounceRequest(event, infoHash)
- if err != nil {
- return fmt.Errorf("getting announce parameters: %w", err)
- }
-
- req := AnnounceRequest{
- Numwant: len(offers),
- Uploaded: request.Uploaded,
- Downloaded: request.Downloaded,
- Left: request.Left,
- Event: request.Event.String(),
- Action: "announce",
- InfoHash: binaryToJsonString(infoHash[:]),
- PeerID: tc.peerIdBinary(),
- }
- for _, offer := range offers {
- req.Offers = append(req.Offers, Offer{
- OfferID: offer.offerId,
- Offer: offer.originalOffer,
- })
- }
-
- data, err := json.Marshal(req)
- if err != nil {
- return fmt.Errorf("marshalling request: %w", err)
- }
-
- tc.mu.Lock()
- defer tc.mu.Unlock()
- err = tc.writeMessage(data)
- if err != nil {
- return fmt.Errorf("write AnnounceRequest: %w", err)
- }
- for _, offer := range offers {
- generics.MakeMapIfNilAndSet(&tc.outboundOffers, offer.offerId, offer.outboundOfferValue)
- }
- return nil
-}
-
-func (tc *TrackerClient) writeMessage(data []byte) error {
- for tc.wsConn == nil {
- if tc.closed {
- return fmt.Errorf("%T closed", tc)
- }
- tc.cond.Wait()
- }
- return tc.wsConn.WriteMessage(websocket.TextMessage, data)
-}
-
-func (tc *TrackerClient) trackerReadLoop(tracker *websocket.Conn) error {
- for {
- _, message, err := tracker.ReadMessage()
- if err != nil {
- return fmt.Errorf("read message error: %w", err)
- }
- // tc.Logger.WithDefaultLevel(log.Debug).Printf("received message from tracker: %q", message)
-
- var ar AnnounceResponse
- if err := json.Unmarshal(message, &ar); err != nil {
- tc.Logger.WithDefaultLevel(log.Warning).Printf("error unmarshalling announce response: %v", err)
- continue
- }
- switch {
- case ar.Offer != nil:
- ih, err := jsonStringToInfoHash(ar.InfoHash)
- if err != nil {
- tc.Logger.WithDefaultLevel(log.Warning).Printf("error decoding info_hash in offer: %v", err)
- break
- }
- err = tc.handleOffer(offerContext{
- SessDesc: *ar.Offer,
- Id: ar.OfferID,
- InfoHash: ih,
- }, ar.PeerID)
- if err != nil {
- tc.Logger.Levelf(log.Error, "handling offer for infohash %x: %v", ih, err)
- }
- case ar.Answer != nil:
- tc.handleAnswer(ar.OfferID, *ar.Answer)
- default:
- tc.Logger.Levelf(log.Warning, "unhandled announce response %q", message)
- }
- }
-}
-
-type offerContext struct {
- SessDesc webrtc.SessionDescription
- Id string
- InfoHash [20]byte
-}
-
-func (tc *TrackerClient) handleOffer(
- offerContext offerContext,
- peerId string,
-) error {
- peerConnection, answer, err := tc.newAnsweringPeerConnection(offerContext)
- if err != nil {
- return fmt.Errorf("creating answering peer connection: %w", err)
- }
- response := AnnounceResponse{
- Action: "announce",
- InfoHash: binaryToJsonString(offerContext.InfoHash[:]),
- PeerID: tc.peerIdBinary(),
- ToPeerID: peerId,
- Answer: &answer,
- OfferID: offerContext.Id,
- }
- data, err := json.Marshal(response)
- if err != nil {
- peerConnection.Close()
- return fmt.Errorf("marshalling response: %w", err)
- }
- tc.mu.Lock()
- defer tc.mu.Unlock()
- if err := tc.writeMessage(data); err != nil {
- peerConnection.Close()
- return fmt.Errorf("writing response: %w", err)
- }
- return nil
-}
-
-func (tc *TrackerClient) handleAnswer(offerId string, answer webrtc.SessionDescription) {
- tc.mu.Lock()
- defer tc.mu.Unlock()
- offer, ok := tc.outboundOffers[offerId]
- if !ok {
- tc.Logger.WithDefaultLevel(log.Warning).Printf("could not find offer for id %+q", offerId)
- return
- }
- // tc.Logger.WithDefaultLevel(log.Debug).Printf("offer %q got answer %v", offerId, answer)
- metrics.Add("outbound offers answered", 1)
- err := offer.peerConnection.SetRemoteDescription(answer)
- if err != nil {
- err = fmt.Errorf("using outbound offer answer: %w", err)
- offer.peerConnection.span.RecordError(err)
- tc.Logger.LevelPrint(log.Error, err)
- return
- }
- delete(tc.outboundOffers, offerId)
- go tc.Announce(tracker.None, offer.infoHash)
-}
+++ /dev/null
-package webtorrent
-
-import (
- "fmt"
- "math"
-
- "github.com/pion/webrtc/v3"
-)
-
-type AnnounceRequest struct {
- Numwant int `json:"numwant"`
- Uploaded int64 `json:"uploaded"`
- Downloaded int64 `json:"downloaded"`
- Left int64 `json:"left"`
- Event string `json:"event,omitempty"`
- Action string `json:"action"`
- InfoHash string `json:"info_hash"`
- PeerID string `json:"peer_id"`
- Offers []Offer `json:"offers"`
-}
-
-type Offer struct {
- OfferID string `json:"offer_id"`
- Offer webrtc.SessionDescription `json:"offer"`
-}
-
-type AnnounceResponse struct {
- InfoHash string `json:"info_hash"`
- Action string `json:"action"`
- Interval *int `json:"interval,omitempty"`
- Complete *int `json:"complete,omitempty"`
- Incomplete *int `json:"incomplete,omitempty"`
- PeerID string `json:"peer_id,omitempty"`
- ToPeerID string `json:"to_peer_id,omitempty"`
- Answer *webrtc.SessionDescription `json:"answer,omitempty"`
- Offer *webrtc.SessionDescription `json:"offer,omitempty"`
- OfferID string `json:"offer_id,omitempty"`
-}
-
-// I wonder if this is a defacto standard way to decode bytes to JSON for webtorrent. I don't really
-// care.
-func binaryToJsonString(b []byte) string {
- var seq []rune
- for _, v := range b {
- seq = append(seq, rune(v))
- }
- return string(seq)
-}
-
-func jsonStringToInfoHash(s string) (ih [20]byte, err error) {
- b, err := decodeJsonByteString(s, ih[:0])
- if err != nil {
- return
- }
- if len(b) != len(ih) {
- err = fmt.Errorf("string decoded to %v bytes", len(b))
- }
- return
-}
-
-func decodeJsonByteString(s string, b []byte) ([]byte, error) {
- defer func() {
- r := recover()
- if r == nil {
- return
- }
- panic(fmt.Sprintf("%q", s))
- }()
- for _, c := range []rune(s) {
- if c < 0 || c > math.MaxUint8 {
- return b, fmt.Errorf("rune out of bounds: %v", c)
- }
- b = append(b, byte(c))
- }
- return b, nil
-}
+++ /dev/null
-package webtorrent
-
-import (
- "context"
- "expvar"
- "fmt"
- "io"
- "sync"
- "time"
-
- "github.com/anacrolix/log"
- "github.com/anacrolix/missinggo/v2/pproffd"
- "github.com/pion/datachannel"
- "github.com/pion/webrtc/v3"
- "go.opentelemetry.io/otel"
- "go.opentelemetry.io/otel/attribute"
- "go.opentelemetry.io/otel/codes"
- "go.opentelemetry.io/otel/trace"
-)
-
-const (
- dataChannelLabel = "webrtc-datachannel"
-)
-
-var (
- metrics = expvar.NewMap("webtorrent")
- api = func() *webrtc.API {
- // Enable the detach API (since it's non-standard but more idiomatic).
- s.DetachDataChannels()
- return webrtc.NewAPI(webrtc.WithSettingEngine(s))
- }()
- config = webrtc.Configuration{ICEServers: []webrtc.ICEServer{{URLs: []string{"stun:stun.l.google.com:19302"}}}}
- newPeerConnectionMu sync.Mutex
-)
-
-type wrappedPeerConnection struct {
- *webrtc.PeerConnection
- closeMu sync.Mutex
- pproffd.CloseWrapper
- span trace.Span
- ctx context.Context
-}
-
-func (me *wrappedPeerConnection) Close() error {
- me.closeMu.Lock()
- defer me.closeMu.Unlock()
- err := me.CloseWrapper.Close()
- me.span.End()
- return err
-}
-
-func newPeerConnection(logger log.Logger) (*wrappedPeerConnection, error) {
- newPeerConnectionMu.Lock()
- defer newPeerConnectionMu.Unlock()
- ctx, span := otel.Tracer(tracerName).Start(context.Background(), "PeerConnection")
- pc, err := api.NewPeerConnection(config)
- if err != nil {
- span.SetStatus(codes.Error, err.Error())
- span.RecordError(err)
- span.End()
- return nil, err
- }
- wpc := &wrappedPeerConnection{
- PeerConnection: pc,
- CloseWrapper: pproffd.NewCloseWrapper(pc),
- ctx: ctx,
- span: span,
- }
- // If the state change handler intends to call Close, it should call it on the wrapper.
- wpc.OnConnectionStateChange(func(state webrtc.PeerConnectionState) {
- logger.Levelf(log.Warning, "webrtc PeerConnection state changed to %v", state)
- span.AddEvent("connection state changed", trace.WithAttributes(attribute.String("state", state.String())))
- })
- return wpc, nil
-}
-
-func setAndGatherLocalDescription(peerConnection *wrappedPeerConnection, sdp webrtc.SessionDescription) (_ webrtc.SessionDescription, err error) {
- gatherComplete := webrtc.GatheringCompletePromise(peerConnection.PeerConnection)
- peerConnection.span.AddEvent("setting local description")
- err = peerConnection.SetLocalDescription(sdp)
- if err != nil {
- err = fmt.Errorf("setting local description: %w", err)
- return
- }
- <-gatherComplete
- peerConnection.span.AddEvent("gathering complete")
- return *peerConnection.LocalDescription(), nil
-}
-
-// newOffer creates a transport and returns a WebRTC offer to be announced. See
-// https://github.com/pion/webrtc/blob/master/examples/data-channels/jsfiddle/main.go for what this is modelled on.
-func (tc *TrackerClient) newOffer(
- logger log.Logger,
- offerId string,
- infoHash [20]byte,
-) (
- peerConnection *wrappedPeerConnection,
- dataChannel *webrtc.DataChannel,
- offer webrtc.SessionDescription,
- err error,
-) {
- peerConnection, err = newPeerConnection(logger)
- if err != nil {
- return
- }
-
- peerConnection.span.SetAttributes(attribute.String(webrtcConnTypeKey, "offer"))
-
- dataChannel, err = peerConnection.CreateDataChannel(dataChannelLabel, nil)
- if err != nil {
- err = fmt.Errorf("creating data channel: %w", err)
- peerConnection.Close()
- }
- initDataChannel(dataChannel, peerConnection, func(dc datachannel.ReadWriteCloser, dcCtx context.Context, dcSpan trace.Span) {
- metrics.Add("outbound offers answered with datachannel", 1)
- tc.mu.Lock()
- tc.stats.ConvertedOutboundConns++
- tc.mu.Unlock()
- tc.OnConn(dc, DataChannelContext{
- OfferId: offerId,
- LocalOffered: true,
- InfoHash: infoHash,
- peerConnection: peerConnection,
- Context: dcCtx,
- Span: dcSpan,
- })
- })
-
- offer, err = peerConnection.CreateOffer(nil)
- if err != nil {
- dataChannel.Close()
- peerConnection.Close()
- return
- }
-
- offer, err = setAndGatherLocalDescription(peerConnection, offer)
- if err != nil {
- dataChannel.Close()
- peerConnection.Close()
- }
- return
-}
-
-type onDetachedDataChannelFunc func(detached datachannel.ReadWriteCloser, ctx context.Context, span trace.Span)
-
-func (tc *TrackerClient) initAnsweringPeerConnection(
- peerConn *wrappedPeerConnection,
- offerContext offerContext,
-) (answer webrtc.SessionDescription, err error) {
- peerConn.span.SetAttributes(attribute.String(webrtcConnTypeKey, "answer"))
-
- timer := time.AfterFunc(30*time.Second, func() {
- peerConn.span.SetStatus(codes.Error, "answer timeout")
- metrics.Add("answering peer connections timed out", 1)
- peerConn.Close()
- })
- peerConn.OnDataChannel(func(d *webrtc.DataChannel) {
- initDataChannel(d, peerConn, func(detached datachannel.ReadWriteCloser, ctx context.Context, span trace.Span) {
- timer.Stop()
- metrics.Add("answering peer connection conversions", 1)
- tc.mu.Lock()
- tc.stats.ConvertedInboundConns++
- tc.mu.Unlock()
- tc.OnConn(detached, DataChannelContext{
- OfferId: offerContext.Id,
- LocalOffered: false,
- InfoHash: offerContext.InfoHash,
- peerConnection: peerConn,
- Context: ctx,
- Span: span,
- })
- })
- })
-
- err = peerConn.SetRemoteDescription(offerContext.SessDesc)
- if err != nil {
- return
- }
- answer, err = peerConn.CreateAnswer(nil)
- if err != nil {
- return
- }
-
- answer, err = setAndGatherLocalDescription(peerConn, answer)
- return
-}
-
-// newAnsweringPeerConnection creates a transport from a WebRTC offer and returns a WebRTC answer to be announced.
-func (tc *TrackerClient) newAnsweringPeerConnection(
- offerContext offerContext,
-) (
- peerConn *wrappedPeerConnection, answer webrtc.SessionDescription, err error,
-) {
- peerConn, err = newPeerConnection(tc.Logger)
- if err != nil {
- err = fmt.Errorf("failed to create new connection: %w", err)
- return
- }
- answer, err = tc.initAnsweringPeerConnection(peerConn, offerContext)
- if err != nil {
- peerConn.span.RecordError(err)
- peerConn.Close()
- }
- return
-}
-
-type datachannelReadWriter interface {
- datachannel.Reader
- datachannel.Writer
- io.Reader
- io.Writer
-}
-
-type ioCloserFunc func() error
-
-func (me ioCloserFunc) Close() error {
- return me()
-}
-
-func initDataChannel(
- dc *webrtc.DataChannel,
- pc *wrappedPeerConnection,
- onOpen onDetachedDataChannelFunc,
-) {
- var span trace.Span
- dc.OnClose(func() {
- span.End()
- })
- dc.OnOpen(func() {
- pc.span.AddEvent("data channel opened")
- var ctx context.Context
- ctx, span = otel.Tracer(tracerName).Start(pc.ctx, "DataChannel")
- raw, err := dc.Detach()
- if err != nil {
- // This shouldn't happen if the API is configured correctly, and we call from OnOpen.
- panic(err)
- }
- onOpen(hookDataChannelCloser(raw, pc, span, dc), ctx, span)
- })
-}
-
-// Hooks the datachannel's Close to Close the owning PeerConnection. The datachannel takes ownership
-// and responsibility for the PeerConnection.
-func hookDataChannelCloser(
- dcrwc datachannel.ReadWriteCloser,
- pc *wrappedPeerConnection,
- dataChannelSpan trace.Span,
- originalDataChannel *webrtc.DataChannel,
-) datachannel.ReadWriteCloser {
- return struct {
- datachannelReadWriter
- io.Closer
- }{
- dcrwc,
- ioCloserFunc(func() error {
- dcrwc.Close()
- pc.Close()
- originalDataChannel.Close()
- dataChannelSpan.End()
- return nil
- }),
- }
-}
+++ /dev/null
-package webtorrent
-
-import (
- "github.com/anacrolix/log"
- qt "github.com/frankban/quicktest"
- "github.com/pion/webrtc/v3"
- "testing"
-)
-
-func TestClosingPeerConnectionDoesNotCloseUnopenedDataChannel(t *testing.T) {
- c := qt.New(t)
- var tc TrackerClient
- pc, dc, _, err := tc.newOffer(log.Default, "", [20]byte{})
- c.Assert(err, qt.IsNil)
- defer pc.Close()
- defer dc.Close()
- peerConnClosed := make(chan struct{})
- pc.OnConnectionStateChange(func(state webrtc.PeerConnectionState) {
- if state == webrtc.PeerConnectionStateClosed {
- close(peerConnClosed)
- }
- })
- dc.OnClose(func() {
- // This should not be called because the DataChannel is never opened.
- t.Fatal("DataChannel.OnClose handler called")
- })
- t.Logf("data channel ready state before close: %v", dc.ReadyState())
- dc.OnError(func(err error) {
- t.Logf("data channel error: %v", err)
- })
- pc.Close()
- c.Check(dc.ReadyState(), qt.Equals, webrtc.DataChannelStateClosed)
- <-peerConnClosed
-}
+++ /dev/null
-package torrent
-
-import (
- "context"
- "fmt"
- "net"
- netHttp "net/http"
- "net/url"
- "sync"
-
- "github.com/anacrolix/log"
- "github.com/gorilla/websocket"
- "github.com/pion/datachannel"
-
- "github.com/anacrolix/torrent/tracker"
- httpTracker "github.com/anacrolix/torrent/tracker/http"
- "github.com/anacrolix/torrent/webtorrent"
-)
-
-type websocketTrackerStatus struct {
- url url.URL
- tc *webtorrent.TrackerClient
-}
-
-func (me websocketTrackerStatus) statusLine() string {
- return fmt.Sprintf("%+v", me.tc.Stats())
-}
-
-func (me websocketTrackerStatus) URL() *url.URL {
- return &me.url
-}
-
-type refCountedWebtorrentTrackerClient struct {
- webtorrent.TrackerClient
- refCount int
-}
-
-type websocketTrackers struct {
- PeerId [20]byte
- Logger log.Logger
- GetAnnounceRequest func(event tracker.AnnounceEvent, infoHash [20]byte) (tracker.AnnounceRequest, error)
- OnConn func(datachannel.ReadWriteCloser, webtorrent.DataChannelContext)
- mu sync.Mutex
- clients map[string]*refCountedWebtorrentTrackerClient
- Proxy httpTracker.ProxyFunc
- DialContext func(ctx context.Context, network, addr string) (net.Conn, error)
- WebsocketTrackerHttpHeader func() netHttp.Header
-}
-
-func (me *websocketTrackers) Get(url string, infoHash [20]byte) (*webtorrent.TrackerClient, func()) {
- me.mu.Lock()
- defer me.mu.Unlock()
- value, ok := me.clients[url]
- if !ok {
- dialer := &websocket.Dialer{Proxy: me.Proxy, NetDialContext: me.DialContext, HandshakeTimeout: websocket.DefaultDialer.HandshakeTimeout}
- value = &refCountedWebtorrentTrackerClient{
- TrackerClient: webtorrent.TrackerClient{
- Dialer: dialer,
- Url: url,
- GetAnnounceRequest: me.GetAnnounceRequest,
- PeerId: me.PeerId,
- OnConn: me.OnConn,
- Logger: me.Logger.WithText(func(m log.Msg) string {
- return fmt.Sprintf("tracker client for %q: %v", url, m)
- }),
- WebsocketTrackerHttpHeader: me.WebsocketTrackerHttpHeader,
- },
- }
- value.TrackerClient.Start(func(err error) {
- if err != nil {
- me.Logger.Printf("error running tracker client for %q: %v", url, err)
- }
- })
- if me.clients == nil {
- me.clients = make(map[string]*refCountedWebtorrentTrackerClient)
- }
- me.clients[url] = value
- }
- value.refCount++
- return &value.TrackerClient, func() {
- me.mu.Lock()
- defer me.mu.Unlock()
- value.TrackerClient.CloseOffersForInfohash(infoHash)
- value.refCount--
- if value.refCount == 0 {
- value.TrackerClient.Close()
- delete(me.clients, url)
- }
- }
-}