import (
"net"
- "time"
"github.com/shazow/rateio"
"golang.org/x/crypto/ssh"
type SSHListener struct {
net.Listener
config *ssh.ServerConfig
- RateLimit bool
+ RateLimit func() rateio.Limiter
}
// Make an SSH listener socket
}
func (l *SSHListener) handleConn(conn net.Conn) (*Terminal, error) {
- if l.RateLimit {
+ if l.RateLimit != nil {
// TODO: Configurable Limiter?
- conn = ReadLimitConn(conn, rateio.NewGracefulLimiter(1024*10, time.Minute*2, time.Second*3))
+ conn = ReadLimitConn(conn, l.RateLimit())
}
// Upgrade TCP connection to SSH connection
import (
"io"
"net"
+ "time"
"github.com/shazow/rateio"
)
Reader: rateio.NewReader(conn, limiter),
}
}
+
+// Count each read as 1 unless it exceeds some number of bytes.
+type inputLimiter struct {
+ // TODO: Could do all kinds of fancy things here, like be more forgiving of
+ // connections that have been around for a while.
+
+ Amount int
+ Frequency time.Duration
+
+ remaining int
+ readCap int
+ numRead int
+ timeRead time.Time
+}
+
+// NewInputLimiter returns a rateio.Limiter with sensible defaults for
+// differentiating between humans typing and bots spamming.
+func NewInputLimiter() rateio.Limiter {
+ grace := time.Second * 3
+ return &inputLimiter{
+ Amount: 200 * 4 * 2, // Assume fairly high typing rate + margin for copypasta of links.
+ Frequency: time.Minute * 2,
+ readCap: 128, // Allow up to 128 bytes per read (anecdotally, 1 character = 52 bytes over ssh)
+ numRead: -1024 * 1024, // Start with a 1mb grace
+ timeRead: time.Now().Add(grace),
+ }
+}
+
+// Count applies 1 if n<readCap, else n
+func (limit *inputLimiter) Count(n int) error {
+ now := time.Now()
+ if now.After(limit.timeRead) {
+ limit.numRead = 0
+ limit.timeRead = now.Add(limit.Frequency)
+ }
+ if n <= limit.readCap {
+ limit.numRead += 1
+ } else {
+ limit.numRead += n
+ }
+ if limit.numRead > limit.Amount {
+ return rateio.ErrRateExceeded
+ }
+ return nil
+}