15 "github.com/anacrolix/dht/v2/krpc"
16 "github.com/anacrolix/missinggo"
17 "github.com/anacrolix/missinggo/pproffd"
18 "github.com/pkg/errors"
24 ActionConnect Action = iota
29 connectRequestConnectionId = 0x41727101980
32 optionTypeEndOfOptions = 0
37 type ConnectionRequest struct {
43 type ConnectionResponse struct {
47 type ResponseHeader struct {
52 type RequestHeader struct {
58 type AnnounceResponseHeader struct {
64 func newTransactionId() int32 {
65 return int32(rand.Uint32())
68 func timeout(contiguousTimeouts int) (d time.Duration) {
69 if contiguousTimeouts > 8 {
70 contiguousTimeouts = 8
73 for ; contiguousTimeouts > 0; contiguousTimeouts-- {
79 type udpAnnounce struct {
80 contiguousTimeouts int
81 connectionIdReceived time.Time
88 func (c *udpAnnounce) Close() error {
90 return c.socket.Close()
95 func (c *udpAnnounce) ipv6() bool {
96 if c.a.UdpNetwork == "udp6" {
99 rip := missinggo.AddrIP(c.socket.RemoteAddr())
100 return rip.To16() != nil && rip.To4() == nil
103 func (c *udpAnnounce) Do(req AnnounceRequest) (res AnnounceResponse, err error) {
108 reqURI := c.url.RequestURI()
112 } else if req.IPAddress == 0 && c.a.ClientIp4.IP != nil {
113 req.IPAddress = binary.BigEndian.Uint32(c.a.ClientIp4.IP.To4())
115 // Clearly this limits the request URI to 255 bytes. BEP 41 supports
116 // longer but I'm not fussed.
117 options := append([]byte{optionTypeURLData, byte(len(reqURI))}, []byte(reqURI)...)
118 vars.Add("udp tracker announces", 1)
119 b, err := c.request(ActionAnnounce, req, options)
123 var h AnnounceResponseHeader
124 err = readBody(b, &h)
127 err = io.ErrUnexpectedEOF
129 err = fmt.Errorf("error parsing announce response: %s", err)
132 res.Interval = h.Interval
133 res.Leechers = h.Leechers
134 res.Seeders = h.Seeders
135 nas := func() interface {
136 encoding.BinaryUnmarshaler
137 NodeAddrs() []krpc.NodeAddr
140 return &krpc.CompactIPv6NodeAddrs{}
142 return &krpc.CompactIPv4NodeAddrs{}
145 err = nas.UnmarshalBinary(b.Bytes())
149 for _, cp := range nas.NodeAddrs() {
150 res.Peers = append(res.Peers, Peer{}.FromNodeAddr(cp))
155 // body is the binary serializable request body. trailer is optional data
156 // following it, such as for BEP 41.
157 func (c *udpAnnounce) write(h *RequestHeader, body interface{}, trailer []byte) (err error) {
159 err = binary.Write(&buf, binary.BigEndian, h)
164 err = binary.Write(&buf, binary.BigEndian, body)
169 _, err = buf.Write(trailer)
173 n, err := c.socket.Write(buf.Bytes())
178 panic("write should send all or error")
183 func read(r io.Reader, data interface{}) error {
184 return binary.Read(r, binary.BigEndian, data)
187 func write(w io.Writer, data interface{}) error {
188 return binary.Write(w, binary.BigEndian, data)
191 // args is the binary serializable request body. trailer is optional data
192 // following it, such as for BEP 41.
193 func (c *udpAnnounce) request(action Action, args interface{}, options []byte) (*bytes.Buffer, error) {
194 tid := newTransactionId()
195 if err := errors.Wrap(
198 ConnectionId: c.connectionId,
206 c.socket.SetReadDeadline(time.Now().Add(timeout(c.contiguousTimeouts)))
207 b := make([]byte, 0x800) // 2KiB
212 readDone = make(chan struct{})
215 defer close(readDone)
216 n, readErr = c.socket.Read(b)
220 ctx = context.Background()
224 return nil, ctx.Err()
227 if opE, ok := readErr.(*net.OpError); ok && opE.Timeout() {
228 c.contiguousTimeouts++
231 return nil, errors.Wrap(readErr, "reading from socket")
233 buf := bytes.NewBuffer(b[:n])
235 err := binary.Read(buf, binary.BigEndian, &h)
239 case io.ErrUnexpectedEOF, io.EOF:
243 if h.TransactionId != tid {
246 c.contiguousTimeouts = 0
247 if h.Action == ActionError {
248 err = errors.New(buf.String())
254 func readBody(r io.Reader, data ...interface{}) (err error) {
255 for _, datum := range data {
256 err = binary.Read(r, binary.BigEndian, datum)
264 func (c *udpAnnounce) connected() bool {
265 return !c.connectionIdReceived.IsZero() && time.Now().Before(c.connectionIdReceived.Add(time.Minute))
268 func (c *udpAnnounce) dialNetwork() string {
269 if c.a.UdpNetwork != "" {
270 return c.a.UdpNetwork
275 func (c *udpAnnounce) connect() (err error) {
279 c.connectionId = connectRequestConnectionId
281 hmp := missinggo.SplitHostMaybePort(c.url.Host)
286 c.socket, err = net.Dial(c.dialNetwork(), hmp.String())
290 c.socket = pproffd.WrapNetConn(c.socket)
292 vars.Add("udp tracker connects", 1)
293 b, err := c.request(ActionConnect, nil, nil)
297 var res ConnectionResponse
298 err = readBody(b, &res)
302 c.connectionId = res.ConnectionId
303 c.connectionIdReceived = time.Now()
307 // TODO: Split on IPv6, as BEP 15 says response peer decoding depends on
309 func announceUDP(opt Announce, _url *url.URL) (AnnounceResponse, error) {
315 return ua.Do(opt.Request)