simnet

package module
v0.0.4 Latest Latest
Warning

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

Go to latest
Published: Dec 1, 2025 License: BSD-3-Clause Imports: 13 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

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.

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