OpenWrt – Rev 4

Subversion Repositories:
Rev:
#!/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() {
        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>]

<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 -enable-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 \\
                --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:" 2/1 "%02x:" 1/1 "%02x"' /dev/urandom
}

parse_args() {
        o_qemu_extra=()
        while [ "$#" -gt 0 ]; do
                case "$1" in
                        --kernel) o_kernel="$2"; shift 2 ;;
                        --rootfs) o_rootfs="$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 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" \
                )
        }

        "$qemu_exe" -machine virt -cpu "$cpu" -nographic \
                -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" \
                -kernel "$kernel" \
                "${o_qemu_extra[@]}"
}

start_qemu_malta() {
        local is64
        local isel
        local qemu_exe
        local kernel="$o_kernel"

        # 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"

        # 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
        "$qemu_exe" -machine malta -nographic \
                -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" \
                -kernel "$kernel" \
                "${o_qemu_extra[@]}"
}

start_qemu_x86() {
        local rootfs="$o_rootfs"
        local qemu_exe

        [ -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

        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" -nographic \
                                -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" \
                                -device ide-drive,drive=drv0 \
                                -drive "file=$rootfs,format=raw,id=drv0,if=none" \
                                "${o_qemu_extra[@]}"
                        ;;
                generic|64)
                        "$qemu_exe" -nographic \
                                -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" \
                                -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
}

check_setup \
        && parse_args "$@" \
        && start_qemu