package tracker
import (
- "bytes"
"context"
- "encoding"
"encoding/binary"
- "fmt"
- "io"
- "math/rand"
- "net"
- "net/url"
- "time"
- "github.com/anacrolix/dht/v2/krpc"
- "github.com/anacrolix/missinggo"
- "github.com/anacrolix/missinggo/pproffd"
- "github.com/pkg/errors"
+ trHttp "github.com/anacrolix/torrent/tracker/http"
+ "github.com/anacrolix/torrent/tracker/udp"
)
-type Action int32
-
-const (
- ActionConnect Action = iota
- ActionAnnounce
- ActionScrape
- ActionError
-
- connectRequestConnectionId = 0x41727101980
-
- // 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 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 {
+ cl *udp.ConnClient
+ requestUri string
}
-type udpAnnounce struct {
- contiguousTimeouts int
- connectionIdReceived time.Time
- connectionId int64
- socket net.Conn
- url url.URL
- a *Announce
+func (c *udpClient) Close() error {
+ return c.cl.Close()
}
-func (c *udpAnnounce) Close() error {
- if c.socket != nil {
- return c.socket.Close()
+func (c *udpClient) Announce(ctx context.Context, req AnnounceRequest, opts trHttp.AnnounceOpt) (res AnnounceResponse, err error) {
+ if req.IPAddress == 0 && opts.ClientIp4 != nil {
+ // I think we're taking bytes in big-endian order (all IPs), and writing it to a natively
+ // ordered uint32. This will be correctly ordered when written back out by the UDP client
+ // later. I'm ignoring the fact that IPv6 announces shouldn't have an IP address, we have a
+ // perfectly good IPv4 address.
+ req.IPAddress = binary.BigEndian.Uint32(opts.ClientIp4.To4())
}
- return nil
-}
-
-func (c *udpAnnounce) ipv6() bool {
- if c.a.UdpNetwork == "udp6" {
- return true
- }
- rip := missinggo.AddrIP(c.socket.RemoteAddr())
- return rip.To16() != nil && rip.To4() == nil
-}
-
-func (c *udpAnnounce) Do(req AnnounceRequest) (res AnnounceResponse, err error) {
- err = c.connect()
- if err != nil {
- return
- }
- reqURI := c.url.RequestURI()
- if c.ipv6() {
- // BEP 15
- req.IPAddress = 0
- } else if req.IPAddress == 0 && c.a.ClientIp4.IP != nil {
- req.IPAddress = binary.BigEndian.Uint32(c.a.ClientIp4.IP.To4())
- }
- // 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(ActionAnnounce, req, options)
+ h, nas, err := c.cl.Announce(ctx, req, udp.Options{RequestUri: c.requestUri})
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
- nas := func() interface {
- encoding.BinaryUnmarshaler
- NodeAddrs() []krpc.NodeAddr
- } {
- if c.ipv6() {
- return &krpc.CompactIPv6NodeAddrs{}
- } else {
- return &krpc.CompactIPv4NodeAddrs{}
- }
- }()
- err = nas.UnmarshalBinary(b.Bytes())
- if err != nil {
- return
- }
for _, cp := range nas.NodeAddrs() {
- res.Peers = append(res.Peers, Peer{}.FromNodeAddr(cp))
- }
- return
-}
-
-// body is the binary serializable request body. trailer is optional data
-// following it, such as for BEP 41.
-func (c *udpAnnounce) write(h *RequestHeader, body interface{}, trailer []byte) (err error) {
- var 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 *udpAnnounce) request(action Action, args interface{}, options []byte) (*bytes.Buffer, error) {
- tid := newTransactionId()
- if err := errors.Wrap(
- c.write(
- &RequestHeader{
- ConnectionId: c.connectionId,
- Action: action,
- TransactionId: tid,
- }, args, options),
- "writing request",
- ); err != nil {
- return nil, err
- }
- c.socket.SetReadDeadline(time.Now().Add(timeout(c.contiguousTimeouts)))
- b := make([]byte, 0x800) // 2KiB
- for {
- var (
- n int
- readErr error
- readDone = make(chan struct{})
- )
- go func() {
- defer close(readDone)
- n, readErr = c.socket.Read(b)
- }()
- ctx := c.a.Context
- if ctx == nil {
- ctx = context.Background()
- }
- select {
- case <-ctx.Done():
- return nil, ctx.Err()
- case <-readDone:
- }
- if opE, ok := readErr.(*net.OpError); ok && opE.Timeout() {
- c.contiguousTimeouts++
- }
- if readErr != nil {
- return nil, errors.Wrap(readErr, "reading from socket")
- }
- buf := bytes.NewBuffer(b[:n])
- var h ResponseHeader
- err := binary.Read(buf, binary.BigEndian, &h)
- switch err {
- default:
- panic(err)
- case io.ErrUnexpectedEOF, io.EOF:
- continue
- case nil:
- }
- if h.TransactionId != tid {
- continue
- }
- c.contiguousTimeouts = 0
- if h.Action == ActionError {
- err = errors.New(buf.String())
- }
- return buf, err
- }
-}
-
-func readBody(r io.Reader, data ...interface{}) (err error) {
- for _, datum := range data {
- err = binary.Read(r, binary.BigEndian, datum)
- if err != nil {
- break
- }
+ res.Peers = append(res.Peers, trHttp.Peer{}.FromNodeAddr(cp))
}
return
}
-
-func (c *udpAnnounce) connected() bool {
- return !c.connectionIdReceived.IsZero() && time.Now().Before(c.connectionIdReceived.Add(time.Minute))
-}
-
-func (c *udpAnnounce) dialNetwork() string {
- if c.a.UdpNetwork != "" {
- return c.a.UdpNetwork
- }
- return "udp"
-}
-
-func (c *udpAnnounce) connect() (err error) {
- if c.connected() {
- return nil
- }
- c.connectionId = connectRequestConnectionId
- if c.socket == nil {
- hmp := missinggo.SplitHostMaybePort(c.url.Host)
- if hmp.NoPort {
- hmp.NoPort = false
- hmp.Port = 80
- }
- c.socket, err = net.Dial(c.dialNetwork(), hmp.String())
- if err != nil {
- return
- }
- c.socket = pproffd.WrapNetConn(c.socket)
- }
- b, err := c.request(ActionConnect, 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
-}
-
-// TODO: Split on IPv6, as BEP 15 says response peer decoding depends on
-// network in use.
-func announceUDP(opt Announce, _url *url.URL) (AnnounceResponse, error) {
- ua := udpAnnounce{
- url: *_url,
- a: &opt,
- }
- defer ua.Close()
- return ua.Do(opt.Request)
-}