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