github.com/anacrolix/torrent@v1.61.0/tracker/udp/conn-client.go (about) 1 package udp 2 3 import ( 4 "context" 5 "log/slog" 6 "net" 7 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 *slog.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.Debug("error dispatching received packet", 53 "source addr", addr, 54 "err", err, 55 ) 56 } 57 } 58 } 59 60 func ipv6(opt *bool, network string, remoteAddr net.Addr) bool { 61 if opt != nil { 62 return *opt 63 } 64 switch network { 65 case "udp4": 66 return false 67 case "udp6": 68 return true 69 } 70 rip := missinggo.AddrIP(remoteAddr) 71 return rip.To16() != nil && rip.To4() == nil 72 } 73 74 // Allows a UDP Client to write packets to an endpoint without knowing about the network specifics. 75 type clientWriter struct { 76 pc net.PacketConn 77 network string 78 address string 79 } 80 81 func (me clientWriter) Write(p []byte) (n int, err error) { 82 addr, err := net.ResolveUDPAddr(me.network, me.address) 83 if err != nil { 84 return 85 } 86 return me.pc.WriteTo(p, addr) 87 } 88 89 func NewConnClient(opts NewConnClientOpts) (cc *ConnClient, err error) { 90 var conn net.PacketConn 91 if opts.ListenPacket != nil { 92 conn, err = opts.ListenPacket(opts.Network, ":0") 93 } else { 94 conn, err = net.ListenPacket(opts.Network, ":0") 95 } 96 if err != nil { 97 return 98 } 99 if opts.Logger == nil { 100 opts.Logger = slog.Default() 101 } 102 opts.Logger.Debug("new udp tracker client packet conn", "local addr", conn.LocalAddr()) 103 cc = &ConnClient{ 104 Client: Client{ 105 Writer: clientWriter{ 106 pc: conn, 107 network: opts.Network, 108 address: opts.Host, 109 }, 110 }, 111 conn: conn, 112 newOpts: opts, 113 } 114 cc.Client.Dispatcher = &cc.d 115 go cc.reader() 116 return 117 } 118 119 func (cc *ConnClient) Close() error { 120 cc.closed = true 121 return cc.conn.Close() 122 } 123 124 func (cc *ConnClient) Announce( 125 ctx context.Context, req AnnounceRequest, opts Options, 126 ) ( 127 h AnnounceResponseHeader, nas AnnounceResponsePeers, err error, 128 ) { 129 return cc.Client.Announce(ctx, req, opts, func(addr net.Addr) bool { 130 return ipv6(cc.newOpts.Ipv6, cc.newOpts.Network, addr) 131 }) 132 } 133 134 func (cc *ConnClient) LocalAddr() net.Addr { 135 return cc.conn.LocalAddr() 136 }