forked from quic-go/quic-go
implement receiving of Public Resets for the client
When a Public Reset is received, the client validates if it was sent from the correct remote address and if the connection ID matches. When a valid Public Reset is received, the connection is closed immediately.
This commit is contained in:
18
client.go
18
client.go
@@ -233,6 +233,24 @@ func (c *client) handlePacket(remoteAddr net.Addr, packet []byte) error {
|
|||||||
c.mutex.Lock()
|
c.mutex.Lock()
|
||||||
defer c.mutex.Unlock()
|
defer c.mutex.Unlock()
|
||||||
|
|
||||||
|
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 nil
|
||||||
|
}
|
||||||
|
pr, err := parsePublicReset(r)
|
||||||
|
if err != nil {
|
||||||
|
utils.Infof("Received a Public Reset for connection %x. An error occurred parsing the packet.")
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
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)))
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
// ignore delayed / duplicated version negotiation packets
|
// ignore delayed / duplicated version negotiation packets
|
||||||
if c.versionNegotiated && hdr.VersionFlag {
|
if c.versionNegotiated && hdr.VersionFlag {
|
||||||
return nil
|
return nil
|
||||||
|
|||||||
@@ -30,11 +30,14 @@ var _ = Describe("Client", func() {
|
|||||||
Eventually(areSessionsRunning).Should(BeFalse())
|
Eventually(areSessionsRunning).Should(BeFalse())
|
||||||
msess, _, _ := newMockSession(nil, 0, 0, nil, nil, nil)
|
msess, _, _ := newMockSession(nil, 0, 0, nil, nil, nil)
|
||||||
sess = msess.(*mockSession)
|
sess = msess.(*mockSession)
|
||||||
packetConn = &mockPacketConn{addr: &net.UDPAddr{IP: net.IPv4(127, 0, 0, 1), Port: 1234}}
|
addr = &net.UDPAddr{IP: net.IPv4(192, 168, 100, 200), Port: 1337}
|
||||||
|
packetConn = &mockPacketConn{
|
||||||
|
addr: &net.UDPAddr{IP: net.IPv4(127, 0, 0, 1), Port: 1234},
|
||||||
|
dataReadFrom: addr,
|
||||||
|
}
|
||||||
config = &Config{
|
config = &Config{
|
||||||
Versions: []protocol.VersionNumber{protocol.SupportedVersions[0], 77, 78},
|
Versions: []protocol.VersionNumber{protocol.SupportedVersions[0], 77, 78},
|
||||||
}
|
}
|
||||||
addr = &net.UDPAddr{IP: net.IPv4(192, 168, 100, 200), Port: 1337}
|
|
||||||
cl = &client{
|
cl = &client{
|
||||||
config: config,
|
config: config,
|
||||||
connectionID: 0x1337,
|
connectionID: 0x1337,
|
||||||
@@ -379,7 +382,7 @@ var _ = Describe("Client", func() {
|
|||||||
|
|
||||||
It("closes the session when encountering an error while handling a packet", func() {
|
It("closes the session when encountering an error while handling a packet", func() {
|
||||||
Expect(sess.closeReason).ToNot(HaveOccurred())
|
Expect(sess.closeReason).ToNot(HaveOccurred())
|
||||||
packetConn.dataToRead = bytes.Repeat([]byte{0xff}, 100)
|
packetConn.dataToRead = []byte("invalid packet")
|
||||||
cl.listen()
|
cl.listen()
|
||||||
Expect(sess.closed).To(BeTrue())
|
Expect(sess.closed).To(BeTrue())
|
||||||
Expect(sess.closeReason).To(HaveOccurred())
|
Expect(sess.closeReason).To(HaveOccurred())
|
||||||
@@ -393,4 +396,37 @@ var _ = Describe("Client", func() {
|
|||||||
Expect(sess.closeReason).To(MatchError(testErr))
|
Expect(sess.closeReason).To(MatchError(testErr))
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
|
Context("Public Reset handling", func() {
|
||||||
|
It("closes the session when receiving a Public Reset", func() {
|
||||||
|
err := cl.handlePacket(addr, writePublicReset(cl.connectionID, 1, 0))
|
||||||
|
Expect(err).ToNot(HaveOccurred())
|
||||||
|
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))
|
||||||
|
})
|
||||||
|
|
||||||
|
It("ignores Public Resets with the wrong connection ID", func() {
|
||||||
|
err := cl.handlePacket(addr, writePublicReset(cl.connectionID+1, 1, 0))
|
||||||
|
Expect(err).ToNot(HaveOccurred())
|
||||||
|
Expect(cl.session.(*mockSession).closed).To(BeFalse())
|
||||||
|
Expect(cl.session.(*mockSession).closedRemote).To(BeFalse())
|
||||||
|
})
|
||||||
|
|
||||||
|
It("ignores Public Resets from the wrong remote address", func() {
|
||||||
|
spoofedAddr := &net.UDPAddr{IP: net.IPv4(1, 2, 3, 4), Port: 5678}
|
||||||
|
err := cl.handlePacket(spoofedAddr, writePublicReset(cl.connectionID, 1, 0))
|
||||||
|
Expect(err).ToNot(HaveOccurred())
|
||||||
|
Expect(cl.session.(*mockSession).closed).To(BeFalse())
|
||||||
|
Expect(cl.session.(*mockSession).closedRemote).To(BeFalse())
|
||||||
|
})
|
||||||
|
|
||||||
|
It("ignores unparseable Public Resets", func() {
|
||||||
|
pr := writePublicReset(cl.connectionID, 1, 0)
|
||||||
|
err := cl.handlePacket(addr, pr[:len(pr)-5])
|
||||||
|
Expect(err).ToNot(HaveOccurred())
|
||||||
|
Expect(cl.session.(*mockSession).closed).To(BeFalse())
|
||||||
|
Expect(cl.session.(*mockSession).closedRemote).To(BeFalse())
|
||||||
|
})
|
||||||
|
})
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -20,6 +20,7 @@ type packetHandler interface {
|
|||||||
Session
|
Session
|
||||||
handlePacket(*receivedPacket)
|
handlePacket(*receivedPacket)
|
||||||
run() error
|
run() error
|
||||||
|
closeRemote(error)
|
||||||
}
|
}
|
||||||
|
|
||||||
// A Listener of QUIC
|
// A Listener of QUIC
|
||||||
|
|||||||
@@ -23,6 +23,7 @@ type mockSession struct {
|
|||||||
packetCount int
|
packetCount int
|
||||||
closed bool
|
closed bool
|
||||||
closeReason error
|
closeReason error
|
||||||
|
closedRemote bool
|
||||||
stopRunLoop chan struct{} // run returns as soon as this channel receives a value
|
stopRunLoop chan struct{} // run returns as soon as this channel receives a value
|
||||||
handshakeChan chan handshakeEvent
|
handshakeChan chan handshakeEvent
|
||||||
handshakeComplete chan error // for WaitUntilHandshakeComplete
|
handshakeComplete chan error // for WaitUntilHandshakeComplete
|
||||||
@@ -51,6 +52,12 @@ func (s *mockSession) Close(e error) error {
|
|||||||
close(s.stopRunLoop)
|
close(s.stopRunLoop)
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
func (s *mockSession) closeRemote(e error) {
|
||||||
|
s.closeReason = e
|
||||||
|
s.closed = true
|
||||||
|
s.closedRemote = true
|
||||||
|
close(s.stopRunLoop)
|
||||||
|
}
|
||||||
func (s *mockSession) AcceptStream() (Stream, error) {
|
func (s *mockSession) AcceptStream() (Stream, error) {
|
||||||
panic("not implemented")
|
panic("not implemented")
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user