| #!/usr/bin/env bash |
| |
| SELF="$0" |
| |
| # Linux bridge for connecting lan and wan network of guest machines |
| BR_LAN="${BR_LAN:-br-lan}" |
| BR_WAN="${BR_WAN:-br-wan}" |
| |
| # Host network interface providing internet access for guest machines |
| IF_INET="${IF_INET:-eth0}" |
| |
| # qemu-bridge-helper does two things here |
| # |
| # - create tap interface |
| # - add the tap interface to bridge |
| # |
| # as such it requires CAP_NET_ADMIN to do its job. It will be convenient to |
| # have it as a root setuid program. Be aware of the security risks implied |
| # |
| # the helper has an acl list which defaults to deny all bridge. we need to add |
| # $BR_LAN and $BR_WAN to its allow list |
| # |
| # # sudo vim /etc/qemu/bridge.conf |
| # allow br-lan |
| # allow br-wan |
| # |
| # Other allowed directives can be 'allow all', 'deny all', 'include xxx', See |
| # qemu-bridge-helper.c of qemu source code for details. |
| # |
| # The helper can be provided by package qemu-system-common on debian, or |
| # qemu-kvm-common on rhel |
| # |
| HELPER="${HELPER:-/usr/libexec/qemu-bridge-helper}" |
| |
| ### end of global settings |
| |
| __errmsg() { |
| echo "$*" >&2 |
| } |
| |
| do_setup() { |
| # setup bridge for LAN network |
| sudo ip link add dev "$BR_LAN" type bridge |
| sudo ip link set dev "$BR_LAN" up |
| sudo ip addr add 192.168.1.3/24 dev "$BR_LAN" |
| |
| # setup bridge for WAN network |
| # |
| # minimal dnsmasq config for configuring guest wan network with dhcp |
| # |
| # # sudo apt-get install dnsmasq |
| # # sudo vi /etc/dnsmasq.conf |
| # interface=br-wan |
| # dhcp-range=192.168.7.50,192.168.7.150,255.255.255.0,30m |
| # |
| sudo ip link add dev "$BR_WAN" type bridge |
| sudo ip link set dev "$BR_WAN" up |
| sudo ip addr add 192.168.7.1/24 dev "$BR_WAN" |
| |
| # guest internet access |
| sudo sysctl -w "net.ipv4.ip_forward=1" |
| sudo sysctl -w "net.ipv4.conf.$BR_WAN.proxy_arp=1" |
| while sudo iptables -t nat -D POSTROUTING -o "$IF_INET" -j MASQUERADE 2>/dev/null; do true; done |
| sudo iptables -t nat -A POSTROUTING -o "$IF_INET" -j MASQUERADE |
| } |
| |
| check_setup_() { |
| ip link show "$BR_LAN" >/dev/null || return 1 |
| ip link show "$BR_WAN" >/dev/null || return 1 |
| [ -x "$HELPER" ] || { |
| __errmsg "helper $HELPER is not an executable" |
| return 1 |
| } |
| } |
| |
| check_setup() { |
| [ -n "$o_network" ] || return 0 |
| check_setup_ || { |
| __errmsg "please check the script content to see the environment requirement" |
| return 1 |
| } |
| } |
| #do_setup; check_setup; exit $? |
| |
| usage() { |
| cat >&2 <<EOF |
| Usage: $SELF [-h|--help] |
| $SELF <target> |
| [<subtarget> [<extra-qemu-options>]] |
| [--kernel <kernel>] |
| [--rootfs <rootfs>] |
| [--machine <machine>] |
| [-n|--network] |
| |
| <subtarget> will default to "generic" and must be specified if |
| <extra-qemu-options> are present |
| |
| e.g. <subtarget> for malta can be le, be, le64, be64, le-glibc, le64-glibc, etc |
| |
| <kernel>, <rootfs> can be required or optional arguments to qemu depending on |
| the actual <target> in use. They will default to files under bin/targets/ |
| |
| Examples |
| |
| $SELF x86 64 |
| $SELF x86 64 --machine q35,accel=kvm -device virtio-balloon-pci |
| $SELF x86 64 -incoming tcp:0:4444 |
| $SELF x86 64-glibc |
| $SELF malta be -m 64 |
| $SELF malta le64 |
| $SELF malta be-glibc |
| $SELF armsr armv7 \\ |
| --machine virt,highmem=off \\ |
| --kernel bin/targets/armsr/armv7/openwrt-armsr-armv7-generic-kernel.bin \\ |
| --rootfs bin/targets/armsr/armv7/openwrt-armsr-armv7-generic-ext4-rootfs.img |
| EOF |
| } |
| |
| rand_mac() { |
| hexdump -n 3 -e '"52:54:00" 3/1 ":%02x"' /dev/urandom |
| } |
| |
| parse_args() { |
| o_network= |
| o_qemu_extra=() |
| while [ "$#" -gt 0 ]; do |
| # Cmdline options for the script itself SHOULD try to be |
| # prefixed with two dashes to distinguish them from those for |
| # qemu executables. |
| # |
| # Also note that qemu accepts both --opt and -opt |
| case "$1" in |
| --kernel) o_kernel="$2"; shift 2 ;; |
| --rootfs) o_rootfs="$2"; shift 2 ;; |
| --machine|-machine|-M) o_mach="$2"; shift 2 ;; |
| --network|-n) o_network=1; shift ;; |
| --help|-h) |
| usage |
| exit 0 |
| ;; |
| *) |
| if [ -z "$o_target" ]; then |
| o_target="$1" |
| elif [ -z "$o_subtarget" ]; then |
| o_subtarget="$1" |
| else |
| o_qemu_extra+=("$1") |
| fi |
| shift |
| ;; |
| esac |
| done |
| |
| MAC_LAN="$(rand_mac)" |
| MAC_WAN="$(rand_mac)" |
| [ -n "$o_target" ] || { |
| usage |
| return 1 |
| } |
| [ -n "$o_subtarget" ] || o_subtarget="generic" |
| eval "$(grep ^CONFIG_BINARY_FOLDER= .config 2>/dev/null)" |
| o_bindir="${CONFIG_BINARY_FOLDER:-bin}/targets/$o_target/$o_subtarget" |
| } |
| |
| start_qemu_armsr() { |
| local kernel="$o_kernel" |
| local rootfs="$o_rootfs" |
| local mach="${o_mach:-virt}" |
| local cpu |
| local qemu_exe |
| |
| case "${o_subtarget%-*}" in |
| armv7) |
| qemu_exe="qemu-system-arm" |
| cpu="cortex-a15" |
| [ -n "$kernel" ] || kernel="$o_bindir/openwrt-$o_target-${o_subtarget%-*}-generic-initramfs-kernel.bin" |
| ;; |
| armv8) |
| qemu_exe="qemu-system-aarch64" |
| cpu="cortex-a57" |
| [ -n "$kernel" ] || kernel="$o_bindir/openwrt-$o_target-${o_subtarget%-*}-generic-initramfs-kernel.bin" |
| ;; |
| *) |
| __errmsg "target $o_target: unknown subtarget $o_subtarget" |
| return 1 |
| ;; |
| esac |
| [ -z "$rootfs" ] || { |
| if [ ! -f "$rootfs" -a -s "$rootfs.gz" ]; then |
| gunzip "$rootfs.gz" |
| fi |
| o_qemu_extra+=( \ |
| "-drive" "file=$rootfs,format=raw,if=virtio" \ |
| "-append" "root=/dev/vda rootwait" \ |
| ) |
| } |
| |
| [ -z "$o_network" ] || { |
| o_qemu_extra+=( \ |
| "-netdev" "bridge,id=lan,br=$BR_LAN,helper=$HELPER" \ |
| "-device" "virtio-net-pci,id=devlan,netdev=lan,mac=$MAC_LAN" \ |
| "-netdev" "bridge,id=wan,br=$BR_WAN,helper=$HELPER" "-device" \ |
| "virtio-net-pci,id=devwan,netdev=wan,mac=$MAC_WAN" \ |
| ) |
| } |
| |
| "$qemu_exe" -machine "$mach" -cpu "$cpu" -nographic \ |
| -kernel "$kernel" \ |
| "${o_qemu_extra[@]}" |
| } |
| |
| start_qemu_malta() { |
| local is64 |
| local isel |
| local qemu_exe |
| local cpu |
| local rootfs="$o_rootfs" |
| local kernel="$o_kernel" |
| local mach="${o_mach:-malta}" |
| |
| # o_subtarget can be le, be, le64, be64, le-glibc, le64-glibc, etc.. |
| is64="$(echo $o_subtarget | grep -o 64)" |
| [ "$(echo "$o_subtarget" | grep -o '^..')" = "le" ] && isel="el" |
| qemu_exe="qemu-system-mips$is64$isel" |
| [ -n "$is64" ] && cpu="MIPS64R2-generic" || cpu="24Kc" |
| |
| [ -n "$kernel" ] || kernel="$o_bindir/openwrt-malta-${o_subtarget%-*}-vmlinux-initramfs.elf" |
| |
| [ -z "$rootfs" ] || { |
| if [ ! -f "$rootfs" -a -s "$rootfs.gz" ]; then |
| gunzip "$rootfs.gz" |
| fi |
| o_qemu_extra+=( \ |
| "-drive" "file=$rootfs,format=raw" \ |
| "-append" "root=/dev/sda rootwait" \ |
| ) |
| } |
| |
| # NOTE: order of wan, lan -device arguments matters as it will affect which |
| # one will be actually used as the wan, lan network interface inside the |
| # guest machine |
| [ -z "$o_network" ] || { |
| o_qemu_extra+=( |
| -netdev bridge,id=wan,br="$BR_WAN,helper=$HELPER" -device pcnet,netdev=wan,mac="$MAC_WAN" |
| -netdev bridge,id=lan,br="$BR_LAN,helper=$HELPER" -device pcnet,netdev=lan,mac="$MAC_LAN" |
| ) |
| } |
| |
| "$qemu_exe" -machine "$mach" -cpu "$cpu" -nographic \ |
| -kernel "$kernel" \ |
| "${o_qemu_extra[@]}" |
| } |
| |
| start_qemu_x86() { |
| local qemu_exe |
| local kernel="$o_kernel" |
| local rootfs="$o_rootfs" |
| local mach="${o_mach:-pc}" |
| |
| [ -n "$rootfs" ] || { |
| rootfs="$o_bindir/openwrt-$o_target-${o_subtarget%-*}-generic-squashfs-combined.img" |
| if [ ! -f "$rootfs" -a -s "$rootfs.gz" ]; then |
| gunzip "$rootfs.gz" |
| fi |
| } |
| # |
| # generic: 32-bit, pentium4 (CONFIG_MPENTIUM4), kvm guest, virtio |
| # legacy: 32-bit, i486 (CONFIG_M486) |
| # 64: 64-bit, kvm guest, virtio |
| # |
| case "${o_subtarget%-*}" in |
| legacy) qemu_exe="qemu-system-i386" ;; |
| generic|64) qemu_exe="qemu-system-x86_64" ;; |
| *) |
| __errmsg "target $o_target: unknown subtarget $o_subtarget" |
| return 1 |
| ;; |
| esac |
| |
| [ -n "$kernel" ] && { |
| o_qemu_extra+=( \ |
| "-kernel" "$kernel" \ |
| "-append" "root=/dev/vda console=ttyS0 rootwait" \ |
| ) |
| } |
| |
| [ -z "$o_network" ] || { |
| case "${o_subtarget%-*}" in |
| legacy) |
| o_qemu_extra+=( |
| -netdev "bridge,id=lan,br=$BR_LAN,helper=$HELPER" -device "e1000,id=devlan,netdev=lan,mac=$MAC_LAN" |
| -netdev "bridge,id=wan,br=$BR_WAN,helper=$HELPER" -device "e1000,id=devwan,netdev=wan,mac=$MAC_WAN" |
| ) |
| ;; |
| generic|64) |
| o_qemu_extra+=( |
| -netdev "bridge,id=lan,br=$BR_LAN,helper=$HELPER" -device "virtio-net-pci,id=devlan,netdev=lan,mac=$MAC_LAN" |
| -netdev "bridge,id=wan,br=$BR_WAN,helper=$HELPER" -device "virtio-net-pci,id=devwan,netdev=wan,mac=$MAC_WAN" |
| ) |
| ;; |
| esac |
| } |
| |
| case "${o_subtarget%-*}" in |
| legacy) |
| # use IDE (PATA) disk instead of AHCI (SATA). Refer to link |
| # [1] for related discussions |
| # |
| # To use AHCI interface |
| # |
| # -device ich9-ahci,id=ahci \ |
| # -device ide-hd,drive=drv0,bus=ahci.0 \ |
| # -drive "file=$rootfs,format=raw,id=drv0,if=none" \ |
| # |
| # [1] https://dev.openwrt.org/ticket/17947 |
| "$qemu_exe" -machine "$mach" -nographic \ |
| -device ide-hd,drive=drv0 \ |
| -drive "file=$rootfs,format=raw,id=drv0,if=none" \ |
| "${o_qemu_extra[@]}" |
| ;; |
| generic|64) |
| "$qemu_exe" -machine "$mach" -nographic \ |
| -drive "file=$rootfs,format=raw,if=virtio" \ |
| "${o_qemu_extra[@]}" |
| ;; |
| esac |
| } |
| |
| start_qemu() { |
| case "$o_target" in |
| armsr) start_qemu_armsr ;; |
| malta) start_qemu_malta ;; |
| x86) start_qemu_x86 ;; |
| *) |
| __errmsg "target $o_target is not supported yet" |
| return 1 |
| ;; |
| esac |
| } |
| |
| parse_args "$@" \ |
| && check_setup \ |
| && start_qemu |