#!/bin/sh
#
### BEGIN INIT INFO
# Provides: cuttlefish-host-resources
# Required-Start: $network $remote_fs
# Required-Stop: $network $remote_fs
# Default-Start: 2 3 4 5
# Default-Stop: 0 1 6
# Short-Description: Set up initial cuttlefish environment
# Description: This script sets up the initial cuttlefist environment,
#              optionally booting a default cuttlefish release.
### END INIT INFO
#
# Copyright (C) 2017 The Android Open Source Project
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
#      http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.

# Make sure calls to this script get redirected to systemctl when
# using systemd

# some system may not support bridge type by default
modprobe bridge

. /lib/lsb/init-functions

if [ -f /etc/default/cuttlefish-host-resources ]; then
    . /etc/default/cuttlefish-host-resources
fi

num_cvd_accounts=${num_cvd_accounts:-10}
wifi_bridge_interface=${bridge_interface:-cvd-wbr}
ethernet_bridge_interface=${bridge_interface:-cvd-ebr}
ipv4_bridge=${ipv4_bridge:-1}
ipv6_bridge=${ipv4_bridge:-1}
dns_servers=${dns_servers:-8.8.8.8,8.8.4.4}
dns6_servers=${dns6_servers:-2001:4860:4860::8888,2001:4860:4860::8844}

if [ -z ${bridge_interface} ]; then
  create_bridges=1
fi

# Newer distros prefer nft, but broute is not supported, so use
# the legacy ebtables instead
ebtables=$(which ebtables-legacy)
# If there's no ebtables-legacy, it's probably an older distro,
# and ebtables should support broute directly
ebtables=${ebtables:-ebtables}
# Similarly for iptables
iptables=$(which iptables-legacy)
iptables=${iptables:-iptables}

# Make sure these directories exist, since they are used multiple times
mkdir -p /run /var/run

# Start DHCP server on an interface
# $1 = interface to bind to
# $2 = listen address (IPv4 gateway)
# $3 = dhcp_range ("a.b.c.d,w.x.y.z")
# $4 = IPv6 address prefix ("a:b::")
# $5 = IPv6 address prefix length
start_dnsmasq() {
    if [ -n "${4}" -a -n "${5}" ]; then
        ipv6_args="--dhcp-range=${4},ra-stateless,${5} --enable-ra"
    else
        ipv6_args=""
    fi
    dnsmasq \
      --port=0 \
      --strict-order \
      --except-interface=lo \
      --interface="${1}" \
      --listen-address="${2}" \
      --bind-interfaces \
      --dhcp-range="${3}" \
      --dhcp-option="option:dns-server,${dns_servers}" \
      --dhcp-option="option6:dns-server,${dns6_servers}" \
      --conf-file="" \
      --pid-file=/var/run/cuttlefish-dnsmasq-"${1}".pid \
      --dhcp-leasefile=/var/run/cuttlefish-dnsmasq-"${1}".leases \
      --dhcp-no-override \
      ${ipv6_args}

}

# Stop DHCP server on an interface
# $1 = interface to stop on
stop_dnsmasq() {
    if [ -f /var/run/cuttlefish-dnsmasq-"${1}".pid ]; then
        kill $(cat /var/run/cuttlefish-dnsmasq-"${1}".pid)
    fi
}

# Create a tap interface (with no address)
# $1 = tap name
create_tap() {
    ip tuntap add dev "${1}" mode tap group cvdnetwork vnet_hdr
    ip link set dev "${1}" up
}

# Destroy a tap interface
# $1 = tap name
destroy_tap() {
    ip link set dev "${1}" down
    ip link delete "${1}"
}

# Create a tap interface
# The tap device will have the IP address and DHCP server running on it, and
# it will be added to a bridge with no address (for routing).
# $1 = tap interface name
# $2 = ip address base ("a.b.c")
# $3 = interface index within ip address base
# $4 = IPv6 prefix ("a:b::")
# $5 = IPv6 prefix length
create_interface() {
    tap="${1}"
    gateway="${2}.$((4*$3 - 3))"
    netmask="/30"
    network="${2}.$((4*$3 - 4))${netmask}"
    ipv6_prefix="${4}"
    ipv6_prefix_length="${5}"

    create_tap "${tap}"
    ip addr add "${gateway}${netmask}" broadcast + dev "${tap}"
    if [ -n "${ipv6_prefix}" -a -n "${ipv6_prefix_length}" ]; then
        ip -6 addr add "${ipv6_prefix}1/${ipv6_prefix_length}" dev "${tap}"
    fi
    "${iptables}" -t nat -A POSTROUTING -s "${network}" -j MASQUERADE
}

# Destroy a tap interface
# $1 = tap interface name
# $2 = ip address base ("a.b.c")
# $3 = interface index within ip address base
# $4 = IPv6 prefix ("a:b::")
# $5 = IPv6 prefix length
destroy_interface() {
    tap="${1}"
    gateway="${2}.$((4*$3 - 3))"
    netmask="/30"
    network="${2}.$((4*$3 - 4))${netmask}"
    ipv6_prefix="${4}"
    ipv6_prefix_length="${5}"

    "${iptables}" -t nat -D POSTROUTING -s "${network}" -j MASQUERADE
    ip addr del "${gateway}${netmask}" dev "${tap}"
    if [ -n "${ipv6_prefix}" -a -n "${ipv6_prefix_length}" ]; then
        ip -6 addr del "${ipv6_prefix}1/${ipv6_prefix_length}" dev "${tap}"
    fi
    destroy_tap "${tap}"
}

# Create many tap devices on a single bridge (wifi or ethernet)
# $1 = IPv4 address base ("a.b.c")
# $2 = bridge interface name
# $3 = tap base name
# $4 = IPv6 prefix ("a:b::")
# $5 = IPv6 prefix length
create_bridged_interfaces() {
    if [ "${create_bridges}" = "1" ]; then
        ip link add name "${2}" type bridge forward_delay 0 stp_state 0
        ip link set dev "${2}" up

        echo 0 > /proc/sys/net/ipv6/conf/${2}/disable_ipv6
        echo 0 > /proc/sys/net/ipv6/conf/${2}/addr_gen_mode
        echo 1 > /proc/sys/net/ipv6/conf/${2}/autoconf
    fi
    for i in $(seq ${num_cvd_accounts}); do
        tap="$(printf ${3}-%02d $i)"
        create_tap "${tap}"
        ip link set dev "${tap}" master "${2}"
        if [ "${create_bridges}" != "1" ]; then
            if [ "$ipv4_bridge" != "1" ]; then
                $ebtables -t broute -A BROUTING -p ipv4 --in-if  "${tap}" -j DROP
                $ebtables -t filter -A FORWARD  -p ipv4 --out-if "${tap}" -j DROP
            fi
            if [ "$ipv6_bridge" != "1" ]; then
                $ebtables -t broute -A BROUTING -p ipv6 --in-if  "${tap}" -j DROP
                $ebtables -t filter -A FORWARD  -p ipv6 --out-if "${tap}" -j DROP
            fi
        fi
    done
    if [ "${create_bridges}" = "1" ]; then
        gateway="${1}.1"
        netmask="/24"
        network="${1}.0${netmask}"
        dhcp_range="${1}.2,${1}.255"
        ipv6_prefix="${4}"
        ipv6_prefix_length="${5}"
        ip addr add "${gateway}${netmask}" broadcast + dev "${2}"
        if [ -n "${ipv6_prefix}" -a -n "${ipv6_prefix_length}" ]; then
            ip -6 addr add "${ipv6_prefix}1/${ipv6_prefix_length}" dev "${2}"
        fi
        start_dnsmasq \
          "${2}" "${gateway}" "${dhcp_range}" \
          "${ipv6_prefix}" "${ipv6_prefix_length}"
        "${iptables}" -t nat -A POSTROUTING -s "${network}" -j MASQUERADE
    fi
}

# Destroy many tap devices and a single bridge (wifi or ethernet)
# $1 = ip address base ("a.b.c")
# $2 = bridge interface name
# $3 = tap base name
# $4 = IPv6 prefix ("a:b::")
# $5 = IPv6 prefix length
destroy_bridged_interfaces() {
    if [ "${create_bridges}" = "1" ]; then
        gateway="${1}.1"
        netmask="/24"
        network="${1}.0${netmask}"
        ipv6_prefix="${4}"
        ipv6_prefix_length="${5}"
        "${iptables}" -t nat -D POSTROUTING -s "${network}" -j MASQUERADE
        stop_dnsmasq "${2}"
        if [ -n "${ipv6_prefix}" -a -n "${ipv6_prefix_length}" ]; then
            ip -6 addr del "${ipv6_prefix}1/${ipv6_prefix_len}" dev "${2}"
        fi
        ip addr del "${gateway}${netmask}" dev "${2}"
    fi
    for i in $(seq ${num_cvd_accounts}); do
        tap="$(printf ${3}-%02d $i)"
        if [ "${create_bridges}" != "1" ]; then
            if [ "$ipv4_bridge" != "1" ]; then
                $ebtables -t filter -D FORWARD  -p ipv4 --out-if "${tap}" -j DROP
                $ebtables -t broute -D BROUTING -p ipv4 --in-if  "${tap}" -j DROP
            fi
            if [ "$ipv6_bridge" != "1" ]; then
                $ebtables -t filter -D FORWARD  -p ipv6 --out-if "${tap}" -j DROP
                $ebtables -t broute -D BROUTING -p ipv6 --in-if  "${tap}" -j DROP
            fi
        fi
        destroy_tap "${tap}"
    done
    if [ "${create_bridges}" = "1" ]; then
        ip link set dev "${2}" down
        ip link delete "${2}"
    fi
}

start() {
    # Enable ip forwarding
    echo 1 > /proc/sys/net/ipv4/ip_forward
    echo 1 > /proc/sys/net/ipv6/conf/all/forwarding

    # Ethernet
    # 192.168.98.X for cvd-ebr and cvd-etap-XX
    create_bridged_interfaces \
      192.168.98 "${ethernet_bridge_interface}" cvd-etap \
      "${ethernet_ipv6_prefix}" "${ethernet_ipv6_prefix_length}"

    # Mobile Network
    # 192.168.97.X from cvd-mtap-01 to cvd-mtap-64
    # 192.168.93.X from cvd-mtap-65 to cvd-mtap-128
    for i in $(seq ${num_cvd_accounts}); do
        tap="$(printf cvd-mtap-%02d $i)"
        if [ $i -lt 65 ]; then
            create_interface $tap 192.168.97 $i
        elif [ $i -lt 129 ]; then
            create_interface $tap 192.168.93 $(($i - 64))
        fi
    done

    # Wireless Network
    # cvd-wbr and cvd-wtap-XX for legacy wireless network without distinguished
    # subnet between tap interfaces, cvd-wifiap-XX with distinguished subnet for
    # running several OpenWRT instances simultaneously.
    # 192.168.96.X for cvd-wbr and cvd-wtap-XX
    # 192.168.94.X from cvd-wifiap-01 to cvd-wifiap-64
    # 192.168.95.X from cvd-wifiap-65 to cvd-wifiap-128
    create_bridged_interfaces \
      192.168.96 "${wifi_bridge_interface}" cvd-wtap \
      "${wifi_ipv6_prefix}" "${wifi_ipv6_prefix_length}"
    for i in $(seq ${num_cvd_accounts}); do
        tap="$(printf cvd-wifiap-%02d $i)"
        if [ $i -lt 65 ]; then
            create_interface $tap 192.168.94 $i
        elif [ $i -lt 129 ]; then
            create_interface $tap 192.168.95 $(($i - 64))
        fi
    done

    # When running inside a privileged container, set the ownership and access
    # of these device nodes.
    if test -f /.dockerenv; then
        chown root:kvm /dev/kvm
        chown root:cvdnetwork /dev/vhost-net
        chown root:cvdnetwork /dev/vhost-vsock
        chmod ug+rw /dev/kvm
        chmod ug+rw /dev/vhost-net
        chmod ug+rw /dev/vhost-vsock
    fi

    # Try to preload the Nvidia modeset kernel module.
    /usr/bin/nvidia-modprobe --modeset || /bin/true
}

stop() {
    # Ethernet
    destroy_bridged_interfaces \
      192.168.98 "${ethernet_bridge_interface}" cvd-etap \
      "${ethernet_ipv6_prefix}" "${ethernet_ipv6_prefix_length}"

    # Mobile Network
    for i in $(seq ${num_cvd_accounts}); do
        tap="$(printf cvd-mtap-%02d $i)"
        if [ $i -lt 65 ]; then
            destroy_interface $tap 192.168.97 $i
        elif [ $i -lt 129 ]; then
            destroy_interface $tap 192.168.93 $(($i - 64))
        fi
    done

    # Wireless Network
    destroy_bridged_interfaces \
      192.168.96 "${wifi_bridge_interface}" cvd-wtap \
      "${wifi_ipv6_prefix}" "${wifi_ipv6_prefix_length}"
    for i in $(seq ${num_cvd_accounts}); do
        tap="$(printf cvd-wifiap-%02d $i)"
        if [ $i -lt 65 ]; then
            destroy_interface $tap 192.168.94 $i
        elif [ $i -lt 129 ]; then
            destroy_interface $tap 192.168.95 $(($i - 64))
        fi
    done
}

usage() {
    echo $0: start\|stop
}

if test $# != 1; then
    usage
fi
case "$1" in
    --help)
        usage 0
        ;;
    start|stop)
        "$1"
        ;;
    restart)
        stop && start
        ;;
    condrestart|try-restart)
        stop && start
        ;;
    reload|force-reload)
        # Nothing to do; we reread configuration on each invocation
        ;;
    status)
        rh_status
        ;;
    shutdown)
        stop
        ;;
    *)
        usage
        ;;
esac
exit $RETVAL
