package tracker
import (
- "encoding"
"encoding/binary"
- "net"
"net/url"
- "github.com/anacrolix/dht/v2/krpc"
- "github.com/anacrolix/missinggo/v2"
trHttp "github.com/anacrolix/torrent/tracker/http"
"github.com/anacrolix/torrent/tracker/udp"
)
a *Announce
}
-func (c *udpAnnounce) Close() error {
- return nil
-}
-
-func (c *udpAnnounce) ipv6(conn net.Conn) bool {
- if c.a.UdpNetwork == "udp6" {
- return true
- }
- rip := missinggo.AddrIP(conn.RemoteAddr())
- return rip.To16() != nil && rip.To4() == nil
-}
-
func (c *udpAnnounce) Do(req AnnounceRequest) (res AnnounceResponse, err error) {
- conn, err := net.Dial(c.dialNetwork(), c.url.Host)
+ cl, err := udp.NewConnClient(udp.NewConnClientOpts{
+ Network: c.dialNetwork(),
+ Host: c.url.Host,
+ Ipv6: nil,
+ })
if err != nil {
return
}
- defer conn.Close()
- if c.ipv6(conn) {
- // BEP 15
- req.IPAddress = 0
- } else if req.IPAddress == 0 && c.a.ClientIp4.IP != nil {
+ defer cl.Close()
+ if req.IPAddress == 0 && c.a.ClientIp4.IP != 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(c.a.ClientIp4.IP.To4())
}
- d := udp.Dispatcher{}
- go func() {
- for {
- b := make([]byte, 0x800)
- n, err := conn.Read(b)
- if err != nil {
- break
- }
- d.Dispatch(b[:n])
- }
- }()
- cl := udp.Client{
- Dispatcher: &d,
- Writer: conn,
- }
- nas := func() interface {
- encoding.BinaryUnmarshaler
- NodeAddrs() []krpc.NodeAddr
- } {
- if c.ipv6(conn) {
- return &krpc.CompactIPv6NodeAddrs{}
- } else {
- return &krpc.CompactIPv4NodeAddrs{}
- }
- }()
- h, err := cl.Announce(c.a.Context, req, nas, udp.Options{RequestUri: c.url.RequestURI()})
+ h, nas, err := cl.Announce(c.a.Context, req, udp.Options{RequestUri: c.url.RequestURI()})
if err != nil {
return
}
url: *_url,
a: &opt,
}
- defer ua.Close()
return ua.Do(opt.Request)
}
--- /dev/null
+package udp
+
+import (
+ "context"
+ "log"
+ "net"
+
+ "github.com/anacrolix/dht/v2/krpc"
+ "github.com/anacrolix/missinggo/v2"
+)
+
+type NewConnClientOpts struct {
+ Network string
+ Host string
+ Ipv6 *bool
+}
+
+type ConnClient struct {
+ cl Client
+ conn net.Conn
+ d Dispatcher
+ readErr error
+ ipv6 bool
+}
+
+func (cc *ConnClient) reader() {
+ for {
+ b := make([]byte, 0x800)
+ n, err := cc.conn.Read(b)
+ if err != nil {
+ // TODO: Do bad things to the dispatcher, and incoming calls to the client if we have a
+ // read error.
+ cc.readErr = err
+ break
+ }
+ err = cc.d.Dispatch(b[:n])
+ if err != nil {
+ log.Printf("dispatching packet received on %v: %v", cc.conn, err)
+ }
+ }
+}
+
+func ipv6(opt *bool, network string, conn net.Conn) bool {
+ if opt != nil {
+ return *opt
+ }
+ switch network {
+ case "udp4":
+ return false
+ case "udp6":
+ return true
+ }
+ rip := missinggo.AddrIP(conn.RemoteAddr())
+ return rip.To16() != nil && rip.To4() == nil
+}
+
+func NewConnClient(opts NewConnClientOpts) (cc ConnClient, err error) {
+ cc.conn, err = net.Dial(opts.Network, opts.Host)
+ if err != nil {
+ return
+ }
+ cc.ipv6 = ipv6(opts.Ipv6, opts.Network, cc.conn)
+ go cc.reader()
+ cc.cl = Client{
+ Dispatcher: &cc.d,
+ Writer: cc.conn,
+ }
+ return
+}
+
+func (c *ConnClient) Close() error {
+ return c.conn.Close()
+}
+
+func (c *ConnClient) Announce(
+ ctx context.Context, req AnnounceRequest, opts Options,
+) (
+ h AnnounceResponseHeader, nas AnnounceResponsePeers, err error,
+) {
+ nas = func() AnnounceResponsePeers {
+ if c.ipv6 {
+ return &krpc.CompactIPv6NodeAddrs{}
+ } else {
+ return &krpc.CompactIPv4NodeAddrs{}
+ }
+ }()
+ h, err = c.cl.Announce(ctx, req, nas, opts)
+ return
+}