]> Sergey Matveev's repositories - btrtrc.git/blob - tracker/udp/conn-client.go
fix: udp tracker panic
[btrtrc.git] / tracker / udp / conn-client.go
1 package udp
2
3 import (
4         "context"
5         "net"
6
7         "github.com/anacrolix/log"
8         "github.com/anacrolix/missinggo/v2"
9 )
10
11 type listenPacketFunc func(network, addr string) (net.PacketConn, error)
12
13 type NewConnClientOpts struct {
14         // The network to operate to use, such as "udp4", "udp", "udp6".
15         Network string
16         // Tracker address
17         Host string
18         // If non-nil, forces either IPv4 or IPv6 in the UDP tracker wire protocol.
19         Ipv6 *bool
20         // Logger to use for internal errors.
21         Logger log.Logger
22         // Custom function to use as a substitute for net.ListenPacket
23         ListenPacket listenPacketFunc
24 }
25
26 // Manages a Client with a specific connection.
27 type ConnClient struct {
28         Client  Client
29         conn    net.PacketConn
30         d       Dispatcher
31         readErr error
32         closed  bool
33         newOpts NewConnClientOpts
34 }
35
36 func (cc *ConnClient) reader() {
37         b := make([]byte, 0x800)
38         for {
39                 n, addr, err := cc.conn.ReadFrom(b)
40                 if err != nil {
41                         // TODO: Do bad things to the dispatcher, and incoming calls to the client if we have a
42                         // read error.
43                         cc.readErr = err
44                         if !cc.closed {
45                                 // don't panic, just close the connection, fix https://github.com/anacrolix/torrent/issues/845
46                                 cc.Close()
47                         }
48                         break
49                 }
50                 err = cc.d.Dispatch(b[:n], addr)
51                 if err != nil {
52                         cc.newOpts.Logger.Levelf(log.Debug, "dispatching packet received on %v: %v", cc.conn.LocalAddr(), err)
53                 }
54         }
55 }
56
57 func ipv6(opt *bool, network string, remoteAddr net.Addr) bool {
58         if opt != nil {
59                 return *opt
60         }
61         switch network {
62         case "udp4":
63                 return false
64         case "udp6":
65                 return true
66         }
67         rip := missinggo.AddrIP(remoteAddr)
68         return rip.To16() != nil && rip.To4() == nil
69 }
70
71 // Allows a UDP Client to write packets to an endpoint without knowing about the network specifics.
72 type clientWriter struct {
73         pc      net.PacketConn
74         network string
75         address string
76 }
77
78 func (me clientWriter) Write(p []byte) (n int, err error) {
79         addr, err := net.ResolveUDPAddr(me.network, me.address)
80         if err != nil {
81                 return
82         }
83         return me.pc.WriteTo(p, addr)
84 }
85
86 func NewConnClient(opts NewConnClientOpts) (cc *ConnClient, err error) {
87         var conn net.PacketConn
88         if opts.ListenPacket != nil {
89                 conn, err = opts.ListenPacket(opts.Network, ":0")
90         } else {
91                 conn, err = net.ListenPacket(opts.Network, ":0")
92         }
93
94         if err != nil {
95                 return
96         }
97         if opts.Logger.IsZero() {
98                 opts.Logger = log.Default
99         }
100         cc = &ConnClient{
101                 Client: Client{
102                         Writer: clientWriter{
103                                 pc:      conn,
104                                 network: opts.Network,
105                                 address: opts.Host,
106                         },
107                 },
108                 conn:    conn,
109                 newOpts: opts,
110         }
111         cc.Client.Dispatcher = &cc.d
112         go cc.reader()
113         return
114 }
115
116 func (cc *ConnClient) Close() error {
117         cc.closed = true
118         return cc.conn.Close()
119 }
120
121 func (cc *ConnClient) Announce(
122         ctx context.Context, req AnnounceRequest, opts Options,
123 ) (
124         h AnnounceResponseHeader, nas AnnounceResponsePeers, err error,
125 ) {
126         return cc.Client.Announce(ctx, req, opts, func(addr net.Addr) bool {
127                 return ipv6(cc.newOpts.Ipv6, cc.newOpts.Network, addr)
128         })
129 }
130
131 func (cc *ConnClient) LocalAddr() net.Addr {
132         return cc.conn.LocalAddr()
133 }