Index: /trunk/Config.kmk
===================================================================
--- /trunk/Config.kmk	(revision 85358)
+++ /trunk/Config.kmk	(revision 85359)
@@ -1276,6 +1276,4 @@
   VBOX_WITH_AUDIO_RECORDING=
  endif
- # Disable cloud network support
- VBOX_WITH_CLOUD_NET=
  # Disable building and packing vboximg-mount.
  VBOX_WITH_VBOXIMGMOUNT=
Index: /trunk/src/VBox/Frontends/VBoxManage/VBoxManageCloud.cpp
===================================================================
--- /trunk/src/VBox/Frontends/VBoxManage/VBoxManageCloud.cpp	(revision 85358)
+++ /trunk/src/VBox/Frontends/VBoxManage/VBoxManageCloud.cpp	(revision 85359)
@@ -2112,5 +2112,5 @@
 #ifndef VBOX_WITH_PROXY_INFO
     RT_NOREF(strUrl, strProxy);
-    LogRel(("OCI-NET: Proxy support is disabled. Using direct connection.\n"));
+    LogRel(("CLOUD-NET: Proxy support is disabled. Using direct connection.\n"));
     return false;
 #else /* VBOX_WITH_PROXY_INFO */
@@ -2119,5 +2119,5 @@
     if (RT_FAILURE(rc))
     {
-        LogRel(("OCI-NET: Failed to create HTTP context (rc=%d)\n", rc));
+        LogRel(("CLOUD-NET: Failed to create HTTP context (rc=%d)\n", rc));
         return false;
     }
@@ -2125,5 +2125,5 @@
     if (RT_FAILURE(rc))
     {
-        LogRel(("OCI-NET: Failed to use system proxy (rc=%d)\n", rc));
+        LogRel(("CLOUD-NET: Failed to use system proxy (rc=%d)\n", rc));
         RTHttpDestroy(hHttp);
         return false;
@@ -2135,5 +2135,5 @@
     if (RT_FAILURE(rc))
     {
-        LogRel(("OCI-NET: Failed to get proxy for %s (rc=%d)\n", strUrl.c_str(), rc));
+        LogRel(("CLOUD-NET: Failed to get proxy for %s (rc=%d)\n", strUrl.c_str(), rc));
         RTHttpDestroy(hHttp);
         return false;
@@ -2155,5 +2155,5 @@
             break;
         case RTHTTPPROXYTYPE_UNKNOWN:
-            LogRel(("OCI-NET: Unknown proxy type. Using direct connecton."));
+            LogRel(("CLOUD-NET: Unknown proxy type. Using direct connecton."));
             RTHttpDestroy(hHttp);
             return false;
Index: /trunk/src/VBox/Main/UnattendedTemplates/lgw_ks.cfg
===================================================================
--- /trunk/src/VBox/Main/UnattendedTemplates/lgw_ks.cfg	(revision 85359)
+++ /trunk/src/VBox/Main/UnattendedTemplates/lgw_ks.cfg	(revision 85359)
@@ -0,0 +1,91 @@
+#platform=x86, AMD64, or Intel EM64T
+#version=DEVEL
+
+# Firewall configuration
+firewall --disabled
+
+# Install OS instead of upgrade
+install
+
+# Use DVD
+cdrom
+
+# Root password
+rootpw --plaintext @@VBOX_INSERT_ROOT_PASSWORD_SH@@
+
+# System authorization information
+auth  --useshadow  --passalgo=sha512
+
+# Use text mode install
+text
+
+# System keyboard
+keyboard us
+
+# System language
+lang @@VBOX_INSERT_LOCALE@@
+
+# Disable the unsupported hardware popup (vmmdev?).
+unsupported_hardware
+
+# SELinux configuration
+# selinux --enforcing
+
+# Installation logging level
+logging --level=info
+
+# System timezone
+timezone@@VBOX_COND_IS_RTC_USING_UTC@@ --utc@@VBOX_COND_END@@ @@VBOX_INSERT_TIME_ZONE_UX@@
+
+# Network information
+network  --bootproto=dhcp --device=enp0s3 --onboot=on --hostname=@@VBOX_INSERT_HOSTNAME_FQDN_SH@@
+
+# System bootloader configuration
+bootloader --location=mbr --append="nomodeset crashkernel=auto rhgb quiet"
+zerombr
+
+# Partition clearing information
+clearpart --all --initlabel
+
+# Disk partitioning information
+part / --fstype ext4 --size 6000 --grow --asprimary
+part swap --size 1024
+
+#Initial user
+user --name=@@VBOX_INSERT_USER_LOGIN_SH@@ --password=@@VBOX_INSERT_USER_PASSWORD_SH@@
+
+# Shut down after installation
+poweroff
+
+# Packages.  We currently ignore missing packages/groups here to keep things simpler.
+%packages --ignoremissing
+@base
+@core
+
+# Prepare building the additions kernel module, try get what we can from the cdrom as it may be impossible
+# to install anything from the post script:
+kernel-headers
+kernel-devel
+glibc-devel
+glibc-headers
+gcc
+dkms
+make
+bzip2
+perl
+
+%end
+
+# Post install happens in a different script.
+# Note! We mount the CDROM explictily here since the location differs between fedora 26 to rhel5
+#       and apparently there isn't any way to be certain that anaconda didn't unmount it already.
+%post --nochroot --log=/mnt/sysimage/root/ks-post.log
+df -h
+mkdir -p /tmp/vboxcdrom
+mount /dev/cdrom /tmp/vboxcdrom
+cp /tmp/vboxcdrom/vboxpostinstall.sh /mnt/sysimage/root/vboxpostinstall.sh
+chmod a+x /mnt/sysimage/root/vboxpostinstall.sh
+/bin/bash /mnt/sysimage/root/vboxpostinstall.sh --rhel
+umount /tmp/vboxcdrom
+%end
+
Index: /trunk/src/VBox/Main/UnattendedTemplates/lgw_postinstall.sh
===================================================================
--- /trunk/src/VBox/Main/UnattendedTemplates/lgw_postinstall.sh	(revision 85359)
+++ /trunk/src/VBox/Main/UnattendedTemplates/lgw_postinstall.sh	(revision 85359)
@@ -0,0 +1,514 @@
+#!/bin/bash
+## @file
+# Post installation script template for local gateway image.
+#
+# Note! This script expects to be running chrooted (inside new sytem).
+#
+
+#
+# Copyright (C) 2020 Oracle Corporation
+#
+# This file is part of VirtualBox Open Source Edition (OSE), as
+# available from http://www.virtualbox.org. This file is free software;
+# you can redistribute it and/or modify it under the terms of the GNU
+# General Public License (GPL) as published by the Free Software
+# Foundation, in version 2 as it comes in the "COPYING" file of the
+# VirtualBox OSE distribution. VirtualBox OSE is distributed in the
+# hope that it will be useful, but WITHOUT ANY WARRANTY of any kind.
+#
+
+
+#
+# Globals.
+#
+MY_TARGET="/mnt/sysimage"
+MY_LOGFILE="${MY_TARGET}/var/log/vboxpostinstall.log"
+MY_CHROOT_CDROM="/cdrom"
+MY_CDROM_NOCHROOT="/tmp/vboxcdrom"
+MY_EXITCODE=0
+MY_DEBUG="" # "yes"
+
+@@VBOX_COND_HAS_PROXY@@
+PROXY="@@VBOX_INSERT_PROXY@@"
+export http_proxy="${PROXY}"
+export https_proxy="${PROXY}"
+echo "HTTP proxy is ${http_proxy}" | tee -a "${MY_LOGFILE}"
+echo "HTTPS proxy is ${https_proxy}" | tee -a "${MY_LOGFILE}"
+@@VBOX_COND_END@@
+
+#
+# Do we need to exec using target bash?  If so, we must do that early
+# or ash will bark 'bad substitution' and fail.
+#
+if [ "$1" = "--need-target-bash" ]; then
+    # Try figure out which directories we might need in the library path.
+    if [ -z "${LD_LIBRARY_PATH}" ]; then
+        LD_LIBRARY_PATH="${MY_TARGET}/lib"
+    fi
+    for x in \
+        ${MY_TARGET}/lib \
+        ${MY_TARGET}/usr/lib \
+        ${MY_TARGET}/lib/*linux-gnu/ \
+        ${MY_TARGET}/lib32/ \
+        ${MY_TARGET}/lib64/ \
+        ${MY_TARGET}/usr/lib/*linux-gnu/ \
+        ${MY_TARGET}/usr/lib32/ \
+        ${MY_TARGET}/usr/lib64/ \
+        ;
+    do
+        if [ -e "$x" ]; then LD_LIBRARY_PATH="${LD_LIBRARY_PATH}:${x}"; fi;
+    done
+    export LD_LIBRARY_PATH
+
+    # Append target bin directories to the PATH as busybox may not have tee.
+    PATH="${PATH}:${MY_TARGET}/bin:${MY_TARGET}/usr/bin:${MY_TARGET}/sbin:${MY_TARGET}/usr/sbin"
+    export PATH
+
+    # Drop the --need-target-bash argument and re-exec.
+    shift
+    echo "******************************************************************************" >> "${MY_LOGFILE}"
+    echo "** Relaunching using ${MY_TARGET}/bin/bash $0 $*" >> "${MY_LOGFILE}"
+    echo "**   LD_LIBRARY_PATH=${LD_LIBRARY_PATH}" >> "${MY_LOGFILE}"
+    echo "**              PATH=${PATH}" >> "${MY_LOGFILE}"
+    exec "${MY_TARGET}/bin/bash" "$0" "$@"
+fi
+
+
+#
+# Commands.
+#
+
+# Logs execution of a command.
+log_command()
+{
+    echo "--------------------------------------------------" >> "${MY_LOGFILE}"
+    echo "** Date:      `date -R`" >> "${MY_LOGFILE}"
+    echo "** Executing: $*" >> "${MY_LOGFILE}"
+    "$@" 2>&1 | tee -a "${MY_LOGFILE}"
+    MY_TMP_EXITCODE="${PIPESTATUS[0]}"      # bashism - whatever.
+    if [ "${MY_TMP_EXITCODE}" != "0" ]; then
+        if [ "${MY_TMP_EXITCODE}" != "${MY_IGNORE_EXITCODE}" ]; then
+            echo "** exit code: ${MY_TMP_EXITCODE}" | tee -a "${MY_LOGFILE}"
+            MY_EXITCODE=1;
+        else
+            echo "** exit code: ${MY_TMP_EXITCODE} (ignored)" | tee -a "${MY_LOGFILE}"
+        fi
+    fi
+}
+
+# Logs execution of a command inside the target.
+log_command_in_target()
+{
+    log_command chroot "${MY_TARGET}" "$@"
+}
+
+# Checks if $1 is a command on the PATH inside the target jail.
+chroot_which()
+{
+    for dir in /bin /usr/bin /sbin /usr/sbin;
+    do
+        if [ -x "${MY_TARGET}${dir}/$1" ]; then
+            return 0;
+        fi
+    done
+    return 1;
+}
+
+#
+# Log header.
+#
+echo "******************************************************************************" >> "${MY_LOGFILE}"
+echo "** VirtualBox Unattended Guest Installation - Late installation actions" >> "${MY_LOGFILE}"
+echo "** Date:    `date -R`" >> "${MY_LOGFILE}"
+echo "** Started: $0 $*" >> "${MY_LOGFILE}"
+
+
+#
+# We want the ISO available inside the target jail.
+#
+if [ -d "${MY_TARGET}${MY_CHROOT_CDROM}" ]; then
+    MY_RMDIR_TARGET_CDROM=
+else
+    MY_RMDIR_TARGET_CDROM="yes"
+    log_command mkdir -p ${MY_TARGET}${MY_CHROOT_CDROM}
+fi
+
+if [ -f "${MY_TARGET}${MY_CHROOT_CDROM}/vboxpostinstall.sh" ]; then
+    MY_UNMOUNT_TARGET_CDROM=
+    echo "** binding cdrom into jail: already done" | tee -a "${MY_LOGFILE}"
+else
+    MY_UNMOUNT_TARGET_CDROM="yes"
+    log_command mount -o bind "${MY_CDROM_NOCHROOT}" "${MY_TARGET}${MY_CHROOT_CDROM}"
+    if [ -f "${MY_TARGET}${MY_CHROOT_CDROM}/vboxpostinstall.sh" ]; then
+        echo "** binding cdrom into jail: success"  | tee -a "${MY_LOGFILE}"
+    else
+        echo "** binding cdrom into jail: failed"   | tee -a "${MY_LOGFILE}"
+    fi
+    if [ "${MY_DEBUG}" = "yes" ]; then
+        log_command find "${MY_TARGET}${MY_CHROOT_CDROM}"
+    fi
+fi
+
+
+#
+# Debug
+#
+if [ "${MY_DEBUG}" = "yes" ]; then
+    log_command id
+    log_command ps
+    log_command ps auxwwwf
+    log_command env
+    log_command df
+    log_command mount
+    log_command_in_target df
+    log_command_in_target mount
+    #log_command find /
+    MY_EXITCODE=0
+fi
+
+
+#
+# Proxy hack for yum
+#
+@@VBOX_COND_HAS_PROXY@@
+echo "" >> "${MY_TARGET}/etc/yum.conf"
+echo "proxy=@@VBOX_INSERT_PROXY@@" >> "${MY_TARGET}/etc/yum.conf"
+@@VBOX_COND_END@@
+
+#
+# Packages needed for GAs.
+#
+echo "--------------------------------------------------" >> "${MY_LOGFILE}"
+echo '** Installing packages for building kernel modules...' | tee -a "${MY_LOGFILE}"
+log_command_in_target yum -y install "kernel-devel-$(uname -r)"
+log_command_in_target yum -y install "kernel-headers-$(uname -r)"
+log_command_in_target yum -y install gcc
+log_command_in_target yum -y install binutils
+log_command_in_target yum -y install make
+log_command_in_target yum -y install dkms
+log_command_in_target yum -y install make
+log_command_in_target yum -y install bzip2
+log_command_in_target yum -y install perl
+
+
+#
+# GAs
+#
+@@VBOX_COND_IS_INSTALLING_ADDITIONS@@
+echo "--------------------------------------------------" >> "${MY_LOGFILE}"
+echo '** Installing VirtualBox Guest Additions...' | tee -a "${MY_LOGFILE}"
+MY_IGNORE_EXITCODE=2  # returned if modules already loaded and reboot required.
+log_command_in_target /bin/bash "${MY_CHROOT_CDROM}/vboxadditions/VBoxLinuxAdditions.run" --nox11
+log_command_in_target /bin/bash -c "udevadm control --reload-rules" # GAs doesn't yet do this.
+log_command_in_target /bin/bash -c "udevadm trigger"                 # (ditto)
+MY_IGNORE_EXITCODE=
+log_command_in_target usermod -a -G vboxsf "@@VBOX_INSERT_USER_LOGIN@@"
+@@VBOX_COND_END@@
+
+#
+# Local gateway support
+#
+log_command_in_target yum -y install https://dl.fedoraproject.org/pub/epel/epel-release-latest-7.noarch.rpm
+#log_command_in_target yum -y update
+log_command_in_target yum -y install openvpn
+log_command_in_target yum -y install connect-proxy
+log_command_in_target usermod -a -G wheel "@@VBOX_INSERT_USER_LOGIN@@"
+
+echo "** Creating ${MY_TARGET}/home/@@VBOX_INSERT_USER_LOGIN@@/cloud-bridge.conf..." | tee -a "${MY_LOGFILE}"
+cat >"${MY_TARGET}/home/@@VBOX_INSERT_USER_LOGIN@@/cloud-bridge.conf" <<'EOT'
+# port 1194
+# proto udp
+port 443
+proto tcp-server
+dev tap0
+secret static.key
+keepalive 10 120
+compress lz4-v2
+push "compress lz4-v2"
+persist-key
+persist-tun
+status /var/log/openvpn-status.log
+log-append /var/log/openvpn.log
+verb 3
+EOT
+
+echo "** Creating ${MY_TARGET}/home/@@VBOX_INSERT_USER_LOGIN@@/cloud-bridge.sh..." | tee -a "${MY_LOGFILE}"
+cat >"${MY_TARGET}/home/@@VBOX_INSERT_USER_LOGIN@@/cloud-bridge.sh" <<'EOT'
+# Initialize variables
+br="br0"
+tap="tap0"
+vnic1=$1
+vnic2=$2
+vnic2_gw=$3
+target_mac=$4
+
+# Install openvpn if it is missing
+if ! yum list installed openvpn; then
+    sudo yum -y install openvpn
+fi
+
+# Let openvpn traffic through Linux firewall
+#sudo iptables -I INPUT -p udp --dport 1194 -j ACCEPT
+sudo iptables -I INPUT -p tcp --dport 443 -j ACCEPT
+
+# Switch to secondary VNIC
+sudo ip route change default via $vnic2_gw dev $vnic2
+sudo ip link set dev $vnic1 down
+
+# Bring up the cloud end of the tunnel
+sudo openvpn --config cloud-bridge.conf --daemon
+
+# Use target MAC for primary VNIC
+sudo ip link set dev $vnic1 address $target_mac
+
+# Bridge tap and primary VNIC
+sudo ip link add name $br type bridge
+sudo ip link set dev $vnic1 master $br
+sudo ip link set dev $tap master $br
+
+# Bring up all interfaces
+sudo ip link set dev $tap up
+sudo ip link set dev $vnic1 up
+sudo ip link set dev $br up
+EOT
+log_command chmod +x "${MY_TARGET}/home/@@VBOX_INSERT_USER_LOGIN@@/cloud-bridge.sh"
+
+echo "** Creating ${MY_TARGET}/home/@@VBOX_INSERT_USER_LOGIN@@/local-bridge.conf..." | tee -a "${MY_LOGFILE}"
+cat >"${MY_TARGET}/home/@@VBOX_INSERT_USER_LOGIN@@/local-bridge.conf" <<'EOT'
+dev tap0
+# proto udp
+# port 1194
+proto tcp-client
+port 443
+persist-key
+persist-tun
+secret static.key
+compress lz4-v2
+log-append /var/log/openvpn.log
+verb 3
+EOT
+
+echo "** Creating ${MY_TARGET}/home/@@VBOX_INSERT_USER_LOGIN@@/local-bridge.sh..." | tee -a "${MY_LOGFILE}"
+cat >"${MY_TARGET}/home/@@VBOX_INSERT_USER_LOGIN@@/local-bridge.sh" <<'EOT'
+echo Complete command line for debugging purposes:
+echo $0 $*
+
+# Make sure we are at home
+cd ~
+
+# Initialize variables
+user=opc
+cbr_ip1=$1
+cbr_ip2=$2
+target_mac=$3
+br="br0"
+tap="tap0"
+eth="enp0s8"
+
+proxy1_ssh=""
+proxy2_ssh=""
+proxy2_vpn=""
+case $4 in
+  HTTP | HTTPS)
+    proxy1_ssh="connect-proxy -w 30 -H $5:$6 %h %p"
+    ;;
+  SOCKS | SOCKS5)
+    proxy1_ssh="connect-proxy -w 30 -S $5:$6 %h %p"
+    ;;
+  SOCKS4)
+    proxy1_ssh="connect-proxy -w 30 -4 -S $5:$6 %h %p"
+    ;;
+esac
+case $7 in
+  HTTP | HTTPS)
+    proxy2_ssh="connect-proxy -w 30 -H $8:$9 %h %p"
+    proxy2_vpn="--http-proxy $8 $9"
+    ;;
+  SOCKS | SOCKS5)
+    proxy2_ssh="connect-proxy -w 30 -S $8:$9 %h %p"
+    proxy2_vpn="--socks-proxy $8 $9"
+    ;;
+  SOCKS4)
+    proxy2_ssh="connect-proxy -w 30 -4 -S $8:$9 %h %p"
+    proxy2_vpn="--socks-proxy $8 $9"
+    ;;
+esac
+
+# Generate pre-shared secret and share it with the server, bypassing proxy if necessary
+/usr/sbin/openvpn --genkey --secret static.key
+for i in 1 2 3 4
+do
+  # Go via proxy if set
+  scp ${proxy1_ssh:+ -o ProxyCommand="$proxy1_ssh"} static.key cloud-bridge.conf cloud-bridge.sh $user@$cbr_ip1:
+  if [ $? -eq 0 ]; then break; fi; sleep 15
+  # Go direct even if proxy is set
+  scp static.key cloud-bridge.conf cloud-bridge.sh $user@$cbr_ip1:
+  if [ $? -eq 0 ]; then proxy1_ssh=""; break; fi; sleep 15
+done
+
+# Get metadata info from the cloud bridge
+for i in 1 2 3 4; do metadata=$(ssh ${proxy1_ssh:+ -o ProxyCommand="$proxy1_ssh"} $user@$cbr_ip1 sudo oci-network-config) && break || sleep 15; done
+
+# Extract primary VNIC info
+vnic1_md=`echo "$metadata"|grep -E "^\S+\s+\S+\s+\S+\s+\S+\s+\S+\s+\S+\s+0\s"`
+vnic1_dev=`echo $vnic1_md|cut -d ' ' -f 8`
+vnic1_mac=`echo $vnic1_md|cut -d ' ' -f 12`
+# Extract secondary VNIC info
+vnic2_md=`echo "$metadata"|grep -E "^\S+\s+\S+\s+\S+\s+\S+\s+\S+\s+\S+\s+1\s"`
+vnic2_dev=`echo $vnic2_md|cut -d ' ' -f 8`
+vnic2_gw=`echo $vnic2_md|cut -d ' ' -f 5`
+
+# Configure secondary VNIC
+ssh ${proxy1_ssh:+ -o ProxyCommand="$proxy1_ssh"} $user@$cbr_ip1 sudo oci-network-config -c
+
+# Bring up the cloud bridge
+ssh ${proxy2_ssh:+ -o ProxyCommand="$proxy2_ssh"} $user@$cbr_ip2 /bin/sh -x cloud-bridge.sh $vnic1_dev $vnic2_dev $vnic2_gw $target_mac
+if [ $? -eq 0 ]
+then
+  # SSH was able to reach cloud via proxy, establish a tunnel via proxy as well
+  sudo /usr/sbin/openvpn $proxy2_vpn --config local-bridge.conf --daemon --remote $cbr_ip2
+else
+  # Retry without proxy
+  ssh $user@$cbr_ip2 /bin/sh -x cloud-bridge.sh $vnic1_dev $vnic2_dev $vnic2_gw $target_mac
+  # Establish a tunnel to the cloud bridge
+  sudo /usr/sbin/openvpn --config local-bridge.conf --daemon --remote $cbr_ip2
+fi
+
+# Bridge the openvpn tap device and the local Ethernet interface
+sudo ip link set dev $eth down
+sudo ip link add name $br type bridge
+sudo ip link set dev $eth master $br
+sudo ip link set dev $tap master $br
+
+# Bring up all interfaces
+sudo ip link set dev $tap up
+sudo ip link set dev $eth up
+sudo ip link set dev $br up
+EOT
+log_command chmod +x "${MY_TARGET}/home/@@VBOX_INSERT_USER_LOGIN@@/local-bridge.sh"
+
+echo "** Creating ${MY_TARGET}/home/@@VBOX_INSERT_USER_LOGIN@@/.ssh/config..." | tee -a "${MY_LOGFILE}"
+log_command mkdir "${MY_TARGET}/home/@@VBOX_INSERT_USER_LOGIN@@/.ssh"
+cat >"${MY_TARGET}/home/@@VBOX_INSERT_USER_LOGIN@@/.ssh/config" <<'EOT'
+Host *
+    StrictHostKeyChecking no
+EOT
+log_command chmod 400 "${MY_TARGET}/home/@@VBOX_INSERT_USER_LOGIN@@/.ssh/config"
+
+log_command_in_target chown -R @@VBOX_INSERT_USER_LOGIN@@:@@VBOX_INSERT_USER_LOGIN@@ "/home/@@VBOX_INSERT_USER_LOGIN@@"
+
+echo '** Creating /etc/systemd/system/keygen.service...' | tee -a "${MY_LOGFILE}"
+cat >"${MY_TARGET}/etc/systemd/system/keygen.service" <<'EOT'
+[Unit]
+Description=Boot-time ssh key pair generator
+After=vboxadd.service
+
+[Service]
+ExecStart=/bin/sh -c 'su - vbox -c "cat /dev/zero | ssh-keygen -q -N \\\"\\\""'
+ExecStartPost=/bin/sh -c 'VBoxControl guestproperty set "/VirtualBox/Gateway/PublicKey" "`cat ~vbox/.ssh/id_rsa.pub`" --flags TRANSIENT'
+Type=oneshot
+RemainAfterExit=yes
+
+[Install]
+WantedBy=multi-user.target
+EOT
+log_command chmod 644 "${MY_TARGET}/etc/systemd/system/keygen.service"
+log_command_in_target systemctl enable keygen.service
+
+echo '** Creating /etc/sudoers.d/020_vbox_sudo...' | tee -a "${MY_LOGFILE}"
+echo "@@VBOX_INSERT_USER_LOGIN@@ ALL=(ALL) NOPASSWD: ALL" > "${MY_TARGET}/etc/sudoers.d/020_vbox_sudo"
+
+#
+# Test Execution Service.
+#
+@@VBOX_COND_IS_INSTALLING_TEST_EXEC_SERVICE@@
+echo "--------------------------------------------------" >> "${MY_LOGFILE}"
+echo '** Installing Test Execution Service...' | tee -a "${MY_LOGFILE}"
+log_command_in_target test "${MY_CHROOT_CDROM}/vboxvalidationkit/linux/@@VBOX_INSERT_OS_ARCH@@/TestExecService"
+log_command mkdir -p "${MY_TARGET}/opt/validationkit" "${MY_TARGET}/media/cdrom"
+log_command cp -R ${MY_CDROM_NOCHROOT}/vboxvalidationkit/* "${MY_TARGET}/opt/validationkit/"
+log_command chmod -R u+rw,a+xr "${MY_TARGET}/opt/validationkit/"
+if [ -e "${MY_TARGET}/usr/bin/chcon" -o -e "${MY_TARGET}/bin/chcon" -o -e "${MY_TARGET}/usr/sbin/chcon" -o -e "${MY_TARGET}/sbin/chcon" ]; then
+    MY_IGNORE_EXITCODE=1
+    log_command_in_target chcon -R -t usr_t "/opt/validationkit/"
+    MY_IGNORE_EXITCODE=
+fi
+
+# systemd service config:
+MY_UNIT_PATH="${MY_TARGET}/lib/systemd/system"
+test -d "${MY_TARGET}/usr/lib/systemd/system" && MY_UNIT_PATH="${MY_TARGET}/usr/lib/systemd/system"
+if [ -d "${MY_UNIT_PATH}" ]; then
+    log_command cp "${MY_TARGET}/opt/validationkit/linux/vboxtxs.service" "${MY_UNIT_PATH}/vboxtxs.service"
+    log_command chmod 644 "${MY_UNIT_PATH}/vboxtxs.service"
+    log_command_in_target systemctl -q enable vboxtxs
+
+# System V like:
+elif [ -e "${MY_TARGET}/etc/init.d/" ]; then
+
+    # Install the script.  On rhel6 scripts are under /etc/rc.d/ with /etc/init.d and /etc/rc?.d being symlinks.
+    if [ -d "${MY_TARGET}/etc/rc.d/init.d/" ]; then
+        MY_INIT_D_PARENT_PATH="${MY_TARGET}/etc/rc.d"
+        log_command ln -s "../../../opt/validationkit/linux/vboxtxs" "${MY_INIT_D_PARENT_PATH}/init.d/"
+    else
+        MY_INIT_D_PARENT_PATH="${MY_TARGET}/etc"
+        log_command ln -s    "../../opt/validationkit/linux/vboxtxs" "${MY_INIT_D_PARENT_PATH}/init.d/"
+    fi
+
+    # Use runlevel management script if found.
+    if chroot_which chkconfig; then     # Redhat based sysvinit systems
+        log_command_in_target chkconfig --add vboxtxs
+    elif chroot_which insserv; then     # SUSE-based sysvinit systems
+        log_command_in_target insserv vboxtxs
+    elif chroot_which update-rc.d; then # Debian/Ubuntu-based systems
+        log_command_in_target update-rc.d vboxtxs defaults
+    elif chroot_which rc-update; then   # Gentoo Linux
+        log_command_in_target rc-update add vboxtxs default
+    # Fall back on hardcoded symlinking.
+    else
+        log_command ln -s "../init.d/vboxtxs" "${MY_INIT_D_PARENT_PATH}/rc0.d/K65vboxtxs"
+        log_command ln -s "../init.d/vboxtxs" "${MY_INIT_D_PARENT_PATH}/rc1.d/K65vboxtxs"
+        log_command ln -s "../init.d/vboxtxs" "${MY_INIT_D_PARENT_PATH}/rc6.d/K65vboxtxs"
+        log_command ln -s "../init.d/vboxtxs" "${MY_INIT_D_PARENT_PATH}/rc2.d/S35vboxtxs"
+        log_command ln -s "../init.d/vboxtxs" "${MY_INIT_D_PARENT_PATH}/rc3.d/S35vboxtxs"
+        log_command ln -s "../init.d/vboxtxs" "${MY_INIT_D_PARENT_PATH}/rc4.d/S35vboxtxs"
+        log_command ln -s "../init.d/vboxtxs" "${MY_INIT_D_PARENT_PATH}/rc5.d/S35vboxtxs"
+    fi
+else
+    echo "** error: Unknown init script system." | tee -a "${MY_LOGFILE}"
+fi
+
+@@VBOX_COND_END@@
+
+
+#
+# Run user command.
+#
+@@VBOX_COND_HAS_POST_INSTALL_COMMAND@@
+echo '** Running custom user command ...'      | tee -a "${MY_LOGFILE}"
+log_command @@VBOX_INSERT_POST_INSTALL_COMMAND@@
+@@VBOX_COND_END@@
+
+
+#
+# Unmount the cdrom if we bound it and clean up the chroot if we set it up.
+#
+if [ -n "${MY_UNMOUNT_TARGET_CDROM}" ]; then
+    echo "** unbinding cdrom from jail..." | tee -a "${MY_LOGFILE}"
+    log_command umount "${MY_TARGET}${MY_CHROOT_CDROM}"
+fi
+
+if [ -n "${MY_RMDIR_TARGET_CDROM}" ]; then
+    log_command rmdir "${MY_TARGET}${MY_CHROOT_CDROM}"
+fi
+
+
+#
+# Log footer.
+#
+echo "******************************************************************************" >> "${MY_LOGFILE}"
+echo "** Date:            `date -R`" >> "${MY_LOGFILE}"
+echo "** Final exit code: ${MY_EXITCODE}" >> "${MY_LOGFILE}"
+echo "******************************************************************************" >> "${MY_LOGFILE}"
+
+exit ${MY_EXITCODE}
+
Index: /trunk/src/VBox/Main/include/CloudGateway.h
===================================================================
--- /trunk/src/VBox/Main/include/CloudGateway.h	(revision 85359)
+++ /trunk/src/VBox/Main/include/CloudGateway.h	(revision 85359)
@@ -0,0 +1,96 @@
+/* $Id$ */
+/** @file
+ * Implementation of local and cloud gateway management.
+ */
+
+/*
+ * Copyright (C) 2019-2020 Oracle Corporation
+ *
+ * This file is part of VirtualBox Open Source Edition (OSE), as
+ * available from http://www.virtualbox.org. This file is free software;
+ * you can redistribute it and/or modify it under the terms of the GNU
+ * General Public License (GPL) as published by the Free Software
+ * Foundation, in version 2 as it comes in the "COPYING" file of the
+ * VirtualBox OSE distribution. VirtualBox OSE is distributed in the
+ * hope that it will be useful, but WITHOUT ANY WARRANTY of any kind.
+ */
+
+#ifndef MAIN_INCLUDED_CloudGateway_h
+#define MAIN_INCLUDED_CloudGateway_h
+#ifndef RT_WITHOUT_PRAGMA_ONCE
+# pragma once
+#endif
+
+struct GatewayInfo
+{
+    Bstr    mTargetVM;
+    Utf8Str mGatewayVM;
+    Utf8Str mGatewayInstanceId;
+    Utf8Str mPublicSshKey;
+    Bstr    mCloudProvider;
+    Bstr    mCloudProfile;
+    Utf8Str mCloudPublicIp;
+    Utf8Str mCloudSecondaryPublicIp;
+    RTMAC   mCloudMacAddress;
+    RTMAC   mLocalMacAddress;
+    int     mAdapterSlot;
+
+    HRESULT setCloudMacAddress(const Utf8Str& mac);
+    HRESULT setLocalMacAddress(const Utf8Str& mac);
+
+    Utf8Str getCloudMacAddressWithoutColons() const;
+    Utf8Str getLocalMacAddressWithoutColons() const;
+    Utf8Str getLocalMacAddressWithColons() const;
+
+    GatewayInfo() {}
+
+    GatewayInfo(const GatewayInfo& other)
+        : mGatewayVM(other.mGatewayVM),
+          mGatewayInstanceId(other.mGatewayInstanceId),
+          mPublicSshKey(other.mPublicSshKey),
+          mCloudProvider(other.mCloudProvider),
+          mCloudProfile(other.mCloudProfile),
+          mCloudPublicIp(other.mCloudPublicIp),
+          mCloudSecondaryPublicIp(other.mCloudSecondaryPublicIp),
+          mCloudMacAddress(other.mCloudMacAddress),
+          mLocalMacAddress(other.mLocalMacAddress),
+          mAdapterSlot(other.mAdapterSlot)
+    {}
+
+    GatewayInfo& operator=(const GatewayInfo& other)
+    {
+        mGatewayVM = other.mGatewayVM;
+        mGatewayInstanceId = other.mGatewayInstanceId;
+        mPublicSshKey = other.mPublicSshKey;
+        mCloudProvider = other.mCloudProvider;
+        mCloudProfile = other.mCloudProfile;
+        mCloudPublicIp = other.mCloudPublicIp;
+        mCloudSecondaryPublicIp = other.mCloudSecondaryPublicIp;
+        mCloudMacAddress = other.mCloudMacAddress;
+        mLocalMacAddress = other.mLocalMacAddress;
+        mAdapterSlot = other.mAdapterSlot;
+        return *this;
+    }
+
+    void setNull()
+    {
+        mGatewayVM.setNull();
+        mGatewayInstanceId.setNull();
+        mPublicSshKey.setNull();
+        mCloudProvider.setNull();
+        mCloudProfile.setNull();
+        mCloudPublicIp.setNull();
+        mCloudSecondaryPublicIp.setNull();
+        memset(&mCloudMacAddress, 0, sizeof(mCloudMacAddress));
+        memset(&mLocalMacAddress, 0, sizeof(mLocalMacAddress));
+        mAdapterSlot = -1;
+    }
+};
+
+class CloudNetwork;
+
+HRESULT startGateways(ComPtr<IVirtualBox> virtualBox, ComPtr<ICloudNetwork> network, GatewayInfo& pGateways);
+HRESULT stopGateways(ComPtr<IVirtualBox> virtualBox, const GatewayInfo& gateways);
+
+#endif /* !MAIN_INCLUDED_CloudGateway_h */
+
Index: /trunk/src/VBox/Main/src-client/CloudGateway.cpp
===================================================================
--- /trunk/src/VBox/Main/src-client/CloudGateway.cpp	(revision 85359)
+++ /trunk/src/VBox/Main/src-client/CloudGateway.cpp	(revision 85359)
@@ -0,0 +1,946 @@
+/* $Id$ */
+/** @file
+ * Implementation of local and cloud gateway management.
+ */
+
+/*
+ * Copyright (C) 2019-2020 Oracle Corporation
+ *
+ * This file is part of VirtualBox Open Source Edition (OSE), as
+ * available from http://www.virtualbox.org. This file is free software;
+ * you can redistribute it and/or modify it under the terms of the GNU
+ * General Public License (GPL) as published by the Free Software
+ * Foundation, in version 2 as it comes in the "COPYING" file of the
+ * VirtualBox OSE distribution. VirtualBox OSE is distributed in the
+ * hope that it will be useful, but WITHOUT ANY WARRANTY of any kind.
+ */
+
+#define LOG_GROUP LOG_GROUP_MAIN_CONSOLE
+
+/* Make sure all the stdint.h macros are included - must come first! */
+#ifndef __STDC_LIMIT_MACROS
+# define __STDC_LIMIT_MACROS
+#endif
+#ifndef __STDC_CONSTANT_MACROS
+# define __STDC_CONSTANT_MACROS
+#endif
+
+#include "LoggingNew.h"
+#include "ApplianceImpl.h"
+#include "CloudNetworkImpl.h"
+#include "CloudGateway.h"
+
+#include <iprt/http.h>
+#include <iprt/inifile.h>
+#include <iprt/net.h>
+#include <iprt/path.h>
+#include <iprt/vfs.h>
+#include <iprt/uri.h>
+
+static HRESULT setMacAddress(const Utf8Str& str, RTMAC& mac)
+{
+    int rc = RTNetStrToMacAddr(str.c_str(), &mac);
+    if (RT_FAILURE(rc))
+    {
+        LogRel(("CLOUD-NET: Invalid MAC address '%s'\n", str.c_str()));
+        return E_INVALIDARG;
+    }
+    return S_OK;
+}
+
+HRESULT GatewayInfo::setCloudMacAddress(const Utf8Str& mac)
+{
+    return setMacAddress(mac, mCloudMacAddress);
+}
+
+HRESULT GatewayInfo::setLocalMacAddress(const Utf8Str& mac)
+{
+    return setMacAddress(mac, mLocalMacAddress);
+}
+
+Utf8Str GatewayInfo::getCloudMacAddressWithoutColons() const
+{
+    return Utf8StrFmt("%02X%02X%02X%02X%02X%02X",
+                      mCloudMacAddress.au8[0], mCloudMacAddress.au8[1], mCloudMacAddress.au8[2],
+                      mCloudMacAddress.au8[3], mCloudMacAddress.au8[4], mCloudMacAddress.au8[5]);
+}
+
+Utf8Str GatewayInfo::getLocalMacAddressWithoutColons() const
+{
+    return Utf8StrFmt("%02X%02X%02X%02X%02X%02X",
+                      mLocalMacAddress.au8[0], mLocalMacAddress.au8[1], mLocalMacAddress.au8[2],
+                      mLocalMacAddress.au8[3], mLocalMacAddress.au8[4], mLocalMacAddress.au8[5]);
+}
+
+Utf8Str GatewayInfo::getLocalMacAddressWithColons() const
+{
+    return Utf8StrFmt("%RTmac", &mLocalMacAddress);
+}
+
+class CloudError
+{
+public:
+    CloudError(HRESULT hrc, const Utf8Str& strText) : mHrc(hrc), mText(strText) {};
+    HRESULT getRc() { return mHrc; };
+    Utf8Str getText() { return mText; };
+
+private:
+    HRESULT mHrc;
+    Utf8Str mText;
+};
+
+static void handleErrors(HRESULT hrc, const char *pszFormat, ...)
+{
+    if (FAILED(hrc))
+    {
+        va_list va;
+        va_start(va, pszFormat);
+        Utf8Str strError(pszFormat, va);
+        va_end(va);
+        LogRel(("CLOUD-NET: %s (rc=%x)\n", strError.c_str(), hrc));
+        throw CloudError(hrc, strError);
+    }
+
+}
+
+class CloudClient
+{
+public:
+    CloudClient(ComPtr<IVirtualBox> virtualBox, const Bstr& strProvider, const Bstr& strProfile);
+    ~CloudClient() {};
+
+    void startCloudGateway(const ComPtr<ICloudNetwork> &network, GatewayInfo& gateways);
+    void stopCloudGateway(const GatewayInfo& gateways);
+
+private:
+    ComPtr<ICloudProviderManager> mManager;
+    ComPtr<ICloudProvider>        mProvider;
+    ComPtr<ICloudProfile>         mProfile;
+    ComPtr<ICloudClient>          mClient;
+};
+
+CloudClient::CloudClient(ComPtr<IVirtualBox> virtualBox, const Bstr& strProvider, const Bstr& strProfile)
+{
+    HRESULT hrc = virtualBox->COMGETTER(CloudProviderManager)(mManager.asOutParam());
+    handleErrors(hrc, "Failed to obtain cloud provider manager object");
+    hrc = mManager->GetProviderByShortName(strProvider.raw(), mProvider.asOutParam());
+    handleErrors(hrc, "Failed to obtain cloud provider '%ls'", strProvider.raw());
+    hrc = mProvider->GetProfileByName(strProfile.raw(), mProfile.asOutParam());
+    handleErrors(hrc, "Failed to obtain cloud profile '%ls'", strProfile.raw());
+    hrc = mProfile->CreateCloudClient(mClient.asOutParam());
+    handleErrors(hrc, "Failed to create cloud client");
+}
+
+void CloudClient::startCloudGateway(const ComPtr<ICloudNetwork> &network, GatewayInfo& gateways)
+{
+    ComPtr<IProgress> progress;
+    ComPtr<ICloudNetworkGatewayInfo> gatewayInfo;
+    HRESULT hrc = mClient->StartCloudNetworkGateway(network, Bstr(gateways.mPublicSshKey).raw(),
+                                                    gatewayInfo.asOutParam(), progress.asOutParam());
+    handleErrors(hrc, "Failed to launch compute instance");
+    hrc = progress->WaitForCompletion(-1);
+    handleErrors(hrc, "Failed to launch compute instance (wait)");
+
+    Bstr instanceId;
+    hrc = gatewayInfo->COMGETTER(InstanceId)(instanceId.asOutParam());
+    handleErrors(hrc, "Failed to get launched compute instance id");
+    gateways.mGatewayInstanceId = instanceId;
+
+    Bstr publicIP;
+    hrc = gatewayInfo->COMGETTER(PublicIP)(publicIP.asOutParam());
+    handleErrors(hrc, "Failed to get cloud gateway public IP address");
+    gateways.mCloudPublicIp = publicIP;
+
+    Bstr secondaryPublicIP;
+    hrc = gatewayInfo->COMGETTER(SecondaryPublicIP)(secondaryPublicIP.asOutParam());
+    handleErrors(hrc, "Failed to get cloud gateway secondary public IP address");
+    gateways.mCloudSecondaryPublicIp = secondaryPublicIP;
+
+    Bstr macAddress;
+    hrc = gatewayInfo->COMGETTER(MacAddress)(macAddress.asOutParam());
+    handleErrors(hrc, "Failed to get cloud gateway public IP address");
+    gateways.setCloudMacAddress(macAddress);
+}
+
+void CloudClient::stopCloudGateway(const GatewayInfo& gateways)
+{
+    ComPtr<IProgress> progress;
+    HRESULT hrc = mClient->TerminateInstance(Bstr(gateways.mGatewayInstanceId).raw(), progress.asOutParam());
+    handleErrors(hrc, "Failed to terminate compute instance");
+#if 0
+    /* Someday we may want to wait until the cloud gateway has terminated. */
+    hrc = progress->WaitForCompletion(-1);
+    handleErrors(hrc, "Failed to terminate compute instance (wait)");
+#endif
+}
+
+
+static HRESULT startCloudGateway(ComPtr<IVirtualBox> virtualBox, ComPtr<ICloudNetwork> network, GatewayInfo& gateways)
+{
+    HRESULT hrc = S_OK;
+
+    try {
+        hrc = network->COMGETTER(Provider)(gateways.mCloudProvider.asOutParam());
+        hrc = network->COMGETTER(Profile)(gateways.mCloudProfile.asOutParam());
+        CloudClient client(virtualBox, gateways.mCloudProvider, gateways.mCloudProfile);
+        client.startCloudGateway(network, gateways);
+    }
+    catch (CloudError e)
+    {
+        hrc = e.getRc();
+    }
+
+    return hrc;
+}
+
+
+static HRESULT attachToLocalNetwork(ComPtr<ISession> aSession, const com::Utf8Str &aCloudNetwork)
+{
+    ComPtr<IMachine> sessionMachine;
+    HRESULT hrc = aSession->COMGETTER(Machine)(sessionMachine.asOutParam());
+    if (FAILED(hrc))
+    {
+        LogRel(("CLOUD-NET: Failed to obtain a mutable machine. hrc=%x\n", hrc));
+        return hrc;
+    }
+
+    ComPtr<INetworkAdapter> networkAdapter;
+    hrc = sessionMachine->GetNetworkAdapter(1, networkAdapter.asOutParam());
+    if (FAILED(hrc))
+    {
+        LogRel(("CLOUD-NET: Failed to locate the second network adapter. hrc=%x\n", hrc));
+        return hrc;
+    }
+
+    BstrFmt network("cloud-%s", aCloudNetwork.c_str());
+    hrc = networkAdapter->COMSETTER(InternalNetwork)(network.raw());
+    if (FAILED(hrc))
+    {
+        LogRel(("CLOUD-NET: Failed to set network name for the second network adapter. hrc=%x\n", hrc));
+        return hrc;
+    }
+
+    hrc = sessionMachine->SaveSettings();
+    if (FAILED(hrc))
+        LogRel(("CLOUD-NET: Failed to save 'lgw' settings. hrc=%x\n", hrc));
+    return hrc;
+}
+
+static HRESULT startLocalGateway(ComPtr<IVirtualBox> virtualBox, ComPtr<ISession> aSession, const com::Utf8Str &aCloudNetwork, GatewayInfo& gateways)
+{
+    /*
+     * It would be really beneficial if we do not create a local gateway VM each time a target starts.
+     * We probably just need to make sure its configuration matches the one required by the cloud network
+     * attachment and update configuration if necessary.
+     */
+    Bstr strGatewayVM = BstrFmt("lgw-%ls", gateways.mTargetVM.raw());
+    ComPtr<IMachine> machine;
+    HRESULT hrc = virtualBox->FindMachine(strGatewayVM.raw(), machine.asOutParam());
+    if (SUCCEEDED(hrc))
+    {
+        hrc = machine->LockMachine(aSession, LockType_Write);
+        if (FAILED(hrc))
+        {
+            LogRel(("CLOUD-NET: Failed to lock '%ls' for modifications. hrc=%x\n", strGatewayVM.raw(), hrc));
+            return hrc;
+        }
+
+        hrc = attachToLocalNetwork(aSession, aCloudNetwork);
+    }
+    else
+    {
+        SafeArray<IN_BSTR> groups;
+        groups.push_back(Bstr("/gateways").mutableRaw());
+        hrc = virtualBox->CreateMachine(NULL, Bstr(strGatewayVM).raw(), ComSafeArrayAsInParam(groups), Bstr("Ubuntu_64").raw(), Bstr("").raw(), machine.asOutParam());
+        if (FAILED(hrc))
+        {
+            LogRel(("CLOUD-NET: Failed to create '%ls'. hrc=%x\n", strGatewayVM.raw(), hrc));
+            return hrc;
+        }
+        /* Initial configuration */
+        hrc = machine->ApplyDefaults(NULL);
+        if (FAILED(hrc))
+        {
+            LogRel(("CLOUD-NET: Failed to apply defaults to '%ls'. hrc=%x\n", strGatewayVM.raw(), hrc));
+            return hrc;
+        }
+
+        /* Add second network adapter */
+        ComPtr<INetworkAdapter> networkAdapter;
+        hrc = machine->GetNetworkAdapter(1, networkAdapter.asOutParam());
+        if (FAILED(hrc))
+        {
+            LogRel(("CLOUD-NET: Failed to locate the second network adapter. hrc=%x\n", hrc));
+            return hrc;
+        }
+
+        hrc = networkAdapter->COMSETTER(AttachmentType)(NetworkAttachmentType_Internal);
+        if (FAILED(hrc))
+        {
+            LogRel(("CLOUD-NET: Failed to set attachment type for the second network adapter. hrc=%x\n", hrc));
+            return hrc;
+        }
+
+        BstrFmt network("cloud-%s", aCloudNetwork.c_str());
+        hrc = networkAdapter->COMSETTER(InternalNetwork)(network.raw());
+        if (FAILED(hrc))
+        {
+            LogRel(("CLOUD-NET: Failed to set network name for the second network adapter. hrc=%x\n", hrc));
+            return hrc;
+        }
+
+        hrc = networkAdapter->COMSETTER(PromiscModePolicy)(NetworkAdapterPromiscModePolicy_AllowAll);
+        if (FAILED(hrc))
+        {
+            LogRel(("CLOUD-NET: Failed to set promiscuous mode policy for the second network adapter. hrc=%x\n", hrc));
+            return hrc;
+        }
+
+        hrc = networkAdapter->COMSETTER(Enabled)(TRUE);
+        if (FAILED(hrc))
+        {
+            LogRel(("CLOUD-NET: Failed to enable the second network adapter. hrc=%x\n", hrc));
+            return hrc;
+        }
+
+        /* No need for audio -- disable it. */
+        ComPtr<IAudioAdapter> audioAdapter;
+        hrc = machine->GetNetworkAdapter(1, networkAdapter.asOutParam());
+        if (FAILED(hrc))
+        {
+            LogRel(("CLOUD-NET: Failed to locate the second network adapter. hrc=%x\n", hrc));
+            return hrc;
+        }
+
+        hrc = machine->COMGETTER(AudioAdapter)(audioAdapter.asOutParam());
+        if (FAILED(hrc))
+        {
+            LogRel(("CLOUD-NET: Failed to set attachment type for the second network adapter. hrc=%x\n", hrc));
+            return hrc;
+        }
+
+        hrc = audioAdapter->COMSETTER(Enabled)(FALSE);
+        if (FAILED(hrc))
+        {
+            LogRel(("CLOUD-NET: Failed to disable the audio adapter. hrc=%x\n", hrc));
+            return hrc;
+        }
+
+        /** @todo Disable USB? */
+
+        /* Register the local gateway VM */
+        hrc = virtualBox->RegisterMachine(machine);
+        if (FAILED(hrc))
+        {
+            LogRel(("CLOUD-NET: Failed to register '%ls'. hrc=%x\n", strGatewayVM.raw(), hrc));
+            return hrc;
+        }
+
+        /*
+        * Storage can only be attached to registered VMs which means we need to use session
+        * to lock VM in order to make it mutable again.
+        */
+        ComPtr<ISystemProperties> systemProperties;
+        ComPtr<IMedium> hd;
+        Bstr defaultMachineFolder;
+        hrc = virtualBox->COMGETTER(SystemProperties)(systemProperties.asOutParam());
+        if (FAILED(hrc))
+        {
+            LogRel(("CLOUD-NET: Failed to obtain system properties. hrc=%x\n", hrc));
+            return hrc;
+        }
+        hrc = systemProperties->COMGETTER(DefaultMachineFolder)(defaultMachineFolder.asOutParam());
+        if (FAILED(hrc))
+        {
+            LogRel(("CLOUD-NET: Failed to obtain default machine folder. hrc=%x\n", hrc));
+            return hrc;
+        }
+        hrc = virtualBox->OpenMedium(BstrFmt("%ls\\gateways\\lgw.vdi", defaultMachineFolder.raw()).raw(), DeviceType_HardDisk, AccessMode_ReadWrite, FALSE, hd.asOutParam());
+        if (FAILED(hrc))
+        {
+            LogRel(("CLOUD-NET: Failed to open medium for '%ls'. hrc=%x\n", strGatewayVM.raw(), hrc));
+            return hrc;
+        }
+
+        hrc = machine->LockMachine(aSession, LockType_Write);
+        if (FAILED(hrc))
+        {
+            LogRel(("CLOUD-NET: Failed to lock '%ls' for modifications. hrc=%x\n", strGatewayVM.raw(), hrc));
+            return hrc;
+        }
+
+        ComPtr<IMachine> sessionMachine;
+        hrc = aSession->COMGETTER(Machine)(sessionMachine.asOutParam());
+        if (FAILED(hrc))
+        {
+            LogRel(("CLOUD-NET: Failed to obtain a mutable machine. hrc=%x\n", hrc));
+            return hrc;
+        }
+
+        hrc = sessionMachine->AttachDevice(Bstr("SATA").raw(), 0, 0, DeviceType_HardDisk, hd);
+        if (FAILED(hrc))
+        {
+            LogRel(("CLOUD-NET: Failed to attach HD to '%ls'. hrc=%x\n", strGatewayVM.raw(), hrc));
+            return hrc;
+        }
+
+        /* Save settings */
+        hrc = sessionMachine->SaveSettings();
+        if (FAILED(hrc))
+        {
+            LogRel(("CLOUD-NET: Failed to save '%ls' settings. hrc=%x\n", strGatewayVM.raw(), hrc));
+            return hrc;
+        }
+    }
+    /* Unlock the machine before start, it will be re-locked by LaunchVMProcess */
+    aSession->UnlockMachine();
+
+#ifdef DEBUG
+ #define LGW_FRONTEND "gui"
+#else
+ #define LGW_FRONTEND "headless"
+#endif
+    ComPtr<IProgress> progress;
+    hrc = machine->LaunchVMProcess(aSession, Bstr(LGW_FRONTEND).raw(), ComSafeArrayNullInParam(), progress.asOutParam());
+    if (FAILED(hrc))
+    {
+        LogRel(("CLOUD-NET: Failed to launch '%ls'. hrc=%x\n", strGatewayVM.raw(), hrc));
+        return hrc;
+    }
+
+    hrc = progress->WaitForCompletion(-1);
+    if (FAILED(hrc))
+        LogRel(("CLOUD-NET: Failed to launch '%ls'. hrc=%x\n", strGatewayVM.raw(), hrc));
+
+    gateways.mGatewayVM = strGatewayVM;
+
+    ComPtr<IEventSource> es;
+    hrc = virtualBox->COMGETTER(EventSource)(es.asOutParam());
+    ComPtr<IEventListener> listener;
+    hrc = es->CreateListener(listener.asOutParam());
+    com::SafeArray <VBoxEventType_T> eventTypes(1);
+    eventTypes.push_back(VBoxEventType_OnGuestPropertyChanged);
+    hrc = es->RegisterListener(listener, ComSafeArrayAsInParam(eventTypes), false);
+
+    Bstr publicKey;
+    Bstr aMachStrGuid;
+    machine->COMGETTER(Id)(aMachStrGuid.asOutParam());
+    Guid aMachGuid(aMachStrGuid);
+
+    uint64_t u64Started = RTTimeMilliTS();
+    do
+    {
+        ComPtr<IEvent> ev;
+        hrc = es->GetEvent(listener, 1000 /* seconds */, ev.asOutParam());
+        if (ev)
+        {
+            VBoxEventType_T aType;
+            hrc = ev->COMGETTER(Type)(&aType);
+            if (aType == VBoxEventType_OnGuestPropertyChanged)
+            {
+                ComPtr<IGuestPropertyChangedEvent> gpcev = ev;
+                Assert(gpcev);
+                Bstr aNextStrGuid;
+                gpcev->COMGETTER(MachineId)(aNextStrGuid.asOutParam());
+                if (aMachGuid != Guid(aNextStrGuid))
+                    continue;
+                Bstr aNextName;
+                gpcev->COMGETTER(Name)(aNextName.asOutParam());
+                if (aNextName == "/VirtualBox/Gateway/PublicKey")
+                {
+                    gpcev->COMGETTER(Value)(publicKey.asOutParam());
+                    LogRel(("CLOUD-NET: Got public key from local gateway '%ls'\n", publicKey.raw()));
+                    break;
+                }
+            }
+
+        }
+    } while (RTTimeMilliTS() - u64Started < 300 * 1000); /** @todo reasonable timeout */
+
+    if (publicKey.isEmpty())
+    {
+        LogRel(("CLOUD-NET: Failed to get ssh public key from '%ls'\n", strGatewayVM.raw()));
+        return E_FAIL;
+    }
+
+    gateways.mPublicSshKey = publicKey;
+
+    return hrc;
+}
+
+static bool getProxyForIpAddr(ComPtr<IVirtualBox> virtualBox, const com::Utf8Str &strIpAddr, Bstr &strProxyType, Bstr &strProxyHost, Bstr &strProxyPort)
+{
+#ifndef VBOX_WITH_PROXY_INFO
+    RT_NOREF(virtualBox, strIpAddr, strProxyType, strProxyHost, strProxyPort);
+    LogRel(("CLOUD-NET: Proxy support is disabled. Using direct connection.\n"));
+    return false;
+#else /* VBOX_WITH_PROXY_INFO */
+    ComPtr<ISystemProperties> systemProperties;
+    ProxyMode_T enmProxyMode;
+    HRESULT hrc = virtualBox->COMGETTER(SystemProperties)(systemProperties.asOutParam());
+    if (FAILED(hrc))
+    {
+        LogRel(("CLOUD-NET: Failed to obtain system properties. hrc=%x\n", hrc));
+        return false;
+    }
+    hrc = systemProperties->COMGETTER(ProxyMode)(&enmProxyMode);
+    if (FAILED(hrc))
+    {
+        LogRel(("CLOUD-NET: Failed to obtain default machine folder. hrc=%x\n", hrc));
+        return false;
+    }
+    if (enmProxyMode == ProxyMode_NoProxy)
+        return false;
+
+    Bstr proxyUrl;
+    if (enmProxyMode == ProxyMode_Manual)
+    {
+        hrc = systemProperties->COMGETTER(ProxyURL)(proxyUrl.asOutParam());
+        if (FAILED(hrc))
+        {
+            LogRel(("CLOUD-NET: Failed to obtain proxy URL. hrc=%x\n", hrc));
+            return false;
+        }
+        Utf8Str strProxyUrl = proxyUrl;
+        if (!strProxyUrl.contains("://"))
+            strProxyUrl = "http://" + strProxyUrl;
+        const char *pcszProxyUrl = strProxyUrl.c_str();
+        RTURIPARSED Parsed;
+        int rc = RTUriParse(pcszProxyUrl, &Parsed);
+        if (RT_FAILURE(rc))
+        {
+            LogRel(("CLOUD-NET: Failed to parse proxy URL: %ls (rc=%d)\n", proxyUrl.raw(), rc));
+            return false;
+        }
+        char *pszHost = RTUriParsedAuthorityHost(pcszProxyUrl, &Parsed);
+        if (!pszHost)
+        {
+            LogRel(("CLOUD-NET: Failed to get proxy host name from proxy URL: %s\n", pcszProxyUrl));
+            return false;
+        }
+        strProxyHost = pszHost;
+        RTStrFree(pszHost);
+        char *pszScheme = RTUriParsedScheme(pcszProxyUrl, &Parsed);
+        if (!pszScheme)
+        {
+            LogRel(("CLOUD-NET: Failed to get proxy scheme from proxy URL: %s\n", pcszProxyUrl));
+            return false;
+        }
+        strProxyType = Utf8Str(pszScheme).toUpper();
+        RTStrFree(pszScheme);
+        uint32_t uProxyPort  = RTUriParsedAuthorityPort(pcszProxyUrl, &Parsed);
+        if (uProxyPort == UINT32_MAX)
+        if (!pszScheme)
+        {
+            LogRel(("CLOUD-NET: Failed to get proxy port from proxy URL: %s\n", pcszProxyUrl));
+            return false;
+        }
+        strProxyPort = BstrFmt("%d", uProxyPort);
+    }
+    else
+    {
+        /* Attempt to use system proxy settings (ProxyMode_System) */
+        RTHTTP hHttp;
+        int rc = RTHttpCreate(&hHttp);
+        if (RT_FAILURE(rc))
+        {
+            LogRel(("CLOUD-NET: Failed to create HTTP context (rc=%d)\n", rc));
+            return false;
+        }
+        rc = RTHttpUseSystemProxySettings(hHttp);
+        if (RT_FAILURE(rc))
+        {
+            LogRel(("CLOUD-NET: Failed to use system proxy (rc=%d)\n", rc));
+            RTHttpDestroy(hHttp);
+            return false;
+        }
+
+        RTHTTPPROXYINFO proxy;
+        RT_ZERO(proxy);
+        rc = RTHttpGetProxyInfoForUrl(hHttp, ("http://" + strIpAddr).c_str(), &proxy);
+        if (RT_FAILURE(rc))
+        {
+            LogRel(("CLOUD-NET: Failed to get proxy for %s (rc=%d)\n", strIpAddr.c_str(), rc));
+            RTHttpDestroy(hHttp);
+            return false;
+        }
+        switch (proxy.enmProxyType)
+        {
+            case RTHTTPPROXYTYPE_HTTP:
+                strProxyType = "HTTP";
+                break;
+            case RTHTTPPROXYTYPE_HTTPS:
+                strProxyType = "HTTPS";
+                break;
+            case RTHTTPPROXYTYPE_SOCKS4:
+                strProxyType = "SOCKS4";
+                break;
+            case RTHTTPPROXYTYPE_SOCKS5:
+                strProxyType = "SOCKS5";
+                break;
+            case RTHTTPPROXYTYPE_UNKNOWN:
+                LogRel(("CLOUD-NET: Unknown proxy type."));
+                break;
+        }
+        strProxyHost = proxy.pszProxyHost;
+        strProxyPort = BstrFmt("%d", proxy.uProxyPort);
+        RTHttpFreeProxyInfo(&proxy);
+        RTHttpDestroy(hHttp);
+    }
+    return true;
+#endif /* VBOX_WITH_PROXY_INFO */
+}
+
+
+static HRESULT exchangeInfoBetweenGateways(ComPtr<IVirtualBox> virtualBox, ComPtr<ISession> aSession, GatewayInfo& gateways)
+{
+    RT_NOREF(virtualBox);
+    HRESULT hrc = S_OK;
+    do
+    {
+        ComPtr<IConsole> console;
+        hrc = aSession->COMGETTER(Console)(console.asOutParam());
+        if (FAILED(hrc))
+        {
+            LogRel(("CLOUD-NET: Failed to obtain console for 'lgw'. hrc=%x\n", hrc));
+            break;
+        }
+
+        ComPtr<IGuest> guest;
+        hrc = console->COMGETTER(Guest)(guest.asOutParam());
+        if (FAILED(hrc))
+        {
+            LogRel(("CLOUD-NET: Failed to obtain guest for 'lgw'. hrc=%x\n", hrc));
+            break;
+        }
+
+        ComPtr<IGuestSession> guestSession;
+
+        GuestSessionWaitResult_T enmWaitResult = GuestSessionWaitResult_None;
+        for (int cTriesLeft = 6; cTriesLeft > 0; cTriesLeft--)
+        {
+            RTThreadSleep(5000 /* ms */);
+            hrc = guest->CreateSession(Bstr("vbox").raw(), Bstr("vbox").raw(), NULL, Bstr("Cloud Gateway Impersonation").raw(), guestSession.asOutParam());
+            if (FAILED(hrc))
+            {
+                LogRel(("CLOUD-NET: Failed to create guest session for 'lgw'%s. hrc=%x\n", cTriesLeft > 1 ? ", will re-try" : "", hrc));
+                continue;
+            }
+            hrc = guestSession->WaitFor(GuestSessionWaitForFlag_Start, 30 * 1000, &enmWaitResult);
+            if (FAILED(hrc))
+            {
+                LogRel(("CLOUD-NET: WARNING! Failed to wait in guest session for 'lgw'%s. waitResult=%x hrc=%x\n",
+                        cTriesLeft > 1 ? ", will re-try" : "", enmWaitResult, hrc));
+                guestSession->Close();
+                guestSession.setNull();
+                continue;
+            }
+            if (enmWaitResult == GuestSessionWaitResult_Start)
+                break;
+            LogRel(("CLOUD-NET: WARNING! 'lgw' guest session waitResult=%x%s\n",
+                    enmWaitResult, cTriesLeft > 1 ? ", will re-try" : ""));
+            guestSession->Close();
+            guestSession.setNull();
+        }
+
+        if (FAILED(hrc))
+        {
+            LogRel(("CLOUD-NET: Failed to start guest session for 'lgw'. waitResult=%x hrc=%x\n", enmWaitResult, hrc));
+            break;
+        }
+
+        GuestSessionStatus_T enmSessionStatus;
+        hrc = guestSession->COMGETTER(Status)(&enmSessionStatus);
+        if (FAILED(hrc))
+        {
+            LogRel(("CLOUD-NET: Failed to get guest session status for 'lgw'. hrc=%x\n", hrc));
+            break;
+        }
+        LogRel(("CLOUD-NET: Session status: %d\n", enmSessionStatus));
+
+        Bstr strPrimaryProxyType;
+        Bstr strPrimaryProxyHost;
+        Bstr strPrimaryProxyPort;
+        Bstr strSecondaryProxyType;
+        Bstr strSecondaryProxyHost;
+        Bstr strSecondaryProxyPort;
+
+        ComPtr<IGuestProcess> guestProcess;
+        com::SafeArray<IN_BSTR> aArgs;
+        com::SafeArray<IN_BSTR> aEnv;
+        com::SafeArray<ProcessCreateFlag_T> aCreateFlags;
+        com::SafeArray<ProcessWaitForFlag_T> aWaitFlags;
+        aCreateFlags.push_back(ProcessCreateFlag_WaitForStdOut);
+        aCreateFlags.push_back(ProcessCreateFlag_WaitForStdErr);
+#define GUEST_CMD "/bin/sh"
+        aArgs.push_back(Bstr(GUEST_CMD).mutableRaw());
+        aArgs.push_back(Bstr("-x").mutableRaw());
+        aArgs.push_back(Bstr("/home/vbox/local-bridge.sh").mutableRaw());
+        aArgs.push_back(Bstr(gateways.mCloudPublicIp).mutableRaw());
+        aArgs.push_back(Bstr(gateways.mCloudSecondaryPublicIp).mutableRaw());
+        aArgs.push_back(Bstr(gateways.getLocalMacAddressWithColons()).mutableRaw());
+        if (getProxyForIpAddr(virtualBox, gateways.mCloudPublicIp, strPrimaryProxyType, strPrimaryProxyHost, strPrimaryProxyPort))
+        {
+            aArgs.push_back(strPrimaryProxyType.mutableRaw());
+            aArgs.push_back(strPrimaryProxyHost.mutableRaw());
+            aArgs.push_back(strPrimaryProxyPort.mutableRaw());
+            if (getProxyForIpAddr(virtualBox, gateways.mCloudSecondaryPublicIp, strSecondaryProxyType, strSecondaryProxyHost, strSecondaryProxyPort))
+            {
+                aArgs.push_back(strSecondaryProxyType.mutableRaw());
+                aArgs.push_back(strSecondaryProxyHost.mutableRaw());
+                aArgs.push_back(strSecondaryProxyPort.mutableRaw());
+            }
+        }
+        hrc = guestSession->ProcessCreate(Bstr(GUEST_CMD).raw(),
+                                        ComSafeArrayAsInParam(aArgs),
+                                        ComSafeArrayAsInParam(aEnv),
+                                        ComSafeArrayAsInParam(aCreateFlags),
+                                        180 * 1000 /* ms */,
+                                        guestProcess.asOutParam());
+        if (FAILED(hrc))
+        {
+            LogRel(("CLOUD-NET: Failed to create guest process '/bin/sh' for 'lgw'. hrc=%x\n", hrc));
+            break;
+        }
+
+        ProcessWaitResult_T waitResult;
+        hrc = guestProcess->WaitFor(ProcessWaitForFlag_Start, 10 * 1000 /* ms */, &waitResult);
+        if (FAILED(hrc) || (waitResult != ProcessWaitResult_Start))
+        {
+            LogRel(("CLOUD-NET: Failed to wait for guest process to start for 'lgw'. waitResult=%x hrc=%x\n",
+                    waitResult, hrc));
+            break;
+        }
+        LogRel(("CLOUD-NET: waitResult=%x\n", waitResult));
+
+        uint32_t cNotSupported = 0;
+        bool fRead, fDone = false;
+        uint64_t u64Start = RTTimeMilliTS();
+        do
+        {
+            /** @todo wait for stdout when it becomes supported! */
+            hrc = guestProcess->WaitFor(ProcessWaitForFlag_Terminate | ProcessWaitForFlag_StdOut, 1000 /* ms */, &waitResult);
+            if (FAILED(hrc))
+            {
+                LogRel(("CLOUD-NET: Failed to get output from guest process for 'lgw'. waitResult=%x hrc=%x\n",
+                        waitResult, hrc));
+                break;
+            }
+            if (waitResult == ProcessWaitResult_WaitFlagNotSupported)
+                ++cNotSupported;
+            else
+            {
+                if (cNotSupported)
+                {
+                    LogRel(("CLOUD-NET: waitResult=9, repeated %u times\n", cNotSupported));
+                    cNotSupported = 0;
+                }
+                LogRel(("CLOUD-NET: waitResult=%x\n", waitResult));
+            }
+
+            fRead = false;
+            switch (waitResult)
+            {
+                case ProcessWaitResult_WaitFlagNotSupported:
+                    RTThreadYield();
+                    /* Fall through */
+                case ProcessWaitResult_StdOut:
+                    fRead = true;
+                    break;
+                case ProcessWaitResult_Terminate:
+                    fDone = true;
+                    break;
+                case ProcessWaitResult_Timeout:
+                {
+                    ProcessStatus_T enmProcStatus;
+                    hrc = guestProcess->COMGETTER(Status)(&enmProcStatus);
+                    if (FAILED(hrc))
+                    {
+                        LogRel(("CLOUD-NET: Failed to query guest process status for 'lgw'. hrc=%x\n", hrc));
+                        fDone = true;
+                    }
+                    else
+                    {
+                        LogRel(("CLOUD-NET: Guest process timeout for 'lgw'. status=%d\n", enmProcStatus));
+                        if (   enmProcStatus == ProcessStatus_TimedOutKilled
+                            || enmProcStatus == ProcessStatus_TimedOutAbnormally)
+                        fDone = true;
+                    }
+                    fRead = true;
+                    break;
+                }
+                default:
+                    LogRel(("CLOUD-NET: Unexpected waitResult=%x\n", waitResult));
+                    break;
+            }
+
+            if (fRead)
+            {
+                SafeArray<BYTE> aStdOutData, aStdErrData;
+                hrc = guestProcess->Read(1 /* StdOut */, _64K, 60 * 1000 /* ms */, ComSafeArrayAsOutParam(aStdOutData));
+                if (FAILED(hrc))
+                {
+                    LogRel(("CLOUD-NET: Failed to read stdout from guest process for 'lgw'. hrc=%x\n", hrc));
+                    break;
+                }
+                hrc = guestProcess->Read(2 /* StdErr */, _64K, 60 * 1000 /* ms */, ComSafeArrayAsOutParam(aStdErrData));
+                if (FAILED(hrc))
+                {
+                    LogRel(("CLOUD-NET: Failed to read stderr from guest process for 'lgw'. hrc=%x\n", hrc));
+                    break;
+                }
+
+                size_t cbStdOutData = aStdOutData.size();
+                size_t cbStdErrData = aStdErrData.size();
+                if (cbStdOutData == 0 && cbStdErrData == 0)
+                {
+                    //LogRel(("CLOUD-NET: Empty output from guest process for 'lgw'. hrc=%x\n", hrc));
+                    continue;
+                }
+
+                if (cNotSupported)
+                {
+                    LogRel(("CLOUD-NET: waitResult=9, repeated %u times\n", cNotSupported));
+                    cNotSupported = 0;
+                }
+                if (cbStdOutData)
+                    LogRel(("CLOUD-NET: Got stdout from 'lgw':\n%.*s", aStdOutData.size(), aStdOutData.raw()));
+                if (cbStdErrData)
+                    LogRel(("CLOUD-NET: Got stderr from 'lgw':\n%.*s", aStdErrData.size(), aStdErrData.raw()));
+            }
+        }
+        while (!fDone && RTTimeMilliTS() - u64Start < 180 * 1000 /* ms */);
+
+    } while (false);
+
+    return hrc;
+}
+
+
+HRESULT destroyLocalGateway(ComPtr<IVirtualBox> virtualBox, const GatewayInfo& gateways)
+{
+    if (gateways.mGatewayVM.isEmpty())
+        return S_OK;
+
+    LogRel(("CLOUD-NET: Shutting down local gateway '%s'...\n", gateways.mGatewayVM.c_str()));
+
+    ComPtr<IMachine> localGateway;
+    HRESULT hrc = virtualBox->FindMachine(Bstr(gateways.mGatewayVM).raw(), localGateway.asOutParam());
+    if (FAILED(hrc))
+    {
+        LogRel(("CLOUD-NET: Failed to locate '%s'. hrc=%x\n", gateways.mGatewayVM.c_str(), hrc));
+        return hrc;
+    }
+
+    MachineState_T tmp;
+    hrc = localGateway->COMGETTER(State)(&tmp);
+    if (tmp == MachineState_Running)
+    {
+        /* If the gateway VM is running we need to stop it */
+        ComPtr<ISession> session;
+        hrc = session.createInprocObject(CLSID_Session);
+        if (FAILED(hrc))
+        {
+            LogRel(("CLOUD-NET: Failed to create a session. hrc=%x\n", hrc));
+            return hrc;
+        }
+
+        hrc = localGateway->LockMachine(session, LockType_Shared);
+        if (FAILED(hrc))
+        {
+            LogRel(("CLOUD-NET: Failed to lock '%s' for control. hrc=%x\n", gateways.mGatewayVM.c_str(), hrc));
+            return hrc;
+        }
+
+        ComPtr<IConsole> console;
+        hrc = session->COMGETTER(Console)(console.asOutParam());
+        if (FAILED(hrc))
+        {
+            LogRel(("CLOUD-NET: Failed to obtain console for '%s'. hrc=%x\n", gateways.mGatewayVM.c_str(), hrc));
+            return hrc;
+        }
+
+        ComPtr<IProgress> progress;
+        console->PowerDown(progress.asOutParam()); /* We assume the gateway disk to be immutable! */
+
+#if 0
+        hrc = progress->WaitForCompletion(-1);
+        if (FAILED(hrc))
+            LogRel(("CLOUD-NET: Failed to stop '%s'. hrc=%x\n", gateways.mGatewayVM.c_str(), hrc));
+#endif
+        session->UnlockMachine();
+    }
+#if 0
+    /*
+     * Unfortunately we cannot unregister a machine we've just powered down and unlocked.
+     * It takes some time for the machine to unlock completely.
+     */
+    /** @todo Removal of VM should probably be optional in the future. */
+    SafeIfaceArray<IMedium> media;
+    hrc = pLocalGateway->Unregister(CleanupMode_DetachAllReturnHardDisksOnly, ComSafeArrayAsOutParam(media));
+    if (FAILED(hrc))
+    {
+        LogRel(("CLOUD-NET: Failed to unregister '%s'. hrc=%x\n", gateways.mGatewayVM.c_str(), hrc));
+        return hrc;
+    }
+    ComPtr<IProgress> removalProgress;
+    hrc = pLocalGateway->DeleteConfig(ComSafeArrayAsInParam(media), removalProgress.asOutParam());
+    if (FAILED(hrc))
+    {
+        LogRel(("CLOUD-NET: Failed to delete machine files for '%s'. hrc=%x\n", gateways.mGatewayVM.c_str(), hrc));
+    }
+#endif
+    return hrc;
+}
+
+static HRESULT terminateCloudGateway(ComPtr<IVirtualBox> virtualBox, const GatewayInfo& gateways)
+{
+    if (gateways.mGatewayInstanceId.isEmpty())
+        return S_OK;
+
+    LogRel(("CLOUD-NET: Terminating cloud gateway instance '%s'...\n", gateways.mGatewayInstanceId.c_str()));
+
+    HRESULT hrc = S_OK;
+    try {
+        CloudClient client(virtualBox, gateways.mCloudProvider, gateways.mCloudProfile);
+        client.stopCloudGateway(gateways);
+    }
+    catch (CloudError e)
+    {
+        hrc = e.getRc();
+        LogRel(("CLOUD-NET: Failed to terminate cloud gateway instance (rc=%x).\n", hrc));
+    }
+    return hrc;
+}
+
+HRESULT startGateways(ComPtr<IVirtualBox> virtualBox, ComPtr<ICloudNetwork> network, GatewayInfo& gateways)
+{
+    /* Session used to launch and control the local gateway VM */
+    ComPtr<ISession> session;
+    Bstr strNetwork;
+    HRESULT hrc = session.createInprocObject(CLSID_Session);
+    if (FAILED(hrc))
+        LogRel(("CLOUD-NET: Failed to create a session. hrc=%x\n", hrc));
+    else
+        hrc = network->COMGETTER(NetworkName)(strNetwork.asOutParam());
+    if (SUCCEEDED(hrc))
+        hrc = startLocalGateway(virtualBox, session, strNetwork, gateways);
+    if (SUCCEEDED(hrc))
+        hrc = startCloudGateway(virtualBox, network, gateways);
+    if (SUCCEEDED(hrc))
+        hrc = exchangeInfoBetweenGateways(virtualBox, session, gateways);
+
+    session->UnlockMachine();
+    /** @todo Destroy gateways if unsuccessful (cleanup) */
+
+    return hrc;
+}
+
+HRESULT stopGateways(ComPtr<IVirtualBox> virtualBox, const GatewayInfo& gateways)
+{
+    HRESULT hrc = destroyLocalGateway(virtualBox, gateways);
+    AssertComRC(hrc);
+    hrc = terminateCloudGateway(virtualBox, gateways);
+    AssertComRC(hrc);
+    return hrc;
+}
