Frontend Labs
⋯

Week 5 Mini Project: TCP Proxy with Traffic Inspector

Architecture

Client → [TCP Proxy :8080] → [Upstream :9090]
                 ↓
          [Traffic Tap: tap.log]
          [Metrics: /debug/vars]

Full Implementation

// proxy/main.go
package main

import (
    "context"
    "expvar"
    "fmt"
    "io"
    "net"
    "net/http"
    _ "net/http/pprof"
    "os"
    "os/signal"
    "sync"
    "sync/atomic"
    "syscall"
    "time"
)

var (
    connAccepted  = expvar.NewInt("connections_accepted")
    connActive    = expvar.NewInt("connections_active")
    connRejected  = expvar.NewInt("connections_rejected")
    bytesInTotal  = expvar.NewInt("bytes_in_total")
    bytesOutTotal = expvar.NewInt("bytes_out_total")
)

const (
    maxConnections = 500
    idleTimeout    = 30 * time.Second
    upstreamAddr   = "127.0.0.1:9090"
    listenAddr     = ":8080"
)

type Proxy struct {
    listener net.Listener
    pool     *Pool
    tap      io.Writer
    active   atomic.Int64
    wg       sync.WaitGroup
    shutdown atomic.Bool
}

func NewProxy(listener net.Listener, upstreamAddr string, tap io.Writer) *Proxy {
    return &Proxy{
        listener: listener,
        pool:     NewPool(20, 30*time.Second),
        tap:      tap,
    }
}

func (p *Proxy) Serve(ctx context.Context) {
    for {
        conn, err := p.listener.Accept()
        if err != nil {
            select {
            case <-ctx.Done():
                return
            default:
                continue
            }
        }

        if p.active.Load() >= maxConnections {
            connRejected.Add(1)
            conn.Close()
            continue
        }

        connAccepted.Add(1)
        connActive.Add(1)
        p.active.Add(1)
        p.wg.Add(1)
        go p.handleConn(conn)
    }
}

func (p *Proxy) handleConn(client net.Conn) {
    defer client.Close()
    defer connActive.Add(-1)
    defer p.active.Add(-1)
    defer p.wg.Done()

    // Get upstream connection from pool
    upstream, err := p.pool.Get(upstreamAddr)
    if err != nil {
        fmt.Fprintf(os.Stderr, "upstream connect: %v\n", err)
        return
    }

    remote := client.RemoteAddr().String()
    var bytesIn, bytesOut int64
    var innerWg sync.WaitGroup
    innerWg.Add(2)

    // client → upstream
    go func() {
        defer innerWg.Done()
        tapped := io.TeeReader(client,
            &hexLogger{w: p.tap, dir: fmt.Sprintf("IN  [%s]", remote)})
        n, _ := io.Copy(upstream, tapped)
        atomic.AddInt64(&bytesIn, n)
        bytesInTotal.Add(n)
        if tc, ok := upstream.(*net.TCPConn); ok {
            tc.CloseWrite()
        }
    }()

    // upstream → client
    go func() {
        defer innerWg.Done()
        tapped := io.TeeReader(upstream,
            &hexLogger{w: p.tap, dir: fmt.Sprintf("OUT [%s]", remote)})
        n, _ := io.Copy(client, tapped)
        atomic.AddInt64(&bytesOut, n)
        bytesOutTotal.Add(n)
        if tc, ok := client.(*net.TCPConn); ok {
            tc.CloseWrite()
        }
    }()

    innerWg.Wait()
    p.pool.Put(upstreamAddr, upstream)
}

func (p *Proxy) Drain() {
    p.wg.Wait()
}

type hexLogger struct {
    w   io.Writer
    dir string
}

func (h *hexLogger) Write(p []byte) (int, error) {
    preview := p
    if len(preview) > 64 {
        preview = preview[:64]
    }
    fmt.Fprintf(h.w, "%s %d bytes: %q\n", h.dir, len(p), preview)
    return len(p), nil
}

func main() {
    // Traffic tap
    tap, _ := os.Create("tap.log")
    defer tap.Close()

    ln, err := net.Listen("tcp", listenAddr)
    if err != nil {
        fmt.Println("listen:", err)
        return
    }

    proxy := NewProxy(ln, upstreamAddr, tap)

    ctx, stop := signal.NotifyContext(context.Background(), syscall.SIGTERM, syscall.SIGINT)
    defer stop()

    // Observability server
    go http.ListenAndServe(":9091", nil) // /debug/vars, /debug/pprof

    fmt.Printf("TCP proxy %s → %s\n", listenAddr, upstreamAddr)
    fmt.Println("Metrics: http://localhost:9091/debug/vars")

    go proxy.Serve(ctx)
    <-ctx.Done()

    fmt.Println("shutting down...")
    ln.Close()
    proxy.Drain()
    fmt.Println("shutdown complete")
}

Engineer Takeaway

A TCP proxy is the foundation of load balancers (Week 7), WAFs (Week 10), and security gateways (Week 12). Cloudflare Spectrum, AWS NLB, and HAProxy in TCP mode all start with this: accept → bidirectional io.Copy → return to pool. The traffic tap is how compliance teams capture data for forensics without changing the application.

Generated from week5.md · 8 Oct 2026 16:36