9 "github.com/anacrolix/log"
10 "github.com/anacrolix/missinggo/perf"
11 "github.com/anacrolix/missinggo/v2"
12 "github.com/pkg/errors"
15 type Listener interface {
16 // Accept waits for and returns the next connection to the listener.
17 Accept() (net.Conn, error)
19 // Addr returns the listener's network address.
23 type socket interface {
29 func listen(n network, addr string, f firewallCallback, logger log.Logger) (socket, error) {
32 return listenTcp(n.String(), addr)
34 return listenUtp(n.String(), addr, f, logger)
40 // Dialing TCP from a local port limits us to a single outgoing TCP connection to each remote
41 // client. Instead, this should be a last resort if we need to use holepunching, and only then to
42 // connect to other clients that actually try to holepunch TCP.
43 const dialTcpFromListenPort = false
45 var tcpListenConfig = net.ListenConfig{
46 Control: func(network, address string, c syscall.RawConn) (err error) {
47 controlErr := c.Control(func(fd uintptr) {
48 if dialTcpFromListenPort {
49 err = setReusePortSockOpts(fd)
58 // BitTorrent connections manage their own keep-alives.
62 func listenTcp(network, address string) (s socket, err error) {
63 l, err := tcpListenConfig.Listen(context.Background(), network, address)
67 netDialer := net.Dialer{
68 // We don't want fallback, as we explicitly manage the IPv4/IPv6 distinction ourselves,
69 // although it's probably not triggered as I think the network is already constrained to
70 // tcp4 or tcp6 at this point.
72 // BitTorrent connections manage their own keepalives.
73 KeepAlive: tcpListenConfig.KeepAlive,
74 Control: func(network, address string, c syscall.RawConn) (err error) {
75 controlErr := c.Control(func(fd uintptr) {
76 err = setSockNoLinger(fd)
78 // Failing to disable linger is undesirable, but not fatal.
79 log.Levelf(log.Debug, "error setting linger socket option on tcp socket: %v", err)
82 // This is no longer required I think, see
83 // https://github.com/anacrolix/torrent/discussions/856. I added this originally to
84 // allow dialling out from the client's listen port, but that doesn't really work. I
85 // think Linux older than ~2013 doesn't support SO_REUSEPORT.
86 if dialTcpFromListenPort {
87 err = setReusePortSockOpts(fd)
96 if dialTcpFromListenPort {
97 netDialer.LocalAddr = l.Addr()
101 NetworkDialer: NetworkDialer{
109 type tcpSocket struct {
114 func listenAll(networks []network, getHost func(string) string, port int, f firewallCallback, logger log.Logger) ([]socket, error) {
115 if len(networks) == 0 {
118 var nahs []networkAndHost
119 for _, n := range networks {
120 nahs = append(nahs, networkAndHost{n, getHost(n.String())})
123 ss, retry, err := listenAllRetry(nahs, port, f, logger)
130 type networkAndHost struct {
135 func listenAllRetry(nahs []networkAndHost, port int, f firewallCallback, logger log.Logger) (ss []socket, retry bool, err error) {
136 ss = make([]socket, 1, len(nahs))
137 portStr := strconv.FormatInt(int64(port), 10)
138 ss[0], err = listen(nahs[0].Network, net.JoinHostPort(nahs[0].Host, portStr), f, logger)
140 return nil, false, errors.Wrap(err, "first listen")
143 if err != nil || retry {
144 for _, s := range ss {
150 portStr = strconv.FormatInt(int64(missinggo.AddrPort(ss[0].Addr())), 10)
151 for _, nah := range nahs[1:] {
152 s, err := listen(nah.Network, net.JoinHostPort(nah.Host, portStr), f, logger)
155 missinggo.IsAddrInUse(err) && port == 0,
156 errors.Wrap(err, "subsequent listen")
163 // This isn't aliased from go-libutp since that assumes CGO.
164 type firewallCallback func(net.Addr) bool
166 func listenUtp(network, addr string, fc firewallCallback, logger log.Logger) (socket, error) {
167 us, err := NewUtpSocket(network, addr, fc, logger)
168 return utpSocketSocket{us, network}, err
171 // utpSocket wrapper, additionally wrapped for the torrent package's socket interface.
172 type utpSocketSocket struct {
177 func (me utpSocketSocket) DialerNetwork() string {
181 func (me utpSocketSocket) Dial(ctx context.Context, addr string) (conn net.Conn, err error) {
182 defer perf.ScopeTimerErr(&err)()
183 return me.utpSocket.DialContext(ctx, me.network, addr)