tracker/tracker.go | 2 +- tracker/udp/udp_tracker.go | 42 ++++++++++++++++++++++++++++++++++++------ tracker/udp/udp_tracker_test.go | 52 ++++++++++++++++++++++++++++++++++++++++++++++++++++ diff --git a/tracker/tracker.go b/tracker/tracker.go index b220c292d45950aac4a9e21d67af8297cc1df34c..888228ec04bfc25054a9df58ec55d3331720143e 100644 --- a/tracker/tracker.go +++ b/tracker/tracker.go @@ -17,7 +17,7 @@ IPAddress int32 Key int32 NumWant int32 // How many peer addresses are desired. -1 for default. Port int16 -} +} // 82 bytes type AnnounceResponse struct { Interval int32 // Minimum seconds the local peer should wait before next announce. diff --git a/tracker/udp/udp_tracker.go b/tracker/udp/udp_tracker.go index 94bb1d78cd0d6d5cf95bc3e2b525bedde4615dfb..3ba30a84800019db275700e59ffa0e009ca458f3 100644 --- a/tracker/udp/udp_tracker.go +++ b/tracker/udp/udp_tracker.go @@ -4,6 +4,7 @@ import ( "bytes" "encoding/binary" "errors" + "fmt" "io" "math/rand" "net" @@ -20,6 +21,11 @@ Connect Action = iota Announce Scrape Error + + // BEP 41 + optionTypeEndOfOptions = 0 + optionTypeNOP = 1 + optionTypeURLData = 2 ) type ConnectionRequest struct { @@ -41,7 +47,7 @@ type RequestHeader struct { ConnectionId int64 Action Action TransactionId int32 -} +} // 16 bytes type AnnounceResponseHeader struct { Interval int32 @@ -100,13 +106,21 @@ if !c.connected() { err = tracker.ErrNotConnected return } - b, err := c.request(Announce, req) + 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 @@ -130,7 +144,9 @@ }) } } -func (c *client) write(h *RequestHeader, body interface{}) (err error) { +// body is the binary serializable request body. trailer is optional data +// following it, such as for BEP 41. +func (c *client) write(h *RequestHeader, body interface{}, trailer []byte) (err error) { buf := &bytes.Buffer{} err = binary.Write(buf, binary.BigEndian, h) if err != nil { @@ -141,6 +157,10 @@ 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 { @@ -152,13 +172,23 @@ } return } -func (c *client) request(action Action, args interface{}) (responseBody *bytes.Reader, err error) { +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 *client) 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) + }, args, options) if err != nil { return } @@ -223,7 +253,7 @@ if err != nil { return } } - b, err := c.request(Connect, nil) + b, err := c.request(Connect, nil, nil) if err != nil { return } diff --git a/tracker/udp/udp_tracker_test.go b/tracker/udp/udp_tracker_test.go index 44ba25fe2ed8a75321219d25cc8b6785e471f756..488cd49a067da9f17382908807d981d2cb8ea523 100644 --- a/tracker/udp/udp_tracker_test.go +++ b/tracker/udp/udp_tracker_test.go @@ -5,7 +5,10 @@ "bytes" "crypto/rand" "encoding/binary" "io" + "io/ioutil" + "log" "net" + "net/url" "sync" "syscall" "testing" @@ -144,3 +147,52 @@ wg.Add(1) } wg.Wait() } + +// Check that URLPath option is done correctly. +func TestURLPathOption(t *testing.T) { + conn, err := net.ListenUDP("udp", nil) + if err != nil { + panic(err) + } + defer conn.Close() + cl := newClient(&url.URL{ + Host: conn.LocalAddr().String(), + Path: "/announce", + }) + go func() { + err = cl.Connect() + if err != nil { + t.Fatal(err) + } + log.Print("connected") + _, err = cl.Announce(&tracker.AnnounceRequest{}) + if err != nil { + t.Fatal(err) + } + }() + var b [512]byte + _, addr, _ := conn.ReadFrom(b[:]) + r := bytes.NewReader(b[:]) + var h RequestHeader + read(r, &h) + w := &bytes.Buffer{} + write(w, ResponseHeader{ + TransactionId: h.TransactionId, + }) + write(w, ConnectionResponse{42}) + conn.WriteTo(w.Bytes(), addr) + n, _, _ := conn.ReadFrom(b[:]) + r = bytes.NewReader(b[:n]) + read(r, &h) + read(r, &tracker.AnnounceRequest{}) + all, _ := ioutil.ReadAll(r) + if string(all) != "\x02\x09/announce" { + t.FailNow() + } + w = &bytes.Buffer{} + write(w, ResponseHeader{ + TransactionId: h.TransactionId, + }) + write(w, AnnounceResponseHeader{}) + conn.WriteTo(w.Bytes(), addr) +}