transport.go 3.66 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
	"net"
Marten Seemann's avatar
Marten Seemann committed
9

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

19
20
21
22
var quicConfig = &quic.Config{
	MaxReceiveStreamFlowControlWindow:     3 * (1 << 20),   // 3 MB
	MaxReceiveConnectionFlowControlWindow: 4.5 * (1 << 20), // 4.5 MB
	Versions: []quic.VersionNumber{101},
23
24
25
26
	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
27
	KeepAlive: true,
28
29
}

30
31
32
33
// The Transport implements the tpt.Transport interface for QUIC connections.
type transport struct {
	privKey   ic.PrivKey
	localPeer peer.ID
34
	tlsConf   *tls.Config
35
	pconn     net.PacketConn
Marten Seemann's avatar
Marten Seemann committed
36
}
Marten Seemann's avatar
Marten Seemann committed
37

38
var _ tpt.Transport = &transport{}
39

40
41
42
43
44
// 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
45
	}
46
47
48
49
	tlsConf, err := generateConfig(key)
	if err != nil {
		return nil, err
	}
50
51
52
53
54
55
56
57
58
59
60

	// create a packet conn for outgoing connections
	addr, err := net.ResolveUDPAddr("udp", "localhost:0")
	if err != nil {
		return nil, err
	}
	conn, err := net.ListenUDP("udp", addr)
	if err != nil {
		return nil, err
	}

61
62
63
	return &transport{
		privKey:   key,
		localPeer: localPeer,
64
		tlsConf:   tlsConf,
65
		pconn:     conn,
66
	}, nil
Marten Seemann's avatar
Marten Seemann committed
67
68
}

69
70
71
72
73
// Dial dials a new QUIC connection
func (t *transport) Dial(ctx context.Context, raddr ma.Multiaddr, p peer.ID) (tpt.Conn, error) {
	_, host, err := manet.DialArgs(raddr)
	if err != nil {
		return nil, err
Marten Seemann's avatar
Marten Seemann committed
74
	}
75
76
77
78
	addr, err := fromQuicMultiaddr(raddr)
	if err != nil {
		return nil, err
	}
79
	var remotePubKey ic.PubKey
80
	tlsConf := t.tlsConf.Clone()
81
82
83
	// 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.
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
	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
102
	}
103
	sess, err := quic.DialContext(ctx, t.pconn, addr, host, tlsConf, quicConfig)
104
105
	if err != nil {
		return nil, err
Marten Seemann's avatar
Marten Seemann committed
106
	}
107
	localMultiaddr, err := toQuicMultiaddr(sess.LocalAddr())
Marten Seemann's avatar
Marten Seemann committed
108
109
110
	if err != nil {
		return nil, err
	}
111
	return &conn{
112
		sess:            sess,
113
114
115
116
117
118
119
120
121
122
123
124
125
		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
126

127
128
// Listen listens for new QUIC connections on the passed multiaddr.
func (t *transport) Listen(addr ma.Multiaddr) (tpt.Listener, error) {
129
	return newListener(addr, t, t.localPeer, t.privKey, t.tlsConf)
Marten Seemann's avatar
Marten Seemann committed
130
131
}

132
133
134
// Proxy returns true if this transport proxies.
func (t *transport) Proxy() bool {
	return false
Marten Seemann's avatar
Marten Seemann committed
135
136
}

137
138
139
140
141
142
143
144
// 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"
}