336 lines
8.5 KiB
Bash
Executable File
336 lines
8.5 KiB
Bash
Executable File
#!/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 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_network=
|
|
o_qemu_extra=()
|
|
while [ "$#" -gt 0 ]; do
|
|
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"
|
|
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" \
|
|
)
|
|
}
|
|
|
|
[ -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 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
|
|
[ -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" -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" \
|
|
)
|
|
}
|
|
|
|
[ -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-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
|