131 lines
3.8 KiB
Go
131 lines
3.8 KiB
Go
package main
|
|
|
|
// The context socket is 0666: every user on the machine may connect, and the
|
|
// daemon behind it runs as root (or SYSTEM). So what one connection can make
|
|
// the daemon hold must be bounded by the daemon, not by the asker.
|
|
|
|
import (
|
|
"net"
|
|
"path/filepath"
|
|
"runtime"
|
|
"strings"
|
|
"testing"
|
|
"time"
|
|
)
|
|
|
|
// An ask is a folder path and a display name. A client that streams a request
|
|
// with no end gets the daemon to read only a bounded amount of it, then a
|
|
// reply and a close; before, the decoder kept every byte until the 30 s
|
|
// deadline, and nothing capped how many such connections were open.
|
|
func TestAContextAskWithNoEndIsReadOnlyToABound(t *testing.T) {
|
|
if runtime.GOOS == "windows" {
|
|
t.Skip("no unix-socket peer credentials on windows; contextsock_windows.go says why")
|
|
}
|
|
sock := filepath.Join(t.TempDir(), "s.sock")
|
|
ln, err := net.Listen("unix", sock)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
defer ln.Close()
|
|
go func() {
|
|
c, err := ln.Accept()
|
|
if err != nil {
|
|
return
|
|
}
|
|
contextSockServe(c)
|
|
}()
|
|
|
|
c, err := net.Dial("unix", sock)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
defer c.Close()
|
|
const total = 16 << 20
|
|
chunk := []byte(strings.Repeat("a", 64<<10))
|
|
sent := 0
|
|
if _, err := c.Write([]byte(`{"root":"`)); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
for sent < total {
|
|
_ = c.SetWriteDeadline(time.Now().Add(2 * time.Second))
|
|
n, err := c.Write(chunk)
|
|
sent += n
|
|
if err != nil {
|
|
break
|
|
}
|
|
}
|
|
// The kernel's socket buffers hold a few hundred KiB the daemon never
|
|
// read; anything near the whole stream means the daemon read it all.
|
|
if sent >= 4<<20 {
|
|
t.Fatalf("the daemon accepted %d MiB of one ask with no end; it must stop at a bound", sent>>20)
|
|
}
|
|
}
|
|
|
|
// Asks in flight are capped: a connection past the cap is closed at once, so
|
|
// many idle or endless connections cannot multiply the bound above.
|
|
func TestContextAsksInFlightAreCapped(t *testing.T) {
|
|
if runtime.GOOS == "windows" {
|
|
t.Skip("no unix-socket peer credentials on windows; contextsock_windows.go says why")
|
|
}
|
|
sock := filepath.Join(t.TempDir(), "s.sock")
|
|
ln, err := net.Listen("unix", sock)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
defer ln.Close()
|
|
// Every connection here is the same user; lift the per-user share so the
|
|
// overall cap is what this test meets (TestOneUserCannotHoldEveryContextSockSlot
|
|
// holds the share).
|
|
oldShare := contextSockMaxPerUser
|
|
contextSockMaxPerUser = contextSockMaxInFlight
|
|
t.Cleanup(func() { contextSockMaxPerUser = oldShare })
|
|
go contextSockAccept(ln)
|
|
|
|
var held []net.Conn
|
|
defer func() {
|
|
for _, h := range held {
|
|
_ = h.Close()
|
|
}
|
|
}()
|
|
// Each of these sends nothing and holds its slot until its deadline.
|
|
for i := 0; i < contextSockMaxInFlight; i++ {
|
|
h, err := net.Dial("unix", sock)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
held = append(held, h)
|
|
}
|
|
// Give the daemon's accept loop time to take every held connection.
|
|
time.Sleep(200 * time.Millisecond)
|
|
|
|
extra, err := net.Dial("unix", sock)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
defer extra.Close()
|
|
_ = extra.SetReadDeadline(time.Now().Add(2 * time.Second))
|
|
buf := make([]byte, 512)
|
|
n, rerr := extra.Read(buf)
|
|
if ne, ok := rerr.(net.Error); ok && ne.Timeout() {
|
|
t.Fatalf("a connection past the cap of %d was left open; it must be refused", contextSockMaxInFlight)
|
|
}
|
|
if n > 0 && !strings.Contains(string(buf[:n]), "busy") {
|
|
t.Fatalf("a connection past the cap got %q; it must be told the daemon is busy", buf[:n])
|
|
}
|
|
|
|
// Once a held connection goes, a new ask gets a slot again.
|
|
_ = held[0].Close()
|
|
held = held[1:]
|
|
time.Sleep(200 * time.Millisecond)
|
|
again, err := net.Dial("unix", sock)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
defer again.Close()
|
|
_ = again.SetReadDeadline(time.Now().Add(500 * time.Millisecond))
|
|
n, rerr = again.Read(buf)
|
|
if ne, ok := rerr.(net.Error); !ok || !ne.Timeout() {
|
|
t.Fatalf("after a slot was freed, a new ask was not served (read %q, %v)", buf[:n], rerr)
|
|
}
|
|
}
|