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