labtunnel: added labtunnel installer, command, and readme

BUG=None
TEST=Installer script, all options, tunnel operations tested for
successful connections.

Change-Id: Ib50a2698cb964e8c83433a2e17f3f1d9b496eac7
Reviewed-on: https://chromium-review.googlesource.com/c/chromiumos/platform/crostestutils/+/3508556
Reviewed-by: danny chan <dchan@chromium.org>
Reviewed-by: Vinayak Suley <vsuley@chromium.org>
Tested-by: Jared Bennett <jaredbennett@google.com>
Reviewed-by: Shijin Abraham <shijinabraham@google.com>
Reviewed-by: Katherine Threlkeld <kathrelkeld@chromium.org>
Commit-Queue: Jared Bennett <jaredbennett@google.com>
diff --git a/.gitignore b/.gitignore
index 478c2b4..022dd54 100644
--- a/.gitignore
+++ b/.gitignore
@@ -2,3 +2,4 @@
 *.pyc
 *~
 *.swp
+.idea
diff --git a/provingground/labtunnel/OWNERS b/provingground/labtunnel/OWNERS
new file mode 100644
index 0000000..bd4cb8e
--- /dev/null
+++ b/provingground/labtunnel/OWNERS
@@ -0,0 +1,2 @@
+jaredbennett@google.com
+harpreet@google.com
diff --git a/provingground/labtunnel/README.md b/provingground/labtunnel/README.md
new file mode 100644
index 0000000..eafbc4d
--- /dev/null
+++ b/provingground/labtunnel/README.md
@@ -0,0 +1,158 @@
+# `labtunnel`
+The `labtunnel` bash script provides a CLI for commonly preformed tunneling
+related commands necessary for accessing and testing with lab devices. Are you
+tired of having to make ssh tunnels to DUTs so that you can run `tast`? Do you
+want to remote desktop into a DUT but do not want to manage all that is
+necessary to do that yourself every time? Use `labtunnel` today to do all that
+and more with a simple single command!
+
+## Configuration
+The `labtunnel` command is meant to be run on the workstation that you want to
+give access to lab devices to, and not inside a chroot. You can either run the
+bash script, `./labtunnel.sh` directly or add it to your path. The
+`./install_labtunnel.sh` bash script can be run to install it so that it may be
+run as a regular command, `labtunnel`.
+
+### install_labtunnel.sh help
+```text
+$ bash ~/chromiumos/src/platform/crostestutils/provingground/labtunnel/install_labtunnel.sh help
+Usage: install_labtunnel.sh [options]
+
+Options:
+ --dir|-d <path>    Path to directory where labtunnel is to be installed, which
+                    should be in your $PATH (default = '~/lib/depot_tools').
+```
+
+## Usage
+Run `labtunnel --help` to print its usage.
+
+## Examples
+The following examples show the normal usage for each supported tunnel operation.
+Once the tunneling is complete the expected behavior is that you keep the process
+running until you no longer need the tunnels. Each example process is stopped by
+sending SIGINT with ^C (ctr+c/cmd+c) to the terminal.
+
+### wificell
+```text
+$ labtunnel wificell chromeos1-dev-host1
+Creating SSH tunnel DUT: localhost:2200 -> chromeos1-dev-host1 -> localhost:22...
+Creating SSH tunnel router: localhost:2201 -> chromeos1-dev-host1-router -> localhost:22...
+Creating SSH tunnel pcap: localhost:2202 -> chromeos1-dev-host1-pcap -> localhost:22...
+Successfully created tunnels
+ DUT: localhost:2200 -> chromeos1-dev-host1 -> localhost:22
+ router: localhost:2201 -> chromeos1-dev-host1-router -> localhost:22
+ pcap: localhost:2202 -> chromeos1-dev-host1-pcap -> localhost:22
+
+Example tast call (in chroot):
+tast run -var=router=localhost:2201 -var=pcap=localhost:2202 localhost:2200 <test>
+
+To shut down tunnels and sub-processes, exit this process (pid=218974) with SIGHUP, SIGINT, or SIGQUIT
+^C
+Closing labtunnel...
+Killing child processes...
+```
+
+### callbox
+```text
+$ labtunnel --callbox chromeos1-donutlab-callbox1.cros callbox chromeos1-donutlab-callbox1-host1
+Creating SSH tunnel DUT: localhost:2200 -> chromeos1-donutlab-callbox1-host1 -> localhost:22...
+Creating SSH tunnel callboxManager: localhost:2201 -> access@chromeos1-proxy -> localhost:5000...
+Creating SSH tunnel callbox: localhost:5025 -> access@chromeos1-proxy -> chromeos1-donutlab-callbox1.cros:5025...
+Successfully created tunnels
+ DUT: localhost:2200 -> chromeos1-donutlab-callbox1-host1 -> localhost:22
+ callboxManager: localhost:2201 -> access@chromeos1-proxy -> localhost:5000
+ callbox: localhost:5025 -> access@chromeos1-proxy -> chromeos1-donutlab-callbox1.cros:5025
+
+Example tast call (in chroot):
+tast run -var=callbox=chromeos1-donutlab-callbox1.cros -var=callboxManager=localhost:2201 localhost:2200 <test>
+
+To shut down tunnels and sub-processes, exit this process (pid=219782) with SIGHUP, SIGINT, or SIGQUIT
+^C
+Closing labtunnel...
+Killing child processes...
+```
+
+### dut
+```text
+$ labtunnel dut chromeos1-dev-host1
+Creating SSH tunnel DUT: localhost:2200 -> chromeos1-dev-host1 -> localhost:22...
+Successfully created tunnels
+ DUT: localhost:2200 -> chromeos1-dev-host1 -> localhost:22
+
+To shut down tunnels and sub-processes, exit this process (pid=220249) with SIGHUP, SIGINT, or SIGQUIT
+^C
+Closing labtunnel...
+Killing child processes...
+```
+
+```text
+$ labtunnel dut crossk-chromeos1-dev-host1
+Creating SSH tunnel DUT: localhost:2200 -> chromeos1-dev-host1 -> localhost:22...
+Successfully created tunnels
+ DUT: localhost:2200 -> chromeos1-dev-host1 -> localhost:22
+
+To shut down tunnels and sub-processes, exit this process (pid=220579) with SIGHUP, SIGINT, or SIGQUIT
+^C
+Closing labtunnel...
+Killing child processes...
+```
+
+### dutvnc
+```text
+$ labtunnel dutvnc chromeos1-dev-host1
+Creating SSH tunnel DUT_VNC: localhost:5900 -> chromeos1-dev-host1 -> localhost:5900...
+Successfully created tunnels
+ DUT_VNC: localhost:5900 -> chromeos1-dev-host1 -> localhost:5900
+Starting kmsvnc on dut...
+Running 'kmsvnc' on 'chromeos1-dev-host1' in local tmux session 'labtunnel_tmux_ssh_1646703196'...
+Launching TigerVNC...
+
+To shut down tunnels and sub-processes, exit this process (pid=221658) with SIGHUP, SIGINT, or SIGQUIT
+
+TigerVNC Viewer 64-bit v1.11.0
+Built on: 2021-04-17 08:22
+Copyright (C) 1999-2020 TigerVNC Team and many others (see README.rst)
+See https://www.tigervnc.org for information on TigerVNC.
+^C
+Closing labtunnel...
+Closing tmux session 'labtunnel_tmux_ssh_1646703196'...
+Killing child processes...
+```
+
+```text
+$ labtunnel --do-not-open-vnc dutvnc chromeos1-dev-host1
+Creating SSH tunnel DUT_VNC: localhost:5900 -> chromeos1-dev-host1 -> localhost:5900...
+Successfully created tunnels
+ DUT_VNC: localhost:5900 -> chromeos1-dev-host1 -> localhost:5900
+Starting kmsvnc on dut...
+Running 'kmsvnc' on 'chromeos1-dev-host1' in local tmux session 'labtunnel_tmux_ssh_1646703939'...
+DUT VNC available at localhost:5900
+
+To shut down tunnels and sub-processes, exit this process (pid=225379) with SIGHUP, SIGINT, or SIGQUIT
+^C
+Closing labtunnel...
+Closing tmux session 'labtunnel_tmux_ssh_1646703939'...
+Killing child processes...
+```
+
+
+## Debugging
+The script is designed to clean itself up if something goes wrong in most cases,
+though there may be a few things can still go wrong.
+
+### Address in use from previous labtunnel call
+Normally when you stop labtunnel it shuts down any tunnels it created so subsequent
+can re-use ports, though if the process was killed its possible cleanup was not
+preformed. To fix this, find the running ssh tunnel processes (`ps -aux | grep ssh`)
+and kill them.
+
+### TigerVNC not installed
+The `dutvnc` tunnel operation uses the `xtigervncviewer` command provided by TigerVNC
+to open a local VNC client to the VNC server on the DUT. You can either install this
+or add the `--do-not-open-vnc` option and use your own client to connect to the
+hostname labtunnel prints out for use.
+
+If you chose to install TigerVNC and are on gLinux, you can install it like so:
+```text
+sudo apt install tigervnc-viewer'
+```
diff --git a/provingground/labtunnel/install_labtunnel.sh b/provingground/labtunnel/install_labtunnel.sh
new file mode 100644
index 0000000..2278233
--- /dev/null
+++ b/provingground/labtunnel/install_labtunnel.sh
@@ -0,0 +1,60 @@
+#!/bin/bash
+# Copyright 2022 The Chromium OS Authors. All rights reserved.
+# Use of this source code is governed by a BSD-style license that can be
+# found in the LICENSE file.
+
+INSTALL_DIR=~/lib/depot_tools
+function print_help {
+  cat <<END
+Usage: install_labtunnel.sh [options]
+
+Options:
+ --dir|-d <path>    Path to directory where labtunnel is to be installed, which
+                    should be in your \$PATH (default = '~/lib/depot_tools').
+END
+}
+
+# Parse args
+while [[ $# -gt 2 ]]; do
+  case $1 in
+    --dir|-d)
+      INSTALL_DIR="$2"
+      shift 2
+      ;;
+    *)
+      echo "Error: Invalid option '$1'"
+      print_help
+      exit 1
+      break
+      ;;
+  esac
+done
+if [ $# -eq 1 ]; then
+  if [ "$1" == "help" ]; then
+    print_help
+    exit
+  fi
+  echo "Error: Invalid option '$1'"
+  print_help
+  exit 1
+fi
+
+# Resolve path of bash script
+SCRIPT_DIR="$(dirname "$(realpath -e "${BASH_SOURCE[0]}")")"
+SCRIPT_PATH="${SCRIPT_DIR}/labtunnel.sh"
+if [ ! -f "${SCRIPT_PATH}" ]; then
+  echo "Error: Failed to resolve path to ./labtunnel.sh"
+  exit 1
+fi
+
+# Create a link to the bash script in the install dir
+INSTALL_PATH="${INSTALL_DIR}/labtunnel"
+if [ -L "${INSTALL_PATH}" ]; then
+  unlink "${INSTALL_PATH}"
+fi
+
+set -e
+ln -s "${SCRIPT_PATH}" "${INSTALL_PATH}"
+chmod +x "${INSTALL_PATH}"
+
+echo "Successfully installed labtunnel to '${INSTALL_PATH}'"
diff --git a/provingground/labtunnel/labtunnel.sh b/provingground/labtunnel/labtunnel.sh
new file mode 100755
index 0000000..e8afc94
--- /dev/null
+++ b/provingground/labtunnel/labtunnel.sh
@@ -0,0 +1,349 @@
+#!/bin/bash
+# Copyright 2022 The Chromium OS Authors. All rights reserved.
+# Use of this source code is governed by a BSD-style license that can be
+# found in the LICENSE file.
+
+SCRIPT_DIR="$(dirname "$(realpath -e "${BASH_SOURCE[0]}")")"
+function print_help {
+  cat "${SCRIPT_DIR}/labtunnel_usage.txt"
+  echo ''
+  cat <<END
+  For more details and examples, view the README at "${SCRIPT_DIR}/README.md".
+END
+}
+
+if [ $# -lt 2 ]; then
+  print_help
+  exit 1
+fi
+
+# Parse args and options
+START_PORT="2200"
+HOST_PROXY="access@chromeos1-proxy"
+HOST_DUT=""
+HOST_PCAP=""
+HOST_CALLBOX=""
+HOST_ROUTER=""
+NO_PCAP=0
+TUNNEL_TYPE=""
+HOST_PARAM=""
+DO_NOT_OPEN_VNC=0
+DO_LEASE=0
+LEASE_OPTIONS=(
+  "--reason=\"manual_testing\""
+  "--minutes=100"
+)
+LEASE_RETAIN=0
+SSH_OPTIONS=(
+  "-o StrictHostKeyChecking=no"
+  "-o ExitOnForwardFailure=yes"
+  "-o UserKnownHostsFile=/dev/null"
+  "-o LogLevel=ERROR"
+)
+while [[ $# -gt 2 ]]; do
+  case $1 in
+    --help|help|-h)
+      print_help
+      exit
+      ;;
+    --port|-p)
+      START_PORT="$2"
+      shift 2
+      ;;
+    --dut)
+      HOST_DUT="$2"
+      HOST_PARAM="$2"
+      shift 2
+      ;;
+    --pcap)
+      HOST_PCAP="$2"
+      shift 2
+      ;;
+    --no-pcap)
+      NO_PCAP=1
+      shift 1
+      ;;
+    --router)
+      HOST_ROUTER="$2"
+      shift 2
+      ;;
+    --callbox)
+      HOST_CALLBOX="$2"
+      shift 2
+      ;;
+    --proxy)
+      HOST_PROXY="$2"
+      shift 2
+      ;;
+    --do-not-open-vnc)
+      DO_NOT_OPEN_VNC=1
+      shift 1
+      ;;
+    --lease)
+      DO_LEASE=1
+      shift 1
+      ;;
+    --lease-retain)
+      LEASE_RETAIN=1
+      shift 1
+      ;;
+    --lease-option)
+      LEASE_OPTIONS+=("$2")
+      shift 2
+      ;;
+    --lease-options)
+      IFS=' ' read -r -a LEASE_OPTIONS <<< "$2"
+      shift 2
+      ;;
+    --ssh-option)
+      SSH_OPTIONS+=("$2")
+      shift 2
+      ;;
+    --ssh-options)
+      IFS=' ' read -r -a SSH_OPTIONS <<< "$2"
+      shift 2
+      ;;
+    *)
+      echo "Error: Invalid option '$1'"
+      print_help
+      exit 1
+      break
+      ;;
+  esac
+done
+if [ "${HOST_PARAM}" == "" ]; then
+  if [ ! $# -eq 2 ]; then
+    echo "Error: Invalid params"
+    print_help
+    exit 1
+  fi
+  TUNNEL_TYPE="$1"
+  HOST_PARAM="$2"
+else
+  if [ ! $# -eq 1 ]; then
+    echo "Error: Invalid params"
+    print_help
+    exit 1
+  fi
+  TUNNEL_TYPE="$1"
+fi
+if [ "${TUNNEL_TYPE}" == "dutvnc" ] && [ "${DO_NOT_OPEN_VNC}" -eq 0 ] \
+&& ! command -v xtigervncviewer &> /dev/null; then
+  cat <<END
+Error: Required xtigervncviewer not found
+
+You can install TigerVNC on gLinux with 'sudo apt install tigervnc-viewer'
+END
+  exit 1
+fi
+
+# Resolve undefined hosts where possible
+function resolve_hostname {
+  local RESOLVED_NAME="$1"
+  local SUFFIX_TO_ADD="$2"
+
+  # Convert crosfleet name to host name by removing prefix
+  if [ "${RESOLVED_NAME:0:7}" = "crossk-" ]; then
+    local RESOLVED_NAME=${RESOLVED_NAME:7}
+  fi
+
+  # Add suffix, keeping any existing '.cros' suffix
+  if [ "${SUFFIX_TO_ADD}" !=  "" ]; then
+    if [ "${RESOLVED_NAME::-5}" == ".cros" ]; then
+      local RESOLVED_NAME="${RESOLVED_NAME:0:-5}${SUFFIX_TO_ADD}.cros"
+    else
+      local RESOLVED_NAME="${RESOLVED_NAME}${SUFFIX_TO_ADD}"
+    fi
+  fi
+
+  echo -n "${RESOLVED_NAME}"
+}
+
+function resolve_host_params {
+  if [ "${HOST_PARAM}" != "" ]; then
+    if [ "${HOST_DUT}" == "" ]; then
+      HOST_DUT=$(resolve_hostname "${HOST_PARAM}")
+    fi
+    if [ "${HOST_PCAP}" == "" ]; then
+      HOST_PCAP=$(resolve_hostname "${HOST_DUT}" "-pcap")
+    fi
+    if [ "${HOST_ROUTER}" == "" ]; then
+      HOST_ROUTER=$(resolve_hostname "${HOST_DUT}" "-router")
+    fi
+  fi
+}
+resolve_host_params
+
+LEASED_DUT=""
+function handle_dut_lease_start {
+  if [ "${DO_LEASE}" -eq 1 ]; then
+    LEASED_DUT="${HOST_PARAM}"
+    echo "Leasing DUT '${LEASED_DUT}' with options '${LEASE_OPTIONS[*]}'..."
+    local LEASE_OUTPUT
+    LEASE_OUTPUT=$(crosfleet dut lease "${LEASE_OPTIONS[@]}" \
+    --host="${LEASED_DUT}" 2>&1)
+    echo "${LEASE_OUTPUT}"
+    local MSG='Found 1 DUT(s) (0 busy) matching the provided DUT dimensions'
+    if echo "${LEASE_OUTPUT}" | grep -q "${MSG}"; then
+      echo "Successfully leased DUT '${LEASED_DUT}'"
+    else
+      echo "Failed to lease DUT '${LEASED_DUT}'"
+      kill_children_and_exit
+    fi
+  fi
+}
+function handle_dut_lease_stop {
+  if [ "${LEASED_DUT}" != "" ]; then
+    if [ "${LEASE_RETAIN}" -eq 1 ]; then
+      echo "Reminder: crosfleet dut '${LEASED_DUT}' is still leased"
+    else
+      echo "Abandoning leased bot '${LEASED_DUT}'..."
+      crosfleet dut abandon "${LEASED_DUT}"
+    fi
+  fi
+}
+
+PIDS=()
+TMUX_SESSIONS=()
+function kill_children {
+  echo -e "\nClosing labtunnel..."
+  for SNAME in "${TMUX_SESSIONS[@]}"; do
+    if tmux has-session -t "${SNAME}" 2>/dev/null; then
+      echo "Closing tmux session '${SNAME}'..."
+      tmux send-keys -t "${SNAME}" C-c "exit" ENTER
+      sleep 1s
+      tmux kill-session -t "${SNAME}" 2>/dev/null
+    fi
+  done
+  echo -e "Killing child processes..."
+  for PID in "${PIDS[@]}"; do
+    if [ -d "/proc/${PID}" ]; then
+      pkill --signal SIGQUIT -P "${PID}"
+    fi
+  done
+  handle_dut_lease_stop
+}
+function kill_children_and_exit {
+  trap - SIGINT SIGQUIT SIGHUP
+  kill_children
+  exit
+}
+
+TUNNEL_SUMMARY="Successfully created tunnels"
+LAST_TUNNEL_ENDPOINT=""
+function make_tunnel {
+  PIDS+=("$$")
+  local TUNNEL_NAME=$1
+  local REMOTE_HOST=$2
+  local LOCAL_PORT=$3
+  local HOST=$4
+  local REMOTE_PORT=$5
+  local LOCAL_ENDPOINT="localhost:${LOCAL_PORT}"
+  local REMOTE_ENDPOINT="${REMOTE_HOST}:${REMOTE_PORT}"
+  local TUNNEL_DESC="${TUNNEL_NAME}: ${LOCAL_ENDPOINT} ->"
+  TUNNEL_DESC="${TUNNEL_DESC} ${HOST} -> ${REMOTE_ENDPOINT}"
+  echo "Creating SSH tunnel ${TUNNEL_DESC}..."
+  local TUNNEL_PATH="${LOCAL_PORT}:${REMOTE_HOST}:${REMOTE_PORT}"
+  ssh "${SSH_OPTIONS[@]}" -fNL "${TUNNEL_PATH}" "${HOST}"
+  local ERROR_CODE=$?
+  if [ ! "${ERROR_CODE}" -eq 0 ]; then
+    echo "Failed to create SSH tunnel ${TUNNEL_DESC} (error=${ERROR_CODE})."
+    echo "Please address the ssh error and try again."
+    kill_children_and_exit
+  fi
+  TUNNEL_SUMMARY="${TUNNEL_SUMMARY}\n ${TUNNEL_DESC}"
+  LAST_TUNNEL_ENDPOINT=${LOCAL_ENDPOINT}
+}
+
+TMUX_SESSION_NAME=""
+function ssh_run_cmd {
+  PIDS+=("$$")
+  local HOST=$1
+  local CMD=$2
+  TMUX_SESSION_NAME="labtunnel_tmux_ssh_$(date +%s)"
+  TMUX_SESSIONS+=("${TMUX_SESSION_NAME}")
+
+  echo "Running '${CMD}' on '${HOST}' in local tmux session '" \
+  "${TMUX_SESSION_NAME}'..."
+
+  local SSH_CMD=("ssh" "${SSH_OPTIONS[@]}" "${HOST}")
+  tmux new-session -d -s "${TMUX_SESSION_NAME}" "${SSH_CMD[@]}"
+  tmux send-keys -t "${TMUX_SESSION_NAME}" "${CMD}" ENTER
+  tmux send-keys -t "${TMUX_SESSION_NAME}" "exit" ENTER
+}
+
+trap kill_children_and_exit SIGINT SIGQUIT SIGHUP
+
+case ${TUNNEL_TYPE} in
+  wificell)
+    handle_dut_lease_start
+    make_tunnel "DUT" "localhost" "${START_PORT}" "${HOST_DUT}" 22
+    DUT=${LAST_TUNNEL_ENDPOINT}
+    make_tunnel "router" "localhost" $((START_PORT+1)) "${HOST_ROUTER}" 22
+    ROUTER=${LAST_TUNNEL_ENDPOINT}
+    if [ "${NO_PCAP}" -eq 0 ]; then
+        make_tunnel "pcap" "localhost" $((START_PORT+2)) "${HOST_PCAP}" 22
+        PCAP=${LAST_TUNNEL_ENDPOINT}
+    fi
+    echo -e "${TUNNEL_SUMMARY}"
+    echo -e "\nExample tast call (in chroot):"
+    if [ "${NO_PCAP}" -eq 0 ]; then
+      echo "tast run -var=router=${ROUTER} -var=pcap=${PCAP} ${DUT} <test>"
+    else
+      echo "tast run -var=router=${ROUTER} ${DUT} <test>"
+    fi
+    ;;
+  callbox)
+    if [ "${HOST_CALLBOX}" = "" ]; then
+      echo "Error: --callbox param required for callbox tunnels"
+      print_help
+      exit 1
+    fi
+    handle_dut_lease_start
+    make_tunnel "DUT" "localhost" "${START_PORT}" "${HOST_DUT}" 22
+    DUT=${LAST_TUNNEL_ENDPOINT}
+    make_tunnel "callboxManager" "localhost" $((START_PORT+1)) \
+    "${HOST_PROXY}" 5000
+    CALLBOX_MANAGER=${LAST_TUNNEL_ENDPOINT}
+    make_tunnel "callbox" "${HOST_CALLBOX}" 5025 "${HOST_PROXY}" 5025
+    echo -e "${TUNNEL_SUMMARY}"
+    echo -e "\nExample tast call (in chroot):"
+    echo "tast run -var=callbox=${HOST_CALLBOX} " \
+    "-var=callboxManager=${CALLBOX_MANAGER} ${DUT} <test>"
+    ;;
+  dut)
+    handle_dut_lease_start
+    make_tunnel "DUT" "localhost" "${START_PORT}" "${HOST_DUT}" 22
+    DUT=${LAST_TUNNEL_ENDPOINT}
+    echo -e "${TUNNEL_SUMMARY}"
+    echo -e "\nExample tast call (in chroot):"
+    echo "tast run ${DUT} <test>"
+    ;;
+  dutvnc)
+    handle_dut_lease_start
+    make_tunnel "DUT_VNC" "localhost" 5900 "${HOST_DUT}" 5900
+    echo -e "${TUNNEL_SUMMARY}"
+    echo "Starting kmsvnc on dut..."
+    ssh_run_cmd "${HOST_DUT}" "kmsvnc"
+    sleep 5s
+    if ! tmux has-session -t "${TMUX_SESSION_NAME}" 2>/dev/null; then
+      echo "Failed to start kmsvnc on dut"
+      kill_children_and_exit
+    fi
+    if [ "${DO_NOT_OPEN_VNC}" -eq 0 ]; then
+      echo "Launching TigerVNC..."
+      xtigervncviewer localhost:5900 -Log "*:stderr:0" &
+    else
+      echo "DUT VNC available at localhost:5900"
+    fi
+    ;;
+  *)
+    echo "Error: Invalid tunnel type '${TUNNEL_TYPE}'"
+    print_help
+    exit 1
+    ;;
+esac
+
+echo -e "\nTo shut down tunnels and sub-processes, exit this process " \
+"(pid=$$) with SIGHUP, SIGINT, or SIGQUIT"
+sleep infinity
diff --git a/provingground/labtunnel/labtunnel_usage.txt b/provingground/labtunnel/labtunnel_usage.txt
new file mode 100644
index 0000000..cf41594
--- /dev/null
+++ b/provingground/labtunnel/labtunnel_usage.txt
@@ -0,0 +1,111 @@
+Usage: labtunnel [options] <tunnel_operation> <dut_host>
+
+  Supported tunnel_operation values:
+
+    wificell        Opens an ssh tunnel for port 22 to the dut, pcap, and router
+                    of a wificell.
+
+                      - The dut hostname is resolved from <dut_host> by removing
+                        the prefix "crossk-" if it is present. This can be
+                        overridden with the --dut option.
+
+                      - The pcap hostname is dut hostname plus the "-pcap"
+                        suffix. This can be overridden with the --pcap option.
+                        If the dut hostname ends with ".cros", the pcap hostname
+                        will end with "-pcap.cros". To skip the creation of
+                        the pcap tunnel, use the --no-pcap option.
+
+                      - The router hostname is dut hostname plus the "-router"
+                        suffix. This can be overridden with the --router option.
+                        If the dut hostname ends with ".cros", the router
+                        hostname will end with "-router.cros".
+
+                      - All tunnels are destroyed upon stopping this script.
+
+
+    callbox         Opens ssh tunnels for dut ssh, the callbox manager on a
+                    proxy server, and the specified callbox through the proxy
+                    server.
+
+                      - The dut tunnel is created in the same manner as with the
+                        wificell tunnel_operation.
+
+                      - The callbox manager tunnel is made to port 5000 on the
+                        proxy server.
+
+                      - The proxy server location defaults to
+                        "access@chromeos1-proxy" and can be overridden with the
+                        --proxy option.
+
+                      - The callbox tunnel is made to <callbox>:5025 on the
+                        proxy server, as the callboxes do not support SSH. Use
+                        the --callbox option to specify the callbox host.
+
+                      - All tunnels are destroyed upon stopping this script.
+
+
+    dut             Opens an ssh tunnel for port 22 to the dut.
+
+                      - The dut tunnel is created in the same manner as with the
+                        wificell tunnel_operation.
+
+                      - All tunnels are destroyed upon stopping this script.
+
+
+    dutvnc          Starts kmsvnc on the dut via ssh, opens a tunnel to it on
+                    port 5900, and connects to it using TigerVNC.
+
+                      - The kmsvnc process on the dut and TigerVNC client on
+                        this machine are stopped and all tunnels are destroyed
+                        upon stopping this script.
+
+                      - To use a different VNC client other than TigerVNC, use
+                        the --do-not-open-vnc option to skip connecting to the
+                        VNC server with TigerVNC and then connect to
+                        localhost:5900 with your preferred VNC client.
+
+
+  Supported options:
+   --help|help|-h             Prints this help text and exists.
+
+   --port|-p <port>           Sets the start port number to <port> for ssh
+                              tunnels to remote port 22. Defaults to 2200.
+
+   --dut <host>               Sets the DUT hostname to <host> and does not
+                              resolve it any further like how <dut_host> is
+                              resolved.
+
+   --pcap <host>              Sets the PCAP hostname to <host>. If unset, it is
+                              resolved from the dut hostname.
+
+   --router <host>            Sets the router hostname to <host>. If unset, it
+                              is resolved from the dut hostname.
+
+   --no-pcap                  Skips pcap tunnel creation during the wificell
+                              tunnel_operation.
+
+   --callbox <host>           Sets the callbox hostname to <host>. This host
+                              must be resolvable by the proxy server. Required
+                              for callbox tunnel_operation.
+
+   --do-not-open-vnc          Skips opening the TigerVNC client for
+                              tunnel_operation dutvnc.
+
+   --lease                    The <dut_host> will be leased with crosfleet prior
+                              to preforming any other operation. The lease is
+                              abandoned upon stopping this script.
+
+   --lease-retain             Prevents the script from abandoning the lease
+                              created with --lease upon closing.
+
+   --lease-option <option>    Adds the CLI option <option> to the crosfleet dut
+                              lease call with --lease. Can be repeated.
+
+   --lease-options <options>  Sets all the CLI options to <options> to the
+                              crosfleet dut lease call with --lease.
+
+   --ssh-option <option>      Adds the CLI option <option> to the options used
+                              in all ssh commands. Can be repeated.
+
+   --ssh-options <options>    Sets all the CLI options to <options> used for all
+                              ssh commands.