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