Skip to content

KarpelesLab/slirp

Repository files navigation

slirp

⚠️ Deprecated: This package is no longer maintained. Use github.com/KarpelesLab/pktkit instead.

Go Reference CI Coverage Status Go Report Card

A user-mode NAT (Network Address Translation) implementation in Go, providing TCP/IP connectivity without requiring root privileges or virtual network interfaces.

Overview

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

Features

  • TCP Connection Handling: Full TCP state machine with flow control, windowing, retransmission, and congestion control (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

Installation

go get github.com/KarpelesLab/slirp

Usage

Basic Example

package 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
    }
}

Integration Example

// 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)
        }
    }
}

Virtual Listeners

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)
        }
    }
}

Testing with Two Slirp Stacks

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
}

API Reference

Types

Stack

The main NAT stack that manages TCP and UDP connections.

func New() *Stack

Creates a new NAT stack instance. Automatically starts a background maintenance goroutine for connection cleanup.

Writer

Callback function type for sending Ethernet frames back to the client.

type Writer func([]byte) error

Methods

HandlePacket

func (s *Stack) HandlePacket(namespace uintptr, clientMAC [6]byte, gwMAC [6]byte, packet []byte, w Writer) error

Processes 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)

Listen

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 the net.Listener interface
  • error: 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...

Listener

Virtual listener type that implements net.Listener:

Methods:

  • Accept() (net.Conn, error): Waits for and returns the next connection
  • Close() error: Closes the listener
  • Addr() net.Addr: Returns the listener's network address

VirtualConn

Virtual connection type that implements net.Conn. Returned by Listener.Accept().

Methods:

  • Read(b []byte) (int, error): Reads data from the connection
  • Write(b []byte) (int, error): Writes data to the connection
  • Close() error: Closes the connection
  • LocalAddr() net.Addr: Returns the local network address
  • RemoteAddr() net.Addr: Returns the remote network address
  • SetDeadline(t time.Time) error: Sets both read and write deadlines
  • SetReadDeadline(t time.Time) error: Sets the read deadline
  • SetWriteDeadline(t time.Time) error: Sets the write deadline

Implementation Details

TCP Handling

The library implements a full, RFC-compliant TCP state machine:

  1. Connection Establishment: SYN packets trigger real TCP connections to destination hosts
  2. Data Transfer: In-order data delivery with flow control based on receive window
  3. Flow Control: Respects client's advertised window size, implements backpressure
  4. Congestion Control: NewReno (RFC 5681) with slow start, congestion avoidance, fast retransmit, and fast recovery
  5. Connection Teardown: Proper FIN handling for graceful connection closure
  6. TCP Options: MSS, Window Scaling (RFC 7323), Timestamps (RFC 7323), SACK (RFC 2018)

Features:

  • Full retransmission with RTO calculation per RFC 6298 (SRTT, RTTVAR, Karn's algorithm)
  • Selective Acknowledgment (SACK) for efficient loss recovery
  • Timestamp-based RTT measurement and PAWS protection
  • Send queue buffering (up to 1MB per connection)
  • Idle timeout: 2 minutes
  • Maintenance interval: 5 seconds

UDP Handling

UDP connections are tracked for bi-directional packet forwarding:

  1. Creates real UDP socket on first packet to destination
  2. Forwards packets between client and destination
  3. Maintains connection tracking for responses
  4. Idle timeout: 60 seconds

Connection Cleanup

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

Performance Considerations

  • 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

Limitations

  • No ICMPv4 support (ping won't work for IPv4)
  • No IPv6 extension header handling
  • No IP fragmentation handling
  • ICMPv6 Router Advertisement not yet implemented (Router Solicitation is ignored)

License

MIT License - See LICENSE file for details

Copyright (c) 2025 Karpelès Lab Inc.

Contributing

Contributions are welcome! Please ensure:

  • Code follows Go conventions and passes go vet
  • New features include tests
  • Public APIs are documented

Related Projects

  • slirp - Original SLIRP library
  • gvisor - More complete userspace network stack

Support

For bugs and feature requests, please open an issue on GitHub.

About

SLiRP, but in Go

Resources

License

Stars

Watchers

Forks

Packages

 
 
 

Contributors