Day 33 — TCP Server Patterns
Learning Objectives
- Implement connection limits to protect against resource exhaustion
- Set deadlines on connections to prevent slowloris-style attacks
- Track connection states for observability and incident response
Exercise: Hardened TCP Server with Connection Limits
package main
import (
"fmt"
"net"
"sync/atomic"
"time"
)
const (
maxConnections = 1000
idleTimeout = 30 * time.Second
readTimeout = 60 * time.Second
)
type HardenedServer struct {
listener net.Listener
active atomic.Int64
total atomic.Int64
rejected atomic.Int64
}
func (s *HardenedServer) Serve() {
for {
conn, err := s.listener.Accept()
if err != nil {
return
}
// Connection limit check — reject immediately if at capacity
current := s.active.Load()
if current >= maxConnections {
s.rejected.Add(1)
fmt.Fprintf(conn, "ERR: server at capacity (%d/%d connections)\n", current, maxConnections)
conn.Close()
continue
}
s.active.Add(1)
s.total.Add(1)
go s.handleConn(conn)
}
}
func (s *HardenedServer) handleConn(conn net.Conn) {
defer conn.Close()
defer s.active.Add(-1)
remote := conn.RemoteAddr()
// Set absolute deadline (protects against very slow clients)
conn.SetDeadline(time.Now().Add(readTimeout))
buf := make([]byte, 4096)
for {
// Reset idle timer on each read
conn.SetReadDeadline(time.Now().Add(idleTimeout))
n, err := conn.Read(buf)
if err != nil {
if ne, ok := err.(net.Error); ok && ne.Timeout() {
fmt.Printf("IDLE_TIMEOUT %s\n", remote)
}
return
}
// Echo data back
conn.SetWriteDeadline(time.Now().Add(5 * time.Second))
if _, err := conn.Write(buf[:n]); err != nil {
return
}
}
}
Real-World Context
Without maxConnections, a DDoS opening 1 million TCP connections would exhaust file descriptors (ulimit -n) and kill your server. Without idleTimeout, a Slowloris attack keeping 10,000 connections open but never sending data would do the same. Both are OWASP A05 (Security Misconfiguration).