| name | fabric-routing |
| description | Configure static routes, IP forwarding, WireGuard VPN, sshuttle tunnels, and performance tuning on FABRIC |
| allowed-tools | ["Read","Grep","Glob","Write","Edit","Bash"] |
Instructions
When invoked, generate code for routing and tunneling on FABRIC nodes. Options include:
- Static Routes — Manual IP routing with IP forwarding between subnets
- WireGuard VPN — Encrypted overlay network between FABRIC nodes or to external hosts
- sshuttle — TCP-over-SSH tunnel for quick access without VPN setup
- Performance Tuning — Network buffer and kernel parameter optimization
Ask the user what connectivity problem they're solving.
Use the modern API pattern: create networks with add_l2network(subnet=...), use iface.set_mode('auto') and net.add_interface(iface) for auto IP configuration.
API Reference
IP Routing on Nodes
node.add_route(
subnet: IPv4Network | IPv6Network,
next_hop: IPv4Address | IPv6Address | NetworkService,
)
node.ip_addr_add(addr, subnet, interface, persistent=None)
node.ip_link_up(subnet, interface)
node.ip_route_add(subnet, gateway, interface=None, persistent=None)
Patterns
Static Routes Between Subnets (Three Nodes)
from ipaddress import IPv4Network
from fabrictestbed_extensions.fablib.fablib import FablibManager
fablib = FablibManager()
slice = fablib.new_slice(name="routing-experiment")
[site1, site2, site3] = fablib.get_random_sites(count=3)
net1_subnet = IPv4Network("192.168.1.0/24")
net2_subnet = IPv4Network("192.168.2.0/24")
net1 = slice.add_l2network(name="net1", subnet=net1_subnet)
net2 = slice.add_l2network(name="net2", subnet=net2_subnet)
node1 = slice.add_node(name="Node1", site=site1, image="default_ubuntu_24")
iface1 = node1.add_component(model="NIC_Basic", name="nic1").get_interfaces()[0]
iface1.set_mode('auto')
net1.add_interface(iface1)
node2 = slice.add_node(name="Node2", site=site2, image="default_ubuntu_24")
iface2a = node2.add_component(model="NIC_Basic", name="nic1").get_interfaces()[0]
iface2a.set_mode('auto')
net1.add_interface(iface2a)
iface2b = node2.add_component(model="NIC_Basic", name="nic2").get_interfaces()[0]
iface2b.set_mode('auto')
net2.add_interface(iface2b)
node3 = slice.add_node(name="Node3", site=site3, image="default_ubuntu_24")
iface3 = node3.add_component(model="NIC_Basic", name="nic1").get_interfaces()[0]
iface3.set_mode('auto')
net2.add_interface(iface3)
slice.submit()
node2 = slice.get_node("Node2")
node2.execute("echo 1 | sudo tee /proc/sys/net/ipv4/ip_forward")
node2.execute("sudo sysctl -w net.ipv4.ip_forward=1")
node1 = slice.get_node("Node1")
node3 = slice.get_node("Node3")
node2_net1_addr = node2.get_interface(network_name="net1").get_ip_addr()
node2_net2_addr = node2.get_interface(network_name="net2").get_ip_addr()
node3_addr = node3.get_interface(network_name="net2").get_ip_addr()
node1.execute(f"sudo ip route add {net2_subnet} via {node2_net1_addr}")
node3.execute(f"sudo ip route add {net1_subnet} via {node2_net2_addr}")
stdout, stderr = node1.execute(f"ping -c 5 {node3_addr}")
print(stdout)
WireGuard VPN Overlay
slice = fablib.new_slice(name="wireguard-overlay")
[site1, site2] = fablib.get_random_sites(count=2)
net1_subnet = IPv4Network("192.168.1.0/24")
net2_subnet = IPv4Network("192.168.2.0/24")
net1 = slice.add_l2network(name="net1", subnet=net1_subnet)
net2 = slice.add_l2network(name="net2", subnet=net2_subnet)
node1 = slice.add_node(name="Node1", site=site1, image="default_ubuntu_24")
iface1 = node1.add_component(model="NIC_Basic", name="nic1").get_interfaces()[0]
iface1.set_mode('auto')
net1.add_interface(iface1)
node2 = slice.add_node(name="Node2", site=site2, image="default_ubuntu_24")
iface2a = node2.add_component(model="NIC_Basic", name="nic1").get_interfaces()[0]
iface2a.set_mode('auto')
net1.add_interface(iface2a)
iface2b = node2.add_component(model="NIC_Basic", name="nic2").get_interfaces()[0]
iface2b.set_mode('auto')
net2.add_interface(iface2b)
node3 = slice.add_node(name="Node3", site=site2, image="default_ubuntu_24")
iface3 = node3.add_component(model="NIC_Basic", name="nic1").get_interfaces()[0]
iface3.set_mode('auto')
net2.add_interface(iface3)
slice.submit()
node1 = slice.get_node("Node1")
node2 = slice.get_node("Node2")
node3 = slice.get_node("Node3")
node1_net1_addr = node1.get_interface(network_name="net1").get_ip_addr()
node2_net2_addr = node2.get_interface(network_name="net2").get_ip_addr()
node3_net2_addr = node3.get_interface(network_name="net2").get_ip_addr()
node2_net2_ifc_name = node2.get_interface(network_name="net2").get_os_interface()
keys = {}
for node in [node1, node2, node3]:
node.execute("sudo apt-get update -qq && sudo apt-get install -y -qq wireguard", quiet=True)
node.execute("wg genkey | tee privatekey | wg pubkey > publickey")
priv_key, _ = node.execute("cat privatekey", quiet=True)
pub_key, _ = node.execute("cat publickey", quiet=True)
keys[node.get_name()] = {"public": pub_key.strip(), "private": priv_key.strip()}
node1_wg_conf = f"""\
[Interface]
PrivateKey = {keys["Node1"]["private"]}
Address = 10.0.0.1/24
ListenPort = 51820
[Peer]
PublicKey = {keys["Node2"]["public"]}
Endpoint = {node1_net1_addr}:51820
AllowedIPs = 10.0.0.0/24, {net2_subnet}
PersistentKeepalive = 25
"""
node1.execute(f"echo '{node1_wg_conf}' | sudo tee /etc/wireguard/wg0.conf > /dev/null")
node2_wg_conf = f"""\
[Interface]
PrivateKey = {keys["Node2"]["private"]}
Address = 10.0.0.2/24
ListenPort = 51820
PostUp = iptables -A FORWARD -i %i -j ACCEPT; iptables -A FORWARD -o %i -j ACCEPT; iptables -t nat -A POSTROUTING -o {node2_net2_ifc_name} -j MASQUERADE
PostDown = iptables -D FORWARD -i %i -j ACCEPT; iptables -D FORWARD -o %i -j ACCEPT; iptables -t nat -D POSTROUTING -o {node2_net2_ifc_name} -j MASQUERADE
[Peer]
PublicKey = {keys["Node1"]["public"]}
Endpoint = {node1_net1_addr}:51820
AllowedIPs = 10.0.0.1/32
PersistentKeepalive = 25
[Peer]
PublicKey = {keys["Node3"]["public"]}
Endpoint = {node3_net2_addr}:51820
AllowedIPs = 10.0.0.3/32
PersistentKeepalive = 25
"""
node2.execute(f"echo '{node2_wg_conf}' | sudo tee /etc/wireguard/wg0.conf > /dev/null")
node3_wg_conf = f"""\
[Interface]
PrivateKey = {keys["Node3"]["private"]}
Address = 10.0.0.3/24
ListenPort = 51820
[Peer]
PublicKey = {keys["Node2"]["public"]}
Endpoint = {node2_net2_addr}:51820
AllowedIPs = 10.0.0.0/24, {net1_subnet}
PersistentKeepalive = 25
"""
node3.execute(f"echo '{node3_wg_conf}' | sudo tee /etc/wireguard/wg0.conf > /dev/null")
node2.execute("sudo sysctl -w net.ipv4.ip_forward=1")
for node in [node1, node2, node3]:
node.execute("sudo systemctl enable wg-quick@wg0 && sudo systemctl start wg-quick@wg0")
stdout, _ = node1.execute("ping -c 5 10.0.0.3")
print(stdout)
stdout, _ = node3.execute("ping -c 5 10.0.0.1")
print(stdout)
sshuttle TCP Tunnel
sshuttle provides transparent TCP proxy access through a relay node via SSH.
for node in slice.get_nodes():
node.execute("sudo apt-get update -qq && sudo apt-get install -y -qq sshuttle net-tools", quiet=True)
node1 = slice.get_node("Node1")
node2 = slice.get_node("Node2")
node3 = slice.get_node("Node3")
node2_net1_addr = node2.get_interface(network_name="net1").get_ip_addr()
node3_addr = node3.get_interface(network_name="net2").get_ip_addr()
cmd = (
f"sudo sshuttle --method=nat "
f"--ssh-cmd 'ssh -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null' "
f"-r root@{node2_net1_addr} {net2_subnet} --daemon"
)
node1.execute(cmd)
node1.execute(f"sudo ip route add {net2_subnet} via 127.0.0.1 dev lo")
stdout, stderr = node1.execute(f"nc -zv {node3_addr} 22")
print(stdout, stderr)
Network Performance Tuning
tune_commands = [
"sudo sysctl -w net.core.rmem_max=67108864",
"sudo sysctl -w net.core.wmem_max=67108864",
"sudo sysctl -w net.core.rmem_default=67108864",
"sudo sysctl -w net.core.wmem_default=67108864",
"sudo sysctl -w net.ipv4.tcp_rmem='4096 87380 67108864'",
"sudo sysctl -w net.ipv4.tcp_wmem='4096 87380 67108864'",
"sudo sysctl -w net.ipv4.tcp_window_scaling=1",
"sudo sysctl -w net.ipv4.tcp_timestamps=1",
"sudo sysctl -w net.core.netdev_max_backlog=250000",
"sudo sysctl -w net.ipv4.tcp_congestion_control=bbr",
"sudo sysctl -w net.core.default_qdisc=fq",
"sudo sysctl -w net.ipv4.tcp_mtu_probing=1",
]
for cmd in tune_commands:
node.execute(cmd)
Docker Networking on FABRIC Nodes
node.execute("sudo apt-get update -qq && sudo apt-get install -y -qq docker.io")
node.execute("sudo systemctl enable docker && sudo systemctl start docker")
node.execute("sudo usermod -aG docker $(whoami)")
node.execute("sudo docker network create --subnet=172.20.0.0/16 fabric-docker-net")
OVS (Open vSwitch) Setup
node.execute("sudo apt-get update -qq && sudo apt-get install -y -qq openvswitch-switch")
node.execute("sudo ovs-vsctl add-br br0")
iface_name = node.get_interface(network_name="lan").get_os_interface()
node.execute(f"sudo ovs-vsctl add-port br0 {iface_name}")
node.execute("sudo ip addr add 192.168.1.1/24 dev br0")
node.execute("sudo ip link set br0 up")
Important Notes
- Always enable IP forwarding on nodes acting as routers:
sudo sysctl -w net.ipv4.ip_forward=1
- Use
persistent=True on ip_route_add and ip_addr_add for routes that survive reboots
- WireGuard keys should be generated per-node, never reused
- sshuttle only tunnels TCP — ICMP (ping) will not work through sshuttle
- Performance tuning values above are for 100G links; adjust for your link speed
- BBR congestion control requires Linux kernel 4.9+
- Use
iface.get_os_interface() to get the OS-level device name for low-level config