#!/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() { [ -z "$o_nonetwork" ] || 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 < [ []] [--kernel ] [--rootfs ] [--machine ] will default to "generic" and must be specified if are present e.g. for malta can be le, be, le64, be64, le-glibc, le64-glibc, etc , can be required or optional arguments to qemu depending on the actual 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 armvirt 32 \\ --machine virt,highmem=off \\ --kernel bin/targets/armvirt/32/openwrt-armvirt-32-zImage \\ --rootfs bin/targets/armvirt/32/openwrt-armvirt-32-root.ext4 EOF } rand_mac() { hexdump -n 3 -e '"52:54:00" 3/1 ":%02x"' /dev/urandom } parse_args() { o_qemu_extra=() while [ "$#" -gt 0 ]; do case "$1" in --no-network|-n) o_nonetwork=1; shift ;; --kernel) o_kernel="$2"; shift 2 ;; --rootfs) o_rootfs="$2"; shift 2 ;; --machine|-machine|-M) o_mach="$2"; shift 2 ;; --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" o_bindir="bin/targets/$o_target/$o_subtarget" } start_qemu_armvirt() { local kernel="$o_kernel" local rootfs="$o_rootfs" local mach="${o_mach:-virt}" local cpu local qemu_exe case "${o_subtarget%-*}" in 32) qemu_exe="qemu-system-arm" cpu="cortex-a15" [ -n "$kernel" ] || kernel="$o_bindir/openwrt-$o_target-${o_subtarget%-*}-zImage-initramfs" ;; 64) qemu_exe="qemu-system-aarch64" cpu="cortex-a57" [ -n "$kernel" ] || kernel="$o_bindir/openwrt-$o_target-${o_subtarget%-*}-Image-initramfs" ;; *) __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" \ ) } [ -n "$o_nonetwork" ] || { 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 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 "$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 [ -n "$o_nonetwork" ] || { o_qemu_extra+=( \ "-netdev" "bridge,id=wan,br=$BR_WAN,helper=$HELPER" "-device" \ "virtio-net-pci,id=devwan,netdev=wan,mac=$MAC_WAN" \ "-netdev" "bridge,id=lan,br=$BR_LAN,helper=$HELPER" \ "-device" "virtio-net-pci,id=devlan,netdev=lan,mac=$MAC_LAN" \ ) } "$qemu_exe" -machine "$mach" -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%-*}-combined-ext4.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" \ ) } [ -n "$o_nonetwork" ] || { 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" \ ) } 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-drive,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-drive,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 armvirt) start_qemu_armvirt ;; 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