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