A user-mode NAT (Network Address Translation) implementation in Go, providing TCP/IP connectivity without requiring root privileges or virtual network interfaces.
This library implements a lightweight, user-space networking stack that intercepts IPv4 packets and translates them to real network connections. It's similar to the original SLIRP project and is useful for:
- Virtual machine networking without elevated privileges
- Network virtualization and testing
- Containerized applications requiring network isolation
- Educational purposes and protocol learning
- TCP Connection Handling: Full TCP state machine with flow control, windowing, and retransmission (IPv4 and IPv6)
- UDP Support: Stateless UDP packet forwarding with connection tracking (IPv4 and IPv6)
- IPv6 Support: Comprehensive IPv6 support including:
- TCP and UDP connection handling
- ICMPv6 (Echo Request/Reply for ping6, Neighbor Discovery)
- Virtual TCP listeners for IPv6
- Proper IPv6 pseudo-header checksumming
- Virtual Listeners: User-land TCP servers (IPv4 and IPv6) for fully virtual testing
- Automatic Cleanup: Background maintenance for idle connection cleanup
- Thread-Safe: Concurrent handling of multiple connections
- Zero Dependencies: Uses only Go standard library
go get github.com/KarpelesLab/slirppackage main
import (
"github.com/KarpelesLab/slirp"
)
func main() {
// Create a new NAT stack
stack := slirp.New()
// Define your packet writer callback
// This function receives complete Ethernet frames to send back to the client
writer := func(frame []byte) error {
// Send the frame to your virtual network interface
// e.g., write to a TAP device or send via a socket
return nil
}
// Process incoming IPv4 packets from your client
// clientMAC: MAC address of the client (for Ethernet frame destination)
// gwMAC: Gateway MAC address (for Ethernet frame source)
// ipPacket: Raw IPv4 packet data (starting at IP header)
// ns: Namespace identifier (use 0 if not using namespaces)
var clientMAC [6]byte = [6]byte{0x52, 0x54, 0x00, 0x12, 0x34, 0x56}
var gwMAC [6]byte = [6]byte{0x52, 0x54, 0x00, 0x12, 0x34, 0x57}
// Handle each packet
err := stack.HandlePacket(0, clientMAC, gwMAC, ipPacket, writer)
if err != nil {
// Handle error
}
}// Example: Integration with a TAP device or packet source
func handleClientPackets(stack *slirp.Stack, packetSource <-chan []byte) {
clientMAC := [6]byte{0x52, 0x54, 0x00, 0x12, 0x34, 0x56}
gwMAC := [6]byte{0x52, 0x54, 0x00, 0x12, 0x34, 0x57}
writer := func(frame []byte) error {
// Send frame back to client
return sendToTAPDevice(frame)
}
for packet := range packetSource {
// Extract IP packet from Ethernet frame if needed
ipPacket := extractIPPacket(packet)
err := stack.HandlePacket(0, clientMAC, gwMAC, ipPacket, writer)
if err != nil {
log.Printf("Error handling packet: %v", err)
}
}
}The library supports virtual TCP listeners that allow services to run entirely within the slirp stack, enabling fully user-land testing by connecting multiple slirp stacks together.
package main
import (
"fmt"
"io"
"log"
"github.com/KarpelesLab/slirp"
)
func main() {
stack := slirp.New()
// Create a virtual listener on a virtual IP address
listener, err := stack.Listen("tcp", "10.0.0.1:8080")
if err != nil {
log.Fatal(err)
}
defer listener.Close()
// Accept connections in a goroutine
go func() {
for {
conn, err := listener.Accept()
if err != nil {
return
}
// Handle connection (echo server example)
go func(c net.Conn) {
defer c.Close()
io.Copy(c, c) // Echo back
}(conn)
}
}()
// Process incoming packets that connect to 10.0.0.1:8080
// The stack will route them to the virtual listener
clientMAC := [6]byte{0x52, 0x54, 0x00, 0x12, 0x34, 0x56}
gwMAC := [6]byte{0x52, 0x54, 0x00, 0x12, 0x34, 0x57}
writer := func(frame []byte) error {
// Send frame back to client
return sendToClient(frame)
}
// When client sends SYN to 10.0.0.1:8080, it will trigger Accept()
for packet := range packetSource {
err := stack.HandlePacket(0, clientMAC, gwMAC, packet, writer)
if err != nil {
log.Printf("Error: %v", err)
}
}
}Virtual listeners enable fully user-land integration testing:
func TestTwoStackCommunication(t *testing.T) {
stack1 := slirp.New()
stack2 := slirp.New()
// Stack2 listens on virtual address
listener, _ := stack2.Listen("tcp", "10.0.0.2:9000")
// Server on stack2
go func() {
conn, _ := listener.Accept()
defer conn.Close()
buf := make([]byte, 1024)
n, _ := conn.Read(buf)
conn.Write(buf[:n]) // Echo
}()
// Client on stack1 sends packets to stack2's virtual address
// Packets from stack1 are forwarded to stack2 via the writer callbacks
// This enables testing without real network interfaces
}The main NAT stack that manages TCP and UDP connections.
func New() *StackCreates a new NAT stack instance. Automatically starts a background maintenance goroutine for connection cleanup.
Callback function type for sending Ethernet frames back to the client.
type Writer func([]byte) errorfunc (s *Stack) HandlePacket(namespace uintptr, clientMAC [6]byte, gwMAC [6]byte, packet []byte, w Writer) errorProcesses an IP packet. This handles traffic in both directions - there is no separate "inbound" handler. Slirp processes packets bidirectionally based on routing and connection state.
The function automatically detects the IP version (IPv4/IPv6) from the packet header.
Parameters:
namespace: Identifier for connection isolation (use 0 for single namespace)clientMAC: MAC address of the endpoint that sent this packet (used as destination in responses)gwMAC: MAC address for this slirp instance (used as source in responses)packet: Raw IP packet data (must start at IP header, not Ethernet header)w: Writer callback for sending Ethernet frames back to the endpoint
Returns:
error: Error if packet is malformed, unsupported IP version, or processing fails
Supported IP Versions:
- IPv4 (version 4) - fully supported
- IPv6 (version 6) - fully supported (TCP, UDP, ICMPv6, virtual listeners)
Supported Protocols:
- TCP (protocol 6) - creates real connections or routes to virtual listeners (IPv4 and IPv6)
- UDP (protocol 17) - creates real sockets for packet forwarding (IPv4 and IPv6)
- ICMPv6 (protocol 58) - Echo Request/Reply (ping6) and Neighbor Discovery (NDP)
- Virtual TCP listeners (packets destined for registered virtual addresses, IPv4 and IPv6)
func (s *Stack) Listen(network, address string) (net.Listener, error)Creates a virtual TCP listener on a virtual network address within the slirp stack.
Parameters:
network: Must be "tcp", "tcp4", or "tcp6"address: Virtual IP:port to listen on (e.g., "10.0.0.1:8080" or "[::1]:8080")
Returns:
*Listener: A listener that implements thenet.Listenerinterfaceerror: Error if the address is invalid or already in use
Examples:
// IPv4 listener
listener, err := stack.Listen("tcp4", "192.168.100.1:9000")
if err != nil {
log.Fatal(err)
}
defer listener.Close()
conn, err := listener.Accept()
// Handle connection...
// IPv6 listener
listener6, err := stack.Listen("tcp6", "[fe80::1]:9000")
if err != nil {
log.Fatal(err)
}
defer listener6.Close()
conn6, err := listener6.Accept()
// Handle connection...Virtual listener type that implements net.Listener:
Methods:
Accept() (net.Conn, error): Waits for and returns the next connectionClose() error: Closes the listenerAddr() net.Addr: Returns the listener's network address
Virtual connection type that implements net.Conn. Returned by Listener.Accept().
Methods:
Read(b []byte) (int, error): Reads data from the connectionWrite(b []byte) (int, error): Writes data to the connectionClose() error: Closes the connectionLocalAddr() net.Addr: Returns the local network addressRemoteAddr() net.Addr: Returns the remote network addressSetDeadline(t time.Time) error: Not implementedSetReadDeadline(t time.Time) error: Not implementedSetWriteDeadline(t time.Time) error: Not implemented
The library implements a simplified TCP state machine:
- Connection Establishment: SYN packets trigger real TCP connections to destination hosts
- Data Transfer: In-order data delivery with flow control based on receive window
- Flow Control: Respects client's advertised window size, implements backpressure
- Connection Teardown: Proper FIN handling for graceful connection closure
- MSS Negotiation: Parses and respects Maximum Segment Size from client SYN
Features:
- Automatic retransmission for window probe
- Send queue buffering (up to 1MB per connection)
- Idle timeout: 2 minutes
- Maintenance interval: 5 seconds
UDP connections are tracked for bi-directional packet forwarding:
- Creates real UDP socket on first packet to destination
- Forwards packets between client and destination
- Maintains connection tracking for responses
- Idle timeout: 60 seconds
A background goroutine runs every 30 seconds to clean up:
- TCP connections idle for >2 minutes or marked as closed
- UDP connections idle for >60 seconds
- Memory: Each TCP connection buffers up to 1MB of send data
- Goroutines: 2 goroutines per TCP connection (reader + maintenance), 1 per UDP connection
- Thread Safety: All operations are protected by mutexes for concurrent access
- Packet Processing: Minimal copying, operates directly on provided buffers where possible
- No ICMPv4 support (ping won't work for IPv4)
- No IPv6 extension header handling
- No IP fragmentation handling
- Simplified TCP implementation (no congestion control, limited retransmission)
- No support for TCP options beyond MSS
- ICMPv6 Router Advertisement not yet implemented (Router Solicitation is ignored)
MIT License - See LICENSE file for details
Copyright (c) 2025 Karpelès Lab Inc.
Contributions are welcome! Please ensure:
- Code follows Go conventions and passes
go vet - New features include tests
- Public APIs are documented
For bugs and feature requests, please open an issue on GitHub.