#!/bin/bash
# ECS Utils IPv6 - freebsd

#LABEL_FOR_BUILD


##################################global define##################################
g_util_name=ecs-utils-ipv6
g_util_desc="ECS Utils IPv6"
g_gen_desc="#GENERATED BY $g_util_desc"
g_util_version=1.0.2
g_ret=0
g_need_reboot=0
g_need_restart_network=0

# const
G_ACTION_DISABLE=Disable

# log
g_log_dir=/var/log/ecs-utils
mkdir -p "$g_log_dir"
g_log_file=${g_log_dir}/"$g_util_name"_$(date +"%Y%m%d").log


##################################common util##################################
function common::get_datetime() {
    date +"%Y-%m-%d %H:%M:%S.%3N"
}

function common::log() {
    local log_msg="$1"
    local log_level="$2"
    if [ "$log_level" == "" ]; then
        log_level="Info"
    fi
    log_info="[$log_level]\t$log_msg"
    log_info_time="$(common::get_datetime) $log_info"
    echo -e "$log_info_time" >> "$g_log_file"
    if [ "$log_level" != "Debug" ]; then
        echo -e "$log_info"
    fi
}

function common::log_function() {
    local log_msg="Function: ${FUNCNAME[1]} $*"
    common::log "$log_msg" "Debug"
}

function common::ret_log() {
    if [ $g_ret -eq 0 ]; then
        log_msg="$1    OK"
        common::log "$log_msg" "Debug"
    else
        log_msg="$1    Failed"
        common::log "$log_msg" "Error"
        exit 1
    fi
}

function common::key_value_editer() {
    common::log_function "$*"
    local file=$1
    local key=$2
    local value_data=$3

    if ! grep -i "^${key}[[:space:]]*=" "$file" &>/dev/null; then
        echo "$key=$value_data" >> "$file"
    else
        sed -i -e "s/^${key}[[:space:]]*=.*/$key=$value_data/" "$file"
    fi
}


function common::key_editer() {
    common::log_function "$*"
    local file=$1
    local key=$2
    local option=$3

    [[ ! -f $file ]] && return

    if [[ "$option" == "add" ]]; then
        if ! grep -i "^$key" "$file" &>/dev/null; then
            echo "$key" >> "$file"
        else
            sed -i -e "s/^$key.*/$key/" "$file"
        fi
    elif [[ "$option" == "del" ]]; then
        if grep -i "^$key" "$file" &>/dev/null; then
            sed -i -e "/^$key.*/d" "$file"
        fi
    fi
}

function common::key_value_deletor() {
    common::log_function "$*"
    local file=$1
    local key=$2

    if grep -i "^${key}[[:space:]]*=" "$file" &>/dev/null; then
        sed -i -e "/^${key}[[:space:]]*=.*/d" "$file"
    fi
}

function common::get_interfaces() {
    common::log_function
    local interfaces=""
    interfaces=$(ifconfig | grep -i "^[^lo].*flags=" | awk -F':' '{printf $1" "}')
    local ret=$?
    if [ $ret -ne 0 ];then
      echo ""
      return
    fi
    echo "$interfaces"
}

function common::print_version() {
    cat <<EOF
$g_util_desc $g_util_version.
EOF
}

function common::print_usage() {
    cat <<EOF
$g_util_desc $g_util_version.
Usage: $(basename "$g_util_name") [OPTION...]
Examples:
  $g_util_name --help                                   # show usage
  $g_util_name --version                                # show version
  $g_util_name                                          # auto config all dev ipv6
  $g_util_name --static [dev] [ip6s] [prefix_len] [gw6] # config dev static ipv6
  e.g. $g_util_name --static eth0
       $g_util_name --static eth0 xxx::x1 64 xxx::x0
       $g_util_name --static eth0 "xxx::x1 xxx:x2 xxx:x3" 64 xxx::x0
  $g_util_name --enable                                 # enable ipv6
  $g_util_name --disable                                # disable ipv6
EOF
}


##################################metadata util##################################
s_metadata_url="100.100.100.200/latest/meta-data/network/interfaces/macs"
function metadata::get_interface_data() {
    common::log_function "metadata::get_interface_data" "$*"
    local mac=$1
    local data_key=$2
    if [ -z "$mac" ]; then
      echo ""
      return
    fi
    local data=""
    data=$(curl -L -s "$s_metadata_url/$mac/$data_key")
    local ret=$?
    if [ $ret -ne 0 ];then
      echo ""
      return
    fi
    if echo "$data" | grep -q "404 - Not Found";then
      echo ""
      return
    fi
    echo "$data"
}

function metadata::get_interface_array() {
    common::log_function "$*"
    local array=""
    array=$(metadata::get_interface_data "$1" "$2")
    if [ -z "$array" ];then
      echo ""
      return
    fi
    local array_len=${#array}
    local datas=""
    datas=${array:(-$array_len+1):($array_len-2)}
    echo "$datas"
}

function metadata::get_interface_array_data0() {
    common::log_function "$*"
    local datas=""
    datas=$(get_interface_array "$1" "$2")
    if [ -z "$datas" ];then
      echo ""
      return
    fi
    local data=""
    data=$(echo "$datas" | awk -F',' '{ printf $1}')
    echo "$data"
}

function metadata::get_ipv6_prefix_len() {
    common::log_function "$*"
    local vswitch_cidr_block=""
    vswitch_cidr_block=$(metadata::get_interface_data "$1" "vswitch-ipv6-cidr-block")
    if [ -z "$vswitch_cidr_block" ];then
      echo ""
      return
    fi
    local prefix_len=""
    prefix_len=${vswitch_cidr_block##*/}
	echo "$prefix_len"
}

function metadata::get_ipv6s() {
    common::log_function "$*"
    local ipv6s=""
    ipv6s=$(metadata::get_interface_array "$1" "ipv6s")
    if [ -z "$ipv6s" ];then
      echo ""
      return
    fi

    local ipv6_array=""
    ipv6_array=${ipv6s//,/ }
    echo "$ipv6_array"
}

function metadata::get_ipv6_gateway() {
    common::log_function "$*"
    metadata::get_interface_data "$1" "ipv6-gateway"
}


# base functions - freebsd
function base::restart_network () {
    common::log_function
    /etc/netstart restart >> "$g_log_file" 2>&1
}

function base::enable_ipv6() {
    common::log_function
    # check /etc/rc.conf
    local rc_conf_file="/etc/rc.conf"
    if [ ! -f "$rc_conf_file" ]; then
        common::log "$rc_conf_file is not exist." "Error"
        exit 1
    fi
    common::key_value_editer "$rc_conf_file" "ipv6_activate_all_interfaces" "\"YES\""
    g_ret=$?
    common::ret_log "config ipv6 enable"
}

function base::disable_ipv6 () {
    common::log_function
    # check /etc/rc.conf
    key_value_deletor
    local rc_conf_file="/etc/rc.conf"
    if [ ! -f "$rc_conf_file" ]; then
        common::log "$rc_conf_file is not exist." "Error"
        exit 1
    fi
    common::key_value_deletor "$rc_conf_file" "ipv6_activate_all_interfaces"
    g_ret=$?
    common::ret_log "config ipv6 disable"
}

##################################ipv6_util##################################
function ipv6::check_inet6() {
    common::log_function
    if ! ifconfig | grep -q inet6; then
        g_ret=1
    fi
    common::ret_log "check inet6"
}

function ipv6::begin_ipv6() {
    common::log_function
    common::log "ECS Utils IPv6 $g_util_version." "Info"
    common::log "IPv6 $1 Begin..." "Info"
}

function base::get_dev_mac() {
    common::log_function "$*"
    local dev=$1
    local mac=""
    mac=$(ifconfig "$dev" | grep -i "ether[[:space:]]\+.*:.*" | awk '{printf $2}')
    local ret=$?
    if [ $ret -ne 0 ];then
      echo ""
      return
    fi
    echo "$mac"
}

function ipv6::config_dev_ipv6_by_metadata() {
    common::log_function "$*"
    # config ipv6
    local dev=$1
    if [[ -z "$dev" ]]; then
        common::log "paramter [dev] is empty." "Error"
        exit 1
    fi
    local ip6s=$2
    local prefix_len=$3
    local gw6=$4
    if [[ -n "$ip6s" ]] && [[ -n "$gw6" ]] && [[ -n "$prefix_len" ]]; then
        config_dev_ipv6 "$dev" "$ip6s" "$prefix_len" "$gw6"
    else
        # parse dev ipv6 metadata
        local mac=""
        mac=$(base::get_dev_mac "$dev")
        if [ -z "$mac" ]; then
            common::log "get [$dev] mac failed" "Error"
            exit 1
        fi
        ip6s=$(metadata::get_ipv6s "$mac")
        if [ -z "$ip6s" ]; then
            common::log "get [$mac] ipv6 metadata null" "Warn"
        fi
        prefix_len=$(metadata::get_ipv6_prefix_len "$mac")
        if [ -z "$prefix_len" ];then
            common::log "get [$mac] prefix len metadata null" "Warn"
        fi
        gw6=$(metadata::get_ipv6_gateway "$mac")
        if [ -z "$gw6" ]; then
            common::log "get [$mac] ipv6 gateway metadata null" "Warn"
        fi
        config_dev_ipv6 "$dev" "$ip6s" "$prefix_len" "$gw6" 
    fi
}

function ipv6::config_ipv6() {
    common::log_function "$*"
    local dev=$1
    local ip6s=$2
    local prefix_len=$3
    local gw6=$4
    if [[ -n "$dev" ]]; then
        # config dev ipv6
        ipv6::config_dev_ipv6_by_metadata "$dev" "$ip6s" "$prefix_len" "$gw6"
    else
        # config all dev ipv6
        local interfaces=""
        interfaces=$(common::get_interfaces)
        if [ -z "$interfaces" ];then
            g_ret=1
        fi
        common::ret_log "get interfaces"
        for interface in $interfaces
        do
            ipv6::config_dev_ipv6_by_metadata "$interface" "" "" ""
        done
    fi
}

function ipv6::enable_ipv6() {
    common::log_function
    base::enable_ipv6

    if [[ g_need_reboot -eq 0 ]] && [[ g_need_restart_network -eq 0 ]]; then
        # check inet6
        ipv6::check_inet6
    fi
}

function ipv6::disable_ipv6() {
    common::log_function
    base::disable_ipv6

    # config all dev ipv6 disable
    local interfaces=""
    interfaces=$(common::get_interfaces)
    for interface in $interfaces
    do
        clean_ipv6_conf
    done
    g_need_restart_network=1
}

function ipv6::finish_ipv6() {
    common::log_function "$*"
    if [[ g_need_restart_network -eq 1 ]]; then
        #restart $dev
        base::restart_network "$1"
        g_ret=$?
        common::ret_log "restart $dev"
        # if inet6 exsits after disabled ipv6, need to reboot.
        if [ "$1" == "Disable" ]; then
            if ifconfig | grep -q inet6; then
                g_need_reboot=1
            fi
        elif [ "$1" == "Enable" ]; then
            if ! (ifconfig | grep -q inet6); then
                g_need_reboot=1
            fi
        elif [ "$1" == "Config" ]; then
            if ! (ifconfig | grep -q inet6); then
                g_need_reboot=1
            fi
        fi
    fi
    # check if need to reboot
    if [[ g_need_reboot -eq 1 ]]; then
        common::log "IPv6 $1 Finished, Need To Reboot." "Done"
    else
        common::log "IPv6 $1 Finished." "Done"
    fi
}

function ipv6::get_first_ipv6() {
    common::log_function "$*"
    local ip6s=$1
    local first_ip6=""
    for ip6_elem in $ip6s; do
        first_ip6=$ip6_elem
        break
    done
    echo "$first_ip6"
}

function ipv6::get_second_ipv6s() {
    common::log_function "$*"
    local ip6s=$1
    local second_ipv6s=""
    local prefix_len=""
    second_ipv6s=${ip6s#* }
    if [ "$second_ipv6s" == "$ip6s" ]; then
        echo ""
        return
    fi
    echo "$second_ipv6s"
}

function ipv6::main_entry() {
    common::log_function "$*"
    local action=$1
    if [ "$action" == '--version' ]; then
        common::print_version
    elif [ "$action" == '--help' ]; then
        common::print_usage
    elif [ "$action" == '--enable' ]; then
        ipv6::begin_ipv6 "Enable"
        ipv6::enable_ipv6
        ipv6::finish_ipv6 "Enable"
    elif [ "$action" == '--disable' ]; then
        ipv6::begin_ipv6 "$G_ACTION_DISABLE"
        ipv6::disable_ipv6
        ipv6::finish_ipv6 "$G_ACTION_DISABLE"
    elif [ "$action" == '--static' ]; then
        local dev_arg=$2
        if [ -z "$dev_arg" ]; then
            common::log "static paramer [dev] is empty." "Error"
            exit 1
        fi

        local ip6s_arg=$3
        local prefix_len_arg=$4
        local gw6_arg=$5
        if [ -z "$ip6s_arg" ] || [ -z "$prefix_len_arg" ] || [ -z "$gw6_arg" ]; then
            common::log "config paramer [ip6s] or [prefix_len]  or [gw6] is empty." "Error"
            exit 1
        fi

        ipv6::begin_ipv6 "Config"
        ipv6::enable_ipv6
        ipv6::config_ipv6 "$dev_arg" "$ip6s_arg" "$prefix_len_arg" "$gw6_arg"
        ipv6::finish_ipv6 "Config"
    elif [ -z "$action" ]; then
        ipv6::begin_ipv6 "Auto Config"
        ipv6::enable_ipv6
        ipv6::config_ipv6
        ipv6::finish_ipv6 "Auto Config"
    else 
        echo "Invalid option: [$action]"
        common::print_usage
        exit 1
    fi

    exit 0
}


clean_ipv6_conf() {
    common::log_function
    local ifcfg_file="/etc/rc.conf"
    if [ -f "$ifcfg_file" ]; then
        sed -i -e "/^ipv6_ifconfig_.*:.*/d" "$ifcfg_file"
        sed -i -e "/^ipv6_defaultrouter.*:.*/d" "$ifcfg_file"
    else
        common::log "$ifcfg_file is not exist" "Error"
    fi
}

config_one_ipv6() {
    common::log_function "$*"
    local dev=$1
    local ip6=$2
    local prefix_len=$3
    local ifcfg_file="/etc/rc.conf"
    echo -e "\nipv6_ifconfig_$dev=\"$ip6/$prefix_len\"" >> "$ifcfg_file"
}

config_gw6() {
    common::log_function "$*"
    local ifcfg_file=$1
    local gw6=$2
    echo -e "\nipv6_defaultrouter=\"$gw6\"" >> "$ifcfg_file"
}

config_dev_ipv6() {
    common::log_function "$*"
    # config dev ipv6
    local dev=$1
    local ip6s=$2
    local prefix_len=$3
    local gw6=$4
    common::log "Config $dev..." "Info"

    local ifcfg_file="/etc/rc.conf"
    if [ ! -f "$ifcfg_file" ]; then
        touch "$ifcfg_file"
    fi

    if [ -n "$ip6s" ] && [ -n "$prefix_len" ] && [ -n "$gw6" ]; then
        # clean ipv6 conf
        clean_ipv6_conf

        # then append ipv6 conf
        # config first ipv6
        local first_ipv6=""
        first_ipv6=$(ipv6::get_first_ipv6 "$ip6s")
        if [ -z "$first_ipv6" ]; then
            g_ret=1
        fi
        common::ret_log "get first ipv6"
        config_one_ipv6 "$dev" "$first_ipv6" "$prefix_len"
        g_ret=$?
        common::ret_log "config $ifcfg_file first ipv6"

        # config second ipv6s
        local second_ipv6s=""
        second_ipv6s=$(ipv6::get_second_ipv6s "$ip6s")
        if [ -n "$second_ipv6s" ]; then
            for second_ipv6 in $second_ipv6s
            do
                if [ -z "$second_ipv6" ]; then
                    continue
                fi
                config_one_ipv6 "$dev" "$second_ipv6" "$prefix_len"
                g_ret=$?
                common::ret_log "config $ifcfg_file second ipv6s"
            done
        fi

        # config default gw6
        config_gw6 "$ifcfg_file" "$gw6"
        g_ret=$?
        common::ret_log "config $ifcfg_file gw6"
    else
        clean_ipv6_conf
    fi

    g_need_restart_network=1
}

ipv6::main_entry "$@"
