1package quic
2
3import (
4	"context"
5	"crypto/tls"
6	"errors"
7	"fmt"
8	"net"
9	"strings"
10
11	"github.com/lucas-clemente/quic-go/internal/protocol"
12	"github.com/lucas-clemente/quic-go/internal/utils"
13	"github.com/lucas-clemente/quic-go/logging"
14)
15
16type client struct {
17	conn sendConn
18	// If the client is created with DialAddr, we create a packet conn.
19	// If it is started with Dial, we take a packet conn as a parameter.
20	createdPacketConn bool
21
22	use0RTT bool
23
24	packetHandlers packetHandlerManager
25
26	tlsConf *tls.Config
27	config  *Config
28
29	srcConnID  protocol.ConnectionID
30	destConnID protocol.ConnectionID
31
32	initialPacketNumber  protocol.PacketNumber
33	hasNegotiatedVersion bool
34	version              protocol.VersionNumber
35
36	handshakeChan chan struct{}
37
38	session quicSession
39
40	tracer logging.ConnectionTracer
41	logger utils.Logger
42}
43
44var (
45	// make it possible to mock connection ID generation in the tests
46	generateConnectionID           = protocol.GenerateConnectionID
47	generateConnectionIDForInitial = protocol.GenerateConnectionIDForInitial
48)
49
50// DialAddr establishes a new QUIC connection to a server.
51// It uses a new UDP connection and closes this connection when the QUIC session is closed.
52// The hostname for SNI is taken from the given address.
53// The tls.Config.CipherSuites allows setting of TLS 1.3 cipher suites.
54func DialAddr(
55	addr string,
56	tlsConf *tls.Config,
57	config *Config,
58) (Session, error) {
59	return DialAddrContext(context.Background(), addr, tlsConf, config)
60}
61
62// DialAddrEarly establishes a new 0-RTT QUIC connection to a server.
63// It uses a new UDP connection and closes this connection when the QUIC session is closed.
64// The hostname for SNI is taken from the given address.
65// The tls.Config.CipherSuites allows setting of TLS 1.3 cipher suites.
66func DialAddrEarly(
67	addr string,
68	tlsConf *tls.Config,
69	config *Config,
70) (EarlySession, error) {
71	return DialAddrEarlyContext(context.Background(), addr, tlsConf, config)
72}
73
74// DialAddrEarlyContext establishes a new 0-RTT QUIC connection to a server using provided context.
75// See DialAddrEarly for details
76func DialAddrEarlyContext(
77	ctx context.Context,
78	addr string,
79	tlsConf *tls.Config,
80	config *Config,
81) (EarlySession, error) {
82	sess, err := dialAddrContext(ctx, addr, tlsConf, config, true)
83	if err != nil {
84		return nil, err
85	}
86	utils.Logger.WithPrefix(utils.DefaultLogger, "client").Debugf("Returning early session")
87	return sess, nil
88}
89
90// DialAddrContext establishes a new QUIC connection to a server using the provided context.
91// See DialAddr for details.
92func DialAddrContext(
93	ctx context.Context,
94	addr string,
95	tlsConf *tls.Config,
96	config *Config,
97) (Session, error) {
98	return dialAddrContext(ctx, addr, tlsConf, config, false)
99}
100
101func dialAddrContext(
102	ctx context.Context,
103	addr string,
104	tlsConf *tls.Config,
105	config *Config,
106	use0RTT bool,
107) (quicSession, error) {
108	udpAddr, err := net.ResolveUDPAddr("udp", addr)
109	if err != nil {
110		return nil, err
111	}
112	udpConn, err := net.ListenUDP("udp", &net.UDPAddr{IP: net.IPv4zero, Port: 0})
113	if err != nil {
114		return nil, err
115	}
116	return dialContext(ctx, udpConn, udpAddr, addr, tlsConf, config, use0RTT, true)
117}
118
119// Dial establishes a new QUIC connection to a server using a net.PacketConn.
120// If the PacketConn satisfies the ECNCapablePacketConn interface (as a net.UDPConn does), ECN support will be enabled.
121// In this case, ReadMsgUDP will be used instead of ReadFrom to read packets.
122// The same PacketConn can be used for multiple calls to Dial and Listen,
123// QUIC connection IDs are used for demultiplexing the different connections.
124// The host parameter is used for SNI.
125// The tls.Config must define an application protocol (using NextProtos).
126func Dial(
127	pconn net.PacketConn,
128	remoteAddr net.Addr,
129	host string,
130	tlsConf *tls.Config,
131	config *Config,
132) (Session, error) {
133	return dialContext(context.Background(), pconn, remoteAddr, host, tlsConf, config, false, false)
134}
135
136// DialEarly establishes a new 0-RTT QUIC connection to a server using a net.PacketConn.
137// The same PacketConn can be used for multiple calls to Dial and Listen,
138// QUIC connection IDs are used for demultiplexing the different connections.
139// The host parameter is used for SNI.
140// The tls.Config must define an application protocol (using NextProtos).
141func DialEarly(
142	pconn net.PacketConn,
143	remoteAddr net.Addr,
144	host string,
145	tlsConf *tls.Config,
146	config *Config,
147) (EarlySession, error) {
148	return DialEarlyContext(context.Background(), pconn, remoteAddr, host, tlsConf, config)
149}
150
151// DialEarlyContext establishes a new 0-RTT QUIC connection to a server using a net.PacketConn using the provided context.
152// See DialEarly for details.
153func DialEarlyContext(
154	ctx context.Context,
155	pconn net.PacketConn,
156	remoteAddr net.Addr,
157	host string,
158	tlsConf *tls.Config,
159	config *Config,
160) (EarlySession, error) {
161	return dialContext(ctx, pconn, remoteAddr, host, tlsConf, config, true, false)
162}
163
164// DialContext establishes a new QUIC connection to a server using a net.PacketConn using the provided context.
165// See Dial for details.
166func DialContext(
167	ctx context.Context,
168	pconn net.PacketConn,
169	remoteAddr net.Addr,
170	host string,
171	tlsConf *tls.Config,
172	config *Config,
173) (Session, error) {
174	return dialContext(ctx, pconn, remoteAddr, host, tlsConf, config, false, false)
175}
176
177func dialContext(
178	ctx context.Context,
179	pconn net.PacketConn,
180	remoteAddr net.Addr,
181	host string,
182	tlsConf *tls.Config,
183	config *Config,
184	use0RTT bool,
185	createdPacketConn bool,
186) (quicSession, error) {
187	if tlsConf == nil {
188		return nil, errors.New("quic: tls.Config not set")
189	}
190	if err := validateConfig(config); err != nil {
191		return nil, err
192	}
193	config = populateClientConfig(config, createdPacketConn)
194	packetHandlers, err := getMultiplexer().AddConn(pconn, config.ConnectionIDLength, config.StatelessResetKey, config.Tracer)
195	if err != nil {
196		return nil, err
197	}
198	c, err := newClient(pconn, remoteAddr, config, tlsConf, host, use0RTT, createdPacketConn)
199	if err != nil {
200		return nil, err
201	}
202	c.packetHandlers = packetHandlers
203
204	if c.config.Tracer != nil {
205		c.tracer = c.config.Tracer.TracerForConnection(protocol.PerspectiveClient, c.destConnID)
206	}
207	if err := c.dial(ctx); err != nil {
208		return nil, err
209	}
210	return c.session, nil
211}
212
213func newClient(
214	pconn net.PacketConn,
215	remoteAddr net.Addr,
216	config *Config,
217	tlsConf *tls.Config,
218	host string,
219	use0RTT bool,
220	createdPacketConn bool,
221) (*client, error) {
222	if tlsConf == nil {
223		tlsConf = &tls.Config{}
224	}
225	if tlsConf.ServerName == "" {
226		sni := host
227		if strings.IndexByte(sni, ':') != -1 {
228			var err error
229			sni, _, err = net.SplitHostPort(sni)
230			if err != nil {
231				return nil, err
232			}
233		}
234
235		tlsConf.ServerName = sni
236	}
237
238	// check that all versions are actually supported
239	if config != nil {
240		for _, v := range config.Versions {
241			if !protocol.IsValidVersion(v) {
242				return nil, fmt.Errorf("%s is not a valid QUIC version", v)
243			}
244		}
245	}
246
247	srcConnID, err := generateConnectionID(config.ConnectionIDLength)
248	if err != nil {
249		return nil, err
250	}
251	destConnID, err := generateConnectionIDForInitial()
252	if err != nil {
253		return nil, err
254	}
255	c := &client{
256		srcConnID:         srcConnID,
257		destConnID:        destConnID,
258		conn:              newSendConn(pconn, remoteAddr),
259		createdPacketConn: createdPacketConn,
260		use0RTT:           use0RTT,
261		tlsConf:           tlsConf,
262		config:            config,
263		version:           config.Versions[0],
264		handshakeChan:     make(chan struct{}),
265		logger:            utils.DefaultLogger.WithPrefix("client"),
266	}
267	return c, nil
268}
269
270func (c *client) dial(ctx context.Context) error {
271	c.logger.Infof("Starting new connection to %s (%s -> %s), source connection ID %s, destination connection ID %s, version %s", c.tlsConf.ServerName, c.conn.LocalAddr(), c.conn.RemoteAddr(), c.srcConnID, c.destConnID, c.version)
272	if c.tracer != nil {
273		c.tracer.StartedConnection(c.conn.LocalAddr(), c.conn.RemoteAddr(), c.version, c.srcConnID, c.destConnID)
274	}
275
276	c.session = newClientSession(
277		c.conn,
278		c.packetHandlers,
279		c.destConnID,
280		c.srcConnID,
281		c.config,
282		c.tlsConf,
283		c.initialPacketNumber,
284		c.version,
285		c.use0RTT,
286		c.hasNegotiatedVersion,
287		c.tracer,
288		c.logger,
289		c.version,
290	)
291	c.packetHandlers.Add(c.srcConnID, c.session)
292
293	errorChan := make(chan error, 1)
294	go func() {
295		err := c.session.run() // returns as soon as the session is closed
296		if !errors.Is(err, errCloseForRecreating{}) && c.createdPacketConn {
297			c.packetHandlers.Destroy()
298		}
299		errorChan <- err
300	}()
301
302	// only set when we're using 0-RTT
303	// Otherwise, earlySessionChan will be nil. Receiving from a nil chan blocks forever.
304	var earlySessionChan <-chan struct{}
305	if c.use0RTT {
306		earlySessionChan = c.session.earlySessionReady()
307	}
308
309	select {
310	case <-ctx.Done():
311		c.session.shutdown()
312		return ctx.Err()
313	case err := <-errorChan:
314		var recreateErr *errCloseForRecreating
315		if errors.As(err, &recreateErr) {
316			c.initialPacketNumber = recreateErr.nextPacketNumber
317			c.version = recreateErr.nextVersion
318			c.hasNegotiatedVersion = true
319			return c.dial(ctx)
320		}
321		return err
322	case <-earlySessionChan:
323		// ready to send 0-RTT data
324		return nil
325	case <-c.session.HandshakeComplete().Done():
326		// handshake successfully completed
327		return nil
328	}
329}
330