simnet

package module
v0.0.6 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Mar 31, 2026 License: BSD-3-Clause Imports: 12 Imported by: 1

README

simnet

A small Go library for simulating packet networks in-process. It provides drop-in net.PacketConn endpoints connected through configurable virtual links with bandwidth, latency, and MTU constraints. Useful for testing networking code without sockets or root privileges.

  • Drop-in API: implements net.PacketConn
  • Realistic links: per-direction bandwidth, latency, and MTU
  • Backpressure/buffering: bandwidth–delay product aware queues
  • Routers: perfect delivery, fixed-latency, simple firewall/NAT-like routing
  • Deterministic testing: opt-in synctest-based tests for time control

Install

go get github.com/marcopolo/simnet

Quick start: high-level Simnet

Create a simulated network and two endpoints. Each endpoint gets a bidirectional link with independent uplink/downlink settings. Start the network, then use the returned net.PacketConns as usual.

package main

import (
    "fmt"
    "net"
    "time"

    "github.com/marcopolo/simnet"
)

func main() {
    n := &simnet.Simnet{}
    settings := simnet.NodeBiDiLinkSettings{
        Downlink: simnet.LinkSettings{BitsPerSecond: 10 * simnet.Mibps},
        Uplink:   simnet.LinkSettings{BitsPerSecond: 10 * simnet.Mibps},
        Latency:  5 * time.Millisecond,
    }

    addrA := &net.UDPAddr{IP: net.ParseIP("1.0.0.1"), Port: 9001}
    addrB := &net.UDPAddr{IP: net.ParseIP("1.0.0.2"), Port: 9002}

    client := n.NewEndpoint(addrA, settings)
    server := n.NewEndpoint(addrB, settings)

    _ = n.Start()
    defer n.Close()

    // Echo server
    go func() {
        buf := make([]byte, 1024)
        server.SetReadDeadline(time.Now().Add(2 * time.Second))
        n, src, err := server.ReadFrom(buf)
        if err != nil { return }
        server.WriteTo(append([]byte("echo: "), buf[:n]...), src)
    }()

    client.SetReadDeadline(time.Now().Add(2 * time.Second))
    _, _ = client.WriteTo([]byte("ping"), addrB)

    buf := make([]byte, 1024)
    nRead, _, _ := client.ReadFrom(buf)
    fmt.Println(string(buf[:nRead]))
}

Configuration

  • LinkSettings
    • BitsPerSecond int: bandwidth cap (bits/sec)
    • MTU int: maximum packet size (bytes). Oversized packets are dropped
  • NodeBiDiLinkSettings
    • Downlink LinkSettings: settings for incoming traffic
    • Uplink LinkSettings: settings for outgoing traffic
    • Latency time.Duration: one-way latency for downlink packets
    • LatencyFunc func(Packet) time.Duration: optional function to compute variable latency per downlink packet
  • Use simnet.Mibps for convenience when computing bitrates

Routers

  • PerfectRouter: instant delivery, in-memory switch
  • FixedLatencyRouter: wraps perfect delivery with a fixed extra latency
  • SimpleFirewallRouter: NAT/firewall-like behavior. A node must first send to a peer before inbound from that peer is allowed. You can also mark addresses as publicly reachable:
fw := &simnet.SimpleFirewallRouter{}
fw.SetAddrPubliclyReachable(serverAddr)

Each endpoint created by Simnet sits behind a SimulatedLink that:

  • Rate-limits using a token bucket (via golang.org/x/time/rate)
  • Adds latency via a timed queue
  • Drops packets over MTU
  • Buffers up to the bandwidth–delay product

Deadlines and stats

  • SimConn implements SetDeadline, SetReadDeadline, SetWriteDeadline
    • Exceeded deadlines return simnet.ErrDeadlineExceeded
  • Stats() returns counts of bytes/packets sent/received

Testing

Run the standard test suite:

go test ./...

Some tests use Go's synctest experimental time control. Enable them with:

GOEXPERIMENT=synctest go test ./...

(requires Go 1.24+)

License

BSD-3

Documentation

Index

Examples

Constants

View Source
const DefaultFlowBucketCount = 128
View Source
const Mibps = 1_000_000

Variables

View Source
var ErrDeadlineExceeded = errors.New("deadline exceeded")

Functions

func IntToPublicIPv4

func IntToPublicIPv4(n int) net.IP

func StaticLatency added in v0.0.4

func StaticLatency(duration time.Duration) func(*Packet) time.Duration

Types

type ConnStats

type ConnStats struct {
	BytesSent   int
	BytesRcvd   int
	PacketsSent int
	PacketsRcvd int
}

type DropReason added in v0.0.4

type DropReason string
const (
	DropReasonUnknownDestination DropReason = "unknown destination"
	DropReasonUnknownSource      DropReason = "unknown source"
	DropReasonFirewalled         DropReason = "Packet firewalled"
)

type LinkSettings

type LinkSettings struct {
	// BitsPerSecond specifies the bandwidth limit in bits per second
	BitsPerSecond int

	// MTU (Maximum Transmission Unit) specifies the maximum packet size in bytes
	MTU int

	// FlowBucketCount sets the number of flow buckets for FQ-CoDel. If zero
	// defaults to DefaultFlowBucketCount
	FlowBucketCount int
}

LinkSettings defines the network characteristics for a simulated link direction

type NodeBiDiLinkSettings

type NodeBiDiLinkSettings struct {
	// Downlink configures the settings for incoming traffic to this node
	Downlink LinkSettings
	// Uplink configures the settings for outgoing traffic from this node
	Uplink LinkSettings
}

NodeBiDiLinkSettings defines the bidirectional link settings for a network node. It specifies separate configurations for downlink (incoming) and uplink (outgoing) traffic, allowing asymmetric network conditions to be simulated.

type OnDrop added in v0.0.4

type OnDrop func(packet *Packet, reason DropReason)

func LogOnDrop added in v0.0.4

func LogOnDrop(logger *slog.Logger) OnDrop

type Packet

type Packet struct {
	To   net.Addr
	From net.Addr
	// contains filtered or unexported fields
}

func (*Packet) Hash added in v0.0.4

func (p *Packet) Hash(h *maphash.Hash) uint64

type PacketReceiver

type PacketReceiver interface {
	RecvPacket(p *Packet)
}

type PerfectRouter

type PerfectRouter struct {
	OnDrop OnDrop
	// contains filtered or unexported fields
}

PerfectRouter is a router that has no latency or jitter and can route to every node

func (*PerfectRouter) AddNode

func (r *PerfectRouter) AddNode(addr net.Addr, conn PacketReceiver)

func (*PerfectRouter) RecvPacket added in v0.0.4

func (r *PerfectRouter) RecvPacket(p *Packet)

func (*PerfectRouter) RemoveNode

func (r *PerfectRouter) RemoveNode(addr net.Addr)
type RateLink struct {
	*rate.Limiter
	BitsPerSecond int
	Receiver      PacketReceiver
}
func NewRateLink(bandwidth int, burstSize int, receiver PacketReceiver) *RateLink

func (*RateLink) RecvPacket added in v0.0.4

func (l *RateLink) RecvPacket(p *Packet)

func (*RateLink) Reserve added in v0.0.4

func (l *RateLink) Reserve(now time.Time, packetSize int) time.Duration

type Router

type Router interface {
	PacketReceiver
	AddNode(addr net.Addr, receiver PacketReceiver)
}

Router handles routing of packets between simulated connections. Implementations are responsible for delivering packets to their destinations.

type SimConn

type SimConn struct {
	// contains filtered or unexported fields
}

SimConn is a simulated network connection that implements net.PacketConn. It provides packet-based communication through a Router for testing and simulation purposes. All send/recv operations are handled through the Router's packet delivery mechanism.

func NewBlockingSimConn

func NewBlockingSimConn(addr *net.UDPAddr) *SimConn

NewBlockingSimConn creates a new simulated connection that blocks if the receive buffer is full. Does not drop packets.

func NewSimConn

func NewSimConn(addr *net.UDPAddr) *SimConn

NewSimConn creates a new simulated connection that drops packets if the receive buffer is full.

func (*SimConn) Close

func (c *SimConn) Close() error

Close implements net.PacketConn

func (*SimConn) LocalAddr

func (c *SimConn) LocalAddr() net.Addr

LocalAddr implements net.PacketConn

func (*SimConn) ReadFrom

func (c *SimConn) ReadFrom(p []byte) (n int, addr net.Addr, err error)

ReadFrom implements net.PacketConn

func (*SimConn) RecvPacket

func (c *SimConn) RecvPacket(p *Packet)

func (*SimConn) SetDeadline

func (c *SimConn) SetDeadline(t time.Time) error

SetDeadline implements net.PacketConn

func (*SimConn) SetLocalAddr

func (c *SimConn) SetLocalAddr(addr net.Addr)

SetLocalAddr only changes what `.LocalAddr()` returns. Packets will still come From the initially configured addr.

func (*SimConn) SetReadBuffer

func (c *SimConn) SetReadBuffer(n int) error

SetReadBuffer only exists to quell the warning message from quic-go

func (*SimConn) SetReadDeadline

func (c *SimConn) SetReadDeadline(t time.Time) error

SetReadDeadline implements net.PacketConn

func (*SimConn) SetUpPacketReceiver added in v0.0.4

func (c *SimConn) SetUpPacketReceiver(r PacketReceiver)

func (*SimConn) SetWriteBuffer

func (c *SimConn) SetWriteBuffer(n int) error

SetReadBuffer only exists to quell the warning message from quic-go

func (*SimConn) SetWriteDeadline

func (c *SimConn) SetWriteDeadline(t time.Time) error

SetWriteDeadline implements net.PacketConn

func (*SimConn) Stats

func (c *SimConn) Stats() ConnStats

func (*SimConn) UnicastAddr

func (c *SimConn) UnicastAddr() net.Addr

func (*SimConn) WriteTo

func (c *SimConn) WriteTo(p []byte, addr net.Addr) (n int, err error)

WriteTo implements net.PacketConn

type Simlink struct {
	// contains filtered or unexported fields
}

Simlink simulates a bidirectional network link with variable latency, bandwidth limiting, and CoDel-based bufferbloat mitigation

func NewSimlink(
	closeSignal chan struct{},
	linkSettings NodeBiDiLinkSettings,
	upPacketReceiver PacketReceiver,
	downPacketReceiver PacketReceiver,
) *Simlink

func (*Simlink) Start added in v0.0.4

func (l *Simlink) Start(wg *sync.WaitGroup)

type Simnet

type Simnet struct {
	// LatencyFunc defines the latency added when routing a given packet.
	// The latency is allowed to be dynamic and change packet to packet (which
	// could lead to packet reordering).
	//
	// A simple use case can use `StaticLatency(duration)` to set a static
	// latency for all packets.
	//
	// More complex use cases can define a latency map between endpoints and
	// have this function return the expected latency.
	LatencyFunc func(*Packet) time.Duration

	// Optional, if unset will use the default slog logger.
	Logger *slog.Logger
	// contains filtered or unexported fields
}

Simnet is a simulated network that manages connections between nodes with configurable network conditions.

Example (Echo)

Example showing a simple echo using Simnet and the returned net.PacketConn.

package main

import (
	"fmt"
	"net"
	"time"

	"github.com/marcopolo/simnet"
)

func main() {

	// Create the simulated network and two endpoints
	n := &simnet.Simnet{
		LatencyFunc: simnet.StaticLatency(5 * time.Millisecond),
	}
	settings := simnet.NodeBiDiLinkSettings{
		Downlink: simnet.LinkSettings{BitsPerSecond: 10 * simnet.Mibps},
		Uplink:   simnet.LinkSettings{BitsPerSecond: 10 * simnet.Mibps},
	}

	addrA := &net.UDPAddr{IP: net.ParseIP("1.0.0.1"), Port: 9001}
	addrB := &net.UDPAddr{IP: net.ParseIP("1.0.0.2"), Port: 9002}

	client := n.NewEndpoint(addrA, settings)
	server := n.NewEndpoint(addrB, settings)

	n.Start()
	defer n.Close()

	// Simple echo server using the returned PacketConn
	done := make(chan struct{})
	go func() {
		defer close(done)
		buf := make([]byte, 1024)
		server.SetReadDeadline(time.Now().Add(1 * time.Second))
		n, src, err := server.ReadFrom(buf)
		if err != nil {
			return
		}
		server.WriteTo(append([]byte("echo: "), buf[:n]...), src)
	}()

	// Client sends a message and waits for the echo response
	client.SetReadDeadline(time.Now().Add(2 * time.Second))
	_, err := client.WriteTo([]byte("ping"), addrB)
	if err != nil {
		fmt.Println("Error writing to server:", err)
		return
	}

	buf := make([]byte, 1024)
	nRead, _, _ := client.ReadFrom(buf)
	fmt.Println(string(buf[:nRead]))

	<-done

}
Output:
echo: ping

func (*Simnet) Close

func (n *Simnet) Close()

func (*Simnet) NewEndpoint

func (n *Simnet) NewEndpoint(addr *net.UDPAddr, linkSettings NodeBiDiLinkSettings) *SimConn

func (*Simnet) Start

func (n *Simnet) Start()

Start starts the simulated network and related goroutines

type SimpleFirewallRouter

type SimpleFirewallRouter struct {
	OnDrop OnDrop
	// contains filtered or unexported fields
}

func (*SimpleFirewallRouter) AddNode

func (r *SimpleFirewallRouter) AddNode(addr net.Addr, conn PacketReceiver)

func (*SimpleFirewallRouter) RecvPacket added in v0.0.4

func (r *SimpleFirewallRouter) RecvPacket(p *Packet)

func (*SimpleFirewallRouter) RemoveNode

func (r *SimpleFirewallRouter) RemoveNode(addr net.Addr)

func (*SimpleFirewallRouter) SetAddrPubliclyReachable

func (r *SimpleFirewallRouter) SetAddrPubliclyReachable(addr net.Addr)

func (*SimpleFirewallRouter) String

func (r *SimpleFirewallRouter) String() string

type VariableLatencyRouter added in v0.0.4

type VariableLatencyRouter struct {
	PerfectRouter
	LatencyFunc func(packet *Packet) time.Duration
	CloseSignal chan struct{}
	// contains filtered or unexported fields
}

func (*VariableLatencyRouter) RecvPacket added in v0.0.4

func (r *VariableLatencyRouter) RecvPacket(p *Packet)

func (*VariableLatencyRouter) Start added in v0.0.4

func (r *VariableLatencyRouter) Start(wg *sync.WaitGroup)

Directories

Path Synopsis
internal

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL