#!/bin/sh
# Copyright (c) 2017-2018 Qualcomm Technologies, Inc.
# All Rights Reserved.
# Confidential and Proprietary - Qualcomm Technologies, Inc.
#
# 2015-2016 Qualcomm Atheros, Inc.
# All Rights Reserved.
# Qualcomm Atheros Confidential and Proprietary.

GWMON_DEBUG_OUTOUT=0
GWMON_SWITCH_CONFIG_COMMAND=swconfig

GWMON_MODE_NO_CHANGE=0
GWMON_MODE_CAP=1
GWMON_MODE_NON_CAP=2
GWMON_KERNEL="4.4.60"
GWMON_PLATFORM=QCA

config_load 'repacd'
config_get_bool gwmon_cfg config 'cfg80211_enable' '0'
if [ "$gwmon_cfg" == "1" ]; then
    REPACD_CFG80211=-cfg80211
else
    REPACD_CFG80211=
fi

. /lib/functions.sh
. /lib/functions/hyfi-iface.sh
. /lib/functions/hyfi-network.sh
. /lib/functions/repacd-wifimon.sh

prev_gw_link='' router_detected=0 gw_iface="" gw_switch_port=""
managed_network='' switch_iface="" vlan_group="" switch_ports=''
cpu_portmap=0
eswitch_support="0"
gw_mac=""
last_hop_count=255

IS_GW_REACHABLE=0
GW_NOT_REACHABLE_TIMESTAMP=0

#Check for Platform & Kernel version
dut_kernel=$(uname -a  | awk '{print $3}')
dut_platform=$(grep -w DISTRIB_RELEASE /etc/openwrt_release | awk -F "='" '{print $2}' | awk '{gsub(/.{3}/,"& ")}1' | awk '{print $1}')

# Emit a log message
# input: $1 - level: the symbolic log level
# input: $2 - msg: the message to log
__gwmon_log() {
    local stderr=''
    if [ "$GWMON_DEBUG_OUTOUT" -gt 0 ]; then
        stderr='-s'
    fi

    logger $stderr -t repacd.gwmon -p "user.$1" "$*"
}

# Emit a log message at debug level
# input: $1 - msg: the message to log
__gwmon_debug() {
    __gwmon_log 'debug' "$1"
}

# Emit a log message at info level
# input: $1 - msg: the message to log
__gwmon_info() {
    __gwmon_log 'info' "$1"
}

# Emit a log message at warning level
# input: $1 - msg: the message to log
__gwmon_warn() {
    __gwmon_log 'warn' "$1"
}

# Obtain a timestamp from the system.
#
# These timestamps will be monontonically increasing and be unaffected by
# any time skew (eg. via NTP or manual date commands).
#
# output: $1 - the timestamp as an integer (with any fractional time truncated)
__gwmon_get_timestamp() {
    timestamp=$(cut -d' ' -f1 < /proc/uptime | cut -d. -f 1)
    eval "$1=$timestamp"
}

__gwmon_find_switch() {
    local vlan_grp

    #Ignore value returned by eswitch_support in repacd. It is to be used by hyd only.
    __hyfi_get_switch_iface switch_iface eswitch_support

    if [ -n "$switch_iface" ]; then
        $GWMON_SWITCH_CONFIG_COMMAND dev switch0 set flush_arl 2>/dev/null
        vlan_grp="$(echo $switch_iface | awk -F. '{print $2}' 2>/dev/null)"
    fi

    if [ -z "$vlan_grp" ]; then
        vlan_group="1"
    else
        vlan_group="$vlan_grp"
    fi
}

__gwmon_get_switch_ports() {
    local config="$1"
    local vlan_group="$2"
    local ports vlan cpu_port __cpu_portmap

    config_get vlan "$config" vlan
    config_get ports "$config" ports

    [ ! "$vlan" = "$vlan_group" ] && return

    cpu_port=$(echo "$ports" | awk '{print $1}')
    ports=$(echo "$ports" | sed "s/$cpu_port //g")
    eval "$3='$ports'"

    cpu_port=$(echo "$cpu_port" | awk -Ft '{print $1}')

    case $cpu_port in
        0) __cpu_portmap=0x01;;
        1) __cpu_portmap=0x02;;
        2) __cpu_portmap=0x04;;
        3) __cpu_portmap=0x08;;
        4) __cpu_portmap=0x10;;
        5) __cpu_portmap=0x20;;
        6) __cpu_portmap=0x40;;
        7) __cpu_portmap=0x80;;
    esac
    eval "$4='$__cpu_portmap'"
}

__gwmon_set_hop_count() {
    local config=$1
    local iface mode

    config_get mode "$config" mode
    config_get iface "$config" ifname

    if [ "$mode" = "ap" ]; then
        __gwmon_info "Setting intf [$iface] hop count $2"
        if [ -z $REPACD_CFG80211 ]; then
            iwpriv "$iface" set_whc_dist "$2"
        else
            cfg80211tool "$iface" set_whc_dist "$2"
        fi
    fi
}

# Check GW reachability over Ethernet backhaul,
# Set hop count correctly to prevent isolated island condition
__gwmon_prevent_island_loop() {
    local retries=3
    local gw_ip next_hop_count=255

    while [ "$retries" -gt 0 ]; do
        gw_ip=$(route -n | grep ^0.0.0.0 | grep "br-$1" | awk '{print $2}')
        [ -z "$gw_ip" ] && break

        # Ping returns zero if at least one response was heard from the specified host
        if ping -W 2 "$gw_ip" -c1 > /dev/null; then
            __gwmon_debug "Ping to GW IP[$gw_ip] success"
            next_hop_count=1
            break
        else
            # no ping response was received, retry
            retries=$((retries -1))
            __gwmon_debug "Ping to GW IP[$gw_ip] failed ($retries retries left)"
        fi
    done

    if [ $last_hop_count -ne $next_hop_count ]; then
        __gwmon_info "Changing hop_count from $last_hop_count to $next_hop_count"
        config_load wireless
        config_foreach __gwmon_set_hop_count wifi-iface $next_hop_count
        last_hop_count=$next_hop_count
    fi
}

__gwmon_check_gwinterface() {
    local gw_iface_found=1
    local giface
    local siface

    # Check the inteface is the vlan of ether_iface
    giface=${1//.[0-9]*/}
    siface=${2//.[0-9]*/}
    if [ "$siface" = "$giface" ]; then
        eval "$3=$gw_iface_found"
        __gwmon_info "Both Switch and Gw Interface Match"
    else
        eval "$3=0"
    fi
}

# Check the link status of the interface connected to the gataway
# returns: 0 if gateway is detected; non-zero if it is not
__gwmon_check_gw_iface_link() {
    local ret
    local interf

    __gwmon_check_gwinterface $gw_iface $switch_iface interf
    if [ "$interf" -gt 0 ]; then
        local link_status

        # Before we check local link status, make sure gw_iface (eth) is up
        ret=$(ifconfig $gw_iface | grep "UP[A-Z' ']*RUNNING")
        __gwmon_info "Checking ifconfig $gw_iface is up and running, ret= $ret"
        [ -z "$ret" ] && prev_gw_link="down" && return 1
        for switch_port in $gw_switch_port;do
            if [ "$switch_port" = '0' ]; then
                continue
            fi
            link_status=$($GWMON_SWITCH_CONFIG_COMMAND dev switch0 port $gw_switch_port show | grep link | awk -F: '{print $NF}')
        done
        if [ ! "$link_status" = "down" ]; then
            # link is up
            if [ ! "$prev_gw_link" = "up" ]; then
                __gwmon_info "Link to GW UP"
                prev_gw_link="up"
            fi
            # Check if GW is reachable, set appropriate hop count to avoid isolated island condition
            __gwmon_prevent_island_loop $managed_network
            return 0
        fi
    else
        ret=$(ifconfig $gw_iface | grep "UP[A-Z' ']*RUNNING")
        [ -n "$ret" ] && return 0
    fi

    if [ ! "$prev_gw_link" = "down" ]; then
        __gwmon_info "Link to GW DOWN"
        prev_gw_link="down"
    fi
    return 1
}

# __gwmon_check_gateway
# input: $1 1905.1 managed bridge
# output: $2 Gateway interface
# returns: 0 if gateway is detected; non-zero if not detected
__gwmon_check_gateway() {
    local gw_ip gw_br_port __gw_iface
    local ether_ifaces_full ether_ifaces
    local ether_iface ret
    local interface_gw

    gw_ip=$(route -n | grep ^0.0.0.0 | grep "br-$1" | awk '{print $2}')
    [ -z "$gw_ip" ] && return 1

    if ping -W 2 "$gw_ip" -c1 > /dev/null; then
        if [ "$IS_GW_REACHABLE" -eq 0 ]; then
            __gwmon_info "GW ($gw_ip) reachable"
        fi
        IS_GW_REACHABLE=1
        GW_NOT_REACHABLE_TIMESTAMP=0
    else
        if [ "$IS_GW_REACHABLE" -eq 1 ]; then
            __gwmon_info "GW ($gw_ip) NOT reachable"
        fi
        IS_GW_REACHABLE=0
        if [ "$GW_NOT_REACHABLE_TIMESTAMP" -eq 0 ]; then
            __gwmon_get_timestamp GW_NOT_REACHABLE_TIMESTAMP
        fi
    fi

    gw_mac=$(grep -w "$gw_ip" /proc/net/arp | grep -w "$1" | awk '{print $4}')
    [ -z "$gw_mac" ] && return 1

    # Instead of using hyctl (which may not be installed if not running the
    # full Hy-Fi build), use brctl instead. One additional step of mapping
    # the port number to an interface name is needed though.
    gw_br_port=$(brctl showmacs "br-$1" | grep -i "$gw_mac" | awk '{print $1}')
    [ -z "$gw_br_port" ] && return 1

    __gw_iface=$(brctl showstp "br-$1" | grep \("$gw_br_port"\) | awk '{print $1}')
    [ -z "$__gw_iface" ] && return 1

    # Get all Ethernet interfaces
    hyfi_get_ether_ifaces "$1" ether_ifaces_full
    hyfi_strip_list "$ether_ifaces_full" ether_ifaces

    # Check if this interface belongs to our network
    for ether_iface in $ether_ifaces; do
        if [ "$ether_iface" = "$__gw_iface" ]; then
            gw_iface=$__gw_iface
            __gwmon_info "Detected Gateway on interface $gw_iface"
            __gwmon_check_gwinterface "$gw_iface" "$switch_iface" interface_gw
            if [ "$interface_gw" -gt 0 ]; then
                local portmap __gw_switch_port=99
                if [[ "$dut_kernel" = "$GWMON_KERNEL" && "$dut_platform" = "$GWMON_PLATFORM" ]]; then
                    __gw_switch_port=$($GWMON_SWITCH_CONFIG_COMMAND dev switch0 get arl_table | grep -i "$gw_mac" | awk -F ":" '{print $1}' | awk '{print $2}')
                else
                    portmap=$($GWMON_SWITCH_CONFIG_COMMAND dev switch0 get dump_arl | grep -i "$gw_mac" | grep -v $cpu_portmap | awk '{print $4}')

                    case $portmap in
                        0x01) __gw_switch_port=0;;
                        0x02) __gw_switch_port=1;;
                        0x04) __gw_switch_port=2;;
                        0x08) __gw_switch_port=3;;
                        0x10) __gw_switch_port=4;;
                        0x20) __gw_switch_port=5;;
                        0x40) __gw_switch_port=6;;
                        0x80) __gw_switch_port=7;;
                    esac
                fi
                if [ "$__gw_switch_port" -eq 99 ]; then
                    __gwmon_warn "invalid port map $portmap"
                    return 1
                fi
                gw_switch_port=$__gw_switch_port
            fi
            __repacd_wifimon_bring_iface_down $sta_iface_5g
            __repacd_wifimon_bring_iface_down $sta_iface_24g
            return 0
        fi
    done

    # also check the loop prevention code to see if it believes we have
    # an upstream facing Ethernet interface
    local num_upstream
    num_upstream=$(lp_numupstream)
    if [ "${num_upstream}" -gt 0 ]; then
        return 0
    fi

    return 1
}

# Determine if the GW is reachable and update the router_detected global
# variable accordingly.
__gwmon_update_router_detected() {
    if __gwmon_check_gateway "$managed_network"; then
        router_detected=1
    else
        router_detected=0
    fi
}

# Check whether the configured mode matches the mode that is determined by
# checking for connectivity to the gateway.
#
# input: $1 cur_role: the current mode that is configured
# input: $2 start_mode: the mode in which the auto-configuration script is being
#                       run; This is used by the init script to help indicate
#                       that it was an explicit change into this mode.
#                       If the mode was CAP, then it should take some time
#                       before it is willing to switch back to non-CAP due
#                       to lack of a gateway.
# input: $3 managed_network: the logical name for the network interfaces to
#                            monitor
#
# return: value indicating the desired mode of operation
#  - $GWMON_MODE_CAP to act as the main AP
#  - $GWMON_MODE_NON_CAP to switch to being a secondary AP
#  - $GWMON_MODE_NO_CHANGE for now change in the mode
__gwmon_init() {
    local cur_mode=$1
    local start_mode=$2
    local eth_mon_enabled

    managed_network=$3
    __gwmon_find_switch "$managed_network"
    [ -n "$switch_iface" ] && __gwmon_info "found switch on $switch_iface VLAN=$vlan_group"

    config_load repacd
    config_get eth_mon_enabled repacd 'EnableEthernetMonitoring' '0'

    config_load network
    config_foreach __gwmon_get_switch_ports switch_vlan "$vlan_group" switch_ports cpu_portmap
    __gwmon_info "switch ports in the $managed_network network: $switch_ports"

    __gwmon_update_router_detected

    if [ "$cur_mode" = "CAP" ]; then
        if [ "$router_detected" -eq 0 ]; then
            if [ "$eth_mon_enabled" -eq 0 ] && [ ! "$start_mode" = "CAP" ]; then
                return $GWMON_MODE_NON_CAP
            else
                local retries=3

                while [ "$retries" -gt 0 ]; do
                    __gwmon_update_router_detected
                    [ "$router_detected" -gt 0 ] && break
                    retries=$((retries - 1))
                    __gwmon_debug "redetecting gateway ($retries retries left)"
                done

                # If gateway was still not detected after our attempts,
                # indicate we should change to non-CAP mode.
                if [ "$router_detected" -eq 0 ]; then
                    if [ "$eth_mon_enabled" -eq 0 ]; then
                        return $GWMON_MODE_NON_CAP
                    else
                        return $GWMON_MODE_NO_CHANGE
                    fi
                fi
            fi
        fi
    else   # non-CAP mode
        if [ "$router_detected" -eq 1 ]; then
            local mixedbh
            mixedbh=$(uci get repacd.repacd.EnableMixedBackhaul 2>/dev/null)
            if [ "$mixedbh" != "1" ]; then
                return $GWMON_MODE_CAP
            fi
        fi
    fi

    return $GWMON_MODE_NO_CHANGE
}

# return: 2 to indicate CAP mode; 1 for non-CAP mode; 0 for no change
__gwmon_check() {
    if [ "$router_detected" -eq 0 ]; then
        __gwmon_update_router_detected

        if [ "$router_detected" -gt 0 ]; then
            local mixedbh
            mixedbh=$(uci get repacd.repacd.EnableMixedBackhaul 2>/dev/null)
            # if we want to support mixed backhaul, e.g., if we want to
            # enable both WiFi and Ethernet backhaul, then we stay in
            # non-cap mode so the STA interfaces remain up.  otherwise,
            # set to cap mode which brings down the STA interfaces.
            if [ "$mixedbh" != "1" ]; then
                return $GWMON_MODE_CAP
            fi
        fi
    else
        if ! __gwmon_check_gw_iface_link "$managed_network"; then
            # Gateway is gone
            router_detected=0
            gw_iface=""
            gw_switch_port=""
            return $GWMON_MODE_NON_CAP
        fi
    fi

    return $GWMON_MODE_NO_CHANGE
}
