Skip to content

Complete implementation for my talk on building a postgres sniffer in Go at GopherCon Africa 2025

Notifications You must be signed in to change notification settings

Emmanuerl/pg-proxy

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

3 Commits
 
 
 
 
 
 

Repository files navigation

[Workshop] PG-proxy

a Postgres aware reverse proxy / protocol sniffer

Overview

This workshop explores the PostgreSQL wire protocol as a living example of binary communication systems while also showcasing Go’s elegance and power in building low-level network tools.

We’ll study, design, and implement a PostgreSQL reverse proxy and sniffer from scratch, using only Go’s standard library. This exercise demonstrates why Go is a first-class citizen in network programming: simple primitives, predictable performance, and a design that encourages understanding of what’s truly happening on the wire.

1. What Are Protocol Sniffers?

A protocol sniffer is a program that captures, inspects, and sometimes routes messages exchanged between a client and a server. It’s like watching two computers talk — byte by byte — and understanding their language.

Examples:

  • Wireshark – a universal packet analyzer.
  • tcpdump – the classic CLI packet capture tool.
  • PgBouncer / Envoy – specialized proxies that can also inspect protocol metadata.
  • PG-proxy – lightweight, concurrent, and fully under your control.

A tiny Go example (3 lines):

// snippets/simple-proxy
ln, _ := net.Listen("tcp", ":5433")
for { conn, _ := ln.Accept(); go io.Copy(os.Stdout, conn) }

That’s a sniffer. A full TCP listener and reader in one line. No frameworks, no external dependencies — just the Go standard library.

2. Binary vs Text Protocols

Feature Text Protocols Binary Protocols
Human-readable
Message Size Larger (delimiters, quotes) Smaller (fixed-size fields)
Examples HTTP, SMTP PostgreSQL, MySQL, Protobuf

3. Endianess Refresher:

Big Endian → Most Significant Byte first.

Little Endian → Least Significant Byte first.

PostgreSQL uses Big Endian, and Go makes this explicit with binary.BigEndian util in the binary/encoding package.

n := binary.BigEndian.Uint32([]byte{0x04, 0xD2, 0x16, 0x2F})
fmt.Println(n) // 80877103 (PostgreSQL SSLRequest code)

Readable, direct, and type-safe — this is Go’s charm: clarity over magic.

Why Golang?

The The net and io Packages is arguably the most beautiful std lib in the entire world — Go’s Superpowers

The net and io packages are what make Go the language for building protocol-aware systems.

Unlike many languages that abstract away the network layer behind complex APIs, Go exposes sockets as first-class citizens — but with safety and simplicity built-in.

1. Unified Interfaces

Every network connection in Go implements net.Conn, giving you a simple, consistent API:

type Conn interface {
    Read(b []byte) (n int, err error)
    Write(b []byte) (n int, err error)
    Close() error
    LocalAddr() Addr
    RemoteAddr() Addr
}

Whether it’s TCP, TLS, or Unix sockets — same interface, same code.

2. Composable Building Blocks

Combine net.Conn with the io package's Writer and Reader interfaces, and you have a universal pipeline!

ln, _ := net.Listen("tcp", ":5432")
for {
    client, _ := ln.Accept()
    backend, _ := net.Dial("tcp", "localhost:5433")
    go io.Copy(backend, client)
    go io.Copy(client, backend)
}

In six lines, you’ve built a full TCP proxy — no dependencies, no event loops. This composability is where Go shines: you write plumbing, not boilerplate.

3. Concurrency Without Pain

  • Each client connection runs in its own goroutine:
  • No manual thread management.
  • No shared-state headaches.
  • No deadlocks caused by I/O blocking.

Go’s scheduler multiplexes goroutines across OS threads efficiently, making network-heavy programs scale effortlessly.

4. Built-in TLS and Context Control

Need to upgrade to TLS? Add tls.Server() or tls.Client() on top of any net.Conn. Need timeouts, cancellation, or deadlines? Add a context.Context.

Go gives you full control — without ceremony.

More reasons why Go Excels at Network Programming:

  1. Predictable resource usage: Goroutines scale linearly, not exponentially.

  2. Minimalism: The net package encourages understanding of TCP and sockets.

  3. Stability: The Go standard library rarely breaks — perfect for long-lived systems.

  4. Portability: Cross-compile once, run anywhere.

  5. Transparency: You see every byte that crosses the wire.

In Go, you don’t hide the complexity of networks — you embrace it, safely.

The PostgreSQL Wire Protocol

PostgreSQL’s binary wire protocol is our playground — a clean, structured system that maps beautifully onto Go’s binary APIs.

Subprotocol Description
Start-up Connection setup, SSL negotiation, authentication
Query Executing SQL statements
Function Call Stored procedure invocation
Copy Bulk data transfer
Termination Connection teardown

The Startup Protocol — The Key to Sniffing

Postgres protocol  --- The Start-up sub-protocol

  • Client sends SSLRequest.
  • Server replies 'S' or 'N'.
  • If S, perform TLS handshake. (we'd impersonate the Postmaster here and reply with S)
  • Client sends StartupMessage (protocol version, user, db).
  • Server authenticates → Query phase begins.

At step (3), we can intercept SNI — that’s where routing decisions are made. The beauty is that Go’s crypto/tls lets us peek at the handshake metadata before finishing the handshake — perfect for SNI-based routing.

The Sad Paths (and Why Go Still Wins)

Challenge Go’s Limitation Why It’s Still Beautiful

Challenge Go’s Limitation Why It’s Still Beautiful
No async I/O Uses goroutines instead of event loops Easier to reason about concurrency
Verbose error handling Explicit if err != nil blocks Encourages discipline and clarity
Limited binary parsing DSLs Manual byte reads Forces understanding of the protocol
Verbose TLS APIs Manual wrapping Full control and transparency

Every "limitation" in Go forces explicitness — a virtue in protocol-level work.

About

Complete implementation for my talk on building a postgres sniffer in Go at GopherCon Africa 2025

Resources

Stars

Watchers

Forks

Releases

No releases published

Packages

No packages published

Languages