Skip to content

Commit 99b1788

Browse files
committed
vmm: Add bridge networking support
1 parent 3548289 commit 99b1788

13 files changed

Lines changed: 1293 additions & 27 deletions

File tree

docs/bridge-networking.md

Lines changed: 157 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,157 @@
1+
# Bridge Networking for VMM
2+
3+
By default, dstack-vmm uses **user** networking (QEMU's built-in SLIRP stack, no host setup required). Bridge networking is an alternative that provides better performance for high-connection workloads by using kernel-level bridging with TAP devices.
4+
5+
## When to use bridge networking
6+
7+
- High connection concurrency (passt becomes CPU-bound at ~25K+ concurrent connections)
8+
- Workloads that need full L2 network access
9+
- Environments where VMs need to be directly reachable on the LAN
10+
11+
## Configuration
12+
13+
### VMM global config (`vmm.toml`)
14+
15+
```toml
16+
[cvm.networking]
17+
mode = "bridge"
18+
bridge = "virbr0"
19+
```
20+
21+
### Per-VM override
22+
23+
Individual VMs can override the global networking mode via:
24+
- **CLI**: `vmm-cli.py deploy --net bridge` or `--net passt`
25+
- **Web UI**: Networking dropdown in the deploy dialog
26+
- **API**: `networking: { mode: "bridge" }` in `VmConfiguration`
27+
28+
Only the mode is per-VM; the bridge interface name always comes from the global config.
29+
30+
## Host setup
31+
32+
### Option A: Using libvirt default network (recommended)
33+
34+
libvirt's default network provides a bridge (`virbr0`) with DHCP (dnsmasq) and NAT out of the box.
35+
36+
```bash
37+
# Install libvirt (if not already present)
38+
sudo apt install -y libvirt-daemon-system
39+
40+
# Ensure default network is active
41+
sudo virsh net-start default 2>/dev/null
42+
sudo virsh net-autostart default
43+
```
44+
45+
Verify:
46+
```bash
47+
ip addr show virbr0
48+
# Should show 192.168.122.1/24
49+
50+
virsh net-dhcp-leases default
51+
# Lists DHCP leases for connected VMs
52+
```
53+
54+
### Option B: Manual bridge without libvirt
55+
56+
Create a bridge with systemd-networkd and run a standalone DHCP server.
57+
58+
**1. Create the bridge:**
59+
60+
```bash
61+
# /etc/systemd/network/10-dstack-br.netdev
62+
[NetDev]
63+
Name=dstack-br0
64+
Kind=bridge
65+
66+
# /etc/systemd/network/11-dstack-br.network
67+
[Match]
68+
Name=dstack-br0
69+
70+
[Network]
71+
Address=10.0.100.1/24
72+
ConfigureWithoutCarrier=yes
73+
IPMasquerade=both
74+
```
75+
76+
```bash
77+
sudo systemctl restart systemd-networkd
78+
```
79+
80+
**2. Enable IP forwarding:**
81+
82+
```bash
83+
echo "net.ipv4.ip_forward=1" | sudo tee /etc/sysctl.d/99-dstack-bridge.conf
84+
sudo sysctl -p /etc/sysctl.d/99-dstack-bridge.conf
85+
```
86+
87+
**3. Run a DHCP server (dnsmasq):**
88+
89+
```bash
90+
sudo apt install -y dnsmasq
91+
92+
# /etc/dnsmasq.d/dstack-br0.conf
93+
interface=dstack-br0
94+
bind-interfaces
95+
dhcp-range=10.0.100.10,10.0.100.254,255.255.255.0,12h
96+
dhcp-option=option:router,10.0.100.1
97+
dhcp-option=option:dns-server,8.8.8.8,1.1.1.1
98+
```
99+
100+
```bash
101+
sudo systemctl restart dnsmasq
102+
```
103+
104+
**4. Update vmm.toml:**
105+
106+
```toml
107+
[cvm.networking]
108+
mode = "bridge"
109+
bridge = "dstack-br0"
110+
```
111+
112+
### QEMU bridge helper setup (required for both options)
113+
114+
The bridge helper allows QEMU to create and attach TAP devices without VMM needing root privileges.
115+
116+
```bash
117+
# Allow QEMU to use the bridge
118+
sudo mkdir -p /etc/qemu
119+
echo "allow virbr0" | sudo tee /etc/qemu/bridge.conf
120+
# Or for manual bridge: echo "allow dstack-br0" | sudo tee /etc/qemu/bridge.conf
121+
122+
# Set setuid on bridge helper
123+
sudo chmod u+s /usr/lib/qemu/qemu-bridge-helper
124+
```
125+
126+
## How it works
127+
128+
- VMM passes `-netdev bridge,id=net0,br=<bridge>` to QEMU
129+
- QEMU's bridge helper (setuid) creates a TAP device and attaches it to the bridge
130+
- Guest MAC address is derived from SHA256 of the VM ID (stable across restarts for DHCP IP consistency)
131+
- When QEMU exits, the TAP device is automatically destroyed
132+
- VMM does not need root or `CAP_NET_ADMIN`
133+
134+
## Operational notes
135+
136+
### Do not restart the bridge while VMs are running
137+
138+
`virsh net-destroy`/`net-start` (or removing/recreating the bridge) will detach all TAP interfaces from the bridge, breaking VM networking. If this happens, affected VMs must be restarted.
139+
140+
### Firewall considerations
141+
142+
- libvirt injects nftables rules for NAT masquerade and forwarding automatically
143+
- If using a manual bridge, ensure your firewall allows forwarding for the bridge subnet and has masquerade rules for outbound NAT
144+
- Docker's nftables chains (`DOCKER-FORWARD`) run before libvirt's but do not block virbr0 traffic
145+
146+
### Mixing networking modes
147+
148+
Bridge and passt VMs can coexist. Set the global default in `vmm.toml` and override per-VM as needed:
149+
150+
```bash
151+
# Global default is bridge, but deploy this VM with passt
152+
vmm-cli.py deploy --name my-vm --image dstack-0.5.6 --compose app.yaml --net passt
153+
```
154+
155+
### vhost-net and TDX
156+
157+
vhost-net (kernel data plane offload for virtio-net) is **not enabled** for bridge mode. TDX encrypts guest memory, which prevents the host kernel from performing DMA-based packet offload. The default QEMU userspace virtio backend is used instead.

gateway/dstack-app/deploy-to-vmm.sh

Lines changed: 26 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -59,6 +59,9 @@ PUBLIC_IP=$(curl -s4 ifconfig.me)
5959
# Whether to use ACME staging (yes/no)
6060
ACME_STAGING=no
6161
62+
# Networking mode: bridge or passt (default: passt)
63+
# NET_MODE=bridge
64+
6265
# Subnet index. 0~15
6366
SUBNET_INDEX=0
6467
@@ -219,17 +222,27 @@ fi
219222

220223
echo "Deploying dstack-gateway to dstack-vmm..."
221224

222-
$CLI deploy \
223-
--name dstack-gateway \
224-
--app-id "$GATEWAY_APP_ID" \
225-
--compose .app-compose.json \
226-
--env-file .app_env \
227-
--image $OS_IMAGE \
228-
--port tcp:$GATEWAY_RPC_ADDR:8000 \
229-
--port tcp:$GATEWAY_ADMIN_RPC_ADDR:8001 \
230-
--port tcp:$GATEWAY_SERVING_ADDR:443 \
231-
--port tcp:$GUEST_AGENT_ADDR:8090 \
232-
--port udp:$WG_ADDR:51820 \
233-
--vcpu 32 \
234-
--memory 32G \
225+
DEPLOY_ARGS=(
226+
--name dstack-gateway
227+
--app-id "$GATEWAY_APP_ID"
228+
--compose .app-compose.json
229+
--env-file .app_env
230+
--image "$OS_IMAGE"
231+
--vcpu 32
232+
--memory 32G
233+
)
234+
235+
if [ "${NET_MODE:-}" = "bridge" ]; then
236+
DEPLOY_ARGS+=(--net bridge)
237+
else
238+
DEPLOY_ARGS+=(
239+
--port "tcp:$GATEWAY_RPC_ADDR:8000"
240+
--port "tcp:$GATEWAY_ADMIN_RPC_ADDR:8001"
241+
--port "tcp:$GATEWAY_SERVING_ADDR:443"
242+
--port "tcp:$GUEST_AGENT_ADDR:8090"
243+
--port "udp:$WG_ADDR:51820"
244+
)
245+
fi
246+
247+
$CLI deploy "${DEPLOY_ARGS[@]}"
235248

0 commit comments

Comments
 (0)