1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
|
#!/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
[ -z "$o_user" ] || return 0
check_setup_ || {
__errmsg "please check the script content to see the environment requirement"
return 1
}
}
# Append user-mode networking args to o_qemu_extra. $1 is the qemu NIC device
# (e.g. virtio-net-pci, e1000). The LAN netdev shares OpenWrt's default LAN
# subnet so the guest's static 192.168.1.1 is reachable through SLIRP; the WAN
# netdev uses SLIRP defaults (10.0.2.0/24) to provide internet via the host.
add_user_netdev() {
local nic="$1"
local ssh="${o_ssh_port:-2222}"
local http="${o_http_port:-8080}"
local https="${o_https_port:-8443}"
__errmsg "user-mode networking: ssh on 127.0.0.1:$ssh, http on 127.0.0.1:$http, https on 127.0.0.1:$https"
local lan="user,id=lan,net=192.168.1.0/24,host=192.168.1.2"
lan+=",hostfwd=tcp:127.0.0.1:$ssh-192.168.1.1:22"
lan+=",hostfwd=tcp:127.0.0.1:$http-192.168.1.1:80"
lan+=",hostfwd=tcp:127.0.0.1:$https-192.168.1.1:443"
o_qemu_extra+=(
-netdev "$lan"
-device "$nic,id=devlan,netdev=lan,mac=$MAC_LAN"
-netdev "user,id=wan"
-device "$nic,id=devwan,netdev=wan,mac=$MAC_WAN"
)
}
#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]
[-u|--user-network [--ssh-port <port>] [--http-port <port>] [--https-port <port>]]
<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/
Networking modes:
-n / --network use bridges \$BR_LAN and \$BR_WAN (needs sudo + helper
setup)
-u / --user-network use rootless qemu user-mode networking; the guest LAN
interface (static 192.168.1.1) is reachable from the
host via 127.0.0.1:<ssh-port> (default 2222),
127.0.0.1:<http-port> (default 8080) and
127.0.0.1:<https-port> (default 8443); the guest WAN
gets internet via SLIRP
--ssh-port <p> override host-side ssh forward port (default 2222)
--http-port <p> override host-side http forward port (default 8080)
--https-port <p> override host-side https forward port (default 8443)
Examples
$SELF x86 64
$SELF x86 64 --user-network
$SELF x86 64 --user-network --ssh-port 2200 --http-port 8000 --https-port 8001
$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 armv8 --user-network
$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_user=
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 ;;
--user-network|-u) o_user=1; o_network=1; shift ;;
--ssh-port) o_ssh_port="$2"; shift 2 ;;
--http-port) o_http_port="$2"; shift 2 ;;
--https-port) o_https_port="$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"
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" ] || {
if [ -n "$o_user" ]; then
add_user_netdev virtio-net-pci
else
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" \
)
fi
}
"$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%-*}-generic-initramfs-kernel.bin"
[ -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 -device arguments matters: the first -device becomes eth0
# in the guest, which OpenWrt's 99-default_network assigns as LAN; the
# second becomes eth1 / WAN. Keep LAN first to match that mapping.
[ -z "$o_network" ] || {
if [ -n "$o_user" ]; then
add_user_netdev pcnet
else
o_qemu_extra+=(
-netdev bridge,id=lan,br="$BR_LAN,helper=$HELPER" -device pcnet,netdev=lan,mac="$MAC_LAN"
-netdev bridge,id=wan,br="$BR_WAN,helper=$HELPER" -device pcnet,netdev=wan,mac="$MAC_WAN"
)
fi
}
"$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)
if [ -n "$o_user" ]; then
add_user_netdev e1000
else
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"
)
fi
;;
generic|64)
if [ -n "$o_user" ]; then
add_user_netdev virtio-net-pci
else
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"
)
fi
;;
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
|