b.liu | e958203 | 2025-04-17 19:18:16 +0800 | [diff] [blame] | 1 | #!/usr/bin/env bash |
| 2 | |
| 3 | SELF="$0" |
| 4 | |
| 5 | # Linux bridge for connecting lan and wan network of guest machines |
| 6 | BR_LAN="${BR_LAN:-br-lan}" |
| 7 | BR_WAN="${BR_WAN:-br-wan}" |
| 8 | |
| 9 | # Host network interface providing internet access for guest machines |
| 10 | IF_INET="${IF_INET:-eth0}" |
| 11 | |
| 12 | # qemu-bridge-helper does two things here |
| 13 | # |
| 14 | # - create tap interface |
| 15 | # - add the tap interface to bridge |
| 16 | # |
| 17 | # as such it requires CAP_NET_ADMIN to do its job. It will be convenient to |
| 18 | # have it as a root setuid program. Be aware of the security risks implied |
| 19 | # |
| 20 | # the helper has an acl list which defaults to deny all bridge. we need to add |
| 21 | # $BR_LAN and $BR_WAN to its allow list |
| 22 | # |
| 23 | # # sudo vim /etc/qemu/bridge.conf |
| 24 | # allow br-lan |
| 25 | # allow br-wan |
| 26 | # |
| 27 | # Other allowed directives can be 'allow all', 'deny all', 'include xxx', See |
| 28 | # qemu-bridge-helper.c of qemu source code for details. |
| 29 | # |
| 30 | # The helper can be provided by package qemu-system-common on debian, or |
| 31 | # qemu-kvm-common on rhel |
| 32 | # |
| 33 | HELPER="${HELPER:-/usr/libexec/qemu-bridge-helper}" |
| 34 | |
| 35 | ### end of global settings |
| 36 | |
| 37 | __errmsg() { |
| 38 | echo "$*" >&2 |
| 39 | } |
| 40 | |
| 41 | do_setup() { |
| 42 | # setup bridge for LAN network |
| 43 | sudo ip link add dev "$BR_LAN" type bridge |
| 44 | sudo ip link set dev "$BR_LAN" up |
| 45 | sudo ip addr add 192.168.1.3/24 dev "$BR_LAN" |
| 46 | |
| 47 | # setup bridge for WAN network |
| 48 | # |
| 49 | # minimal dnsmasq config for configuring guest wan network with dhcp |
| 50 | # |
| 51 | # # sudo apt-get install dnsmasq |
| 52 | # # sudo vi /etc/dnsmasq.conf |
| 53 | # interface=br-wan |
| 54 | # dhcp-range=192.168.7.50,192.168.7.150,255.255.255.0,30m |
| 55 | # |
| 56 | sudo ip link add dev "$BR_WAN" type bridge |
| 57 | sudo ip link set dev "$BR_WAN" up |
| 58 | sudo ip addr add 192.168.7.1/24 dev "$BR_WAN" |
| 59 | |
| 60 | # guest internet access |
| 61 | sudo sysctl -w "net.ipv4.ip_forward=1" |
| 62 | sudo sysctl -w "net.ipv4.conf.$BR_WAN.proxy_arp=1" |
| 63 | while sudo iptables -t nat -D POSTROUTING -o "$IF_INET" -j MASQUERADE 2>/dev/null; do true; done |
| 64 | sudo iptables -t nat -A POSTROUTING -o "$IF_INET" -j MASQUERADE |
| 65 | } |
| 66 | |
| 67 | check_setup_() { |
| 68 | ip link show "$BR_LAN" >/dev/null || return 1 |
| 69 | ip link show "$BR_WAN" >/dev/null || return 1 |
| 70 | [ -x "$HELPER" ] || { |
| 71 | __errmsg "helper $HELPER is not an executable" |
| 72 | return 1 |
| 73 | } |
| 74 | } |
| 75 | |
| 76 | check_setup() { |
| 77 | [ -n "$o_network" ] || return 0 |
| 78 | check_setup_ || { |
| 79 | __errmsg "please check the script content to see the environment requirement" |
| 80 | return 1 |
| 81 | } |
| 82 | } |
| 83 | #do_setup; check_setup; exit $? |
| 84 | |
| 85 | usage() { |
| 86 | cat >&2 <<EOF |
| 87 | Usage: $SELF [-h|--help] |
| 88 | $SELF <target> |
| 89 | [<subtarget> [<extra-qemu-options>]] |
| 90 | [--kernel <kernel>] |
| 91 | [--rootfs <rootfs>] |
| 92 | [--machine <machine>] |
| 93 | [-n|--network] |
| 94 | |
| 95 | <subtarget> will default to "generic" and must be specified if |
| 96 | <extra-qemu-options> are present |
| 97 | |
| 98 | e.g. <subtarget> for malta can be le, be, le64, be64, le-glibc, le64-glibc, etc |
| 99 | |
| 100 | <kernel>, <rootfs> can be required or optional arguments to qemu depending on |
| 101 | the actual <target> in use. They will default to files under bin/targets/ |
| 102 | |
| 103 | Examples |
| 104 | |
| 105 | $SELF x86 64 |
| 106 | $SELF x86 64 --machine q35,accel=kvm -device virtio-balloon-pci |
| 107 | $SELF x86 64 -incoming tcp:0:4444 |
| 108 | $SELF x86 64-glibc |
| 109 | $SELF malta be -m 64 |
| 110 | $SELF malta le64 |
| 111 | $SELF malta be-glibc |
| 112 | $SELF armsr armv7 \\ |
| 113 | --machine virt,highmem=off \\ |
| 114 | --kernel bin/targets/armsr/armv7/openwrt-armsr-armv7-generic-kernel.bin \\ |
| 115 | --rootfs bin/targets/armsr/armv7/openwrt-armsr-armv7-generic-ext4-rootfs.img |
| 116 | EOF |
| 117 | } |
| 118 | |
| 119 | rand_mac() { |
| 120 | hexdump -n 3 -e '"52:54:00" 3/1 ":%02x"' /dev/urandom |
| 121 | } |
| 122 | |
| 123 | parse_args() { |
| 124 | o_network= |
| 125 | o_qemu_extra=() |
| 126 | while [ "$#" -gt 0 ]; do |
| 127 | # Cmdline options for the script itself SHOULD try to be |
| 128 | # prefixed with two dashes to distinguish them from those for |
| 129 | # qemu executables. |
| 130 | # |
| 131 | # Also note that qemu accepts both --opt and -opt |
| 132 | case "$1" in |
| 133 | --kernel) o_kernel="$2"; shift 2 ;; |
| 134 | --rootfs) o_rootfs="$2"; shift 2 ;; |
| 135 | --machine|-machine|-M) o_mach="$2"; shift 2 ;; |
| 136 | --network|-n) o_network=1; shift ;; |
| 137 | --help|-h) |
| 138 | usage |
| 139 | exit 0 |
| 140 | ;; |
| 141 | *) |
| 142 | if [ -z "$o_target" ]; then |
| 143 | o_target="$1" |
| 144 | elif [ -z "$o_subtarget" ]; then |
| 145 | o_subtarget="$1" |
| 146 | else |
| 147 | o_qemu_extra+=("$1") |
| 148 | fi |
| 149 | shift |
| 150 | ;; |
| 151 | esac |
| 152 | done |
| 153 | |
| 154 | MAC_LAN="$(rand_mac)" |
| 155 | MAC_WAN="$(rand_mac)" |
| 156 | [ -n "$o_target" ] || { |
| 157 | usage |
| 158 | return 1 |
| 159 | } |
| 160 | [ -n "$o_subtarget" ] || o_subtarget="generic" |
| 161 | eval "$(grep ^CONFIG_BINARY_FOLDER= .config 2>/dev/null)" |
| 162 | o_bindir="${CONFIG_BINARY_FOLDER:-bin}/targets/$o_target/$o_subtarget" |
| 163 | } |
| 164 | |
| 165 | start_qemu_armsr() { |
| 166 | local kernel="$o_kernel" |
| 167 | local rootfs="$o_rootfs" |
| 168 | local mach="${o_mach:-virt}" |
| 169 | local cpu |
| 170 | local qemu_exe |
| 171 | |
| 172 | case "${o_subtarget%-*}" in |
| 173 | armv7) |
| 174 | qemu_exe="qemu-system-arm" |
| 175 | cpu="cortex-a15" |
| 176 | [ -n "$kernel" ] || kernel="$o_bindir/openwrt-$o_target-${o_subtarget%-*}-generic-initramfs-kernel.bin" |
| 177 | ;; |
| 178 | armv8) |
| 179 | qemu_exe="qemu-system-aarch64" |
| 180 | cpu="cortex-a57" |
| 181 | [ -n "$kernel" ] || kernel="$o_bindir/openwrt-$o_target-${o_subtarget%-*}-generic-initramfs-kernel.bin" |
| 182 | ;; |
| 183 | *) |
| 184 | __errmsg "target $o_target: unknown subtarget $o_subtarget" |
| 185 | return 1 |
| 186 | ;; |
| 187 | esac |
| 188 | [ -z "$rootfs" ] || { |
| 189 | if [ ! -f "$rootfs" -a -s "$rootfs.gz" ]; then |
| 190 | gunzip "$rootfs.gz" |
| 191 | fi |
| 192 | o_qemu_extra+=( \ |
| 193 | "-drive" "file=$rootfs,format=raw,if=virtio" \ |
| 194 | "-append" "root=/dev/vda rootwait" \ |
| 195 | ) |
| 196 | } |
| 197 | |
| 198 | [ -z "$o_network" ] || { |
| 199 | o_qemu_extra+=( \ |
| 200 | "-netdev" "bridge,id=lan,br=$BR_LAN,helper=$HELPER" \ |
| 201 | "-device" "virtio-net-pci,id=devlan,netdev=lan,mac=$MAC_LAN" \ |
| 202 | "-netdev" "bridge,id=wan,br=$BR_WAN,helper=$HELPER" "-device" \ |
| 203 | "virtio-net-pci,id=devwan,netdev=wan,mac=$MAC_WAN" \ |
| 204 | ) |
| 205 | } |
| 206 | |
| 207 | "$qemu_exe" -machine "$mach" -cpu "$cpu" -nographic \ |
| 208 | -kernel "$kernel" \ |
| 209 | "${o_qemu_extra[@]}" |
| 210 | } |
| 211 | |
| 212 | start_qemu_malta() { |
| 213 | local is64 |
| 214 | local isel |
| 215 | local qemu_exe |
| 216 | local cpu |
| 217 | local rootfs="$o_rootfs" |
| 218 | local kernel="$o_kernel" |
| 219 | local mach="${o_mach:-malta}" |
| 220 | |
| 221 | # o_subtarget can be le, be, le64, be64, le-glibc, le64-glibc, etc.. |
| 222 | is64="$(echo $o_subtarget | grep -o 64)" |
| 223 | [ "$(echo "$o_subtarget" | grep -o '^..')" = "le" ] && isel="el" |
| 224 | qemu_exe="qemu-system-mips$is64$isel" |
| 225 | [ -n "$is64" ] && cpu="MIPS64R2-generic" || cpu="24Kc" |
| 226 | |
| 227 | [ -n "$kernel" ] || kernel="$o_bindir/openwrt-malta-${o_subtarget%-*}-vmlinux-initramfs.elf" |
| 228 | |
| 229 | [ -z "$rootfs" ] || { |
| 230 | if [ ! -f "$rootfs" -a -s "$rootfs.gz" ]; then |
| 231 | gunzip "$rootfs.gz" |
| 232 | fi |
| 233 | o_qemu_extra+=( \ |
| 234 | "-drive" "file=$rootfs,format=raw" \ |
| 235 | "-append" "root=/dev/sda rootwait" \ |
| 236 | ) |
| 237 | } |
| 238 | |
| 239 | # NOTE: order of wan, lan -device arguments matters as it will affect which |
| 240 | # one will be actually used as the wan, lan network interface inside the |
| 241 | # guest machine |
| 242 | [ -z "$o_network" ] || { |
| 243 | o_qemu_extra+=( |
| 244 | -netdev bridge,id=wan,br="$BR_WAN,helper=$HELPER" -device pcnet,netdev=wan,mac="$MAC_WAN" |
| 245 | -netdev bridge,id=lan,br="$BR_LAN,helper=$HELPER" -device pcnet,netdev=lan,mac="$MAC_LAN" |
| 246 | ) |
| 247 | } |
| 248 | |
| 249 | "$qemu_exe" -machine "$mach" -cpu "$cpu" -nographic \ |
| 250 | -kernel "$kernel" \ |
| 251 | "${o_qemu_extra[@]}" |
| 252 | } |
| 253 | |
| 254 | start_qemu_x86() { |
| 255 | local qemu_exe |
| 256 | local kernel="$o_kernel" |
| 257 | local rootfs="$o_rootfs" |
| 258 | local mach="${o_mach:-pc}" |
| 259 | |
| 260 | [ -n "$rootfs" ] || { |
| 261 | rootfs="$o_bindir/openwrt-$o_target-${o_subtarget%-*}-generic-squashfs-combined.img" |
| 262 | if [ ! -f "$rootfs" -a -s "$rootfs.gz" ]; then |
| 263 | gunzip "$rootfs.gz" |
| 264 | fi |
| 265 | } |
| 266 | # |
| 267 | # generic: 32-bit, pentium4 (CONFIG_MPENTIUM4), kvm guest, virtio |
| 268 | # legacy: 32-bit, i486 (CONFIG_M486) |
| 269 | # 64: 64-bit, kvm guest, virtio |
| 270 | # |
| 271 | case "${o_subtarget%-*}" in |
| 272 | legacy) qemu_exe="qemu-system-i386" ;; |
| 273 | generic|64) qemu_exe="qemu-system-x86_64" ;; |
| 274 | *) |
| 275 | __errmsg "target $o_target: unknown subtarget $o_subtarget" |
| 276 | return 1 |
| 277 | ;; |
| 278 | esac |
| 279 | |
| 280 | [ -n "$kernel" ] && { |
| 281 | o_qemu_extra+=( \ |
| 282 | "-kernel" "$kernel" \ |
| 283 | "-append" "root=/dev/vda console=ttyS0 rootwait" \ |
| 284 | ) |
| 285 | } |
| 286 | |
| 287 | [ -z "$o_network" ] || { |
| 288 | case "${o_subtarget%-*}" in |
| 289 | legacy) |
| 290 | o_qemu_extra+=( |
| 291 | -netdev "bridge,id=lan,br=$BR_LAN,helper=$HELPER" -device "e1000,id=devlan,netdev=lan,mac=$MAC_LAN" |
| 292 | -netdev "bridge,id=wan,br=$BR_WAN,helper=$HELPER" -device "e1000,id=devwan,netdev=wan,mac=$MAC_WAN" |
| 293 | ) |
| 294 | ;; |
| 295 | generic|64) |
| 296 | o_qemu_extra+=( |
| 297 | -netdev "bridge,id=lan,br=$BR_LAN,helper=$HELPER" -device "virtio-net-pci,id=devlan,netdev=lan,mac=$MAC_LAN" |
| 298 | -netdev "bridge,id=wan,br=$BR_WAN,helper=$HELPER" -device "virtio-net-pci,id=devwan,netdev=wan,mac=$MAC_WAN" |
| 299 | ) |
| 300 | ;; |
| 301 | esac |
| 302 | } |
| 303 | |
| 304 | case "${o_subtarget%-*}" in |
| 305 | legacy) |
| 306 | # use IDE (PATA) disk instead of AHCI (SATA). Refer to link |
| 307 | # [1] for related discussions |
| 308 | # |
| 309 | # To use AHCI interface |
| 310 | # |
| 311 | # -device ich9-ahci,id=ahci \ |
| 312 | # -device ide-hd,drive=drv0,bus=ahci.0 \ |
| 313 | # -drive "file=$rootfs,format=raw,id=drv0,if=none" \ |
| 314 | # |
| 315 | # [1] https://dev.openwrt.org/ticket/17947 |
| 316 | "$qemu_exe" -machine "$mach" -nographic \ |
| 317 | -device ide-hd,drive=drv0 \ |
| 318 | -drive "file=$rootfs,format=raw,id=drv0,if=none" \ |
| 319 | "${o_qemu_extra[@]}" |
| 320 | ;; |
| 321 | generic|64) |
| 322 | "$qemu_exe" -machine "$mach" -nographic \ |
| 323 | -drive "file=$rootfs,format=raw,if=virtio" \ |
| 324 | "${o_qemu_extra[@]}" |
| 325 | ;; |
| 326 | esac |
| 327 | } |
| 328 | |
| 329 | start_qemu() { |
| 330 | case "$o_target" in |
| 331 | armsr) start_qemu_armsr ;; |
| 332 | malta) start_qemu_malta ;; |
| 333 | x86) start_qemu_x86 ;; |
| 334 | *) |
| 335 | __errmsg "target $o_target is not supported yet" |
| 336 | return 1 |
| 337 | ;; |
| 338 | esac |
| 339 | } |
| 340 | |
| 341 | parse_args "$@" \ |
| 342 | && check_setup \ |
| 343 | && start_qemu |