transport.go 4.36 KB
Newer Older
Marten Seemann's avatar
Marten Seemann committed
1
2
3
package libp2pquic

import (
4
	"context"
5
	"crypto/tls"
6
	"crypto/x509"
7
	"errors"
8
	"fmt"
9
	"net"
10
	"sync"
Marten Seemann's avatar
Marten Seemann committed
11

12
13
	ic "github.com/libp2p/go-libp2p-crypto"
	peer "github.com/libp2p/go-libp2p-peer"
Marten Seemann's avatar
Marten Seemann committed
14
	tpt "github.com/libp2p/go-libp2p-transport"
15
	quic "github.com/lucas-clemente/quic-go"
Marten Seemann's avatar
Marten Seemann committed
16
	ma "github.com/multiformats/go-multiaddr"
17
	manet "github.com/multiformats/go-multiaddr-net"
Marten Seemann's avatar
Marten Seemann committed
18
	"github.com/whyrusleeping/mafmt"
Marten Seemann's avatar
Marten Seemann committed
19
20
)

21
22
23
24
var quicConfig = &quic.Config{
	MaxReceiveStreamFlowControlWindow:     3 * (1 << 20),   // 3 MB
	MaxReceiveConnectionFlowControlWindow: 4.5 * (1 << 20), // 4.5 MB
	Versions: []quic.VersionNumber{101},
25
26
27
28
	AcceptCookie: func(clientAddr net.Addr, cookie *quic.Cookie) bool {
		// TODO(#6): require source address validation when under load
		return true
	},
Marten Seemann's avatar
Marten Seemann committed
29
	KeepAlive: true,
30
31
}

32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
type connManager struct {
	connIPv4Once sync.Once
	connIPv4     net.PacketConn

	connIPv6Once sync.Once
	connIPv6     net.PacketConn
}

func (c *connManager) GetConnForAddr(network string) (net.PacketConn, error) {
	switch network {
	case "udp4":
		var err error
		c.connIPv4Once.Do(func() {
			c.connIPv4, err = c.createConn(network, "0.0.0.0:0")
		})
		return c.connIPv4, err
	case "udp6":
		var err error
		c.connIPv6Once.Do(func() {
			c.connIPv6, err = c.createConn(network, ":0")
		})
		return c.connIPv6, err
	default:
		return nil, fmt.Errorf("unsupported network: %s", network)
	}
}

func (c *connManager) createConn(network, host string) (net.PacketConn, error) {
	addr, err := net.ResolveUDPAddr(network, host)
	if err != nil {
		return nil, err
	}
	return net.ListenUDP(network, addr)
}

67
68
// The Transport implements the tpt.Transport interface for QUIC connections.
type transport struct {
69
70
71
72
	privKey     ic.PrivKey
	localPeer   peer.ID
	tlsConf     *tls.Config
	connManager *connManager
Marten Seemann's avatar
Marten Seemann committed
73
}
Marten Seemann's avatar
Marten Seemann committed
74

75
var _ tpt.Transport = &transport{}
76

77
78
79
80
81
// NewTransport creates a new QUIC transport
func NewTransport(key ic.PrivKey) (tpt.Transport, error) {
	localPeer, err := peer.IDFromPrivateKey(key)
	if err != nil {
		return nil, err
Marten Seemann's avatar
Marten Seemann committed
82
	}
83
84
85
86
	tlsConf, err := generateConfig(key)
	if err != nil {
		return nil, err
	}
87

88
	return &transport{
89
90
91
92
		privKey:     key,
		localPeer:   localPeer,
		tlsConf:     tlsConf,
		connManager: &connManager{},
93
	}, nil
Marten Seemann's avatar
Marten Seemann committed
94
95
}

96
97
// Dial dials a new QUIC connection
func (t *transport) Dial(ctx context.Context, raddr ma.Multiaddr, p peer.ID) (tpt.Conn, error) {
98
99
100
101
102
	network, host, err := manet.DialArgs(raddr)
	if err != nil {
		return nil, err
	}
	pconn, err := t.connManager.GetConnForAddr(network)
103
104
	if err != nil {
		return nil, err
Marten Seemann's avatar
Marten Seemann committed
105
	}
106
107
108
109
	addr, err := fromQuicMultiaddr(raddr)
	if err != nil {
		return nil, err
	}
110
	var remotePubKey ic.PubKey
111
	tlsConf := t.tlsConf.Clone()
112
113
114
	// We need to check the peer ID in the VerifyPeerCertificate callback.
	// The tls.Config it is also used for listening, and we might also have concurrent dials.
	// Clone it so we can check for the specific peer ID we're dialing here.
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
	tlsConf.VerifyPeerCertificate = func(rawCerts [][]byte, _ [][]*x509.Certificate) error {
		chain := make([]*x509.Certificate, len(rawCerts))
		for i := 0; i < len(rawCerts); i++ {
			cert, err := x509.ParseCertificate(rawCerts[i])
			if err != nil {
				return err
			}
			chain[i] = cert
		}
		var err error
		remotePubKey, err = getRemotePubKey(chain)
		if err != nil {
			return err
		}
		if !p.MatchesPublicKey(remotePubKey) {
			return errors.New("peer IDs don't match")
		}
		return nil
133
	}
134
	sess, err := quic.DialContext(ctx, pconn, addr, host, tlsConf, quicConfig)
135
136
	if err != nil {
		return nil, err
Marten Seemann's avatar
Marten Seemann committed
137
	}
138
	localMultiaddr, err := toQuicMultiaddr(sess.LocalAddr())
Marten Seemann's avatar
Marten Seemann committed
139
140
141
	if err != nil {
		return nil, err
	}
142
	return &conn{
143
		sess:            sess,
144
145
146
147
148
149
150
151
152
153
154
155
156
		privKey:         t.privKey,
		localPeer:       t.localPeer,
		localMultiaddr:  localMultiaddr,
		remotePubKey:    remotePubKey,
		remotePeerID:    p,
		remoteMultiaddr: raddr,
	}, nil
}

// CanDial determines if we can dial to an address
func (t *transport) CanDial(addr ma.Multiaddr) bool {
	return mafmt.QUIC.Matches(addr)
}
Marten Seemann's avatar
Marten Seemann committed
157

158
159
// Listen listens for new QUIC connections on the passed multiaddr.
func (t *transport) Listen(addr ma.Multiaddr) (tpt.Listener, error) {
160
	return newListener(addr, t, t.localPeer, t.privKey, t.tlsConf)
Marten Seemann's avatar
Marten Seemann committed
161
162
}

163
164
165
// Proxy returns true if this transport proxies.
func (t *transport) Proxy() bool {
	return false
Marten Seemann's avatar
Marten Seemann committed
166
167
}

168
169
170
171
172
173
174
175
// Protocols returns the set of protocols handled by this transport.
func (t *transport) Protocols() []int {
	return []int{ma.P_QUIC}
}

func (t *transport) String() string {
	return "QUIC"
}