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. If
120// the PacketConn satisfies the OOBCapablePacketConn interface (as a net.UDPConn
121// does), ECN and packet info support will be enabled. In this case, ReadMsgUDP
122// and WriteMsgUDP will be used instead of ReadFrom and WriteTo to read/write
123// packets. The same PacketConn can be used for multiple calls to Dial and
124// Listen, QUIC connection IDs are used for demultiplexing the different
125// connections. The host parameter is used for SNI. The tls.Config must define
126// an application protocol (using NextProtos).
127func Dial(
128	pconn net.PacketConn,
129	remoteAddr net.Addr,
130	host string,
131	tlsConf *tls.Config,
132	config *Config,
133) (Session, error) {
134	return dialContext(context.Background(), pconn, remoteAddr, host, tlsConf, config, false, false)
135}
136
137// DialEarly establishes a new 0-RTT QUIC connection to a server using a net.PacketConn.
138// The same PacketConn can be used for multiple calls to Dial and Listen,
139// QUIC connection IDs are used for demultiplexing the different connections.
140// The host parameter is used for SNI.
141// The tls.Config must define an application protocol (using NextProtos).
142func DialEarly(
143	pconn net.PacketConn,
144	remoteAddr net.Addr,
145	host string,
146	tlsConf *tls.Config,
147	config *Config,
148) (EarlySession, error) {
149	return DialEarlyContext(context.Background(), pconn, remoteAddr, host, tlsConf, config)
150}
151
152// DialEarlyContext establishes a new 0-RTT QUIC connection to a server using a net.PacketConn using the provided context.
153// See DialEarly for details.
154func DialEarlyContext(
155	ctx context.Context,
156	pconn net.PacketConn,
157	remoteAddr net.Addr,
158	host string,
159	tlsConf *tls.Config,
160	config *Config,
161) (EarlySession, error) {
162	return dialContext(ctx, pconn, remoteAddr, host, tlsConf, config, true, false)
163}
164
165// DialContext establishes a new QUIC connection to a server using a net.PacketConn using the provided context.
166// See Dial for details.
167func DialContext(
168	ctx context.Context,
169	pconn net.PacketConn,
170	remoteAddr net.Addr,
171	host string,
172	tlsConf *tls.Config,
173	config *Config,
174) (Session, error) {
175	return dialContext(ctx, pconn, remoteAddr, host, tlsConf, config, false, false)
176}
177
178func dialContext(
179	ctx context.Context,
180	pconn net.PacketConn,
181	remoteAddr net.Addr,
182	host string,
183	tlsConf *tls.Config,
184	config *Config,
185	use0RTT bool,
186	createdPacketConn bool,
187) (quicSession, error) {
188	if tlsConf == nil {
189		return nil, errors.New("quic: tls.Config not set")
190	}
191	if err := validateConfig(config); err != nil {
192		return nil, err
193	}
194	config = populateClientConfig(config, createdPacketConn)
195	packetHandlers, err := getMultiplexer().AddConn(pconn, config.ConnectionIDLength, config.StatelessResetKey, config.Tracer)
196	if err != nil {
197		return nil, err
198	}
199	c, err := newClient(pconn, remoteAddr, config, tlsConf, host, use0RTT, createdPacketConn)
200	if err != nil {
201		return nil, err
202	}
203	c.packetHandlers = packetHandlers
204
205	if c.config.Tracer != nil {
206		c.tracer = c.config.Tracer.TracerForConnection(protocol.PerspectiveClient, c.destConnID)
207	}
208	if c.tracer != nil {
209		c.tracer.StartedConnection(c.conn.LocalAddr(), c.conn.RemoteAddr(), c.srcConnID, c.destConnID)
210	}
211	if err := c.dial(ctx); err != nil {
212		return nil, err
213	}
214	return c.session, nil
215}
216
217func newClient(
218	pconn net.PacketConn,
219	remoteAddr net.Addr,
220	config *Config,
221	tlsConf *tls.Config,
222	host string,
223	use0RTT bool,
224	createdPacketConn bool,
225) (*client, error) {
226	if tlsConf == nil {
227		tlsConf = &tls.Config{}
228	}
229	if tlsConf.ServerName == "" {
230		sni := host
231		if strings.IndexByte(sni, ':') != -1 {
232			var err error
233			sni, _, err = net.SplitHostPort(sni)
234			if err != nil {
235				return nil, err
236			}
237		}
238
239		tlsConf.ServerName = sni
240	}
241
242	// check that all versions are actually supported
243	if config != nil {
244		for _, v := range config.Versions {
245			if !protocol.IsValidVersion(v) {
246				return nil, fmt.Errorf("%s is not a valid QUIC version", v)
247			}
248		}
249	}
250
251	srcConnID, err := generateConnectionID(config.ConnectionIDLength)
252	if err != nil {
253		return nil, err
254	}
255	destConnID, err := generateConnectionIDForInitial()
256	if err != nil {
257		return nil, err
258	}
259	c := &client{
260		srcConnID:         srcConnID,
261		destConnID:        destConnID,
262		conn:              newSendPconn(pconn, remoteAddr),
263		createdPacketConn: createdPacketConn,
264		use0RTT:           use0RTT,
265		tlsConf:           tlsConf,
266		config:            config,
267		version:           config.Versions[0],
268		handshakeChan:     make(chan struct{}),
269		logger:            utils.DefaultLogger.WithPrefix("client"),
270	}
271	return c, nil
272}
273
274func (c *client) dial(ctx context.Context) error {
275	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)
276
277	c.session = newClientSession(
278		c.conn,
279		c.packetHandlers,
280		c.destConnID,
281		c.srcConnID,
282		c.config,
283		c.tlsConf,
284		c.initialPacketNumber,
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