transport.go 4.54 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
var quicConfig = &quic.Config{
22
	Versions:                              []quic.VersionNumber{quic.VersionMilestone0_9_0},
23
24
	MaxIncomingStreams:                    1000,
	MaxIncomingUniStreams:                 -1,              // disable unidirectional streams
25
26
	MaxReceiveStreamFlowControlWindow:     3 * (1 << 20),   // 3 MB
	MaxReceiveConnectionFlowControlWindow: 4.5 * (1 << 20), // 4.5 MB
27
28
29
30
	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
31
	KeepAlive: true,
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
67
68
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)
}

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

77
var _ tpt.Transport = &transport{}
78

79
80
81
82
83
// 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
84
	}
85
86
87
88
	tlsConf, err := generateConfig(key)
	if err != nil {
		return nil, err
	}
89

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

98
99
// Dial dials a new QUIC connection
func (t *transport) Dial(ctx context.Context, raddr ma.Multiaddr, p peer.ID) (tpt.Conn, error) {
100
101
102
103
104
	network, host, err := manet.DialArgs(raddr)
	if err != nil {
		return nil, err
	}
	pconn, err := t.connManager.GetConnForAddr(network)
105
106
	if err != nil {
		return nil, err
Marten Seemann's avatar
Marten Seemann committed
107
	}
108
109
110
111
	addr, err := fromQuicMultiaddr(raddr)
	if err != nil {
		return nil, err
	}
112
	var remotePubKey ic.PubKey
113
	tlsConf := t.tlsConf.Clone()
114
115
116
	// 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.
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
	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
135
	}
136
	sess, err := quic.DialContext(ctx, pconn, addr, host, tlsConf, quicConfig)
137
138
	if err != nil {
		return nil, err
Marten Seemann's avatar
Marten Seemann committed
139
	}
140
	localMultiaddr, err := toQuicMultiaddr(sess.LocalAddr())
Marten Seemann's avatar
Marten Seemann committed
141
142
143
	if err != nil {
		return nil, err
	}
144
	return &conn{
145
		sess:            sess,
146
147
148
149
150
151
152
153
154
155
156
157
158
		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
159

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

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

170
171
172
173
174
175
176
177
// 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"
}