Skip to content
quarry Artifact [119c74e047]
Login

Artifact [119c74e047]

Artifact 119c74e04797be13f1660a4bbfbdd7d5f65a183be0c153e0a61ad02684b8cde1:


// Package streamcap bounds how many live streams anonymous readers may
// hold open. Each stream keeps a goroutine and, for chat, a broker
// consumer alive for as long as the connection lasts, and an anonymous
// reader can open them without signing in, so nothing else stops one
// client from holding as many as it likes.
package streamcap

import (
	"net"
	"net/http"
	"strings"
	"sync"
)

const (
	// PerAddress is how many anonymous streams one address may hold.
	// A reader with several tabs open on several channels needs a few,
	// and a household or office behind one address needs a few more;
	// a client past eight is opening streams rather than reading them.
	PerAddress = 8
	// Total is how many anonymous streams the whole process may hold.
	// It bounds the goroutines and broker consumers anonymous readers
	// can pin at once, with room for a busy public channel, while
	// staying small beside what the broker and the process can carry.
	Total = 256
)

// Anonymous is the process-wide budget every anonymous stream draws
// on. It is one budget rather than one per surface because the cost it
// bounds, goroutines and broker consumers, is the process's, however
// the stream was reached.
var Anonymous = New(PerAddress, Total)

// Limiter counts open streams per address and in total.
type Limiter struct {
	perAddr, total int

	mu     sync.Mutex
	n      int
	byAddr map[string]int
}

// New returns a limiter admitting perAddr streams from one address and
// total streams overall.
func New(perAddr, total int) *Limiter {
	return &Limiter{perAddr: perAddr, total: total, byAddr: map[string]int{}}
}

// Acquire admits one stream from addr, or reports that addr or the
// process is at its cap. The caller must call release when the stream
// ends; calling it more than once frees the slot only once.
func (l *Limiter) Acquire(addr string) (release func(), ok bool) {
	l.mu.Lock()
	defer l.mu.Unlock()
	if l.n >= l.total || l.byAddr[addr] >= l.perAddr {
		return nil, false
	}
	l.n++
	l.byAddr[addr]++
	var once sync.Once
	return func() {
		once.Do(func() {
			l.mu.Lock()
			defer l.mu.Unlock()
			l.n--
			if l.byAddr[addr]--; l.byAddr[addr] <= 0 {
				delete(l.byAddr, addr)
			}
		})
	}, true
}

// Addr is the address a stream is counted against: the first address
// the proxy forwards when there is one, else the socket's host. It is
// the same rule the forge's other per-address limits already use, and
// it must stay so, because the forge runs behind a TLS proxy where
// every socket is the proxy's and counting sockets would put every
// anonymous reader under one address's cap.
func Addr(req *http.Request) string {
	if f := req.Header.Get("X-Forwarded-For"); f != "" {
		return strings.TrimSpace(strings.Split(f, ",")[0])
	}
	host, _, err := net.SplitHostPort(req.RemoteAddr)
	if err != nil {
		return req.RemoteAddr
	}
	return host
}