When a server rejects an incoming channel request via NewChannel.Reject, the channel is left in the multiplexer's channel list. Because the channel is never explicitly removed or closed, its internal buffers and sync primitives remain allocated for the lifetime of the SSH connection. A malicious client could exploit this behavior by repeatedly requesting to open channels that are destined to be rejected, causing unbounded memory growth and potentially leading to a Denial of Service (DoS) via resource exhaustion. This change fixes the leak by calling ch.mux.chanList.remove within the Reject method, removing the channel from the list and allowing the garbage collector to reclaim the associated memory immediately. Fixes golang/go#35127 Fixes CVE-2026-3982 Change-Id: Iaa177f5dfd151812dd404e528a4a1c77527a0e29 Reviewed-on: https://go-review.googlesource.com/c/crypto/+/781320 LUCI-TryBot-Result: golang-scoped@luci-project-accounts.iam.gserviceaccount.com <golang-scoped@luci-project-accounts.iam.gserviceaccount.com> Reviewed-by: Roland Shoemaker <roland@golang.org> Reviewed-by: Nicholas Husin <nsh@golang.org> Reviewed-by: Nicholas Husin <husin@google.com>
884 lines
19 KiB
Go
884 lines
19 KiB
Go
// Copyright 2013 The Go Authors. All rights reserved.
|
|
// Use of this source code is governed by a BSD-style
|
|
// license that can be found in the LICENSE file.
|
|
|
|
package ssh
|
|
|
|
import (
|
|
"errors"
|
|
"fmt"
|
|
"io"
|
|
"sync"
|
|
"testing"
|
|
)
|
|
|
|
func muxPair() (*mux, *mux) {
|
|
a, b := memPipe()
|
|
|
|
s := newMux(a)
|
|
c := newMux(b)
|
|
|
|
return s, c
|
|
}
|
|
|
|
// Returns both ends of a channel, and the mux for the 2nd
|
|
// channel.
|
|
func channelPair(t *testing.T) (*channel, *channel, *mux) {
|
|
c, s := muxPair()
|
|
|
|
res := make(chan *channel, 1)
|
|
go func() {
|
|
newCh, ok := <-s.incomingChannels
|
|
if !ok {
|
|
t.Error("no incoming channel")
|
|
close(res)
|
|
return
|
|
}
|
|
if newCh.ChannelType() != "chan" {
|
|
t.Errorf("got type %q want chan", newCh.ChannelType())
|
|
newCh.Reject(Prohibited, fmt.Sprintf("got type %q want chan", newCh.ChannelType()))
|
|
close(res)
|
|
return
|
|
}
|
|
ch, _, err := newCh.Accept()
|
|
if err != nil {
|
|
t.Errorf("accept: %v", err)
|
|
close(res)
|
|
return
|
|
}
|
|
res <- ch.(*channel)
|
|
}()
|
|
|
|
ch, err := c.openChannel("chan", nil)
|
|
if err != nil {
|
|
t.Fatalf("OpenChannel: %v", err)
|
|
}
|
|
w := <-res
|
|
if w == nil {
|
|
t.Fatal("unable to get write channel")
|
|
}
|
|
|
|
return w, ch, c
|
|
}
|
|
|
|
// Test that stderr and stdout can be addressed from different
|
|
// goroutines. This is intended for use with the race detector.
|
|
func TestMuxChannelExtendedThreadSafety(t *testing.T) {
|
|
writer, reader, mux := channelPair(t)
|
|
defer writer.Close()
|
|
defer reader.Close()
|
|
defer mux.Close()
|
|
|
|
var wr, rd sync.WaitGroup
|
|
magic := "hello world"
|
|
|
|
wr.Add(2)
|
|
go func() {
|
|
io.WriteString(writer, magic)
|
|
wr.Done()
|
|
}()
|
|
go func() {
|
|
io.WriteString(writer.Stderr(), magic)
|
|
wr.Done()
|
|
}()
|
|
|
|
rd.Add(2)
|
|
go func() {
|
|
c, err := io.ReadAll(reader)
|
|
if string(c) != magic {
|
|
t.Errorf("stdout read got %q, want %q (error %s)", c, magic, err)
|
|
}
|
|
rd.Done()
|
|
}()
|
|
go func() {
|
|
c, err := io.ReadAll(reader.Stderr())
|
|
if string(c) != magic {
|
|
t.Errorf("stderr read got %q, want %q (error %s)", c, magic, err)
|
|
}
|
|
rd.Done()
|
|
}()
|
|
|
|
wr.Wait()
|
|
writer.CloseWrite()
|
|
rd.Wait()
|
|
}
|
|
|
|
func TestMuxReadWrite(t *testing.T) {
|
|
s, c, mux := channelPair(t)
|
|
defer s.Close()
|
|
defer c.Close()
|
|
defer mux.Close()
|
|
|
|
magic := "hello world"
|
|
magicExt := "hello stderr"
|
|
var wg sync.WaitGroup
|
|
t.Cleanup(wg.Wait)
|
|
wg.Add(1)
|
|
go func() {
|
|
defer wg.Done()
|
|
_, err := s.Write([]byte(magic))
|
|
if err != nil {
|
|
t.Errorf("Write: %v", err)
|
|
return
|
|
}
|
|
_, err = s.Extended(1).Write([]byte(magicExt))
|
|
if err != nil {
|
|
t.Errorf("Write: %v", err)
|
|
return
|
|
}
|
|
}()
|
|
|
|
var buf [1024]byte
|
|
n, err := c.Read(buf[:])
|
|
if err != nil {
|
|
t.Fatalf("server Read: %v", err)
|
|
}
|
|
got := string(buf[:n])
|
|
if got != magic {
|
|
t.Fatalf("server: got %q want %q", got, magic)
|
|
}
|
|
|
|
n, err = c.Extended(1).Read(buf[:])
|
|
if err != nil {
|
|
t.Fatalf("server Read: %v", err)
|
|
}
|
|
|
|
got = string(buf[:n])
|
|
if got != magicExt {
|
|
t.Fatalf("server: got %q want %q", got, magic)
|
|
}
|
|
}
|
|
|
|
func TestMuxChannelOverflow(t *testing.T) {
|
|
reader, writer, mux := channelPair(t)
|
|
defer reader.Close()
|
|
defer writer.Close()
|
|
defer mux.Close()
|
|
|
|
var wg sync.WaitGroup
|
|
t.Cleanup(wg.Wait)
|
|
wg.Add(1)
|
|
go func() {
|
|
defer wg.Done()
|
|
if _, err := writer.Write(make([]byte, channelWindowSize)); err != nil {
|
|
t.Errorf("could not fill window: %v", err)
|
|
}
|
|
writer.Write(make([]byte, 1))
|
|
}()
|
|
writer.remoteWin.waitWriterBlocked()
|
|
|
|
// Send 1 byte.
|
|
packet := make([]byte, 1+4+4+1)
|
|
packet[0] = msgChannelData
|
|
marshalUint32(packet[1:], writer.remoteId)
|
|
marshalUint32(packet[5:], uint32(1))
|
|
packet[9] = 42
|
|
|
|
if err := writer.mux.conn.writePacket(packet); err != nil {
|
|
t.Errorf("could not send packet")
|
|
}
|
|
if _, err := reader.SendRequest("hello", true, nil); err == nil {
|
|
t.Errorf("SendRequest succeeded.")
|
|
}
|
|
}
|
|
|
|
func TestMuxChannelReadUnblock(t *testing.T) {
|
|
reader, writer, mux := channelPair(t)
|
|
defer reader.Close()
|
|
defer writer.Close()
|
|
defer mux.Close()
|
|
|
|
var wg sync.WaitGroup
|
|
t.Cleanup(wg.Wait)
|
|
wg.Add(1)
|
|
go func() {
|
|
defer wg.Done()
|
|
if _, err := writer.Write(make([]byte, channelWindowSize)); err != nil {
|
|
t.Errorf("could not fill window: %v", err)
|
|
}
|
|
if _, err := writer.Write(make([]byte, 1)); err != nil {
|
|
t.Errorf("Write: %v", err)
|
|
}
|
|
writer.Close()
|
|
}()
|
|
|
|
writer.remoteWin.waitWriterBlocked()
|
|
|
|
buf := make([]byte, 32768)
|
|
for {
|
|
_, err := reader.Read(buf)
|
|
if err == io.EOF {
|
|
break
|
|
}
|
|
if err != nil {
|
|
t.Fatalf("Read: %v", err)
|
|
}
|
|
}
|
|
}
|
|
|
|
func TestMuxChannelCloseWriteUnblock(t *testing.T) {
|
|
reader, writer, mux := channelPair(t)
|
|
defer reader.Close()
|
|
defer writer.Close()
|
|
defer mux.Close()
|
|
|
|
var wg sync.WaitGroup
|
|
t.Cleanup(wg.Wait)
|
|
wg.Add(1)
|
|
go func() {
|
|
defer wg.Done()
|
|
if _, err := writer.Write(make([]byte, channelWindowSize)); err != nil {
|
|
t.Errorf("could not fill window: %v", err)
|
|
}
|
|
if _, err := writer.Write(make([]byte, 1)); err != io.EOF {
|
|
t.Errorf("got %v, want EOF for unblock write", err)
|
|
}
|
|
}()
|
|
|
|
writer.remoteWin.waitWriterBlocked()
|
|
reader.Close()
|
|
}
|
|
|
|
func TestMuxConnectionCloseWriteUnblock(t *testing.T) {
|
|
reader, writer, mux := channelPair(t)
|
|
defer reader.Close()
|
|
defer writer.Close()
|
|
defer mux.Close()
|
|
|
|
var wg sync.WaitGroup
|
|
t.Cleanup(wg.Wait)
|
|
wg.Add(1)
|
|
go func() {
|
|
defer wg.Done()
|
|
if _, err := writer.Write(make([]byte, channelWindowSize)); err != nil {
|
|
t.Errorf("could not fill window: %v", err)
|
|
}
|
|
if _, err := writer.Write(make([]byte, 1)); err != io.EOF {
|
|
t.Errorf("got %v, want EOF for unblock write", err)
|
|
}
|
|
}()
|
|
|
|
writer.remoteWin.waitWriterBlocked()
|
|
mux.Close()
|
|
}
|
|
|
|
func TestMuxReject(t *testing.T) {
|
|
client, server := muxPair()
|
|
defer server.Close()
|
|
defer client.Close()
|
|
|
|
var wg sync.WaitGroup
|
|
t.Cleanup(wg.Wait)
|
|
wg.Add(1)
|
|
go func() {
|
|
defer wg.Done()
|
|
|
|
ch, ok := <-server.incomingChannels
|
|
if !ok {
|
|
t.Error("cannot accept channel")
|
|
return
|
|
}
|
|
if ch.ChannelType() != "ch" || string(ch.ExtraData()) != "extra" {
|
|
t.Errorf("unexpected channel: %q, %q", ch.ChannelType(), ch.ExtraData())
|
|
ch.Reject(RejectionReason(UnknownChannelType), UnknownChannelType.String())
|
|
return
|
|
}
|
|
ch.Reject(RejectionReason(42), "message")
|
|
}()
|
|
|
|
ch, err := client.openChannel("ch", []byte("extra"))
|
|
if ch != nil {
|
|
t.Fatal("openChannel not rejected")
|
|
}
|
|
|
|
ocf, ok := err.(*OpenChannelError)
|
|
if !ok {
|
|
t.Errorf("got %#v want *OpenChannelError", err)
|
|
} else if ocf.Reason != 42 || ocf.Message != "message" {
|
|
t.Errorf("got %#v, want {Reason: 42, Message: %q}", ocf, "message")
|
|
}
|
|
|
|
want := "ssh: rejected: unknown reason 42 (message)"
|
|
if err.Error() != want {
|
|
t.Errorf("got %q, want %q", err.Error(), want)
|
|
}
|
|
}
|
|
|
|
func TestMuxChannelRequest(t *testing.T) {
|
|
client, server, mux := channelPair(t)
|
|
defer server.Close()
|
|
defer client.Close()
|
|
defer mux.Close()
|
|
|
|
var received int
|
|
var wg sync.WaitGroup
|
|
t.Cleanup(wg.Wait)
|
|
wg.Add(1)
|
|
go func() {
|
|
for r := range server.incomingRequests {
|
|
received++
|
|
r.Reply(r.Type == "yes", nil)
|
|
}
|
|
wg.Done()
|
|
}()
|
|
_, err := client.SendRequest("yes", false, nil)
|
|
if err != nil {
|
|
t.Fatalf("SendRequest: %v", err)
|
|
}
|
|
ok, err := client.SendRequest("yes", true, nil)
|
|
if err != nil {
|
|
t.Fatalf("SendRequest: %v", err)
|
|
}
|
|
|
|
if !ok {
|
|
t.Errorf("SendRequest(yes): %v", ok)
|
|
|
|
}
|
|
|
|
ok, err = client.SendRequest("no", true, nil)
|
|
if err != nil {
|
|
t.Fatalf("SendRequest: %v", err)
|
|
}
|
|
if ok {
|
|
t.Errorf("SendRequest(no): %v", ok)
|
|
}
|
|
|
|
client.Close()
|
|
wg.Wait()
|
|
|
|
if received != 3 {
|
|
t.Errorf("got %d requests, want %d", received, 3)
|
|
}
|
|
}
|
|
|
|
func TestMuxUnknownChannelRequests(t *testing.T) {
|
|
clientPipe, serverPipe := memPipe()
|
|
client := newMux(clientPipe)
|
|
defer serverPipe.Close()
|
|
defer client.Close()
|
|
|
|
kDone := make(chan error, 1)
|
|
go func() {
|
|
// Ignore unknown channel messages that don't want a reply.
|
|
err := serverPipe.writePacket(Marshal(channelRequestMsg{
|
|
PeersID: 1,
|
|
Request: "keepalive@openssh.com",
|
|
WantReply: false,
|
|
RequestSpecificData: []byte{},
|
|
}))
|
|
if err != nil {
|
|
kDone <- fmt.Errorf("send: %w", err)
|
|
return
|
|
}
|
|
|
|
// Send a keepalive, which should get a channel failure message
|
|
// in response.
|
|
err = serverPipe.writePacket(Marshal(channelRequestMsg{
|
|
PeersID: 2,
|
|
Request: "keepalive@openssh.com",
|
|
WantReply: true,
|
|
RequestSpecificData: []byte{},
|
|
}))
|
|
if err != nil {
|
|
kDone <- fmt.Errorf("send: %w", err)
|
|
return
|
|
}
|
|
|
|
packet, err := serverPipe.readPacket()
|
|
if err != nil {
|
|
kDone <- fmt.Errorf("read packet: %w", err)
|
|
return
|
|
}
|
|
decoded, err := decode(packet)
|
|
if err != nil {
|
|
kDone <- fmt.Errorf("decode failed: %w", err)
|
|
return
|
|
}
|
|
|
|
switch msg := decoded.(type) {
|
|
case *channelRequestFailureMsg:
|
|
if msg.PeersID != 2 {
|
|
kDone <- fmt.Errorf("received response to wrong message: %v", msg)
|
|
return
|
|
|
|
}
|
|
default:
|
|
kDone <- fmt.Errorf("unexpected channel message: %v", msg)
|
|
return
|
|
}
|
|
|
|
kDone <- nil
|
|
|
|
// Receive and respond to the keepalive to confirm the mux is
|
|
// still processing requests.
|
|
packet, err = serverPipe.readPacket()
|
|
if err != nil {
|
|
kDone <- fmt.Errorf("read packet: %w", err)
|
|
return
|
|
}
|
|
if packet[0] != msgGlobalRequest {
|
|
kDone <- errors.New("expected global request")
|
|
return
|
|
}
|
|
|
|
err = serverPipe.writePacket(Marshal(globalRequestFailureMsg{
|
|
Data: []byte{},
|
|
}))
|
|
if err != nil {
|
|
kDone <- fmt.Errorf("failed to send failure msg: %w", err)
|
|
return
|
|
}
|
|
|
|
close(kDone)
|
|
}()
|
|
|
|
// Wait for the server to send the keepalive message and receive back a
|
|
// response.
|
|
if err := <-kDone; err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
// Confirm client hasn't closed.
|
|
if _, _, err := client.SendRequest("keepalive@golang.org", true, nil); err != nil {
|
|
t.Fatalf("failed to send keepalive: %v", err)
|
|
}
|
|
|
|
// Wait for the server to shut down.
|
|
if err := <-kDone; err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
}
|
|
|
|
func TestMuxClosedChannel(t *testing.T) {
|
|
clientPipe, serverPipe := memPipe()
|
|
client := newMux(clientPipe)
|
|
defer serverPipe.Close()
|
|
defer client.Close()
|
|
|
|
kDone := make(chan error, 1)
|
|
go func() {
|
|
// Open the channel.
|
|
packet, err := serverPipe.readPacket()
|
|
if err != nil {
|
|
kDone <- fmt.Errorf("read packet: %w", err)
|
|
return
|
|
}
|
|
if packet[0] != msgChannelOpen {
|
|
kDone <- errors.New("expected chan open")
|
|
return
|
|
}
|
|
|
|
var openMsg channelOpenMsg
|
|
if err := Unmarshal(packet, &openMsg); err != nil {
|
|
kDone <- fmt.Errorf("unmarshal: %w", err)
|
|
return
|
|
}
|
|
|
|
// Send back the opened channel confirmation.
|
|
err = serverPipe.writePacket(Marshal(channelOpenConfirmMsg{
|
|
PeersID: openMsg.PeersID,
|
|
MyID: 0,
|
|
MyWindow: 0,
|
|
MaxPacketSize: channelMaxPacket,
|
|
}))
|
|
if err != nil {
|
|
kDone <- fmt.Errorf("send: %w", err)
|
|
return
|
|
}
|
|
|
|
// Close the channel.
|
|
err = serverPipe.writePacket(Marshal(channelCloseMsg{
|
|
PeersID: openMsg.PeersID,
|
|
}))
|
|
if err != nil {
|
|
kDone <- fmt.Errorf("send: %w", err)
|
|
return
|
|
}
|
|
|
|
// Send a keepalive message on the channel we just closed.
|
|
err = serverPipe.writePacket(Marshal(channelRequestMsg{
|
|
PeersID: openMsg.PeersID,
|
|
Request: "keepalive@openssh.com",
|
|
WantReply: true,
|
|
RequestSpecificData: []byte{},
|
|
}))
|
|
if err != nil {
|
|
kDone <- fmt.Errorf("send: %w", err)
|
|
return
|
|
}
|
|
|
|
// Receive the channel closed response.
|
|
packet, err = serverPipe.readPacket()
|
|
if err != nil {
|
|
kDone <- fmt.Errorf("read packet: %w", err)
|
|
return
|
|
}
|
|
if packet[0] != msgChannelClose {
|
|
kDone <- errors.New("expected channel close")
|
|
return
|
|
}
|
|
|
|
// Receive the keepalive response failure.
|
|
packet, err = serverPipe.readPacket()
|
|
if err != nil {
|
|
kDone <- fmt.Errorf("read packet: %w", err)
|
|
return
|
|
}
|
|
if packet[0] != msgChannelFailure {
|
|
kDone <- errors.New("expected channel failure")
|
|
return
|
|
}
|
|
kDone <- nil
|
|
|
|
// Receive and respond to the keepalive to confirm the mux is
|
|
// still processing requests.
|
|
packet, err = serverPipe.readPacket()
|
|
if err != nil {
|
|
kDone <- fmt.Errorf("read packet: %w", err)
|
|
return
|
|
}
|
|
if packet[0] != msgGlobalRequest {
|
|
kDone <- errors.New("expected global request")
|
|
return
|
|
}
|
|
|
|
err = serverPipe.writePacket(Marshal(globalRequestFailureMsg{
|
|
Data: []byte{},
|
|
}))
|
|
if err != nil {
|
|
kDone <- fmt.Errorf("failed to send failure msg: %w", err)
|
|
return
|
|
}
|
|
|
|
close(kDone)
|
|
}()
|
|
|
|
// Open a channel.
|
|
ch, err := client.openChannel("chan", nil)
|
|
if err != nil {
|
|
t.Fatalf("OpenChannel: %v", err)
|
|
}
|
|
defer ch.Close()
|
|
|
|
// Wait for the server to close the channel and send the keepalive.
|
|
<-kDone
|
|
|
|
// Make sure the channel closed.
|
|
if _, ok := <-ch.incomingRequests; ok {
|
|
t.Fatalf("channel not closed")
|
|
}
|
|
|
|
// Confirm client hasn't closed
|
|
if _, _, err := client.SendRequest("keepalive@golang.org", true, nil); err != nil {
|
|
t.Fatalf("failed to send keepalive: %v", err)
|
|
}
|
|
|
|
// Wait for the server to shut down.
|
|
<-kDone
|
|
}
|
|
|
|
func TestMuxGlobalRequest(t *testing.T) {
|
|
var sawPeek bool
|
|
var wg sync.WaitGroup
|
|
defer func() {
|
|
wg.Wait()
|
|
if !sawPeek {
|
|
t.Errorf("never saw 'peek' request")
|
|
}
|
|
}()
|
|
|
|
clientMux, serverMux := muxPair()
|
|
defer serverMux.Close()
|
|
defer clientMux.Close()
|
|
|
|
wg.Add(1)
|
|
go func() {
|
|
defer wg.Done()
|
|
for r := range serverMux.incomingRequests {
|
|
sawPeek = sawPeek || r.Type == "peek"
|
|
if r.WantReply {
|
|
err := r.Reply(r.Type == "yes",
|
|
append([]byte(r.Type), r.Payload...))
|
|
if err != nil {
|
|
t.Errorf("AckRequest: %v", err)
|
|
}
|
|
}
|
|
}
|
|
}()
|
|
|
|
_, _, err := clientMux.SendRequest("peek", false, nil)
|
|
if err != nil {
|
|
t.Errorf("SendRequest: %v", err)
|
|
}
|
|
|
|
ok, data, err := clientMux.SendRequest("yes", true, []byte("a"))
|
|
if !ok || string(data) != "yesa" || err != nil {
|
|
t.Errorf("SendRequest(\"yes\", true, \"a\"): %v %v %v",
|
|
ok, data, err)
|
|
}
|
|
if ok, data, err := clientMux.SendRequest("yes", true, []byte("a")); !ok || string(data) != "yesa" || err != nil {
|
|
t.Errorf("SendRequest(\"yes\", true, \"a\"): %v %v %v",
|
|
ok, data, err)
|
|
}
|
|
|
|
if ok, data, err := clientMux.SendRequest("no", true, []byte("a")); ok || string(data) != "noa" || err != nil {
|
|
t.Errorf("SendRequest(\"no\", true, \"a\"): %v %v %v",
|
|
ok, data, err)
|
|
}
|
|
}
|
|
|
|
func TestMuxGlobalRequestUnblock(t *testing.T) {
|
|
clientMux, serverMux := muxPair()
|
|
defer serverMux.Close()
|
|
defer clientMux.Close()
|
|
|
|
result := make(chan error, 1)
|
|
go func() {
|
|
_, _, err := clientMux.SendRequest("hello", true, nil)
|
|
result <- err
|
|
}()
|
|
|
|
<-serverMux.incomingRequests
|
|
serverMux.conn.Close()
|
|
err := <-result
|
|
|
|
if err != io.EOF {
|
|
t.Errorf("want EOF, got %v", io.EOF)
|
|
}
|
|
}
|
|
|
|
func TestMuxChannelRequestUnblock(t *testing.T) {
|
|
a, b, connB := channelPair(t)
|
|
defer a.Close()
|
|
defer b.Close()
|
|
defer connB.Close()
|
|
|
|
result := make(chan error, 1)
|
|
go func() {
|
|
_, err := a.SendRequest("hello", true, nil)
|
|
result <- err
|
|
}()
|
|
|
|
<-b.incomingRequests
|
|
connB.conn.Close()
|
|
err := <-result
|
|
|
|
if err != io.EOF {
|
|
t.Errorf("want EOF, got %v", err)
|
|
}
|
|
}
|
|
|
|
func TestMuxCloseChannel(t *testing.T) {
|
|
r, w, mux := channelPair(t)
|
|
defer mux.Close()
|
|
defer r.Close()
|
|
defer w.Close()
|
|
|
|
result := make(chan error, 1)
|
|
go func() {
|
|
var b [1024]byte
|
|
_, err := r.Read(b[:])
|
|
result <- err
|
|
}()
|
|
if err := w.Close(); err != nil {
|
|
t.Errorf("w.Close: %v", err)
|
|
}
|
|
|
|
if _, err := w.Write([]byte("hello")); err != io.EOF {
|
|
t.Errorf("got err %v, want io.EOF after Close", err)
|
|
}
|
|
|
|
if err := <-result; err != io.EOF {
|
|
t.Errorf("got %v (%T), want io.EOF", err, err)
|
|
}
|
|
}
|
|
|
|
func TestMuxCloseWriteChannel(t *testing.T) {
|
|
r, w, mux := channelPair(t)
|
|
defer mux.Close()
|
|
|
|
result := make(chan error, 1)
|
|
go func() {
|
|
var b [1024]byte
|
|
_, err := r.Read(b[:])
|
|
result <- err
|
|
}()
|
|
if err := w.CloseWrite(); err != nil {
|
|
t.Errorf("w.CloseWrite: %v", err)
|
|
}
|
|
|
|
if _, err := w.Write([]byte("hello")); err != io.EOF {
|
|
t.Errorf("got err %v, want io.EOF after CloseWrite", err)
|
|
}
|
|
|
|
if err := <-result; err != io.EOF {
|
|
t.Errorf("got %v (%T), want io.EOF", err, err)
|
|
}
|
|
}
|
|
|
|
func TestMuxInvalidRecord(t *testing.T) {
|
|
a, b := muxPair()
|
|
defer a.Close()
|
|
defer b.Close()
|
|
|
|
packet := make([]byte, 1+4+4+1)
|
|
packet[0] = msgChannelData
|
|
marshalUint32(packet[1:], 29348723 /* invalid channel id */)
|
|
marshalUint32(packet[5:], 1)
|
|
packet[9] = 42
|
|
|
|
a.conn.writePacket(packet)
|
|
go a.SendRequest("hello", false, nil)
|
|
// 'a' wrote an invalid packet, so 'b' has exited.
|
|
req, ok := <-b.incomingRequests
|
|
if ok {
|
|
t.Errorf("got request %#v after receiving invalid packet", req)
|
|
}
|
|
}
|
|
|
|
func TestZeroWindowAdjust(t *testing.T) {
|
|
a, b, mux := channelPair(t)
|
|
defer a.Close()
|
|
defer b.Close()
|
|
defer mux.Close()
|
|
|
|
go func() {
|
|
io.WriteString(a, "hello")
|
|
// bogus adjust.
|
|
a.sendMessage(windowAdjustMsg{})
|
|
io.WriteString(a, "world")
|
|
a.Close()
|
|
}()
|
|
|
|
want := "helloworld"
|
|
c, _ := io.ReadAll(b)
|
|
if string(c) != want {
|
|
t.Errorf("got %q want %q", c, want)
|
|
}
|
|
}
|
|
|
|
func TestMuxMaxPacketSize(t *testing.T) {
|
|
a, b, mux := channelPair(t)
|
|
defer a.Close()
|
|
defer b.Close()
|
|
defer mux.Close()
|
|
|
|
large := make([]byte, a.maxRemotePayload+1)
|
|
packet := make([]byte, 1+4+4+1+len(large))
|
|
packet[0] = msgChannelData
|
|
marshalUint32(packet[1:], a.remoteId)
|
|
marshalUint32(packet[5:], uint32(len(large)))
|
|
packet[9] = 42
|
|
|
|
if err := a.mux.conn.writePacket(packet); err != nil {
|
|
t.Errorf("could not send packet")
|
|
}
|
|
|
|
var wg sync.WaitGroup
|
|
t.Cleanup(wg.Wait)
|
|
wg.Add(1)
|
|
go func() {
|
|
a.SendRequest("hello", false, nil)
|
|
wg.Done()
|
|
}()
|
|
|
|
_, ok := <-b.incomingRequests
|
|
if ok {
|
|
t.Errorf("connection still alive after receiving large packet.")
|
|
}
|
|
}
|
|
|
|
func TestMuxChannelWindowDeferredUpdates(t *testing.T) {
|
|
s, c, mux := channelPair(t)
|
|
cTransport := mux.conn.(*memTransport)
|
|
defer s.Close()
|
|
defer c.Close()
|
|
defer mux.Close()
|
|
|
|
var wg sync.WaitGroup
|
|
t.Cleanup(wg.Wait)
|
|
|
|
data := make([]byte, 1024)
|
|
|
|
wg.Add(1)
|
|
go func() {
|
|
defer wg.Done()
|
|
_, err := s.Write(data)
|
|
if err != nil {
|
|
t.Errorf("Write: %v", err)
|
|
return
|
|
}
|
|
}()
|
|
cWritesInit := cTransport.getWriteCount()
|
|
buf := make([]byte, 1)
|
|
for i := 0; i < len(data); i++ {
|
|
n, err := c.Read(buf)
|
|
if n != len(buf) || err != nil {
|
|
t.Fatalf("Read: %v, %v", n, err)
|
|
}
|
|
}
|
|
cWrites := cTransport.getWriteCount() - cWritesInit
|
|
// reading 1 KiB should not cause any window updates to be sent, but allow
|
|
// for some unexpected writes
|
|
if cWrites > 30 {
|
|
t.Fatalf("reading 1 KiB from channel caused %v writes", cWrites)
|
|
}
|
|
}
|
|
|
|
func TestMuxChannelRejectRemovesFromMux(t *testing.T) {
|
|
serverMux, clientMux := muxPair()
|
|
defer serverMux.Close()
|
|
defer clientMux.Close()
|
|
|
|
var wg sync.WaitGroup
|
|
t.Cleanup(wg.Wait)
|
|
wg.Add(1)
|
|
|
|
go func() {
|
|
defer wg.Done()
|
|
|
|
// The server waits for the channel creation request
|
|
newCh, ok := <-serverMux.incomingChannels
|
|
if !ok {
|
|
t.Error("failed to accept channel")
|
|
return
|
|
}
|
|
ch := newCh.(*channel)
|
|
|
|
if serverMux.chanList.getChan(ch.localId) == nil {
|
|
t.Errorf("channel %d is not in the chanList before Reject", ch.localId)
|
|
}
|
|
|
|
if err := ch.Reject(Prohibited, "rejecting this channel"); err != nil {
|
|
t.Errorf("Reject failed: %v", err)
|
|
}
|
|
|
|
if serverMux.chanList.getChan(ch.localId) != nil {
|
|
t.Errorf("channel %d is still in the chanList after Reject", ch.localId)
|
|
}
|
|
}()
|
|
|
|
_, _, err := clientMux.OpenChannel("test_leak", nil)
|
|
|
|
if err == nil {
|
|
t.Fatal("expected an error (channel rejected), but got nil")
|
|
}
|
|
|
|
if _, ok := err.(*OpenChannelError); !ok {
|
|
t.Errorf("expected *OpenChannelError, got: %T", err)
|
|
}
|
|
}
|
|
|
|
// Don't ship code with debug=true.
|
|
func TestDebug(t *testing.T) {
|
|
if debugMux {
|
|
t.Error("mux debug switched on")
|
|
}
|
|
if debugHandshake {
|
|
t.Error("handshake debug switched on")
|
|
}
|
|
if debugTransport {
|
|
t.Error("transport debug switched on")
|
|
}
|
|
}
|