2017-05-27 14:30:11 -05:00
package quic
import (
"bytes"
2017-07-27 17:11:56 -05:00
"crypto/tls"
2017-05-27 14:30:11 -05:00
"errors"
"fmt"
"net"
"strings"
"sync"
"time"
2018-02-17 00:29:53 -05:00
"github.com/lucas-clemente/quic-go/internal/handshake"
"github.com/lucas-clemente/quic-go/internal/protocol"
2017-07-27 17:11:56 -05:00
"github.com/lucas-clemente/quic-go/internal/utils"
2018-02-17 00:29:53 -05:00
"github.com/lucas-clemente/quic-go/internal/wire"
2017-05-27 14:30:11 -05:00
"github.com/lucas-clemente/quic-go/qerr"
)
type client struct {
2018-02-17 00:29:53 -05:00
mutex sync . Mutex
2017-05-27 14:30:11 -05:00
conn connection
hostname string
2018-02-17 00:29:53 -05:00
versionNegotiationChan chan struct { } // the versionNegotiationChan is closed as soon as the server accepted the suggested version
versionNegotiated bool // has the server accepted our version
receivedVersionNegotiationPacket bool
negotiatedVersions [ ] protocol . VersionNumber // the list of versions from the version negotiation packet
2017-05-27 14:30:11 -05:00
2018-02-17 00:29:53 -05:00
tlsConf * tls . Config
config * Config
tls handshake . MintTLS // only used when using TLS
2017-05-27 14:30:11 -05:00
connectionID protocol . ConnectionID
2018-02-17 00:29:53 -05:00
initialVersion protocol . VersionNumber
version protocol . VersionNumber
2017-05-27 14:30:11 -05:00
session packetHandler
}
var (
2018-02-17 00:29:53 -05:00
// make it possible to mock connection ID generation in the tests
generateConnectionID = utils . GenerateConnectionID
2017-05-27 14:30:11 -05:00
errCloseSessionForNewVersion = errors . New ( "closing session in order to recreate it with a new version" )
)
// DialAddr establishes a new QUIC connection to a server.
// The hostname for SNI is taken from the given address.
2017-07-27 17:11:56 -05:00
func DialAddr ( addr string , tlsConf * tls . Config , config * Config ) ( Session , error ) {
2017-05-27 14:30:11 -05:00
udpAddr , err := net . ResolveUDPAddr ( "udp" , addr )
if err != nil {
return nil , err
}
udpConn , err := net . ListenUDP ( "udp" , & net . UDPAddr { IP : net . IPv4zero , Port : 0 } )
if err != nil {
return nil , err
}
2017-07-27 17:11:56 -05:00
return Dial ( udpConn , udpAddr , addr , tlsConf , config )
2017-05-27 14:30:11 -05:00
}
2018-02-17 00:29:53 -05:00
// Dial establishes a new QUIC connection to a server using a net.PacketConn.
2017-05-27 14:30:11 -05:00
// The host parameter is used for SNI.
2018-02-17 00:29:53 -05:00
func Dial (
2017-07-27 17:11:56 -05:00
pconn net . PacketConn ,
remoteAddr net . Addr ,
host string ,
tlsConf * tls . Config ,
config * Config ,
2018-02-17 00:29:53 -05:00
) ( Session , error ) {
connID , err := generateConnectionID ( )
2017-05-27 14:30:11 -05:00
if err != nil {
return nil , err
}
2017-07-27 17:11:56 -05:00
var hostname string
if tlsConf != nil {
hostname = tlsConf . ServerName
}
if hostname == "" {
hostname , _ , err = net . SplitHostPort ( host )
if err != nil {
return nil , err
}
2017-05-27 14:30:11 -05:00
}
2018-03-25 23:37:41 -05:00
// check that all versions are actually supported
if config != nil {
for _ , v := range config . Versions {
if ! protocol . IsValidVersion ( v ) {
return nil , fmt . Errorf ( "%s is not a valid QUIC version" , v )
}
}
}
2017-05-27 14:30:11 -05:00
clientConfig := populateClientConfig ( config )
c := & client {
2018-02-17 00:29:53 -05:00
conn : & conn { pconn : pconn , currentAddr : remoteAddr } ,
connectionID : connID ,
hostname : hostname ,
tlsConf : tlsConf ,
config : clientConfig ,
version : clientConfig . Versions [ 0 ] ,
versionNegotiationChan : make ( chan struct { } ) ,
2017-05-27 14:30:11 -05:00
}
2018-02-17 00:29:53 -05:00
utils . Infof ( "Starting new connection to %s (%s -> %s), connectionID %x, version %s" , hostname , c . conn . LocalAddr ( ) . String ( ) , c . conn . RemoteAddr ( ) . String ( ) , c . connectionID , c . version )
2017-05-27 14:30:11 -05:00
2018-02-17 00:29:53 -05:00
if err := c . dial ( ) ; err != nil {
2017-05-27 14:30:11 -05:00
return nil , err
}
2018-02-17 00:29:53 -05:00
return c . session , nil
2017-05-27 14:30:11 -05:00
}
2017-07-27 17:11:56 -05:00
// populateClientConfig populates fields in the quic.Config with their default values, if none are set
// it may be called with nil
2017-05-27 14:30:11 -05:00
func populateClientConfig ( config * Config ) * Config {
2017-07-27 17:11:56 -05:00
if config == nil {
config = & Config { }
}
2017-05-27 14:30:11 -05:00
versions := config . Versions
if len ( versions ) == 0 {
versions = protocol . SupportedVersions
}
2017-07-27 17:11:56 -05:00
handshakeTimeout := protocol . DefaultHandshakeTimeout
if config . HandshakeTimeout != 0 {
handshakeTimeout = config . HandshakeTimeout
}
2018-02-17 00:29:53 -05:00
idleTimeout := protocol . DefaultIdleTimeout
if config . IdleTimeout != 0 {
idleTimeout = config . IdleTimeout
}
2017-07-27 17:11:56 -05:00
maxReceiveStreamFlowControlWindow := config . MaxReceiveStreamFlowControlWindow
if maxReceiveStreamFlowControlWindow == 0 {
maxReceiveStreamFlowControlWindow = protocol . DefaultMaxReceiveStreamFlowControlWindowClient
}
maxReceiveConnectionFlowControlWindow := config . MaxReceiveConnectionFlowControlWindow
if maxReceiveConnectionFlowControlWindow == 0 {
maxReceiveConnectionFlowControlWindow = protocol . DefaultMaxReceiveConnectionFlowControlWindowClient
}
2018-03-25 23:37:41 -05:00
maxIncomingStreams := config . MaxIncomingStreams
if maxIncomingStreams == 0 {
maxIncomingStreams = protocol . DefaultMaxIncomingStreams
} else if maxIncomingStreams < 0 {
maxIncomingStreams = 0
}
maxIncomingUniStreams := config . MaxIncomingUniStreams
if maxIncomingUniStreams == 0 {
maxIncomingUniStreams = protocol . DefaultMaxIncomingUniStreams
} else if maxIncomingUniStreams < 0 {
maxIncomingUniStreams = 0
}
2017-07-27 17:11:56 -05:00
2017-05-27 14:30:11 -05:00
return & Config {
2017-07-27 17:11:56 -05:00
Versions : versions ,
HandshakeTimeout : handshakeTimeout ,
2018-02-17 00:29:53 -05:00
IdleTimeout : idleTimeout ,
RequestConnectionIDOmission : config . RequestConnectionIDOmission ,
2017-07-27 17:11:56 -05:00
MaxReceiveStreamFlowControlWindow : maxReceiveStreamFlowControlWindow ,
MaxReceiveConnectionFlowControlWindow : maxReceiveConnectionFlowControlWindow ,
2018-03-25 23:37:41 -05:00
MaxIncomingStreams : maxIncomingStreams ,
MaxIncomingUniStreams : maxIncomingUniStreams ,
KeepAlive : config . KeepAlive ,
2017-05-27 14:30:11 -05:00
}
}
2018-02-17 00:29:53 -05:00
func ( c * client ) dial ( ) error {
var err error
if c . version . UsesTLS ( ) {
err = c . dialTLS ( )
} else {
err = c . dialGQUIC ( )
}
if err == errCloseSessionForNewVersion {
return c . dial ( )
}
return err
}
func ( c * client ) dialGQUIC ( ) error {
if err := c . createNewGQUICSession ( ) ; err != nil {
return err
}
2017-05-27 14:30:11 -05:00
go c . listen ( )
2018-02-17 00:29:53 -05:00
return c . establishSecureConnection ( )
}
2017-05-27 14:30:11 -05:00
2018-02-17 00:29:53 -05:00
func ( c * client ) dialTLS ( ) error {
params := & handshake . TransportParameters {
StreamFlowControlWindow : protocol . ReceiveStreamFlowControlWindow ,
ConnectionFlowControlWindow : protocol . ReceiveConnectionFlowControlWindow ,
IdleTimeout : c . config . IdleTimeout ,
OmitConnectionID : c . config . RequestConnectionIDOmission ,
2018-03-25 23:37:41 -05:00
MaxBidiStreams : uint16 ( c . config . MaxIncomingStreams ) ,
MaxUniStreams : uint16 ( c . config . MaxIncomingUniStreams ) ,
2018-02-17 00:29:53 -05:00
}
csc := handshake . NewCryptoStreamConn ( nil )
extHandler := handshake . NewExtensionHandlerClient ( params , c . initialVersion , c . config . Versions , c . version )
mintConf , err := tlsToMintConfig ( c . tlsConf , protocol . PerspectiveClient )
if err != nil {
return err
}
mintConf . ExtensionHandler = extHandler
mintConf . ServerName = c . hostname
c . tls = newMintController ( csc , mintConf , protocol . PerspectiveClient )
if err := c . createNewTLSSession ( extHandler . GetPeerParams ( ) , c . version ) ; err != nil {
return err
}
go c . listen ( )
if err := c . establishSecureConnection ( ) ; err != nil {
if err != handshake . ErrCloseSessionForRetry {
return err
}
utils . Infof ( "Received a Retry packet. Recreating session." )
if err := c . createNewTLSSession ( extHandler . GetPeerParams ( ) , c . version ) ; err != nil {
return err
2017-05-27 14:30:11 -05:00
}
2018-02-17 00:29:53 -05:00
if err := c . establishSecureConnection ( ) ; err != nil {
return err
2017-05-27 14:30:11 -05:00
}
2018-02-17 00:29:53 -05:00
}
return nil
}
// establishSecureConnection runs the session, and tries to establish a secure connection
// It returns:
// - errCloseSessionForNewVersion when the server sends a version negotiation packet
// - handshake.ErrCloseSessionForRetry when the server performs a stateless retry (for IETF QUIC)
// - any other error that might occur
// - when the connection is secure (for gQUIC), or forward-secure (for IETF QUIC)
func ( c * client ) establishSecureConnection ( ) error {
var runErr error
errorChan := make ( chan struct { } )
go func ( ) {
runErr = c . session . run ( ) // returns as soon as the session is closed
close ( errorChan )
utils . Infof ( "Connection %x closed." , c . connectionID )
if runErr != handshake . ErrCloseSessionForRetry && runErr != errCloseSessionForNewVersion {
c . conn . Close ( )
}
} ( )
// wait until the server accepts the QUIC version (or an error occurs)
select {
case <- errorChan :
return runErr
case <- c . versionNegotiationChan :
}
select {
case <- errorChan :
return runErr
case err := <- c . session . handshakeStatus ( ) :
return err
2017-05-27 14:30:11 -05:00
}
}
2018-02-17 00:29:53 -05:00
// Listen listens on the underlying connection and passes packets on for handling.
// It returns when the connection is closed.
2017-05-27 14:30:11 -05:00
func ( c * client ) listen ( ) {
var err error
for {
var n int
var addr net . Addr
2018-03-25 23:37:41 -05:00
data := * getPacketBuffer ( )
2017-05-27 14:30:11 -05:00
data = data [ : protocol . MaxReceivePacketSize ]
// The packet size should not exceed protocol.MaxReceivePacketSize bytes
// If it does, we only read a truncated packet, which will then end up undecryptable
n , addr , err = c . conn . Read ( data )
if err != nil {
if ! strings . HasSuffix ( err . Error ( ) , "use of closed network connection" ) {
2018-02-17 00:29:53 -05:00
c . mutex . Lock ( )
if c . session != nil {
c . session . Close ( err )
}
c . mutex . Unlock ( )
2017-05-27 14:30:11 -05:00
}
break
}
2018-02-17 00:29:53 -05:00
c . handlePacket ( addr , data [ : n ] )
2017-05-27 14:30:11 -05:00
}
}
2017-07-27 17:11:56 -05:00
func ( c * client ) handlePacket ( remoteAddr net . Addr , packet [ ] byte ) {
2017-05-27 14:30:11 -05:00
rcvTime := time . Now ( )
r := bytes . NewReader ( packet )
2018-02-17 00:29:53 -05:00
hdr , err := wire . ParseHeaderSentByServer ( r , c . version )
2017-05-27 14:30:11 -05:00
if err != nil {
2017-07-27 17:11:56 -05:00
utils . Errorf ( "error parsing packet from %s: %s" , remoteAddr . String ( ) , err . Error ( ) )
2018-02-17 00:29:53 -05:00
// drop this packet if we can't parse the header
return
}
// reject packets with truncated connection id if we didn't request truncation
if hdr . OmitConnectionID && ! c . config . RequestConnectionIDOmission {
2017-07-27 17:11:56 -05:00
return
2017-05-27 14:30:11 -05:00
}
hdr . Raw = packet [ : len ( packet ) - r . Len ( ) ]
c . mutex . Lock ( )
defer c . mutex . Unlock ( )
2018-02-17 00:29:53 -05:00
// reject packets with the wrong connection ID
if ! hdr . OmitConnectionID && hdr . ConnectionID != c . connectionID {
return
}
2017-07-27 17:11:56 -05:00
if hdr . ResetFlag {
cr := c . conn . RemoteAddr ( )
// check if the remote address and the connection ID match
// otherwise this might be an attacker trying to inject a PUBLIC_RESET to kill the connection
if cr . Network ( ) != remoteAddr . Network ( ) || cr . String ( ) != remoteAddr . String ( ) || hdr . ConnectionID != c . connectionID {
utils . Infof ( "Received a spoofed Public Reset. Ignoring." )
return
}
2018-02-17 00:29:53 -05:00
pr , err := wire . ParsePublicReset ( r )
2017-07-27 17:11:56 -05:00
if err != nil {
2018-02-17 00:29:53 -05:00
utils . Infof ( "Received a Public Reset. An error occurred parsing the packet: %s" , err )
2017-07-27 17:11:56 -05:00
return
}
2018-02-17 00:29:53 -05:00
utils . Infof ( "Received Public Reset, rejected packet number: %#x." , pr . RejectedPacketNumber )
c . session . closeRemote ( qerr . Error ( qerr . PublicReset , fmt . Sprintf ( "Received a Public Reset for packet number %#x" , pr . RejectedPacketNumber ) ) )
2017-07-27 17:11:56 -05:00
return
}
2018-02-17 00:29:53 -05:00
// handle Version Negotiation Packets
if hdr . IsVersionNegotiation {
// ignore delayed / duplicated version negotiation packets
if c . receivedVersionNegotiationPacket || c . versionNegotiated {
return
}
2017-05-27 14:30:11 -05:00
// version negotiation packets have no payload
2018-02-17 00:29:53 -05:00
if err := c . handleVersionNegotiationPacket ( hdr ) ; err != nil {
2017-07-27 17:11:56 -05:00
c . session . Close ( err )
}
return
2017-05-27 14:30:11 -05:00
}
2018-02-17 00:29:53 -05:00
// this is the first packet we are receiving
// since it is not a Version Negotiation Packet, this means the server supports the suggested version
if ! c . versionNegotiated {
c . versionNegotiated = true
close ( c . versionNegotiationChan )
}
// TODO: validate packet number and connection ID on Retry packets (for IETF QUIC)
2017-05-27 14:30:11 -05:00
c . session . handlePacket ( & receivedPacket {
2018-02-17 00:29:53 -05:00
remoteAddr : remoteAddr ,
header : hdr ,
data : packet [ len ( packet ) - r . Len ( ) : ] ,
rcvTime : rcvTime ,
2017-05-27 14:30:11 -05:00
} )
}
2018-02-17 00:29:53 -05:00
func ( c * client ) handleVersionNegotiationPacket ( hdr * wire . Header ) error {
2017-05-27 14:30:11 -05:00
for _ , v := range hdr . SupportedVersions {
if v == c . version {
// the version negotiation packet contains the version that we offered
// this might be a packet sent by an attacker (or by a terribly broken server implementation)
// ignore it
return nil
}
}
2018-03-25 23:37:41 -05:00
utils . Infof ( "Received a Version Negotiation Packet. Supported Versions: %s" , hdr . SupportedVersions )
2018-02-17 00:29:53 -05:00
newVersion , ok := protocol . ChooseSupportedVersion ( c . config . Versions , hdr . SupportedVersions )
if ! ok {
2017-05-27 14:30:11 -05:00
return qerr . InvalidVersion
}
2018-02-17 00:29:53 -05:00
c . receivedVersionNegotiationPacket = true
c . negotiatedVersions = hdr . SupportedVersions
2017-05-27 14:30:11 -05:00
// switch to negotiated version
2018-02-17 00:29:53 -05:00
c . initialVersion = c . version
2017-05-27 14:30:11 -05:00
c . version = newVersion
var err error
c . connectionID , err = utils . GenerateConnectionID ( )
if err != nil {
return err
}
2018-02-17 00:29:53 -05:00
utils . Infof ( "Switching to QUIC version %s. New connection ID: %x" , newVersion , c . connectionID )
2017-05-27 14:30:11 -05:00
c . session . Close ( errCloseSessionForNewVersion )
2018-02-17 00:29:53 -05:00
return nil
2017-05-27 14:30:11 -05:00
}
2018-02-17 00:29:53 -05:00
func ( c * client ) createNewGQUICSession ( ) ( err error ) {
c . mutex . Lock ( )
defer c . mutex . Unlock ( )
c . session , err = newClientSession (
2017-05-27 14:30:11 -05:00
c . conn ,
c . hostname ,
c . version ,
c . connectionID ,
2017-07-27 17:11:56 -05:00
c . tlsConf ,
2017-05-27 14:30:11 -05:00
c . config ,
2018-02-17 00:29:53 -05:00
c . initialVersion ,
c . negotiatedVersions ,
2017-05-27 14:30:11 -05:00
)
2018-02-17 00:29:53 -05:00
return err
}
2017-05-27 14:30:11 -05:00
2018-02-17 00:29:53 -05:00
func ( c * client ) createNewTLSSession (
paramsChan <- chan handshake . TransportParameters ,
version protocol . VersionNumber ,
) ( err error ) {
c . mutex . Lock ( )
defer c . mutex . Unlock ( )
c . session , err = newTLSClientSession (
c . conn ,
c . hostname ,
c . version ,
c . connectionID ,
c . config ,
c . tls ,
paramsChan ,
1 ,
)
return err
2017-05-27 14:30:11 -05:00
}