299 lines
6.0 KiB
Go
299 lines
6.0 KiB
Go
|
package tracker
|
||
|
|
||
|
import (
|
||
|
"bytes"
|
||
|
"encoding"
|
||
|
"encoding/binary"
|
||
|
"errors"
|
||
|
"fmt"
|
||
|
"io"
|
||
|
"math/rand"
|
||
|
"net"
|
||
|
"net/url"
|
||
|
"time"
|
||
|
|
||
|
"github.com/anacrolix/dht/krpc"
|
||
|
"github.com/anacrolix/missinggo"
|
||
|
"github.com/anacrolix/missinggo/pproffd"
|
||
|
)
|
||
|
|
||
|
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 udpAnnounce struct {
|
||
|
contiguousTimeouts int
|
||
|
connectionIdReceived time.Time
|
||
|
connectionId int64
|
||
|
socket net.Conn
|
||
|
url url.URL
|
||
|
a *Announce
|
||
|
}
|
||
|
|
||
|
func (c *udpAnnounce) Close() error {
|
||
|
if c.socket != nil {
|
||
|
return c.socket.Close()
|
||
|
}
|
||
|
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)
|
||
|
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) (responseBody *bytes.Buffer, 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, 0x800) // 2KiB
|
||
|
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 == ActionError {
|
||
|
err = errors.New(buf.String())
|
||
|
}
|
||
|
responseBody = buf
|
||
|
return
|
||
|
}
|
||
|
}
|
||
|
|
||
|
func readBody(r io.Reader, data ...interface{}) (err error) {
|
||
|
for _, datum := range data {
|
||
|
err = binary.Read(r, binary.BigEndian, datum)
|
||
|
if err != nil {
|
||
|
break
|
||
|
}
|
||
|
}
|
||
|
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)
|
||
|
}
|