github.com/mikelsr/quic-go@v0.36.1-0.20230701132136-1d9415b66898/client.go (about) 1 package quic 2 3 import ( 4 "context" 5 "crypto/tls" 6 "errors" 7 "net" 8 9 "github.com/mikelsr/quic-go/internal/protocol" 10 "github.com/mikelsr/quic-go/internal/utils" 11 "github.com/mikelsr/quic-go/logging" 12 ) 13 14 type client struct { 15 sendConn sendConn 16 17 use0RTT bool 18 19 packetHandlers packetHandlerManager 20 onClose func() 21 22 tlsConf *tls.Config 23 config *Config 24 25 connIDGenerator ConnectionIDGenerator 26 srcConnID protocol.ConnectionID 27 destConnID protocol.ConnectionID 28 29 initialPacketNumber protocol.PacketNumber 30 hasNegotiatedVersion bool 31 version protocol.VersionNumber 32 33 handshakeChan chan struct{} 34 35 conn quicConn 36 37 tracer logging.ConnectionTracer 38 tracingID uint64 39 logger utils.Logger 40 } 41 42 // make it possible to mock connection ID for initial generation in the tests 43 var generateConnectionIDForInitial = protocol.GenerateConnectionIDForInitial 44 45 // DialAddr establishes a new QUIC connection to a server. 46 // It resolves the address, and then creates a new UDP connection to dial the QUIC server. 47 // When the QUIC connection is closed, this UDP connection is closed. 48 // See Dial for more details. 49 func DialAddr(ctx context.Context, addr string, tlsConf *tls.Config, conf *Config) (Connection, error) { 50 udpConn, err := net.ListenUDP("udp", &net.UDPAddr{IP: net.IPv4zero, Port: 0}) 51 if err != nil { 52 return nil, err 53 } 54 udpAddr, err := net.ResolveUDPAddr("udp", addr) 55 if err != nil { 56 return nil, err 57 } 58 dl, err := setupTransport(udpConn, tlsConf, true) 59 if err != nil { 60 return nil, err 61 } 62 return dl.Dial(ctx, udpAddr, tlsConf, conf) 63 } 64 65 // DialAddrEarly establishes a new 0-RTT QUIC connection to a server. 66 // See DialAddr for more details. 67 func DialAddrEarly(ctx context.Context, addr string, tlsConf *tls.Config, conf *Config) (EarlyConnection, error) { 68 udpConn, err := net.ListenUDP("udp", &net.UDPAddr{IP: net.IPv4zero, Port: 0}) 69 if err != nil { 70 return nil, err 71 } 72 udpAddr, err := net.ResolveUDPAddr("udp", addr) 73 if err != nil { 74 return nil, err 75 } 76 dl, err := setupTransport(udpConn, tlsConf, true) 77 if err != nil { 78 return nil, err 79 } 80 conn, err := dl.DialEarly(ctx, udpAddr, tlsConf, conf) 81 if err != nil { 82 dl.Close() 83 return nil, err 84 } 85 return conn, nil 86 } 87 88 // DialEarly establishes a new 0-RTT QUIC connection to a server using a net.PacketConn. 89 // See Dial for more details. 90 func DialEarly(ctx context.Context, c net.PacketConn, addr net.Addr, tlsConf *tls.Config, conf *Config) (EarlyConnection, error) { 91 dl, err := setupTransport(c, tlsConf, false) 92 if err != nil { 93 return nil, err 94 } 95 conn, err := dl.DialEarly(ctx, addr, tlsConf, conf) 96 if err != nil { 97 dl.Close() 98 return nil, err 99 } 100 return conn, nil 101 } 102 103 // Dial establishes a new QUIC connection to a server using a net.PacketConn. 104 // If the PacketConn satisfies the OOBCapablePacketConn interface (as a net.UDPConn does), 105 // ECN and packet info support will be enabled. In this case, ReadMsgUDP and WriteMsgUDP 106 // will be used instead of ReadFrom and WriteTo to read/write packets. 107 // The tls.Config must define an application protocol (using NextProtos). 108 // 109 // This is a convenience function. More advanced use cases should instantiate a Transport, 110 // which offers configuration options for a more fine-grained control of the connection establishment, 111 // including reusing the underlying UDP socket for multiple QUIC connections. 112 func Dial(ctx context.Context, c net.PacketConn, addr net.Addr, tlsConf *tls.Config, conf *Config) (Connection, error) { 113 dl, err := setupTransport(c, tlsConf, false) 114 if err != nil { 115 return nil, err 116 } 117 conn, err := dl.Dial(ctx, addr, tlsConf, conf) 118 if err != nil { 119 dl.Close() 120 return nil, err 121 } 122 return conn, nil 123 } 124 125 func setupTransport(c net.PacketConn, tlsConf *tls.Config, createdPacketConn bool) (*Transport, error) { 126 if tlsConf == nil { 127 return nil, errors.New("quic: tls.Config not set") 128 } 129 return &Transport{ 130 Conn: c, 131 createdConn: createdPacketConn, 132 isSingleUse: true, 133 }, nil 134 } 135 136 func dial( 137 ctx context.Context, 138 conn sendConn, 139 connIDGenerator ConnectionIDGenerator, 140 packetHandlers packetHandlerManager, 141 tlsConf *tls.Config, 142 config *Config, 143 onClose func(), 144 use0RTT bool, 145 ) (quicConn, error) { 146 c, err := newClient(conn, connIDGenerator, config, tlsConf, onClose, use0RTT) 147 if err != nil { 148 return nil, err 149 } 150 c.packetHandlers = packetHandlers 151 152 c.tracingID = nextConnTracingID() 153 if c.config.Tracer != nil { 154 c.tracer = c.config.Tracer(context.WithValue(ctx, ConnectionTracingKey, c.tracingID), protocol.PerspectiveClient, c.destConnID) 155 } 156 if c.tracer != nil { 157 c.tracer.StartedConnection(c.sendConn.LocalAddr(), c.sendConn.RemoteAddr(), c.srcConnID, c.destConnID) 158 } 159 if err := c.dial(ctx); err != nil { 160 return nil, err 161 } 162 return c.conn, nil 163 } 164 165 func newClient(sendConn sendConn, connIDGenerator ConnectionIDGenerator, config *Config, tlsConf *tls.Config, onClose func(), use0RTT bool) (*client, error) { 166 if tlsConf == nil { 167 tlsConf = &tls.Config{} 168 } else { 169 tlsConf = tlsConf.Clone() 170 } 171 172 srcConnID, err := connIDGenerator.GenerateConnectionID() 173 if err != nil { 174 return nil, err 175 } 176 destConnID, err := generateConnectionIDForInitial() 177 if err != nil { 178 return nil, err 179 } 180 c := &client{ 181 connIDGenerator: connIDGenerator, 182 srcConnID: srcConnID, 183 destConnID: destConnID, 184 sendConn: sendConn, 185 use0RTT: use0RTT, 186 onClose: onClose, 187 tlsConf: tlsConf, 188 config: config, 189 version: config.Versions[0], 190 handshakeChan: make(chan struct{}), 191 logger: utils.DefaultLogger.WithPrefix("client"), 192 } 193 return c, nil 194 } 195 196 func (c *client) dial(ctx context.Context) error { 197 c.logger.Infof("Starting new connection to %s (%s -> %s), source connection ID %s, destination connection ID %s, version %s", c.tlsConf.ServerName, c.sendConn.LocalAddr(), c.sendConn.RemoteAddr(), c.srcConnID, c.destConnID, c.version) 198 199 c.conn = newClientConnection( 200 c.sendConn, 201 c.packetHandlers, 202 c.destConnID, 203 c.srcConnID, 204 c.connIDGenerator, 205 c.config, 206 c.tlsConf, 207 c.initialPacketNumber, 208 c.use0RTT, 209 c.hasNegotiatedVersion, 210 c.tracer, 211 c.tracingID, 212 c.logger, 213 c.version, 214 ) 215 c.packetHandlers.Add(c.srcConnID, c.conn) 216 217 errorChan := make(chan error, 1) 218 recreateChan := make(chan errCloseForRecreating) 219 go func() { 220 err := c.conn.run() 221 var recreateErr *errCloseForRecreating 222 if errors.As(err, &recreateErr) { 223 recreateChan <- *recreateErr 224 return 225 } 226 if c.onClose != nil { 227 c.onClose() 228 } 229 errorChan <- err // returns as soon as the connection is closed 230 }() 231 232 // only set when we're using 0-RTT 233 // Otherwise, earlyConnChan will be nil. Receiving from a nil chan blocks forever. 234 var earlyConnChan <-chan struct{} 235 if c.use0RTT { 236 earlyConnChan = c.conn.earlyConnReady() 237 } 238 239 select { 240 case <-ctx.Done(): 241 c.conn.shutdown() 242 return ctx.Err() 243 case err := <-errorChan: 244 return err 245 case recreateErr := <-recreateChan: 246 c.initialPacketNumber = recreateErr.nextPacketNumber 247 c.version = recreateErr.nextVersion 248 c.hasNegotiatedVersion = true 249 return c.dial(ctx) 250 case <-earlyConnChan: 251 // ready to send 0-RTT data 252 return nil 253 case <-c.conn.HandshakeComplete(): 254 // handshake successfully completed 255 return nil 256 } 257 }