package tracker import ( "bytes" "encoding/binary" "errors" "fmt" "io" "math/rand" "net" "net/url" "time" "github.com/anacrolix/torrent/util" ) type Action int32 const ( Connect Action = iota Announce Scrape Error // BEP 41 optionTypeEndOfOptions = 0 optionTypeNOP = 1 optionTypeURLData = 2 ) type ConnectionRequest struct { ConnectionId int64 Action int32 TransctionId int32 } type ConnectionResponse struct { ConnectionId int64 } type ResponseHeader struct { Action Action TransactionId int32 } type RequestHeader struct { ConnectionId int64 Action Action TransactionId int32 } // 16 bytes type AnnounceResponseHeader struct { Interval int32 Leechers int32 Seeders int32 } func init() { RegisterClientScheme("udp", newClient) } func newClient(url *url.URL) Client { return &udpClient{ url: *url, } } func newTransactionId() int32 { return int32(rand.Uint32()) } func timeout(contiguousTimeouts int) (d time.Duration) { if contiguousTimeouts > 8 { contiguousTimeouts = 8 } d = 15 * time.Second for ; contiguousTimeouts > 0; contiguousTimeouts-- { d *= 2 } return } type udpClient struct { contiguousTimeouts int connectionIdReceived time.Time connectionId int64 socket net.Conn url url.URL } func (c *udpClient) URL() string { return c.url.String() } func (c *udpClient) String() string { return c.URL() } func (c *udpClient) Announce(req *AnnounceRequest) (res AnnounceResponse, err error) { if !c.connected() { err = ErrNotConnected return } reqURI := c.url.RequestURI() // Clearly this limits the request URI to 255 bytes. BEP 41 supports // longer but I'm not fussed. options := append([]byte{optionTypeURLData, byte(len(reqURI))}, []byte(reqURI)...) b, err := c.request(Announce, req, options) if err != nil { return } var h AnnounceResponseHeader err = readBody(b, &h) if err != nil { if err == io.EOF { err = io.ErrUnexpectedEOF } err = fmt.Errorf("error parsing announce response: %s", err) return } res.Interval = h.Interval res.Leechers = h.Leechers res.Seeders = h.Seeders for { var p util.CompactPeer err = binary.Read(b, binary.BigEndian, &p) switch err { case nil: case io.EOF: err = nil fallthrough default: return } res.Peers = append(res.Peers, Peer{ IP: p.IP[:], Port: int(p.Port), }) } } // body is the binary serializable request body. trailer is optional data // following it, such as for BEP 41. func (c *udpClient) write(h *RequestHeader, body interface{}, trailer []byte) (err error) { buf := &bytes.Buffer{} err = binary.Write(buf, binary.BigEndian, h) if err != nil { panic(err) } if body != nil { err = binary.Write(buf, binary.BigEndian, body) if err != nil { panic(err) } } _, err = buf.Write(trailer) if err != nil { return } n, err := c.socket.Write(buf.Bytes()) if err != nil { return } if n != buf.Len() { panic("write should send all or error") } return } func read(r io.Reader, data interface{}) error { return binary.Read(r, binary.BigEndian, data) } func write(w io.Writer, data interface{}) error { return binary.Write(w, binary.BigEndian, data) } // args is the binary serializable request body. trailer is optional data // following it, such as for BEP 41. func (c *udpClient) request(action Action, args interface{}, options []byte) (responseBody *bytes.Reader, err error) { tid := newTransactionId() err = c.write(&RequestHeader{ ConnectionId: c.connectionId, Action: action, TransactionId: tid, }, args, options) if err != nil { return } c.socket.SetReadDeadline(time.Now().Add(timeout(c.contiguousTimeouts))) b := make([]byte, 0x10000) // IP limits packet size to 64KB for { var n int n, err = c.socket.Read(b) if opE, ok := err.(*net.OpError); ok { if opE.Timeout() { c.contiguousTimeouts++ return } } if err != nil { return } buf := bytes.NewBuffer(b[:n]) var h ResponseHeader err = binary.Read(buf, binary.BigEndian, &h) switch err { case io.ErrUnexpectedEOF: continue case nil: default: return } if h.TransactionId != tid { continue } c.contiguousTimeouts = 0 if h.Action == Error { err = errors.New(buf.String()) } responseBody = bytes.NewReader(buf.Bytes()) return } } func readBody(r *bytes.Reader, data ...interface{}) (err error) { for _, datum := range data { err = binary.Read(r, binary.BigEndian, datum) if err != nil { break } } return } func (c *udpClient) connected() bool { return !c.connectionIdReceived.IsZero() && time.Now().Before(c.connectionIdReceived.Add(time.Minute)) } func (c *udpClient) Connect() (err error) { if c.connected() { return nil } c.connectionId = 0x41727101980 if c.socket == nil { c.socket, err = net.Dial("udp", c.url.Host) if err != nil { return } } b, err := c.request(Connect, nil, nil) if err != nil { return } var res ConnectionResponse err = readBody(b, &res) if err != nil { return } c.connectionId = res.ConnectionId c.connectionIdReceived = time.Now() return }