15 "github.com/anacrolix/dht/krpc"
16 "github.com/anacrolix/missinggo"
17 "github.com/anacrolix/missinggo/pproffd"
23 ActionConnect Action = iota
28 connectRequestConnectionId = 0x41727101980
31 optionTypeEndOfOptions = 0
36 type ConnectionRequest struct {
42 type ConnectionResponse struct {
46 type ResponseHeader struct {
51 type RequestHeader struct {
57 type AnnounceResponseHeader struct {
63 func newTransactionId() int32 {
64 return int32(rand.Uint32())
67 func timeout(contiguousTimeouts int) (d time.Duration) {
68 if contiguousTimeouts > 8 {
69 contiguousTimeouts = 8
72 for ; contiguousTimeouts > 0; contiguousTimeouts-- {
78 type udpAnnounce struct {
79 contiguousTimeouts int
80 connectionIdReceived time.Time
87 func (c *udpAnnounce) Close() error {
89 return c.socket.Close()
94 func (c *udpAnnounce) ipv6() bool {
95 if c.a.UdpNetwork == "udp6" {
98 rip := missinggo.AddrIP(c.socket.RemoteAddr())
99 return rip.To16() != nil && rip.To4() == nil
102 func (c *udpAnnounce) Do(req AnnounceRequest) (res AnnounceResponse, err error) {
107 reqURI := c.url.RequestURI()
111 } else if req.IPAddress == 0 && c.a.ClientIp4.IP != nil {
112 req.IPAddress = binary.BigEndian.Uint32(c.a.ClientIp4.IP.To4())
114 // Clearly this limits the request URI to 255 bytes. BEP 41 supports
115 // longer but I'm not fussed.
116 options := append([]byte{optionTypeURLData, byte(len(reqURI))}, []byte(reqURI)...)
117 b, err := c.request(ActionAnnounce, req, options)
121 var h AnnounceResponseHeader
122 err = readBody(b, &h)
125 err = io.ErrUnexpectedEOF
127 err = fmt.Errorf("error parsing announce response: %s", err)
130 res.Interval = h.Interval
131 res.Leechers = h.Leechers
132 res.Seeders = h.Seeders
133 nas := func() interface {
134 encoding.BinaryUnmarshaler
135 NodeAddrs() []krpc.NodeAddr
138 return &krpc.CompactIPv6NodeAddrs{}
140 return &krpc.CompactIPv4NodeAddrs{}
143 err = nas.UnmarshalBinary(b.Bytes())
147 for _, cp := range nas.NodeAddrs() {
148 res.Peers = append(res.Peers, Peer{}.FromNodeAddr(cp))
153 // body is the binary serializable request body. trailer is optional data
154 // following it, such as for BEP 41.
155 func (c *udpAnnounce) write(h *RequestHeader, body interface{}, trailer []byte) (err error) {
157 err = binary.Write(&buf, binary.BigEndian, h)
162 err = binary.Write(&buf, binary.BigEndian, body)
167 _, err = buf.Write(trailer)
171 n, err := c.socket.Write(buf.Bytes())
176 panic("write should send all or error")
181 func read(r io.Reader, data interface{}) error {
182 return binary.Read(r, binary.BigEndian, data)
185 func write(w io.Writer, data interface{}) error {
186 return binary.Write(w, binary.BigEndian, data)
189 // args is the binary serializable request body. trailer is optional data
190 // following it, such as for BEP 41.
191 func (c *udpAnnounce) request(action Action, args interface{}, options []byte) (responseBody *bytes.Buffer, err error) {
192 tid := newTransactionId()
193 err = c.write(&RequestHeader{
194 ConnectionId: c.connectionId,
201 c.socket.SetReadDeadline(time.Now().Add(timeout(c.contiguousTimeouts)))
202 b := make([]byte, 0x800) // 2KiB
205 n, err = c.socket.Read(b)
206 if opE, ok := err.(*net.OpError); ok {
208 c.contiguousTimeouts++
215 buf := bytes.NewBuffer(b[:n])
217 err = binary.Read(buf, binary.BigEndian, &h)
219 case io.ErrUnexpectedEOF:
225 if h.TransactionId != tid {
228 c.contiguousTimeouts = 0
229 if h.Action == ActionError {
230 err = errors.New(buf.String())
237 func readBody(r io.Reader, data ...interface{}) (err error) {
238 for _, datum := range data {
239 err = binary.Read(r, binary.BigEndian, datum)
247 func (c *udpAnnounce) connected() bool {
248 return !c.connectionIdReceived.IsZero() && time.Now().Before(c.connectionIdReceived.Add(time.Minute))
251 func (c *udpAnnounce) dialNetwork() string {
252 if c.a.UdpNetwork != "" {
253 return c.a.UdpNetwork
258 func (c *udpAnnounce) connect() (err error) {
262 c.connectionId = connectRequestConnectionId
264 hmp := missinggo.SplitHostMaybePort(c.url.Host)
269 c.socket, err = net.Dial(c.dialNetwork(), hmp.String())
273 c.socket = pproffd.WrapNetConn(c.socket)
275 b, err := c.request(ActionConnect, nil, nil)
279 var res ConnectionResponse
280 err = readBody(b, &res)
284 c.connectionId = res.ConnectionId
285 c.connectionIdReceived = time.Now()
289 // TODO: Split on IPv6, as BEP 15 says response peer decoding depends on
291 func announceUDP(opt Announce, _url *url.URL) (AnnounceResponse, error) {
297 return ua.Do(opt.Request)