mirror of
https://github.com/tailscale/tailscale.git
synced 2025-02-18 02:48:40 +00:00
tstest/natlab: add latency & loss simulation
A simple implementation of latency and loss simulation, applied to writes to the ethernet interface of the NIC. The latency implementation could be optimized substantially later if necessary. Updates #13355 Signed-off-by: James Tucker <james@tailscale.com>
This commit is contained in:
parent
41aac26106
commit
c0a1ed86cb
@ -10,6 +10,7 @@ import (
|
|||||||
"net/netip"
|
"net/netip"
|
||||||
"os"
|
"os"
|
||||||
"slices"
|
"slices"
|
||||||
|
"time"
|
||||||
|
|
||||||
"github.com/google/gopacket/layers"
|
"github.com/google/gopacket/layers"
|
||||||
"github.com/google/gopacket/pcapgo"
|
"github.com/google/gopacket/pcapgo"
|
||||||
@ -279,10 +280,28 @@ type Network struct {
|
|||||||
|
|
||||||
svcs set.Set[NetworkService]
|
svcs set.Set[NetworkService]
|
||||||
|
|
||||||
|
latency time.Duration // latency applied to interface writes
|
||||||
|
lossRate float64 // chance of packet loss (0.0 to 1.0)
|
||||||
|
|
||||||
// ...
|
// ...
|
||||||
err error // carried error
|
err error // carried error
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// SetLatency sets the simulated network latency for this network.
|
||||||
|
func (n *Network) SetLatency(d time.Duration) {
|
||||||
|
n.latency = d
|
||||||
|
}
|
||||||
|
|
||||||
|
// SetPacketLoss sets the packet loss rate for this network 0.0 (no loss) to 1.0 (total loss).
|
||||||
|
func (n *Network) SetPacketLoss(rate float64) {
|
||||||
|
if rate < 0 {
|
||||||
|
rate = 0
|
||||||
|
} else if rate > 1 {
|
||||||
|
rate = 1
|
||||||
|
}
|
||||||
|
n.lossRate = rate
|
||||||
|
}
|
||||||
|
|
||||||
// SetBlackholedIPv4 sets whether the network should blackhole all IPv4 traffic
|
// SetBlackholedIPv4 sets whether the network should blackhole all IPv4 traffic
|
||||||
// out to the Internet. (DHCP etc continues to work on the LAN.)
|
// out to the Internet. (DHCP etc continues to work on the LAN.)
|
||||||
func (n *Network) SetBlackholedIPv4(v bool) {
|
func (n *Network) SetBlackholedIPv4(v bool) {
|
||||||
@ -361,6 +380,8 @@ func (s *Server) initFromConfig(c *Config) error {
|
|||||||
wanIP4: conf.wanIP4,
|
wanIP4: conf.wanIP4,
|
||||||
lanIP4: conf.lanIP4,
|
lanIP4: conf.lanIP4,
|
||||||
breakWAN4: conf.breakWAN4,
|
breakWAN4: conf.breakWAN4,
|
||||||
|
latency: conf.latency,
|
||||||
|
lossRate: conf.lossRate,
|
||||||
nodesByIP4: map[netip.Addr]*node{},
|
nodesByIP4: map[netip.Addr]*node{},
|
||||||
nodesByMAC: map[MAC]*node{},
|
nodesByMAC: map[MAC]*node{},
|
||||||
logf: logger.WithPrefix(s.logf, fmt.Sprintf("[net-%v] ", conf.mac)),
|
logf: logger.WithPrefix(s.logf, fmt.Sprintf("[net-%v] ", conf.mac)),
|
||||||
|
@ -3,7 +3,10 @@
|
|||||||
|
|
||||||
package vnet
|
package vnet
|
||||||
|
|
||||||
import "testing"
|
import (
|
||||||
|
"testing"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
func TestConfig(t *testing.T) {
|
func TestConfig(t *testing.T) {
|
||||||
tests := []struct {
|
tests := []struct {
|
||||||
@ -18,6 +21,16 @@ func TestConfig(t *testing.T) {
|
|||||||
c.AddNode(c.AddNetwork("2.2.2.2", "10.2.0.1/16", HardNAT))
|
c.AddNode(c.AddNetwork("2.2.2.2", "10.2.0.1/16", HardNAT))
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
name: "latency-and-loss",
|
||||||
|
setup: func(c *Config) {
|
||||||
|
n1 := c.AddNetwork("2.1.1.1", "192.168.1.1/24", EasyNAT, NATPMP)
|
||||||
|
n1.SetLatency(time.Second)
|
||||||
|
n1.SetPacketLoss(0.1)
|
||||||
|
c.AddNode(n1)
|
||||||
|
c.AddNode(c.AddNetwork("2.2.2.2", "10.2.0.1/16", HardNAT))
|
||||||
|
},
|
||||||
|
},
|
||||||
{
|
{
|
||||||
name: "indirect",
|
name: "indirect",
|
||||||
setup: func(c *Config) {
|
setup: func(c *Config) {
|
||||||
|
@ -515,6 +515,8 @@ type network struct {
|
|||||||
wanIP4 netip.Addr // router's LAN IPv4, if any
|
wanIP4 netip.Addr // router's LAN IPv4, if any
|
||||||
lanIP4 netip.Prefix // router's LAN IP + CIDR (e.g. 192.168.2.1/24)
|
lanIP4 netip.Prefix // router's LAN IP + CIDR (e.g. 192.168.2.1/24)
|
||||||
breakWAN4 bool // break WAN IPv4 connectivity
|
breakWAN4 bool // break WAN IPv4 connectivity
|
||||||
|
latency time.Duration // latency applied to interface writes
|
||||||
|
lossRate float64 // probability of dropping a packet (0.0 to 1.0)
|
||||||
nodesByIP4 map[netip.Addr]*node // by LAN IPv4
|
nodesByIP4 map[netip.Addr]*node // by LAN IPv4
|
||||||
nodesByMAC map[MAC]*node
|
nodesByMAC map[MAC]*node
|
||||||
logf func(format string, args ...any)
|
logf func(format string, args ...any)
|
||||||
@ -977,7 +979,7 @@ func (n *network) writeEth(res []byte) bool {
|
|||||||
for mac, nw := range n.writers.All() {
|
for mac, nw := range n.writers.All() {
|
||||||
if mac != srcMAC {
|
if mac != srcMAC {
|
||||||
num++
|
num++
|
||||||
nw.write(res)
|
n.conditionedWrite(nw, res)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return num > 0
|
return num > 0
|
||||||
@ -987,7 +989,7 @@ func (n *network) writeEth(res []byte) bool {
|
|||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
if nw, ok := n.writers.Load(dstMAC); ok {
|
if nw, ok := n.writers.Load(dstMAC); ok {
|
||||||
nw.write(res)
|
n.conditionedWrite(nw, res)
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1000,6 +1002,23 @@ func (n *network) writeEth(res []byte) bool {
|
|||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (n *network) conditionedWrite(nw networkWriter, packet []byte) {
|
||||||
|
if n.lossRate > 0 && rand.Float64() < n.lossRate {
|
||||||
|
// packet lost
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if n.latency > 0 {
|
||||||
|
// copy the packet as there's no guarantee packet is owned long enough.
|
||||||
|
// TODO(raggi): this could be optimized substantially if necessary,
|
||||||
|
// a pool of buffers and a cheaper delay mechanism are both obvious improvements.
|
||||||
|
var pkt = make([]byte, len(packet))
|
||||||
|
copy(pkt, packet)
|
||||||
|
time.AfterFunc(n.latency, func() { nw.write(pkt) })
|
||||||
|
} else {
|
||||||
|
nw.write(packet)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
var (
|
var (
|
||||||
macAllNodes = MAC{0: 0x33, 1: 0x33, 5: 0x01}
|
macAllNodes = MAC{0: 0x33, 1: 0x33, 5: 0x01}
|
||||||
macAllRouters = MAC{0: 0x33, 1: 0x33, 5: 0x02}
|
macAllRouters = MAC{0: 0x33, 1: 0x33, 5: 0x02}
|
||||||
|
Loading…
x
Reference in New Issue
Block a user