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  }