1. #!/bin/bash
  2. # ref: http://vincent.bernat.im/en/blog/2011-uml-network-lab.html
  3. # Adopt to Archlinux by dlin.tw at gmail
  4. # user-mode-linux's config should modified
  5. #   CONFIG_UML_NET_VDE=y
  6. #   CONFIG_HOSTFS=y
  7. # arch packages: vde2 user-mode-linux screen start-stop-daemon rsyslog
  8.  
  9. LABNAME="2vm"
  10. # network layout: sw - C1-2
  11.  
  12. DEPS="screen ip vde_switch vmlinux bash rsyslogd"
  13. PROGNAME="$0"
  14. PROGARGS="$@"
  15.  
  16. # Check for dependencies needed by this tool
  17. check_dependencies() {
  18.   for dep in $DEPS; do
  19.     which $dep 2> /dev/null > /dev/null || {
  20.       echo "[!] Missing dependency: $dep"
  21.       exit 1
  22.     }
  23.   done
  24. }
  25.  
  26. # Run our lab in screen
  27. setup_screen() {
  28.   [ x"$TERM" = x"screen" ] || \
  29.     exec screen -ln -S $LABNAME -c /dev/null -t main "$PROGNAME" "$PROGARGS"
  30.   sleep 1
  31.   screen -X zombie cr
  32.   screen -X caption always "%{= wk}%-w%{= BW}%n %t%{-}%+w %-="
  33. }
  34.  
  35. # Setup a VDE switch
  36. setup_switch() {
  37.   echo "[+] Setup switch $1"
  38.   screen -t "sw-$1" \
  39.     start-stop-daemon --make-pidfile --pidfile "$TMP/switch-$1.pid" \
  40.     --start --startas $(which vde_switch) -- \
  41.     --sock "$TMP/switch-$1.sock"
  42.   screen -X select 0
  43. }
  44.  
  45. # Start a VM
  46. start_vm() {
  47.   echo "[+] Start VM $1"
  48.   name="$1"
  49.   shift
  50.   screen -t $name \
  51.   start-stop-daemon --make-pidfile --pidfile "$TMP/vm-$name.pid" \
  52.   --start --startas $(which vmlinux) -- \
  53.   uts=$name mem=64M \
  54.   root=/dev/root rootfstype=hostfs init=$(readlink -f "$PROGNAME") \
  55.   "$@"
  56.   screen -X select 0
  57. }
  58.  
  59. display_help() {
  60.     cat <<EOF
  61.  
  62. Some screen commands :
  63.  ^A ?     - display 'screen' help
  64.  ^A ^A    - type original ^A
  65.  ^A d     - Detach the screen (resume with screen -r $LABNAME)
  66.  ^A a "   - Select a window
  67.  ^A space - Next window
  68.  ^A ^A    - Toggle with Previous window
  69.  ^A [     - Copy mode, use PgUp/PgDn to scroll screen
  70. EOF
  71.   echo "Press enter to exit the lab - [$LABNAME]"
  72.   read a
  73. }
  74.  
  75. cleanup() {
  76.   for pid in $TMP/*.pid; do
  77.     kill $(cat $pid)
  78.   done
  79.   screen -X quit
  80. }
  81.  
  82. case $$ in
  83.   1)
  84.     # Inside UML. Three states:
  85.     #   1. Provide Job Control (equal to getty)
  86.     #   2. Setup AUFS
  87.     #   3. Remaining setup
  88.     STATE=${STATE:-1}
  89.  
  90.     case $STATE in
  91.       1)
  92.         echo "[+] Set hostname"
  93.         hostname ${uts}
  94.         echo "[+] Set path"
  95.         export TERM=xterm
  96.         export PATH=/usr/local/bin:/usr/bin:/bin:/sbin:/usr/local/sbin:/usr/sbin
  97.  
  98.         # Provide Job Control (^C)
  99.         export STATE=3 # FIXME:skip AUFS
  100.         exec setsid python -c '
  101. import os, sys
  102. os.close(0)
  103. os.open("/dev/tty0", os.O_RDWR | os.O_NOCTTY | os.O_NONBLOCK, 0)
  104. os.dup2(0, 1)
  105. os.dup2(0, 2)
  106. # os.tcsetpgrp(0, 1)
  107. os.execv(sys.argv[1], [sys.argv[1]])' "$PROGNAME"
  108.       ;;
  109.       2)
  110.         echo "[+] Setup AUFS"
  111.         mount -n -t proc proc /proc
  112.         mount -n -t sysfs sysfs /sys
  113.         mount -o bind /usr/lib/uml/modules /lib/modules
  114.         mount -n -t tmpfs tmpfs /tmp -o rw,nosuid,nodev
  115.         mkdir /tmp/ro
  116.         mkdir /tmp/rw
  117.         mkdir /tmp/aufs
  118.         mount -n -t hostfs hostfs /tmp/ro -o /,ro
  119.         mount -n -t aufs aufs /tmp/aufs -o noatime,dirs=/tmp/rw:/tmp/ro=ro
  120.  
  121.         # Chroot inside our new root
  122.         export STATE=3
  123.         exec chroot /tmp/aufs "$PROGNAME"
  124.       ;;
  125.     esac
  126.  
  127.     echo "[+] Set filesystems"
  128.     # FIXME for aufs: rm /etc/mtab
  129.     mount -t proc proc /proc
  130.     mount -t sysfs sysfs /sys
  131.     mount -t tmpfs tmpfs /dev -o rw && {
  132.       cd /dev
  133.       if [ -f $(dirname "$PROGNAME")/dev.tar ]; then
  134.         tar xf $(dirname "$PROGNAME")/dev.tar
  135.       else
  136.         MAKEDEV null consoleonly
  137.       fi
  138.     }
  139.     mount -o bind /usr/lib/uml/modules /lib/modules
  140.     for fs in /var/run /var/tmp /var/log /tmp; do
  141.       mount -t tmpfs tmpfs $fs -o rw,nosuid,nodev
  142.     done
  143.     mount -t hostfs hostfs $(dirname "$PROGNAME") -o $(dirname "$PROGNAME")
  144.  
  145.     # Interfaces
  146.     echo "[+] Set interfaces"
  147.     for intf in /sys/class/net/*; do
  148.       intf=$(basename $intf)
  149.       ip a l dev $intf 2> /dev/null >/dev/null && ip link set up dev $intf
  150.     done
  151.  
  152.     echo "[+] Start syslog"
  153.     rsyslogd
  154.  
  155.     cd $(dirname "$PROGNAME")
  156.     [ -f dev.tar ] || {
  157.       tar -C /dev -cf dev.tar.$uts . && mv dev.tar.$uts dev.tar
  158.     }
  159.  
  160.     # Configure each UML
  161.     echo "[+] Setup UML"
  162.     sysctl -w net.ipv4.ip_forward=1
  163.     case ${uts} in
  164.       C*)
  165.         n=${uts#C}  # strip prefix
  166.         ip="192.168.0.$n"
  167.         echo "IP=$ip"
  168.         ip addr add $ip/24 dev eth0
  169.       ;;
  170.     esac
  171.  
  172.     echo "[+] Drop to a shell"
  173.     exec /bin/bash
  174.  
  175.   ;;
  176.   *)
  177.     TMP=$(mktemp -d /tmp/net.XXX)
  178.     trap "rm -rf $TMP" EXIT
  179.     check_dependencies
  180.     setup_screen
  181.     echo "[+] clean up tmp:$TMP"
  182.  
  183.     # Setup switches
  184.     setup_switch sw1
  185.  
  186.     # Start VM
  187.     start_vm C1 eth0=vde,$TMP/switch-sw1.sock
  188.     start_vm C2 eth0=vde,$TMP/switch-sw1.sock
  189.     display_help
  190.     cleanup
  191.   ;;
  192. esac
  193.  
  194. # vi:set et sw=2 ts=2 sta: