1package tcp
2
3import (
4	"context"
5	"net"
6	"time"
7
8	logging "github.com/ipfs/go-log"
9	"github.com/libp2p/go-libp2p-core/peer"
10	"github.com/libp2p/go-libp2p-core/transport"
11	tptu "github.com/libp2p/go-libp2p-transport-upgrader"
12	rtpt "github.com/libp2p/go-reuseport-transport"
13
14	ma "github.com/multiformats/go-multiaddr"
15	mafmt "github.com/multiformats/go-multiaddr-fmt"
16	manet "github.com/multiformats/go-multiaddr-net"
17)
18
19// DefaultConnectTimeout is the (default) maximum amount of time the TCP
20// transport will spend on the initial TCP connect before giving up.
21var DefaultConnectTimeout = 5 * time.Second
22
23var log = logging.Logger("tcp-tpt")
24
25// try to set linger on the connection, if possible.
26func tryLinger(conn net.Conn, sec int) {
27	type canLinger interface {
28		SetLinger(int) error
29	}
30
31	if lingerConn, ok := conn.(canLinger); ok {
32		_ = lingerConn.SetLinger(sec)
33	}
34}
35
36type lingerListener struct {
37	manet.Listener
38	sec int
39}
40
41func (ll *lingerListener) Accept() (manet.Conn, error) {
42	c, err := ll.Listener.Accept()
43	if err != nil {
44		return nil, err
45	}
46	tryLinger(c, ll.sec)
47	return c, nil
48}
49
50// TcpTransport is the TCP transport.
51type TcpTransport struct {
52	// Connection upgrader for upgrading insecure stream connections to
53	// secure multiplex connections.
54	Upgrader *tptu.Upgrader
55
56	// Explicitly disable reuseport.
57	DisableReuseport bool
58
59	// TCP connect timeout
60	ConnectTimeout time.Duration
61
62	reuse rtpt.Transport
63}
64
65var _ transport.Transport = &TcpTransport{}
66
67// NewTCPTransport creates a tcp transport object that tracks dialers and listeners
68// created. It represents an entire tcp stack (though it might not necessarily be)
69func NewTCPTransport(upgrader *tptu.Upgrader) *TcpTransport {
70	return &TcpTransport{Upgrader: upgrader, ConnectTimeout: DefaultConnectTimeout}
71}
72
73var dialMatcher = mafmt.And(mafmt.IP, mafmt.Base(ma.P_TCP))
74
75// CanDial returns true if this transport believes it can dial the given
76// multiaddr.
77func (t *TcpTransport) CanDial(addr ma.Multiaddr) bool {
78	return dialMatcher.Matches(addr)
79}
80
81func (t *TcpTransport) maDial(ctx context.Context, raddr ma.Multiaddr) (manet.Conn, error) {
82	// Apply the deadline iff applicable
83	if t.ConnectTimeout > 0 {
84		deadline := time.Now().Add(t.ConnectTimeout)
85		if d, ok := ctx.Deadline(); !ok || deadline.Before(d) {
86			var cancel func()
87			ctx, cancel = context.WithDeadline(ctx, deadline)
88			defer cancel()
89		}
90	}
91
92	if t.UseReuseport() {
93		return t.reuse.DialContext(ctx, raddr)
94	}
95	var d manet.Dialer
96	return d.DialContext(ctx, raddr)
97}
98
99// Dial dials the peer at the remote address.
100func (t *TcpTransport) Dial(ctx context.Context, raddr ma.Multiaddr, p peer.ID) (transport.CapableConn, error) {
101	conn, err := t.maDial(ctx, raddr)
102	if err != nil {
103		return nil, err
104	}
105	// Set linger to 0 so we never get stuck in the TIME-WAIT state. When
106	// linger is 0, connections are _reset_ instead of closed with a FIN.
107	// This means we can immediately reuse the 5-tuple and reconnect.
108	tryLinger(conn, 0)
109	return t.Upgrader.UpgradeOutbound(ctx, t, conn, p)
110}
111
112// UseReuseport returns true if reuseport is enabled and available.
113func (t *TcpTransport) UseReuseport() bool {
114	return !t.DisableReuseport && ReuseportIsAvailable()
115}
116
117func (t *TcpTransport) maListen(laddr ma.Multiaddr) (manet.Listener, error) {
118	if t.UseReuseport() {
119		return t.reuse.Listen(laddr)
120	}
121	return manet.Listen(laddr)
122}
123
124// Listen listens on the given multiaddr.
125func (t *TcpTransport) Listen(laddr ma.Multiaddr) (transport.Listener, error) {
126	list, err := t.maListen(laddr)
127	if err != nil {
128		return nil, err
129	}
130	list = &lingerListener{list, 0}
131	return t.Upgrader.UpgradeListener(t, list), nil
132}
133
134// Protocols returns the list of terminal protocols this transport can dial.
135func (t *TcpTransport) Protocols() []int {
136	return []int{ma.P_TCP}
137}
138
139// Proxy always returns false for the TCP transport.
140func (t *TcpTransport) Proxy() bool {
141	return false
142}
143
144func (t *TcpTransport) String() string {
145	return "TCP"
146}
147