Skip to content

Instantly share code, notes, and snippets.

@lschuermann
Created January 7, 2022 21:56
Show Gist options
  • Star 1 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save lschuermann/7e5de6e00358d1312c86e2144d7352b4 to your computer and use it in GitHub Desktop.
Save lschuermann/7e5de6e00358d1312c86e2144d7352b4 to your computer and use it in GitHub Desktop.
Mininet network to test WireGuard peer and endpoint-to-endpoint MTU issues
#!/usr/bin/env python3
import subprocess
import tempfile
from mininet.net import Mininet
from mininet.cli import CLI
from mininet.log import setLogLevel, info
basePort = 50000
tunnelIdx = 0
def wireguardKeypair():
privkeyGen = subprocess.run(
["/usr/bin/env", "wg", "genkey"],
stdout=subprocess.PIPE,
)
privkeyGen.check_returncode()
pubkeyGen = subprocess.run(
["/usr/bin/env", "wg", "pubkey"],
input=privkeyGen.stdout,
stdout=subprocess.PIPE,
)
privkeyGen.check_returncode()
return (
privkeyGen.stdout.decode("utf-8").strip(),
pubkeyGen.stdout.decode("utf-8").strip(),
)
def wireguardTunnel(hostA, hostAIP, tunnelIPA, hostB, hostBIP, tunnelIPB, allowedIPsA=None, allowedIPsB=None):
global tunnelIdx
for host in [hostA, hostB]:
if not hasattr(host, "wireguardTunnels"):
setattr(host, "wireguardTunnels", {})
hostA.wireguardTunnels[tunnelIdx] = {
"keypair": wireguardKeypair(),
"listenPort": basePort + tunnelIdx,
"tunnelIP": tunnelIPA,
"endpointIP": hostAIP,
"allowedIPs": allowedIPsA if allowedIPsA is not None else [f"{tunnelIPB}/32"],
}
hostB.wireguardTunnels[tunnelIdx] = {
"keypair": wireguardKeypair(),
"listenPort": basePort + tunnelIdx,
"tunnelIP": tunnelIPB,
"endpointIP": hostBIP,
"allowedIPs": allowedIPsB if allowedIPsB is not None else [f"{tunnelIPA}/32"],
}
for host, peer in [(hostA, hostB), (hostB, hostA)]:
tunnel = host.wireguardTunnels[tunnelIdx]
peerTunnel = peer.wireguardTunnels[tunnelIdx]
host.cmd(f"ip link add wg{tunnelIdx} type wireguard")
host.cmd(f"ip link set wg{tunnelIdx} up")
host.cmd(f"ip address add {tunnel['tunnelIP']}/32 dev wg{tunnelIdx}")
with tempfile.NamedTemporaryFile() as privkeyFile:
privkeyFile.write(tunnel["keypair"][0].encode("utf-8"))
privkeyFile.flush()
host.cmd(f"wg set wg{tunnelIdx} private-key {privkeyFile.name}")
host.cmd(f"wg set wg{tunnelIdx} listen-port {tunnel['listenPort']}")
host.cmd(("wg set wg{} peer {} endpoint {}:{} {} "
+ "persistent-keepalive 10").format(
tunnelIdx,
peerTunnel["keypair"][1],
peerTunnel["endpointIP"],
peerTunnel["listenPort"],
" ".join([f"allowed-ips {ip}" for ip in tunnel["allowedIPs"]])
))
host.cmd(f"ip route add {peerTunnel['tunnelIP']} dev wg{tunnelIdx} src {tunnel['tunnelIP']}")
tunnelIdx += 1
return tunnelIdx - 1
setLogLevel("info")
net = Mininet(topo=None, build=False)
info("*** Add switches\n")
s1 = net.addSwitch("s1", failMode="standalone")
s2 = net.addSwitch("s2", failMode="standalone")
s3 = net.addSwitch("s3", failMode="standalone")
s4 = net.addSwitch("s4", failMode="standalone")
info("*** Add hosts\n")
h1 = net.addHost("h1", ip="10.0.0.1/24")
h2 = net.addHost("h2", ip="10.0.0.2/24")
h3 = net.addHost("h3", ip="10.0.1.2/24")
h4 = net.addHost("h4", ip="10.0.2.2/24")
h5 = net.addHost("h5", ip="192.168.1.2/24")
# Manually assign v6 addresses, as mininet does not support IPv6
v6Links = []
def v6Link(link, addr):
global v6Links
v6Links += [(link.intf1, addr)]
# Connect h1 to h2
v6Link(net.addLink(h1, s1), "fd00:0:0:0::1/64")
v6Link(net.addLink(h2, s1), "fd00:0:0:0::2/64")
# Connect h2 to h3
v6Link(net.addLink(h2, s2, params1={"ip": "10.0.1.1/24"}), "fd00:0:0:1::1/64")
v6Link(net.addLink(h3, s2), "fd00:0:0:1::2/64")
# Connect h3 to h4
v6Link(net.addLink(h3, s3, params1={"ip": "10.0.2.1/24"}), "fd00:0:0:2::1/64")
v6Link(net.addLink(h4, s3), "fd00:0:0:2::2/64")
# Connect h4 to h5
net.addLink(h4, s4, params1={"ip": "192.168.1.1/24"})
net.addLink(h5, s4)
info("*** Starting network\n")
net.start()
# Install all of the IPv6 addresses
for intf, v6addr in v6Links:
intf.node.cmd(f"ip -6 addr add {v6addr} dev {intf.name}")
for host in [h1, h2, h3, h4, h5]:
host.cmd("sysctl net.ipv4.ipfrag_low_thresh=0")
host.cmd("sysctl net.ipv4.ipfrag_high_thresh=0")
host.cmd("sysctl net.ipv4.ip_forward=1")
host.cmd("sysctl net.ipv6.ipfrag_low_thresh=0")
host.cmd("sysctl net.ipv6.ipfrag_high_thresh=0")
host.cmd("sysctl net.ipv6.conf.all.forwarding=1")
# Populate regular L3 routing tables (done manually here, tedious but
# works)
h1.cmd("ip route add 10.0.1.0/24 via 10.0.0.2")
h1.cmd("ip -6 route add fd00:0:0:1::/64 via fd00:0:0:0::2")
h1.cmd("ip route add 10.0.2.0/24 via 10.0.0.2")
h1.cmd("ip -6 route add fd00:0:0:2::/64 via fd00:0:0:0::2")
h1.cmd("ip route add 10.0.3.0/24 via 10.0.0.2")
h1.cmd("ip -6 route add fd00:0:0:3::/64 via fd00:0:0:0::2")
h2.cmd("ip route add 10.0.2.0/24 via 10.0.1.2")
h2.cmd("ip -6 route add fd00:0:0:2::/64 via fd00:0:0:1::2")
h2.cmd("ip route add 10.0.3.0/24 via 10.0.1.2")
h2.cmd("ip -6 route add fd00:0:0:3::/64 via fd00:0:0:1::2")
h3.cmd("ip route add 10.0.0.0/24 via 10.0.1.1")
h3.cmd("ip -6 route add fd00:0:0:0::/64 via fd00:0:0:1::1")
h3.cmd("ip route add 10.0.3.0/24 via 10.0.2.2")
h3.cmd("ip -6 route add fd00:0:0:3::/64 via fd00:0:0:2::2")
h4.cmd("ip route add 10.0.0.0/24 via 10.0.2.1")
h4.cmd("ip -6 route add fd00:0:0:0::/64 via fd00:0:0:2::1")
h4.cmd("ip route add 10.0.1.0/24 via 10.0.2.1")
h4.cmd("ip -6 route add fd00:0:0:1::/64 via fd00:0:0:2::1")
h1h4Tunnel = wireguardTunnel(
h1, "fd00:0:0:0::1", "192.168.0.1",
h4, "fd00:0:0:2::2", "192.168.0.2",
allowedIPsA = ["192.168.0.2/32", "192.168.1.0/24"],
)
h1.cmd(f"ip route add 192.168.1.0/24 dev wg{h1h4Tunnel}")
h5.cmd("ip route add 192.168.0.0/24 via 192.168.1.1")
CLI(net)
net.stop()
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment