2017-12-12 02:51:45 +00:00
|
|
|
package quic
|
|
|
|
|
|
|
|
import (
|
|
|
|
"bytes"
|
2018-01-03 19:19:49 +00:00
|
|
|
"crypto/tls"
|
2017-12-12 02:51:45 +00:00
|
|
|
"errors"
|
|
|
|
"net"
|
2018-01-03 19:19:49 +00:00
|
|
|
"sync/atomic"
|
|
|
|
"time"
|
2017-12-12 02:51:45 +00:00
|
|
|
|
2018-01-03 19:19:49 +00:00
|
|
|
"github.com/lucas-clemente/quic-go/internal/handshake"
|
|
|
|
"github.com/lucas-clemente/quic-go/internal/protocol"
|
|
|
|
"github.com/lucas-clemente/quic-go/internal/wire"
|
2017-12-12 02:51:45 +00:00
|
|
|
"github.com/lucas-clemente/quic-go/qerr"
|
|
|
|
|
|
|
|
. "github.com/onsi/ginkgo"
|
|
|
|
. "github.com/onsi/gomega"
|
|
|
|
)
|
|
|
|
|
|
|
|
var _ = Describe("Client", func() {
|
|
|
|
var (
|
2018-01-03 19:19:49 +00:00
|
|
|
cl *client
|
|
|
|
config *Config
|
|
|
|
sess *mockSession
|
|
|
|
packetConn *mockPacketConn
|
|
|
|
addr net.Addr
|
|
|
|
|
|
|
|
originalClientSessConstructor func(conn connection, hostname string, v protocol.VersionNumber, connectionID protocol.ConnectionID, tlsConf *tls.Config, config *Config, initialVersion protocol.VersionNumber, negotiatedVersions []protocol.VersionNumber) (packetHandler, error)
|
2017-12-12 02:51:45 +00:00
|
|
|
)
|
|
|
|
|
2018-01-03 19:19:49 +00:00
|
|
|
// generate a packet sent by the server that accepts the QUIC version suggested by the client
|
|
|
|
acceptClientVersionPacket := func(connID protocol.ConnectionID) []byte {
|
|
|
|
b := &bytes.Buffer{}
|
|
|
|
err := (&wire.Header{
|
|
|
|
ConnectionID: connID,
|
|
|
|
PacketNumber: 1,
|
|
|
|
PacketNumberLen: 1,
|
|
|
|
}).Write(b, protocol.PerspectiveServer, protocol.VersionWhatever)
|
|
|
|
Expect(err).ToNot(HaveOccurred())
|
|
|
|
return b.Bytes()
|
|
|
|
}
|
|
|
|
|
2017-12-12 02:51:45 +00:00
|
|
|
BeforeEach(func() {
|
2018-01-03 19:19:49 +00:00
|
|
|
originalClientSessConstructor = newClientSession
|
2017-12-12 02:51:45 +00:00
|
|
|
Eventually(areSessionsRunning).Should(BeFalse())
|
2018-01-03 19:19:49 +00:00
|
|
|
msess, _ := newMockSession(nil, 0, 0, nil, nil, nil)
|
|
|
|
sess = msess.(*mockSession)
|
|
|
|
addr = &net.UDPAddr{IP: net.IPv4(192, 168, 100, 200), Port: 1337}
|
|
|
|
packetConn = newMockPacketConn()
|
|
|
|
packetConn.addr = &net.UDPAddr{IP: net.IPv4(127, 0, 0, 1), Port: 1234}
|
|
|
|
packetConn.dataReadFrom = addr
|
2017-12-12 02:51:45 +00:00
|
|
|
config = &Config{
|
2018-01-03 19:19:49 +00:00
|
|
|
Versions: []protocol.VersionNumber{protocol.SupportedVersions[0], 77, 78},
|
2017-12-12 02:51:45 +00:00
|
|
|
}
|
|
|
|
cl = &client{
|
|
|
|
config: config,
|
|
|
|
connectionID: 0x1337,
|
|
|
|
session: sess,
|
2018-01-03 19:19:49 +00:00
|
|
|
version: protocol.SupportedVersions[0],
|
2017-12-12 02:51:45 +00:00
|
|
|
conn: &conn{pconn: packetConn, currentAddr: addr},
|
2018-01-03 19:19:49 +00:00
|
|
|
versionNegotiationChan: make(chan struct{}),
|
2017-12-12 02:51:45 +00:00
|
|
|
}
|
|
|
|
})
|
|
|
|
|
2018-01-03 19:19:49 +00:00
|
|
|
AfterEach(func() {
|
|
|
|
newClientSession = originalClientSessConstructor
|
|
|
|
})
|
|
|
|
|
2017-12-12 02:51:45 +00:00
|
|
|
AfterEach(func() {
|
|
|
|
if s, ok := cl.session.(*session); ok {
|
|
|
|
s.Close(nil)
|
|
|
|
}
|
|
|
|
Eventually(areSessionsRunning).Should(BeFalse())
|
|
|
|
})
|
|
|
|
|
|
|
|
Context("Dialing", func() {
|
2018-01-03 19:19:49 +00:00
|
|
|
var origGenerateConnectionID func() (protocol.ConnectionID, error)
|
|
|
|
|
|
|
|
BeforeEach(func() {
|
|
|
|
newClientSession = func(
|
|
|
|
conn connection,
|
|
|
|
_ string,
|
|
|
|
_ protocol.VersionNumber,
|
|
|
|
_ protocol.ConnectionID,
|
|
|
|
_ *tls.Config,
|
|
|
|
_ *Config,
|
|
|
|
_ protocol.VersionNumber,
|
|
|
|
_ []protocol.VersionNumber,
|
|
|
|
) (packetHandler, error) {
|
|
|
|
Expect(conn.Write([]byte("0 fake CHLO"))).To(Succeed())
|
|
|
|
return sess, nil
|
|
|
|
}
|
|
|
|
origGenerateConnectionID = generateConnectionID
|
|
|
|
generateConnectionID = func() (protocol.ConnectionID, error) {
|
|
|
|
return cl.connectionID, nil
|
|
|
|
}
|
2017-12-12 02:51:45 +00:00
|
|
|
})
|
|
|
|
|
2018-01-03 19:19:49 +00:00
|
|
|
AfterEach(func() {
|
|
|
|
generateConnectionID = origGenerateConnectionID
|
2017-12-12 02:51:45 +00:00
|
|
|
})
|
|
|
|
|
2018-01-03 19:19:49 +00:00
|
|
|
It("dials non-forward-secure", func() {
|
|
|
|
packetConn.dataToRead <- acceptClientVersionPacket(cl.connectionID)
|
|
|
|
dialed := make(chan struct{})
|
|
|
|
go func() {
|
|
|
|
defer GinkgoRecover()
|
|
|
|
s, err := DialNonFWSecure(packetConn, addr, "quic.clemente.io:1337", nil, config)
|
|
|
|
Expect(err).ToNot(HaveOccurred())
|
|
|
|
Expect(s).ToNot(BeNil())
|
|
|
|
close(dialed)
|
|
|
|
}()
|
|
|
|
Consistently(dialed).ShouldNot(BeClosed())
|
|
|
|
sess.handshakeChan <- handshakeEvent{encLevel: protocol.EncryptionSecure}
|
|
|
|
Eventually(dialed).Should(BeClosed())
|
2017-12-12 02:51:45 +00:00
|
|
|
})
|
|
|
|
|
2018-01-03 19:19:49 +00:00
|
|
|
It("dials a non-forward-secure address", func() {
|
|
|
|
serverAddr, err := net.ResolveUDPAddr("udp", "127.0.0.1:0")
|
|
|
|
Expect(err).ToNot(HaveOccurred())
|
|
|
|
server, err := net.ListenUDP("udp", serverAddr)
|
|
|
|
Expect(err).ToNot(HaveOccurred())
|
|
|
|
defer server.Close()
|
|
|
|
done := make(chan struct{})
|
|
|
|
go func() {
|
|
|
|
defer GinkgoRecover()
|
|
|
|
defer close(done)
|
|
|
|
for {
|
|
|
|
_, clientAddr, err := server.ReadFromUDP(make([]byte, 200))
|
|
|
|
if err != nil {
|
|
|
|
return
|
|
|
|
}
|
|
|
|
_, err = server.WriteToUDP(acceptClientVersionPacket(cl.connectionID), clientAddr)
|
|
|
|
Expect(err).ToNot(HaveOccurred())
|
|
|
|
}
|
|
|
|
}()
|
|
|
|
|
|
|
|
dialed := make(chan struct{})
|
2017-12-12 02:51:45 +00:00
|
|
|
go func() {
|
|
|
|
defer GinkgoRecover()
|
2018-01-03 19:19:49 +00:00
|
|
|
s, err := DialAddrNonFWSecure(server.LocalAddr().String(), nil, config)
|
2017-12-12 02:51:45 +00:00
|
|
|
Expect(err).ToNot(HaveOccurred())
|
2018-01-03 19:19:49 +00:00
|
|
|
Expect(s).ToNot(BeNil())
|
|
|
|
close(dialed)
|
2017-12-12 02:51:45 +00:00
|
|
|
}()
|
2018-01-03 19:19:49 +00:00
|
|
|
Consistently(dialed).ShouldNot(BeClosed())
|
|
|
|
sess.handshakeChan <- handshakeEvent{encLevel: protocol.EncryptionSecure}
|
|
|
|
Eventually(dialed).Should(BeClosed())
|
|
|
|
server.Close()
|
|
|
|
Eventually(done).Should(BeClosed())
|
2017-12-12 02:51:45 +00:00
|
|
|
})
|
|
|
|
|
2018-01-03 19:19:49 +00:00
|
|
|
It("Dial only returns after the handshake is complete", func() {
|
|
|
|
packetConn.dataToRead <- acceptClientVersionPacket(cl.connectionID)
|
|
|
|
dialed := make(chan struct{})
|
2017-12-12 02:51:45 +00:00
|
|
|
go func() {
|
|
|
|
defer GinkgoRecover()
|
2018-01-03 19:19:49 +00:00
|
|
|
s, err := Dial(packetConn, addr, "quic.clemente.io:1337", nil, config)
|
2017-12-12 02:51:45 +00:00
|
|
|
Expect(err).ToNot(HaveOccurred())
|
2018-01-03 19:19:49 +00:00
|
|
|
Expect(s).ToNot(BeNil())
|
|
|
|
close(dialed)
|
|
|
|
}()
|
|
|
|
sess.handshakeChan <- handshakeEvent{encLevel: protocol.EncryptionSecure}
|
|
|
|
Consistently(dialed).ShouldNot(BeClosed())
|
|
|
|
close(sess.handshakeComplete)
|
|
|
|
Eventually(dialed).Should(BeClosed())
|
|
|
|
})
|
|
|
|
|
|
|
|
It("resolves the address", func() {
|
|
|
|
closeErr := errors.New("peer doesn't reply")
|
|
|
|
remoteAddrChan := make(chan string)
|
|
|
|
newClientSession = func(
|
|
|
|
conn connection,
|
|
|
|
_ string,
|
|
|
|
_ protocol.VersionNumber,
|
|
|
|
_ protocol.ConnectionID,
|
|
|
|
_ *tls.Config,
|
|
|
|
_ *Config,
|
|
|
|
_ protocol.VersionNumber,
|
|
|
|
_ []protocol.VersionNumber,
|
|
|
|
) (packetHandler, error) {
|
|
|
|
remoteAddrChan <- conn.RemoteAddr().String()
|
|
|
|
return sess, nil
|
|
|
|
}
|
|
|
|
dialed := make(chan struct{})
|
|
|
|
go func() {
|
|
|
|
defer GinkgoRecover()
|
|
|
|
_, err := DialAddr("localhost:17890", nil, &Config{HandshakeTimeout: time.Millisecond})
|
|
|
|
Expect(err).To(MatchError(closeErr))
|
|
|
|
close(dialed)
|
|
|
|
}()
|
|
|
|
Eventually(remoteAddrChan).Should(Receive(Equal("127.0.0.1:17890")))
|
|
|
|
sess.Close(closeErr)
|
|
|
|
Eventually(dialed).Should(BeClosed())
|
|
|
|
})
|
|
|
|
|
|
|
|
It("uses the tls.Config.ServerName as the hostname, if present", func() {
|
|
|
|
closeErr := errors.New("peer doesn't reply")
|
|
|
|
hostnameChan := make(chan string)
|
|
|
|
newClientSession = func(
|
|
|
|
_ connection,
|
|
|
|
h string,
|
|
|
|
_ protocol.VersionNumber,
|
|
|
|
_ protocol.ConnectionID,
|
|
|
|
_ *tls.Config,
|
|
|
|
_ *Config,
|
|
|
|
_ protocol.VersionNumber,
|
|
|
|
_ []protocol.VersionNumber,
|
|
|
|
) (packetHandler, error) {
|
|
|
|
hostnameChan <- h
|
|
|
|
return sess, nil
|
|
|
|
}
|
|
|
|
dialed := make(chan struct{})
|
|
|
|
go func() {
|
|
|
|
defer GinkgoRecover()
|
|
|
|
_, err := DialAddr("localhost:17890", &tls.Config{ServerName: "foobar"}, nil)
|
|
|
|
Expect(err).To(MatchError(closeErr))
|
|
|
|
close(dialed)
|
|
|
|
}()
|
|
|
|
Eventually(hostnameChan).Should(Receive(Equal("foobar")))
|
|
|
|
sess.Close(closeErr)
|
|
|
|
Eventually(dialed).Should(BeClosed())
|
|
|
|
})
|
|
|
|
|
|
|
|
It("returns an error that occurs during version negotiation", func() {
|
|
|
|
testErr := errors.New("early handshake error")
|
|
|
|
done := make(chan struct{})
|
|
|
|
go func() {
|
|
|
|
defer GinkgoRecover()
|
|
|
|
_, err := Dial(packetConn, addr, "quic.clemente.io:1337", nil, config)
|
|
|
|
Expect(err).To(MatchError(testErr))
|
|
|
|
close(done)
|
|
|
|
}()
|
|
|
|
sess.Close(testErr)
|
|
|
|
Eventually(done).Should(BeClosed())
|
|
|
|
})
|
|
|
|
|
|
|
|
It("returns an error that occurs while waiting for the connection to become secure", func() {
|
|
|
|
testErr := errors.New("early handshake error")
|
|
|
|
packetConn.dataToRead <- acceptClientVersionPacket(cl.connectionID)
|
|
|
|
done := make(chan struct{})
|
|
|
|
go func() {
|
|
|
|
defer GinkgoRecover()
|
|
|
|
_, err := Dial(packetConn, addr, "quic.clemente.io:1337", nil, config)
|
|
|
|
Expect(err).To(MatchError(testErr))
|
|
|
|
close(done)
|
|
|
|
}()
|
|
|
|
sess.handshakeChan <- handshakeEvent{err: testErr}
|
|
|
|
Eventually(done).Should(BeClosed())
|
|
|
|
})
|
|
|
|
|
|
|
|
It("returns an error that occurs while waiting for the handshake to complete", func() {
|
|
|
|
testErr := errors.New("late handshake error")
|
|
|
|
packetConn.dataToRead <- acceptClientVersionPacket(cl.connectionID)
|
|
|
|
done := make(chan struct{})
|
|
|
|
go func() {
|
|
|
|
defer GinkgoRecover()
|
|
|
|
_, err := Dial(packetConn, addr, "quic.clemente.io:1337", nil, config)
|
|
|
|
Expect(err).To(MatchError(testErr))
|
|
|
|
close(done)
|
2017-12-12 02:51:45 +00:00
|
|
|
}()
|
2018-01-03 19:19:49 +00:00
|
|
|
sess.handshakeChan <- handshakeEvent{encLevel: protocol.EncryptionSecure}
|
|
|
|
sess.handshakeComplete <- testErr
|
|
|
|
Eventually(done).Should(BeClosed())
|
|
|
|
})
|
|
|
|
|
|
|
|
It("setups with the right values", func() {
|
|
|
|
config := &Config{
|
|
|
|
HandshakeTimeout: 1337 * time.Minute,
|
|
|
|
IdleTimeout: 42 * time.Hour,
|
|
|
|
RequestConnectionIDOmission: true,
|
|
|
|
}
|
|
|
|
c := populateClientConfig(config)
|
|
|
|
Expect(c.HandshakeTimeout).To(Equal(1337 * time.Minute))
|
|
|
|
Expect(c.IdleTimeout).To(Equal(42 * time.Hour))
|
|
|
|
Expect(c.RequestConnectionIDOmission).To(BeTrue())
|
|
|
|
})
|
|
|
|
|
|
|
|
It("fills in default values if options are not set in the Config", func() {
|
|
|
|
c := populateClientConfig(&Config{})
|
|
|
|
Expect(c.Versions).To(Equal(protocol.SupportedVersions))
|
|
|
|
Expect(c.HandshakeTimeout).To(Equal(protocol.DefaultHandshakeTimeout))
|
|
|
|
Expect(c.IdleTimeout).To(Equal(protocol.DefaultIdleTimeout))
|
|
|
|
Expect(c.RequestConnectionIDOmission).To(BeFalse())
|
|
|
|
})
|
|
|
|
|
|
|
|
It("errors when receiving an error from the connection", func() {
|
|
|
|
testErr := errors.New("connection error")
|
|
|
|
packetConn.readErr = testErr
|
|
|
|
_, err := Dial(packetConn, addr, "quic.clemente.io:1337", nil, config)
|
|
|
|
Expect(err).To(MatchError(testErr))
|
|
|
|
})
|
|
|
|
|
|
|
|
It("errors if it can't create a session", func() {
|
|
|
|
testErr := errors.New("error creating session")
|
|
|
|
newClientSession = func(
|
|
|
|
_ connection,
|
|
|
|
_ string,
|
|
|
|
_ protocol.VersionNumber,
|
|
|
|
_ protocol.ConnectionID,
|
|
|
|
_ *tls.Config,
|
|
|
|
_ *Config,
|
|
|
|
_ protocol.VersionNumber,
|
|
|
|
_ []protocol.VersionNumber,
|
|
|
|
) (packetHandler, error) {
|
|
|
|
return nil, testErr
|
|
|
|
}
|
|
|
|
_, err := DialNonFWSecure(packetConn, addr, "quic.clemente.io:1337", nil, config)
|
|
|
|
Expect(err).To(MatchError(testErr))
|
|
|
|
})
|
|
|
|
|
|
|
|
Context("version negotiation", func() {
|
|
|
|
It("recognizes that a packet without VersionFlag means that the server accepted the suggested version", func() {
|
|
|
|
ph := wire.Header{
|
|
|
|
PacketNumber: 1,
|
|
|
|
PacketNumberLen: protocol.PacketNumberLen2,
|
|
|
|
ConnectionID: 0x1337,
|
|
|
|
}
|
|
|
|
b := &bytes.Buffer{}
|
|
|
|
err := ph.Write(b, protocol.PerspectiveServer, protocol.VersionWhatever)
|
|
|
|
Expect(err).ToNot(HaveOccurred())
|
|
|
|
cl.handlePacket(nil, b.Bytes())
|
|
|
|
Expect(cl.versionNegotiated).To(BeTrue())
|
|
|
|
Expect(cl.versionNegotiationChan).To(BeClosed())
|
|
|
|
})
|
|
|
|
|
|
|
|
It("changes the version after receiving a version negotiation packet", func() {
|
|
|
|
var initialVersion protocol.VersionNumber
|
|
|
|
var negotiatedVersions []protocol.VersionNumber
|
|
|
|
newVersion := protocol.VersionNumber(77)
|
|
|
|
Expect(newVersion).ToNot(Equal(cl.version))
|
|
|
|
Expect(config.Versions).To(ContainElement(newVersion))
|
|
|
|
sessionChan := make(chan *mockSession)
|
|
|
|
handshakeChan := make(chan handshakeEvent)
|
|
|
|
newClientSession = func(
|
|
|
|
_ connection,
|
|
|
|
_ string,
|
|
|
|
_ protocol.VersionNumber,
|
|
|
|
connectionID protocol.ConnectionID,
|
|
|
|
_ *tls.Config,
|
|
|
|
_ *Config,
|
|
|
|
initialVersionP protocol.VersionNumber,
|
|
|
|
negotiatedVersionsP []protocol.VersionNumber,
|
|
|
|
) (packetHandler, error) {
|
|
|
|
initialVersion = initialVersionP
|
|
|
|
negotiatedVersions = negotiatedVersionsP
|
|
|
|
|
|
|
|
sess := &mockSession{
|
|
|
|
connectionID: connectionID,
|
|
|
|
stopRunLoop: make(chan struct{}),
|
|
|
|
handshakeChan: handshakeChan,
|
|
|
|
}
|
|
|
|
sessionChan <- sess
|
|
|
|
return sess, nil
|
|
|
|
}
|
|
|
|
|
|
|
|
established := make(chan struct{})
|
|
|
|
go func() {
|
|
|
|
defer GinkgoRecover()
|
|
|
|
err := cl.dial()
|
|
|
|
Expect(err).ToNot(HaveOccurred())
|
|
|
|
close(established)
|
|
|
|
}()
|
|
|
|
go cl.listen()
|
|
|
|
|
|
|
|
actualInitialVersion := cl.version
|
|
|
|
var firstSession, secondSession *mockSession
|
|
|
|
Eventually(sessionChan).Should(Receive(&firstSession))
|
|
|
|
packetConn.dataToRead <- wire.ComposeGQUICVersionNegotiation(
|
|
|
|
cl.connectionID,
|
|
|
|
[]protocol.VersionNumber{newVersion},
|
|
|
|
)
|
|
|
|
// it didn't pass the version negoation packet to the old session (since it has no payload)
|
|
|
|
Eventually(func() bool { return firstSession.closed }).Should(BeTrue())
|
|
|
|
Expect(firstSession.closeReason).To(Equal(errCloseSessionForNewVersion))
|
|
|
|
Expect(firstSession.packetCount).To(BeZero())
|
|
|
|
Eventually(sessionChan).Should(Receive(&secondSession))
|
|
|
|
// make the server accept the new version
|
|
|
|
packetConn.dataToRead <- acceptClientVersionPacket(secondSession.connectionID)
|
|
|
|
Consistently(func() bool { return secondSession.closed }).Should(BeFalse())
|
|
|
|
Expect(cl.connectionID).ToNot(BeEquivalentTo(0x1337))
|
|
|
|
Expect(negotiatedVersions).To(ContainElement(newVersion))
|
|
|
|
Expect(initialVersion).To(Equal(actualInitialVersion))
|
|
|
|
|
|
|
|
handshakeChan <- handshakeEvent{encLevel: protocol.EncryptionSecure}
|
|
|
|
Eventually(established).Should(BeClosed())
|
|
|
|
})
|
|
|
|
|
|
|
|
It("only accepts one version negotiation packet", func() {
|
|
|
|
sessionCounter := uint32(0)
|
|
|
|
newClientSession = func(
|
|
|
|
_ connection,
|
|
|
|
_ string,
|
|
|
|
_ protocol.VersionNumber,
|
|
|
|
connectionID protocol.ConnectionID,
|
|
|
|
_ *tls.Config,
|
|
|
|
_ *Config,
|
|
|
|
_ protocol.VersionNumber,
|
|
|
|
_ []protocol.VersionNumber,
|
|
|
|
) (packetHandler, error) {
|
|
|
|
atomic.AddUint32(&sessionCounter, 1)
|
|
|
|
return &mockSession{
|
|
|
|
connectionID: connectionID,
|
|
|
|
stopRunLoop: make(chan struct{}),
|
|
|
|
}, nil
|
|
|
|
}
|
|
|
|
go cl.dial()
|
|
|
|
Eventually(func() uint32 { return atomic.LoadUint32(&sessionCounter) }).Should(BeEquivalentTo(1))
|
|
|
|
newVersion := protocol.VersionNumber(77)
|
|
|
|
Expect(newVersion).ToNot(Equal(cl.version))
|
|
|
|
Expect(config.Versions).To(ContainElement(newVersion))
|
|
|
|
cl.handlePacket(nil, wire.ComposeGQUICVersionNegotiation(0x1337, []protocol.VersionNumber{newVersion}))
|
|
|
|
Eventually(func() uint32 { return atomic.LoadUint32(&sessionCounter) }).Should(BeEquivalentTo(2))
|
|
|
|
newVersion = protocol.VersionNumber(78)
|
|
|
|
Expect(newVersion).ToNot(Equal(cl.version))
|
|
|
|
Expect(config.Versions).To(ContainElement(newVersion))
|
|
|
|
cl.handlePacket(nil, wire.ComposeGQUICVersionNegotiation(0x1337, []protocol.VersionNumber{newVersion}))
|
|
|
|
Consistently(func() uint32 { return atomic.LoadUint32(&sessionCounter) }).Should(BeEquivalentTo(2))
|
|
|
|
})
|
|
|
|
|
|
|
|
It("errors if no matching version is found", func() {
|
|
|
|
cl.handlePacket(nil, wire.ComposeGQUICVersionNegotiation(0x1337, []protocol.VersionNumber{1}))
|
|
|
|
Expect(cl.session.(*mockSession).closed).To(BeTrue())
|
|
|
|
Expect(cl.session.(*mockSession).closeReason).To(MatchError(qerr.InvalidVersion))
|
|
|
|
})
|
|
|
|
|
|
|
|
It("errors if the version is supported by quic-go, but disabled by the quic.Config", func() {
|
|
|
|
v := protocol.VersionNumber(111)
|
|
|
|
Expect(v).ToNot(Equal(cl.version))
|
|
|
|
Expect(config.Versions).ToNot(ContainElement(v))
|
|
|
|
cl.handlePacket(nil, wire.ComposeGQUICVersionNegotiation(0x1337, []protocol.VersionNumber{v}))
|
|
|
|
Expect(cl.session.(*mockSession).closed).To(BeTrue())
|
|
|
|
Expect(cl.session.(*mockSession).closeReason).To(MatchError(qerr.InvalidVersion))
|
|
|
|
})
|
|
|
|
|
|
|
|
It("changes to the version preferred by the quic.Config", func() {
|
|
|
|
cl.handlePacket(nil, wire.ComposeGQUICVersionNegotiation(0x1337, []protocol.VersionNumber{config.Versions[2], config.Versions[1]}))
|
|
|
|
Expect(cl.version).To(Equal(config.Versions[1]))
|
|
|
|
})
|
|
|
|
|
|
|
|
It("ignores delayed version negotiation packets", func() {
|
|
|
|
// if the version was not yet negotiated, handlePacket would return a VersionNegotiationMismatch error, see above test
|
|
|
|
cl.versionNegotiated = true
|
|
|
|
Expect(sess.packetCount).To(BeZero())
|
|
|
|
cl.handlePacket(nil, wire.ComposeGQUICVersionNegotiation(0x1337, []protocol.VersionNumber{1}))
|
|
|
|
Expect(cl.versionNegotiated).To(BeTrue())
|
|
|
|
Expect(sess.packetCount).To(BeZero())
|
|
|
|
})
|
|
|
|
|
|
|
|
It("drops version negotiation packets that contain the offered version", func() {
|
|
|
|
ver := cl.version
|
|
|
|
cl.handlePacket(nil, wire.ComposeGQUICVersionNegotiation(0x1337, []protocol.VersionNumber{ver}))
|
|
|
|
Expect(cl.version).To(Equal(ver))
|
|
|
|
})
|
2017-12-12 02:51:45 +00:00
|
|
|
})
|
|
|
|
})
|
|
|
|
|
2018-01-03 19:19:49 +00:00
|
|
|
It("ignores packets with an invalid public header", func() {
|
|
|
|
cl.handlePacket(addr, []byte("invalid packet"))
|
|
|
|
Expect(sess.packetCount).To(BeZero())
|
|
|
|
Expect(sess.closed).To(BeFalse())
|
2017-12-12 02:51:45 +00:00
|
|
|
})
|
|
|
|
|
2018-01-03 19:19:49 +00:00
|
|
|
It("ignores packets without connection id, if it didn't request connection id trunctation", func() {
|
|
|
|
cl.config.RequestConnectionIDOmission = false
|
|
|
|
buf := &bytes.Buffer{}
|
|
|
|
(&wire.Header{
|
|
|
|
OmitConnectionID: true,
|
|
|
|
PacketNumber: 1,
|
|
|
|
PacketNumberLen: 1,
|
|
|
|
}).Write(buf, protocol.PerspectiveServer, protocol.VersionWhatever)
|
|
|
|
cl.handlePacket(addr, buf.Bytes())
|
|
|
|
Expect(sess.packetCount).To(BeZero())
|
|
|
|
Expect(sess.closed).To(BeFalse())
|
|
|
|
})
|
|
|
|
|
|
|
|
It("ignores packets with the wrong connection ID", func() {
|
|
|
|
buf := &bytes.Buffer{}
|
|
|
|
(&wire.Header{
|
|
|
|
ConnectionID: cl.connectionID + 1,
|
|
|
|
PacketNumber: 1,
|
|
|
|
PacketNumberLen: 1,
|
|
|
|
}).Write(buf, protocol.PerspectiveServer, protocol.VersionWhatever)
|
|
|
|
cl.handlePacket(addr, buf.Bytes())
|
|
|
|
Expect(sess.packetCount).To(BeZero())
|
|
|
|
Expect(sess.closed).To(BeFalse())
|
|
|
|
})
|
2017-12-12 02:51:45 +00:00
|
|
|
|
2018-01-03 19:19:49 +00:00
|
|
|
It("creates new GQUIC sessions with the right parameters", func() {
|
|
|
|
closeErr := errors.New("peer doesn't reply")
|
|
|
|
c := make(chan struct{})
|
|
|
|
var cconn connection
|
|
|
|
var hostname string
|
|
|
|
var version protocol.VersionNumber
|
|
|
|
var conf *Config
|
|
|
|
newClientSession = func(
|
|
|
|
connP connection,
|
|
|
|
hostnameP string,
|
|
|
|
versionP protocol.VersionNumber,
|
|
|
|
_ protocol.ConnectionID,
|
|
|
|
_ *tls.Config,
|
|
|
|
configP *Config,
|
|
|
|
_ protocol.VersionNumber,
|
|
|
|
_ []protocol.VersionNumber,
|
|
|
|
) (packetHandler, error) {
|
|
|
|
cconn = connP
|
|
|
|
hostname = hostnameP
|
|
|
|
version = versionP
|
|
|
|
conf = configP
|
|
|
|
close(c)
|
|
|
|
return sess, nil
|
|
|
|
}
|
|
|
|
dialed := make(chan struct{})
|
2017-12-12 02:51:45 +00:00
|
|
|
go func() {
|
2018-01-03 19:19:49 +00:00
|
|
|
defer GinkgoRecover()
|
|
|
|
_, err := Dial(packetConn, addr, "quic.clemente.io:1337", nil, config)
|
|
|
|
Expect(err).To(MatchError(closeErr))
|
|
|
|
close(dialed)
|
2017-12-12 02:51:45 +00:00
|
|
|
}()
|
2018-01-03 19:19:49 +00:00
|
|
|
Eventually(c).Should(BeClosed())
|
|
|
|
Expect(cconn.(*conn).pconn).To(Equal(packetConn))
|
|
|
|
Expect(hostname).To(Equal("quic.clemente.io"))
|
|
|
|
Expect(version).To(Equal(config.Versions[0]))
|
|
|
|
Expect(conf.Versions).To(Equal(config.Versions))
|
|
|
|
sess.Close(closeErr)
|
|
|
|
Eventually(dialed).Should(BeClosed())
|
|
|
|
})
|
2017-12-12 02:51:45 +00:00
|
|
|
|
2018-01-03 19:19:49 +00:00
|
|
|
It("creates new TLS sessions with the right parameters", func() {
|
|
|
|
config.Versions = []protocol.VersionNumber{protocol.VersionTLS}
|
|
|
|
c := make(chan struct{})
|
|
|
|
var cconn connection
|
|
|
|
var hostname string
|
|
|
|
var version protocol.VersionNumber
|
|
|
|
var conf *Config
|
|
|
|
newTLSClientSession = func(
|
|
|
|
connP connection,
|
|
|
|
hostnameP string,
|
|
|
|
versionP protocol.VersionNumber,
|
|
|
|
_ protocol.ConnectionID,
|
|
|
|
configP *Config,
|
|
|
|
tls handshake.MintTLS,
|
|
|
|
paramsChan <-chan handshake.TransportParameters,
|
|
|
|
_ protocol.PacketNumber,
|
|
|
|
) (packetHandler, error) {
|
|
|
|
cconn = connP
|
|
|
|
hostname = hostnameP
|
|
|
|
version = versionP
|
|
|
|
conf = configP
|
|
|
|
close(c)
|
|
|
|
return sess, nil
|
|
|
|
}
|
|
|
|
dialed := make(chan struct{})
|
|
|
|
go func() {
|
|
|
|
defer GinkgoRecover()
|
|
|
|
Dial(packetConn, addr, "quic.clemente.io:1337", nil, config)
|
|
|
|
close(dialed)
|
|
|
|
}()
|
|
|
|
Eventually(c).Should(BeClosed())
|
|
|
|
Expect(cconn.(*conn).pconn).To(Equal(packetConn))
|
|
|
|
Expect(hostname).To(Equal("quic.clemente.io"))
|
|
|
|
Expect(version).To(Equal(config.Versions[0]))
|
|
|
|
Expect(conf.Versions).To(Equal(config.Versions))
|
|
|
|
sess.Close(errors.New("peer doesn't reply"))
|
|
|
|
Eventually(dialed).Should(BeClosed())
|
|
|
|
})
|
2017-12-12 02:51:45 +00:00
|
|
|
|
2018-01-03 19:19:49 +00:00
|
|
|
It("creates a new session when the server performs a retry", func() {
|
|
|
|
config.Versions = []protocol.VersionNumber{protocol.VersionTLS}
|
|
|
|
sessionChan := make(chan *mockSession)
|
|
|
|
newTLSClientSession = func(
|
|
|
|
connP connection,
|
|
|
|
hostnameP string,
|
|
|
|
versionP protocol.VersionNumber,
|
|
|
|
_ protocol.ConnectionID,
|
|
|
|
configP *Config,
|
|
|
|
tls handshake.MintTLS,
|
|
|
|
paramsChan <-chan handshake.TransportParameters,
|
|
|
|
_ protocol.PacketNumber,
|
|
|
|
) (packetHandler, error) {
|
|
|
|
sess := &mockSession{
|
|
|
|
stopRunLoop: make(chan struct{}),
|
|
|
|
}
|
|
|
|
sessionChan <- sess
|
|
|
|
return sess, nil
|
|
|
|
}
|
|
|
|
dialed := make(chan struct{})
|
|
|
|
go func() {
|
|
|
|
defer GinkgoRecover()
|
|
|
|
Dial(packetConn, addr, "quic.clemente.io:1337", nil, config)
|
|
|
|
close(dialed)
|
|
|
|
}()
|
|
|
|
var firstSession, secondSession *mockSession
|
|
|
|
Eventually(sessionChan).Should(Receive(&firstSession))
|
|
|
|
firstSession.Close(handshake.ErrCloseSessionForRetry)
|
|
|
|
Eventually(sessionChan).Should(Receive(&secondSession))
|
|
|
|
secondSession.Close(errors.New("stop test"))
|
|
|
|
Eventually(dialed).Should(BeClosed())
|
2017-12-12 02:51:45 +00:00
|
|
|
})
|
|
|
|
|
|
|
|
Context("handling packets", func() {
|
|
|
|
It("handles packets", func() {
|
2018-01-03 19:19:49 +00:00
|
|
|
ph := wire.Header{
|
2017-12-12 02:51:45 +00:00
|
|
|
PacketNumber: 1,
|
|
|
|
PacketNumberLen: protocol.PacketNumberLen2,
|
|
|
|
ConnectionID: 0x1337,
|
|
|
|
}
|
|
|
|
b := &bytes.Buffer{}
|
2018-01-03 19:19:49 +00:00
|
|
|
err := ph.Write(b, protocol.PerspectiveServer, cl.version)
|
2017-12-12 02:51:45 +00:00
|
|
|
Expect(err).ToNot(HaveOccurred())
|
2018-01-03 19:19:49 +00:00
|
|
|
packetConn.dataToRead <- b.Bytes()
|
2017-12-12 02:51:45 +00:00
|
|
|
|
|
|
|
Expect(sess.packetCount).To(BeZero())
|
2018-01-03 19:19:49 +00:00
|
|
|
stoppedListening := make(chan struct{})
|
2017-12-12 02:51:45 +00:00
|
|
|
go func() {
|
|
|
|
cl.listen()
|
|
|
|
// it should continue listening when receiving valid packets
|
2018-01-03 19:19:49 +00:00
|
|
|
close(stoppedListening)
|
2017-12-12 02:51:45 +00:00
|
|
|
}()
|
|
|
|
|
|
|
|
Eventually(func() int { return sess.packetCount }).Should(Equal(1))
|
|
|
|
Expect(sess.closed).To(BeFalse())
|
2018-01-03 19:19:49 +00:00
|
|
|
Consistently(stoppedListening).ShouldNot(BeClosed())
|
2017-12-12 02:51:45 +00:00
|
|
|
})
|
|
|
|
|
|
|
|
It("closes the session when encountering an error while reading from the connection", func() {
|
|
|
|
testErr := errors.New("test error")
|
|
|
|
packetConn.readErr = testErr
|
|
|
|
cl.listen()
|
|
|
|
Expect(sess.closed).To(BeTrue())
|
|
|
|
Expect(sess.closeReason).To(MatchError(testErr))
|
|
|
|
})
|
|
|
|
})
|
|
|
|
|
2018-01-03 19:19:49 +00:00
|
|
|
Context("Public Reset handling", func() {
|
|
|
|
It("closes the session when receiving a Public Reset", func() {
|
|
|
|
cl.handlePacket(addr, wire.WritePublicReset(cl.connectionID, 1, 0))
|
|
|
|
Expect(cl.session.(*mockSession).closed).To(BeTrue())
|
|
|
|
Expect(cl.session.(*mockSession).closedRemote).To(BeTrue())
|
|
|
|
Expect(cl.session.(*mockSession).closeReason.(*qerr.QuicError).ErrorCode).To(Equal(qerr.PublicReset))
|
2017-12-12 02:51:45 +00:00
|
|
|
})
|
|
|
|
|
2018-01-03 19:19:49 +00:00
|
|
|
It("ignores Public Resets with the wrong connection ID", func() {
|
|
|
|
cl.handlePacket(addr, wire.WritePublicReset(cl.connectionID+1, 1, 0))
|
|
|
|
Expect(cl.session.(*mockSession).closed).To(BeFalse())
|
|
|
|
Expect(cl.session.(*mockSession).closedRemote).To(BeFalse())
|
2017-12-12 02:51:45 +00:00
|
|
|
})
|
|
|
|
|
2018-01-03 19:19:49 +00:00
|
|
|
It("ignores Public Resets from the wrong remote address", func() {
|
|
|
|
spoofedAddr := &net.UDPAddr{IP: net.IPv4(1, 2, 3, 4), Port: 5678}
|
|
|
|
cl.handlePacket(spoofedAddr, wire.WritePublicReset(cl.connectionID, 1, 0))
|
|
|
|
Expect(cl.session.(*mockSession).closed).To(BeFalse())
|
|
|
|
Expect(cl.session.(*mockSession).closedRemote).To(BeFalse())
|
2017-12-12 02:51:45 +00:00
|
|
|
})
|
|
|
|
|
2018-01-03 19:19:49 +00:00
|
|
|
It("ignores unparseable Public Resets", func() {
|
|
|
|
pr := wire.WritePublicReset(cl.connectionID, 1, 0)
|
|
|
|
cl.handlePacket(addr, pr[:len(pr)-5])
|
|
|
|
Expect(cl.session.(*mockSession).closed).To(BeFalse())
|
|
|
|
Expect(cl.session.(*mockSession).closedRemote).To(BeFalse())
|
2017-12-12 02:51:45 +00:00
|
|
|
})
|
|
|
|
})
|
|
|
|
})
|