forked from Dstack-TEE/dstack
-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathsetup-bridge.sh
More file actions
executable file
·893 lines (773 loc) · 28 KB
/
Copy pathsetup-bridge.sh
File metadata and controls
executable file
·893 lines (773 loc) · 28 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
#!/usr/bin/env bash
# SPDX-FileCopyrightText: © 2025 Phala Network <dstack@phala.network>
# SPDX-License-Identifier: Apache-2.0
#
# Bridge networking setup and diagnostics for dstack-vmm.
#
# Setup modes:
# libvirt Use libvirt default network (virbr0, dnsmasq, NAT included)
# standalone Create bridge + dnsmasq + NAT without libvirt
#
# Usage:
# setup-bridge.sh check [--bridge NAME]
# setup-bridge.sh setup --mode libvirt [--bridge virbr0]
# setup-bridge.sh setup --mode standalone [--bridge dstack-br0]
# setup-bridge.sh destroy [--bridge NAME]
set -euo pipefail
BRIDGE="virbr0"
SETUP_MODE="" # libvirt | standalone
DRY_RUN=false
COMMAND=""
PASS=0
FAIL=0
WARN=0
# --- Output helpers ---
red() { printf '\033[31m%s\033[0m' "$*"; }
green() { printf '\033[32m%s\033[0m' "$*"; }
yellow() { printf '\033[33m%s\033[0m' "$*"; }
bold() { printf '\033[1m%s\033[0m' "$*"; }
check_pass() { PASS=$((PASS+1)); echo " $(green '[PASS]') $*"; }
check_fail() { FAIL=$((FAIL+1)); echo " $(red '[FAIL]') $*"; }
check_warn() { WARN=$((WARN+1)); echo " $(yellow '[WARN]') $*"; }
check_info() { echo " [INFO] $*"; }
run_cmd() {
if $DRY_RUN; then
echo " [DRY-RUN] $*"
else
"$@"
fi
}
# --- Detect qemu-bridge-helper path ---
find_bridge_helper() {
local paths=(
/usr/lib/qemu/qemu-bridge-helper
/usr/libexec/qemu-bridge-helper
/usr/local/lib/qemu/qemu-bridge-helper
/usr/local/libexec/qemu-bridge-helper
)
for p in "${paths[@]}"; do
if [[ -f "$p" ]]; then
echo "$p"
return 0
fi
done
return 1
}
# --- Detect current bridge provider ---
# Returns "libvirt:<net_name>" if bridge is managed by a libvirt network,
# "standalone" otherwise.
detect_bridge_provider() {
if command -v virsh &>/dev/null; then
local name br
while read -r name; do
[[ -z "$name" ]] && continue
br=$(virsh net-dumpxml "$name" 2>/dev/null | grep -oP "<bridge name='\K[^']*" | head -1 || true)
if [[ "$br" == "$BRIDGE" ]]; then
echo "libvirt:$name"
return 0
fi
done < <(virsh net-list --all --name 2>/dev/null)
fi
echo "standalone"
}
# --- Check functions ---
check_bridge_helper() {
echo
bold "qemu-bridge-helper"
local helper
if ! helper=$(find_bridge_helper); then
check_fail "qemu-bridge-helper not found"
check_info "Install QEMU: sudo apt install qemu-system-x86"
return
fi
check_pass "found at $helper"
if [[ -u "$helper" ]]; then
check_pass "setuid bit is set"
else
check_fail "setuid bit not set"
check_info "Fix: sudo chmod u+s $helper"
fi
}
check_bridge_conf() {
echo
bold "/etc/qemu/bridge.conf"
local conf="/etc/qemu/bridge.conf"
if [[ ! -f "$conf" ]]; then
check_fail "$conf does not exist"
check_info "Fix: sudo mkdir -p /etc/qemu && echo 'allow $BRIDGE' | sudo tee $conf"
return
fi
check_pass "$conf exists"
if grep -qE "^allow[[:space:]]+($BRIDGE|all)[[:space:]]*$" "$conf" 2>/dev/null; then
check_pass "bridge '$BRIDGE' is allowed"
else
check_fail "bridge '$BRIDGE' not found in $conf"
check_info "Fix: echo 'allow $BRIDGE' | sudo tee -a $conf"
fi
}
check_bridge_interface() {
echo
bold "bridge interface: $BRIDGE"
if ! ip link show "$BRIDGE" &>/dev/null; then
check_fail "bridge '$BRIDGE' does not exist"
check_info "Run: $(basename "$0") setup --mode libvirt"
check_info " or: $(basename "$0") setup --mode standalone --bridge $BRIDGE"
return
fi
local state
state=$(ip -j link show "$BRIDGE" 2>/dev/null | python3 -c "import sys,json;print(json.load(sys.stdin)[0].get('operstate',''))" 2>/dev/null || echo "UNKNOWN")
if [[ "$state" == "UP" ]]; then
check_pass "interface is UP"
else
check_warn "interface state: $state (expected UP)"
fi
local addr
addr=$(ip -4 -o addr show "$BRIDGE" 2>/dev/null | awk '{print $4}' | head -1)
if [[ -n "$addr" ]]; then
check_pass "has IP address: $addr"
else
check_fail "no IPv4 address assigned"
fi
}
check_dhcp() {
echo
bold "DHCP service"
local provider
provider=$(detect_bridge_provider)
if [[ "$provider" == libvirt:* ]]; then
local net_name="${provider#libvirt:}"
if virsh net-info "$net_name" 2>/dev/null | grep "Active:.*yes" >/dev/null; then
check_pass "libvirt network '$net_name' active on $BRIDGE (includes DHCP)"
return
else
check_fail "libvirt network '$net_name' exists but not active"
check_info "Fix: sudo virsh net-start $net_name"
return
fi
fi
# Standalone: check dnsmasq
if pgrep -f "dnsmasq.*$BRIDGE" &>/dev/null; then
local pid
pid=$(pgrep -f "dnsmasq.*$BRIDGE" | head -1)
check_pass "dnsmasq running for $BRIDGE (pid $pid)"
return
fi
if [[ -d /etc/dnsmasq.d ]]; then
if [[ -f "/etc/dnsmasq.d/dstack-${BRIDGE}.conf" ]] || grep -rl "interface=$BRIDGE" /etc/dnsmasq.d/ &>/dev/null 2>&1; then
if systemctl is-active dnsmasq &>/dev/null; then
check_pass "dnsmasq service active with $BRIDGE config"
return
else
check_fail "dnsmasq config exists but service not running"
check_info "Fix: sudo systemctl start dnsmasq"
return
fi
fi
fi
check_fail "no DHCP server found for $BRIDGE"
check_info "Run: $(basename "$0") setup --mode standalone --bridge $BRIDGE"
}
check_dhcp_notify() {
echo
bold "DHCP lease notification"
local provider
provider=$(detect_bridge_provider)
if [[ "$provider" == libvirt:* ]]; then
check_warn "libvirt DHCP does not support dhcp-script callback"
check_info "port forwarding requires manual PRPC call or alternative notification"
return
fi
# Check dnsmasq config for dhcp-script
local conf_files=(/etc/dnsmasq.d/*"$BRIDGE"* /etc/dnsmasq.d/*.conf)
local found_script=""
for f in "${conf_files[@]}"; do
[[ -f "$f" ]] || continue
local script_path
script_path=$(grep -oP '^dhcp-script=\K.*' "$f" 2>/dev/null || true)
if [[ -n "$script_path" ]]; then
found_script="$script_path"
break
fi
done
if [[ -z "$found_script" ]]; then
check_warn "no dhcp-script configured in dnsmasq"
check_info "port forwarding will not be set up automatically"
check_info "add 'dhcp-script=/usr/local/bin/dhcp-notify.sh' to dnsmasq config"
return
fi
if [[ -x "$found_script" ]]; then
check_pass "dhcp-script configured: $found_script"
else
check_fail "dhcp-script $found_script is not executable or missing"
check_info "Fix: sudo chmod +x $found_script"
fi
}
check_ip_forward() {
echo
bold "IP forwarding"
local val
val=$(sysctl -n net.ipv4.ip_forward 2>/dev/null || echo "0")
if [[ "$val" == "1" ]]; then
check_pass "net.ipv4.ip_forward = 1"
else
check_fail "net.ipv4.ip_forward = $val"
check_info "Fix: sudo sysctl -w net.ipv4.ip_forward=1"
fi
}
check_nat_rules() {
echo
bold "NAT / masquerade rules"
local subnet
subnet=$(ip -4 -o addr show "$BRIDGE" 2>/dev/null | awk '{print $4}' | head -1)
if [[ -z "$subnet" ]]; then
check_warn "cannot determine subnet (bridge has no IP)"
return
fi
local net_cidr
net_cidr=$(python3 -c "
import ipaddress
iface = ipaddress.ip_interface('$subnet')
print(iface.network)
" 2>/dev/null || echo "")
if [[ -z "$net_cidr" ]]; then
check_warn "cannot parse subnet $subnet"
return
fi
local nft_rules=""
if command -v nft &>/dev/null; then
nft_rules=$(sudo nft list ruleset 2>/dev/null || true)
fi
if [[ -n "$nft_rules" ]]; then
if echo "$nft_rules" | grep -q "masquerade" && \
echo "$nft_rules" | grep -q "${net_cidr%/*}"; then
check_pass "nftables masquerade rules found for $net_cidr"
return
fi
fi
if command -v iptables &>/dev/null; then
if sudo iptables -t nat -L -n 2>/dev/null | grep -i "masquerade" >/dev/null && \
sudo iptables -t nat -L -n 2>/dev/null | grep "${net_cidr%/*}" >/dev/null; then
check_pass "iptables masquerade rules found for $net_cidr"
return
fi
fi
check_fail "no NAT masquerade rules found for $net_cidr"
check_info "Libvirt adds these automatically."
check_info "For standalone: systemd-networkd IPMasquerade=both or manual nftables rules."
}
check_dhcp_firewall() {
echo
bold "DHCP/DNS firewall rules"
local nft_rules=""
if command -v nft &>/dev/null; then
nft_rules=$(sudo nft list ruleset 2>/dev/null || true)
fi
if [[ -z "$nft_rules" ]]; then
check_warn "nft not available, cannot check firewall rules"
return
fi
# Check INPUT policy
local input_policy
input_policy=$(echo "$nft_rules" | grep -A1 'chain INPUT' | grep -oP 'policy \K\w+' | head -1 || echo "")
if [[ "$input_policy" == "accept" ]]; then
check_pass "INPUT policy is accept (DHCP/DNS allowed by default)"
return
fi
# Restrictive INPUT policy — need explicit rules
if echo "$nft_rules" | grep -q "iifname \"$BRIDGE\".*udp dport 67.*accept"; then
check_pass "DHCP input rule for $BRIDGE"
else
check_fail "no DHCP input rule for $BRIDGE (INPUT policy: ${input_policy:-unknown})"
check_info "VMs will not get DHCP leases without this rule"
check_info "Fix: sudo nft add rule ip filter INPUT iifname \"$BRIDGE\" udp dport 67 counter accept"
fi
if echo "$nft_rules" | grep -q "iifname \"$BRIDGE\".*udp dport 53.*accept"; then
check_pass "DNS input rule for $BRIDGE"
else
check_warn "no DNS input rule for $BRIDGE"
fi
}
check_forward_rules() {
echo
bold "forwarding rules"
local nft_rules=""
if command -v nft &>/dev/null; then
nft_rules=$(sudo nft list ruleset 2>/dev/null || true)
fi
if [[ -n "$nft_rules" ]] && echo "$nft_rules" | grep -q "iifname \"$BRIDGE\".*accept"; then
check_pass "nftables forward accept rule for $BRIDGE"
return
fi
if command -v iptables &>/dev/null; then
if sudo iptables -L FORWARD -n 2>/dev/null | grep -i "$BRIDGE.*ACCEPT" >/dev/null; then
check_pass "iptables forward accept rule for $BRIDGE"
return
fi
fi
local policy
policy=$(sudo iptables -L FORWARD 2>/dev/null | head -1 | grep -oP '\(policy \K[^)]+' || echo "")
if [[ "$policy" == "ACCEPT" ]]; then
check_pass "forward policy is ACCEPT"
return
fi
check_warn "no explicit forward rules found for $BRIDGE (may still work via libvirt chains)"
}
# --- Setup: common ---
setup_bridge_conf() {
echo
bold "Setting up /etc/qemu/bridge.conf"
run_cmd sudo mkdir -p /etc/qemu
if [[ -f /etc/qemu/bridge.conf ]] && grep -qE "^allow[[:space:]]+($BRIDGE|all)" /etc/qemu/bridge.conf 2>/dev/null; then
echo " already configured"
else
run_cmd bash -c "echo 'allow $BRIDGE' | sudo tee -a /etc/qemu/bridge.conf"
fi
}
setup_bridge_helper() {
echo
bold "Setting up qemu-bridge-helper"
local helper
if ! helper=$(find_bridge_helper); then
echo " $(red 'ERROR'): qemu-bridge-helper not found. Install QEMU first."
return 1
fi
if [[ -u "$helper" ]]; then
echo " setuid already set on $helper"
else
run_cmd sudo chmod u+s "$helper"
echo " setuid set on $helper"
fi
}
setup_ip_forward() {
echo
bold "Enabling IP forwarding"
local val
val=$(sysctl -n net.ipv4.ip_forward 2>/dev/null || echo "0")
if [[ "$val" == "1" ]]; then
echo " already enabled"
else
run_cmd sudo sysctl -w net.ipv4.ip_forward=1
run_cmd bash -c "echo 'net.ipv4.ip_forward=1' | sudo tee /etc/sysctl.d/99-dstack-bridge.conf"
fi
}
# --- Setup: libvirt mode ---
setup_libvirt() {
echo
bold "Setting up libvirt network"
if ! command -v virsh &>/dev/null; then
echo " $(red 'ERROR'): virsh not found. Install libvirt first:"
echo " sudo apt install -y libvirt-daemon-system"
return 1
fi
# Find existing libvirt network for this bridge
local existing_net=""
local name br
while read -r name; do
[[ -z "$name" ]] && continue
br=$(virsh net-dumpxml "$name" 2>/dev/null | grep -oP "<bridge name='\K[^']*" | head -1 || true)
if [[ "$br" == "$BRIDGE" ]]; then
existing_net="$name"
break
fi
done < <(virsh net-list --all --name 2>/dev/null)
if [[ -n "$existing_net" ]]; then
echo " found libvirt network '$existing_net' for bridge $BRIDGE"
if virsh net-info "$existing_net" 2>/dev/null | grep "Active:.*yes" >/dev/null; then
echo " already active"
else
run_cmd sudo virsh net-start "$existing_net"
echo " started network '$existing_net'"
fi
if ! virsh net-info "$existing_net" 2>/dev/null | grep "Autostart:.*yes" >/dev/null; then
run_cmd sudo virsh net-autostart "$existing_net"
fi
elif [[ "$BRIDGE" == "virbr0" ]]; then
if virsh net-info default &>/dev/null; then
if ! virsh net-info default 2>/dev/null | grep "Active:.*yes" >/dev/null; then
run_cmd sudo virsh net-start default
fi
run_cmd sudo virsh net-autostart default
echo " libvirt default network active on virbr0"
else
echo " $(red 'ERROR'): no libvirt default network found"
echo " Recreate it or use --mode standalone"
return 1
fi
else
echo " $(red 'ERROR'): no libvirt network found for bridge '$BRIDGE'"
echo " Create a libvirt network XML or use --mode standalone"
return 1
fi
echo
echo " libvirt provides: bridge, DHCP (dnsmasq), NAT, forwarding rules"
}
# --- Setup: standalone mode ---
setup_standalone() {
echo
bold "Setting up standalone bridge: $BRIDGE"
# 1. Create bridge via systemd-networkd
if ip link show "$BRIDGE" &>/dev/null; then
echo " bridge $BRIDGE already exists"
else
echo " creating bridge $BRIDGE via systemd-networkd"
local netdev="/etc/systemd/network/50-dstack-${BRIDGE}.netdev"
local network="/etc/systemd/network/51-dstack-${BRIDGE}.network"
if [[ ! -f "$netdev" ]]; then
run_cmd bash -c "cat > /tmp/.dstack-br-netdev <<'HEREDOC'
[NetDev]
Name=$BRIDGE
Kind=bridge
HEREDOC
sudo mv /tmp/.dstack-br-netdev $netdev"
echo " created $netdev"
fi
if [[ ! -f "$network" ]]; then
run_cmd bash -c "cat > /tmp/.dstack-br-network <<'HEREDOC'
[Match]
Name=$BRIDGE
[Network]
Address=10.0.100.1/24
ConfigureWithoutCarrier=yes
IPMasquerade=both
HEREDOC
sudo mv /tmp/.dstack-br-network $network"
echo " created $network"
fi
run_cmd sudo systemctl restart systemd-networkd
echo " restarted systemd-networkd"
echo " waiting for bridge to come up..."
local i
for i in $(seq 1 10); do
if ip link show "$BRIDGE" &>/dev/null; then
break
fi
sleep 0.5
done
fi
# 2. DHCP server (dnsmasq)
echo
bold "Setting up dnsmasq for $BRIDGE"
if ! command -v dnsmasq &>/dev/null; then
echo " installing dnsmasq..."
run_cmd sudo apt install -y dnsmasq
fi
local conf="/etc/dnsmasq.d/dstack-${BRIDGE}.conf"
if [[ -f "$conf" ]]; then
echo " dnsmasq config already exists at $conf"
else
# Derive DHCP range from bridge IP
local bridge_ip="10.0.100.1"
local dhcp_start="10.0.100.10"
local dhcp_end="10.0.100.254"
local addr
addr=$(ip -4 -o addr show "$BRIDGE" 2>/dev/null | awk '{print $4}' | head -1 || true)
if [[ -n "$addr" ]]; then
bridge_ip="${addr%/*}"
local prefix="${bridge_ip%.*}"
dhcp_start="${prefix}.10"
dhcp_end="${prefix}.254"
fi
# Install dhcp-notify.sh if present
local notify_script="/usr/local/bin/dhcp-notify.sh"
local dhcp_script_line=""
local src_notify
src_notify="$(cd "$(dirname "$0")" && pwd)/dhcp-notify.sh"
if [[ -f "$src_notify" ]]; then
run_cmd sudo cp "$src_notify" "$notify_script"
run_cmd sudo chmod +x "$notify_script"
dhcp_script_line="dhcp-script=${notify_script}"
echo " installed $notify_script"
else
echo " $(yellow '[WARN]') dhcp-notify.sh not found at $src_notify"
echo " VM port forwarding will not be set up automatically"
fi
run_cmd bash -c "cat > /tmp/.dstack-dnsmasq <<HEREDOC
interface=$BRIDGE
bind-interfaces
dhcp-range=${dhcp_start},${dhcp_end},255.255.255.0,12h
dhcp-option=option:router,${bridge_ip}
dhcp-option=option:dns-server,8.8.8.8,1.1.1.1
${dhcp_script_line}
HEREDOC
sudo mv /tmp/.dstack-dnsmasq $conf"
echo " created $conf"
run_cmd sudo systemctl restart dnsmasq
echo " restarted dnsmasq"
fi
# 3. Firewall rules for standalone bridge
setup_standalone_firewall
echo
echo " standalone provides: bridge, DHCP (dnsmasq), NAT, firewall rules"
}
# --- Setup: standalone firewall ---
setup_standalone_firewall() {
echo
bold "Setting up firewall rules for $BRIDGE"
local subnet
subnet=$(ip -4 -o addr show "$BRIDGE" 2>/dev/null | awk '{print $4}' | head -1)
if [[ -z "$subnet" ]]; then
echo " $(red 'ERROR'): bridge $BRIDGE has no IP, cannot configure firewall"
return 1
fi
local net_cidr
net_cidr=$(python3 -c "
import ipaddress
iface = ipaddress.ip_interface('$subnet')
print(iface.network)
" 2>/dev/null || echo "")
if [[ -z "$net_cidr" ]]; then
echo " $(red 'ERROR'): cannot parse subnet $subnet"
return 1
fi
if ! command -v nft &>/dev/null; then
echo " $(yellow '[WARN]') nft not found, skipping firewall setup"
echo " you may need to configure iptables manually"
return 0
fi
# Detect whether to use libvirt chains or default chains
local inp_chain="INPUT"
local out_chain="OUTPUT"
local fwd_chain="FORWARD"
local nat_chain="POSTROUTING"
if sudo nft list chain ip filter LIBVIRT_INP &>/dev/null 2>&1; then
inp_chain="LIBVIRT_INP"
out_chain="LIBVIRT_OUT"
echo " using libvirt filter chains (LIBVIRT_INP/LIBVIRT_OUT)"
fi
if sudo nft list chain ip filter LIBVIRT_FWO &>/dev/null 2>&1; then
fwd_chain="" # use individual libvirt chains
echo " using libvirt forward chains (LIBVIRT_FWO/FWI/FWX)"
fi
if sudo nft list chain ip nat LIBVIRT_PRT &>/dev/null 2>&1; then
nat_chain="LIBVIRT_PRT"
echo " using libvirt NAT chain (LIBVIRT_PRT)"
fi
# Check if rules already exist (simple heuristic: look for bridge name in ruleset)
local existing
existing=$(sudo nft list ruleset 2>/dev/null || true)
if echo "$existing" | grep -q "iifname \"$BRIDGE\".*udp dport 67.*accept"; then
echo " firewall rules for $BRIDGE already present, skipping"
return 0
fi
echo " adding INPUT/OUTPUT rules for DHCP and DNS"
run_cmd sudo nft add rule ip filter "$inp_chain" iifname "$BRIDGE" udp dport 67 counter accept
run_cmd sudo nft add rule ip filter "$inp_chain" iifname "$BRIDGE" udp dport 53 counter accept
run_cmd sudo nft add rule ip filter "$inp_chain" iifname "$BRIDGE" tcp dport 53 counter accept
run_cmd sudo nft add rule ip filter "$out_chain" oifname "$BRIDGE" udp dport 68 counter accept
run_cmd sudo nft add rule ip filter "$out_chain" oifname "$BRIDGE" udp dport 53 counter accept
echo " adding FORWARD rules for $net_cidr"
if [[ -z "$fwd_chain" ]]; then
# libvirt forward chains
run_cmd sudo nft add rule ip filter LIBVIRT_FWO ip saddr "$net_cidr" iifname "$BRIDGE" counter accept
run_cmd sudo nft add rule ip filter LIBVIRT_FWI ip daddr "$net_cidr" oifname "$BRIDGE" ct state related,established counter accept
run_cmd sudo nft add rule ip filter LIBVIRT_FWX iifname "$BRIDGE" oifname "$BRIDGE" counter accept
else
run_cmd sudo nft add rule ip filter FORWARD ip saddr "$net_cidr" iifname "$BRIDGE" counter accept
run_cmd sudo nft add rule ip filter FORWARD ip daddr "$net_cidr" oifname "$BRIDGE" ct state related,established counter accept
run_cmd sudo nft add rule ip filter FORWARD iifname "$BRIDGE" oifname "$BRIDGE" counter accept
fi
echo " adding NAT masquerade for $net_cidr"
run_cmd sudo nft add rule ip nat "$nat_chain" ip saddr "$net_cidr" ip daddr 224.0.0.0/24 counter return
run_cmd sudo nft add rule ip nat "$nat_chain" ip saddr "$net_cidr" ip daddr 255.255.255.255 counter return
run_cmd sudo nft add rule ip nat "$nat_chain" ip saddr "$net_cidr" ip daddr != "$net_cidr" counter masquerade
echo " firewall rules configured"
}
# --- Main ---
usage() {
cat <<'USAGE'
Usage: setup-bridge.sh <command> [options]
Commands:
check Check bridge networking prerequisites
setup Configure bridge networking prerequisites
destroy Tear down bridge networking configuration
Options:
--bridge NAME Bridge interface name (default: virbr0)
--mode MODE Setup mode: libvirt or standalone (required for setup)
--dry-run Show what would be done without executing (setup only)
-h, --help Show this help
Setup modes:
libvirt Use libvirt network. Provides bridge, DHCP, NAT, and
firewall rules automatically. Recommended if libvirt is
already installed. Default bridge: virbr0.
standalone Create bridge with systemd-networkd and dnsmasq. Does
not require libvirt. Use a custom bridge name to avoid
conflicts with libvirt.
Examples:
setup-bridge.sh check
setup-bridge.sh check --bridge dstack-br0
setup-bridge.sh setup --mode libvirt
setup-bridge.sh setup --mode standalone --bridge dstack-br0
setup-bridge.sh setup --mode standalone --bridge dstack-br0 --dry-run
setup-bridge.sh destroy
setup-bridge.sh destroy --bridge dstack-br0 --dry-run
USAGE
}
parse_args() {
while [[ $# -gt 0 ]]; do
case "$1" in
check|setup|destroy)
COMMAND="$1"; shift ;;
--bridge)
BRIDGE="${2:?--bridge requires a value}"; shift 2 ;;
--mode)
SETUP_MODE="${2:?--mode requires a value}"; shift 2 ;;
--dry-run)
DRY_RUN=true; shift ;;
-h|--help)
usage; exit 0 ;;
*)
echo "unknown argument: $1"; usage; exit 1 ;;
esac
done
if [[ -z "$COMMAND" ]]; then
usage
exit 1
fi
}
cmd_check() {
echo
echo "$(bold 'dstack bridge networking diagnostics')"
echo "target bridge: $(bold "$BRIDGE")"
local provider
provider=$(detect_bridge_provider)
if [[ "$provider" == libvirt:* ]]; then
echo "provider: $(bold 'libvirt') (network: ${provider#libvirt:})"
else
echo "provider: $(bold 'standalone')"
fi
check_bridge_helper
check_bridge_conf
check_bridge_interface
check_dhcp
check_dhcp_notify
check_dhcp_firewall
check_ip_forward
check_nat_rules
check_forward_rules
echo
echo "---"
echo "$(green "PASS: $PASS") $(red "FAIL: $FAIL") $(yellow "WARN: $WARN")"
if [[ $FAIL -gt 0 ]]; then
echo
echo "Run '$(basename "$0") setup --mode <libvirt|standalone> --bridge $BRIDGE' to fix issues."
return 1
fi
}
cmd_setup() {
if [[ -z "$SETUP_MODE" ]]; then
echo
echo "$(red 'ERROR'): --mode is required for setup."
echo
echo "Choose a mode:"
echo " --mode libvirt Use libvirt (provides bridge + DHCP + NAT)"
echo " --mode standalone No libvirt (systemd-networkd + dnsmasq)"
echo
echo "Example:"
echo " $(basename "$0") setup --mode libvirt"
echo " $(basename "$0") setup --mode standalone --bridge dstack-br0"
exit 1
fi
if [[ "$SETUP_MODE" != "libvirt" && "$SETUP_MODE" != "standalone" ]]; then
echo "$(red 'ERROR'): unknown mode '$SETUP_MODE'. Use 'libvirt' or 'standalone'."
exit 1
fi
echo
echo "$(bold 'dstack bridge networking setup')"
echo "target bridge: $(bold "$BRIDGE")"
echo "mode: $(bold "$SETUP_MODE")"
$DRY_RUN && echo "dry-run: $(yellow 'yes')"
# Common setup
setup_bridge_conf
setup_bridge_helper
setup_ip_forward
# Mode-specific setup
case "$SETUP_MODE" in
libvirt) setup_libvirt ;;
standalone) setup_standalone ;;
esac
echo
echo "---"
echo "$(bold 'Setup complete.')"
echo
echo "Add to vmm.toml:"
echo
echo " [cvm.networking]"
echo " mode = \"bridge\""
echo " bridge = \"$BRIDGE\""
echo
echo "Verify: $(basename "$0") check --bridge $BRIDGE"
}
cmd_destroy() {
local provider
provider=$(detect_bridge_provider)
echo
echo "$(bold 'dstack bridge networking destroy')"
echo "target bridge: $(bold "$BRIDGE")"
if [[ "$provider" == libvirt:* ]]; then
echo "provider: $(bold 'libvirt') (network: ${provider#libvirt:})"
else
echo "provider: $(bold 'standalone')"
fi
$DRY_RUN && echo "dry-run: $(yellow 'yes')"
if [[ "$provider" == libvirt:* ]]; then
local net_name="${provider#libvirt:}"
echo
bold "Stopping libvirt network '$net_name'"
if virsh net-info "$net_name" 2>/dev/null | grep "Active:.*yes" >/dev/null; then
run_cmd sudo virsh net-destroy "$net_name"
echo " stopped"
else
echo " already inactive"
fi
if virsh net-info "$net_name" 2>/dev/null | grep "Autostart:.*yes" >/dev/null; then
run_cmd sudo virsh net-autostart --disable "$net_name"
echo " autostart disabled"
fi
else
# Standalone: remove dnsmasq config and systemd-networkd units
local dnsmasq_conf="/etc/dnsmasq.d/dstack-${BRIDGE}.conf"
if [[ -f "$dnsmasq_conf" ]]; then
echo
bold "Removing dnsmasq config"
run_cmd sudo rm -f "$dnsmasq_conf"
echo " removed $dnsmasq_conf"
run_cmd sudo systemctl restart dnsmasq 2>/dev/null || true
echo " restarted dnsmasq"
fi
local netdev="/etc/systemd/network/50-dstack-${BRIDGE}.netdev"
local network="/etc/systemd/network/51-dstack-${BRIDGE}.network"
if [[ -f "$netdev" ]] || [[ -f "$network" ]]; then
echo
bold "Removing systemd-networkd units"
[[ -f "$netdev" ]] && { run_cmd sudo rm -f "$netdev"; echo " removed $netdev"; }
[[ -f "$network" ]] && { run_cmd sudo rm -f "$network"; echo " removed $network"; }
run_cmd sudo systemctl restart systemd-networkd
echo " restarted systemd-networkd"
fi
# Delete bridge interface if it still exists
if ip link show "$BRIDGE" &>/dev/null; then
echo
bold "Deleting bridge interface $BRIDGE"
run_cmd sudo ip link set "$BRIDGE" down
run_cmd sudo ip link delete "$BRIDGE" type bridge
echo " deleted"
fi
fi
# Remove bridge.conf entry
local conf="/etc/qemu/bridge.conf"
if [[ -f "$conf" ]] && grep -qE "^allow[[:space:]]+${BRIDGE}[[:space:]]*$" "$conf" 2>/dev/null; then
echo
bold "Removing '$BRIDGE' from $conf"
run_cmd sudo sed -i "/^allow[[:space:]]\+${BRIDGE}[[:space:]]*$/d" "$conf"
echo " removed"
fi
echo
echo "---"
echo "$(bold 'Destroy complete.')"
}
parse_args "$@"
case "$COMMAND" in
check) cmd_check ;;
setup) cmd_setup ;;
destroy) cmd_destroy ;;
esac