#!/bin/sh
# Install ebtables rules

BR_RULE=""
BR6_RULE=""
BR_RULE_DSCP2PBIT=""
DSCP2PBIT_MAPPING=""

init_broute_rule() {
    BR_RULE=""
    BR6_RULE=""
}

init_broute_dscp2pbit_rule() {
    BR_RULE_DSCP2PBIT=""
    DSCP2PBIT_MAPPING=""
}

broute_filter_on_l3_if() {
    BR_RULE="$BR_RULE --logical-in $1"
}

broute_filter_on_src_if() {
    BR_RULE="$BR_RULE --in-if $1"
}

broute_filter_on_src_mac() {
    BR_RULE="$BR_RULE --src $1"
}

broute_filter_on_dst_mac() {
    BR_RULE="$BR_RULE --dst $1"
}

broute_filter_on_pcp() {
    # 5.04 onwards the vlan extension in ebtables is used for classification
    # on the basis of vlan params which needs proto to be defined as 802_1Q in
    # order to add a rule, now, proto can also be defined by specifying proto uci
    # option as well as the rule may have a vlan id as well in which case the
    # proto will already be present, hence, this check to not add --proto more
    # than once
    case "$BR_RULE" in
    *proto*)
        BR_RULE="$BR_RULE --vlan-prio $1"
        ;;
    *)
        BR_RULE="$BR_RULE --proto 802_1Q --vlan-prio $1"
        ;;
    esac
}

broute_filter_on_ether_type() {
    BR_RULE="$BR_RULE --proto $1"
}

broute_filter_on_ether_type6() {
    BR6_RULE="$BR6_RULE --proto IPv6"
}

ebt_match_src_ip() {
    BR_RULE="$BR_RULE --ip-src $1"
}

ebt_match_dst_ip() {
    BR_RULE="$BR_RULE --ip-dst $1"
}

ebt_match_ipv6_src_ip() {
    BR_RULE="$BR_RULE --ip6-src $1"
}

ebt_match_ipv6_dst_ip() {
    BR_RULE="$BR_RULE --ip6-dst $1"
}

ebt_match_ip_src_port() {
    BR_RULE="$BR_RULE --ip-source-port $1"
}

ebt_match_ip_dst_port() {
    BR_RULE="$BR_RULE --ip-destination-port $1"
}

ebt_match_ipv6_src_port() {
    #when ethertype is not configured by user then both proto rules of ipv4
    #and ipv6 to be installed so update BR6_RULE string as well otherwise
    #update BR_RULE only for installation of ipv6 proto rule only.
    if [ -n "$BR6_RULE" ]; then
        BR6_RULE="$BR6_RULE --ip6-source-port $1"
    else
        BR_RULE="$BR_RULE --ip6-source-port $1"
    fi
}

ebt_match_ipv6_dst_port() {
    #when ethertype is not configured by user then both proto rules of ipv4
    #and ipv6 to be installed so update BR6_RULE string as well otherwise
    #update BR_RULE only for installation of ipv6 proto rule only.
    if [ -n "$BR6_RULE" ]; then
        BR6_RULE="$BR6_RULE --ip6-destination-port $1"
    else
        BR_RULE="$BR_RULE --ip6-destination-port $1"
    fi
}

ebt_match_ip_protocol() {
    BR_RULE="$BR_RULE --ip-proto $1"
}

ebt_match_ip_icmp_type() {
    BR_RULE="$BR_RULE --ip-icmp-type $1"
}

ebt_match_ipv6_protocol() {
    #when ethertype is not configured by user then both proto rules of ipv4
    #and ipv6 to be installed so update BR6_RULE string as well otherwise
    #update BR_RULE only for installation of ipv6 proto rule only.
    if [ -n "$BR6_RULE" ]; then
        BR6_RULE="$BR6_RULE --ip6-proto $1"
    else
        BR_RULE="$BR_RULE --ip6-proto $1"
    fi
}

ebt_match_ipv6_icmp_type() {
    #when ethertype is not configured by user then both proto rules of ipv4
    #and ipv6 to be installed so update BR6_RULE string as well otherwise
    #update BR_RULE only for installation of ipv6 proto rule only.	
    if [ -n "$BR6_RULE" ]; then
        BR6_RULE="$BR6_RULE --ip6-icmp-type $1"
    else
        BR_RULE="$BR_RULE --ip6-icmp-type $1"
    fi
}

broute_filter_on_vid() {

    if [ -z "$1" ] || [ "$1" -lt 0 ]; then
        return
    fi

    case "$BR_RULE" in
    *proto*)
        BR_RULE="$BR_RULE --vlan-id $1"
        ;;
    *)
        BR_RULE="$BR_RULE --proto 802_1Q --vlan-id $1"
        ;;
    esac
}

broute_append_rule() {
    # if src_if is loopback, then add the rule to OUTPUT(qos_output) chain of nat table
    if [ "$src_if" = "lo" ]; then
        echo "ebtables --concurrent -t nat -A qos_output $BR_RULE" >> /tmp/qos/classify.ebtables
        if [ -n "$BR6_RULE" ]; then
            echo "ebtables --concurrent -t nat -A qos_output $BR6_RULE" >> /tmp/qos/classify.ebtables
        fi
	return
    fi

    local broute_chain="$1"
    #when ethertype is not configured by user then both proto rules of ipv4
    #and ipv6 to be installed otherwise install ipv6 proto rule only.
    echo "ebtables --concurrent -t broute -A $broute_chain $BR_RULE" >> /tmp/qos/classify.ebtables
    if [ -n "$BR6_RULE" ]; then
        echo "ebtables --concurrent -t broute -A $broute_chain $BR6_RULE" >> /tmp/qos/classify.ebtables
    fi
}

broute_apply_dscp2pbit_rule() {
    # Write dscp2pbit broute rule to classify.ebtables file
    echo "ebtables --concurrent -t broute -A dscp2pbits -p 0x8100 $BR_RULE_DSCP2PBIT" >> /tmp/qos/classify.ebtables
}

broute_rule_set_xlate_vid_pbit() {
    local vid_mark="$1"
    local pcp_mark="$2"

    BR_RULE="$BR_RULE -j vlantranslation"
    [ -n "$vid_mark" ] && BR_RULE="$BR_RULE --vlanxlate-vid-set $vid_mark"
    [ -n "$pcp_mark" ] && BR_RULE="$BR_RULE --vlanxlate-prio-set $pcp_mark"
    BR_RULE="$BR_RULE --vlanxlate-target CONTINUE"
}

set_ip_addr()
{
    local cid="$1"
    local match_src_ip_func="$2"
    local match_dst_ip_func="$3"

    config_get src_ip "$cid" "src_ip"
    config_get dst_ip "$cid" "dest_ip"

    if [ -n "$src_ip" ]; then
        $match_src_ip_func "$src_ip"
    fi

    if [ -n "$dst_ip" ]; then
        $match_dst_ip_func "$dst_ip"
    fi
}

set_ports()
{
    local cid="$1"
    local match_src_port_func="$2"
    local match_dst_port_func="$3"
    local src_port=""
    local dst_port=""
    local src_port_range=""
    local dst_port_range=""

    config_get src_port "$cid" "src_port"
    config_get dst_port "$cid" "dest_port"
    config_get src_port_range "$cid" "src_port_range"
    config_get dst_port_range "$cid" "dest_port_range"

    if [ -n "$src_port" ] && [ -n "$src_port_range" ] ; then
        $match_src_port_func "$src_port:$src_port_range"
    elif [ -n "$src_port" ] ; then
        $match_src_port_func "$src_port"
    fi

    if [ -n "$dst_port" ] && [ -n "$dst_port_range" ] ; then
        $match_dst_port_func "$dst_port:$dst_port_range"
    elif [ -n "$dst_port" ] ; then
        $match_dst_port_func "$dst_port"
    fi
}

protocol_string_to_num()
{
    local value="-1"

    case "$1" in
    TCP|tcp)
        value=6
        ;;
    UDP|udp)
        value=17
        ;;
    ICMP|icmp)
        value=1
        ;;
    ICMPv6|icmpv6)
        value=58
        ;;
    IGMP|igmp)
        value=2
        ;;
    SCTP|sctp)
        value=132
        ;;
    *[0-9]*)
        value="$1"
        ;;
    *)
        value=-1
        ;;
    esac
    echo $value
}

handle_ebtables_rules() {
    local sid="$1"
    local is_l2_rule=0
    local src_dhcp_options=""
    local dst_dhcp_options=""
    local protocol=""
    local ip_version=""

    config_get pcp_mark "$sid" "pcp_mark"
    config_get dscp_filter "$sid" "dscp_filter"
    # return if its a classfy section for DSCP to p-bit mapping
    if [ -n "$pcp_mark" ] && [ -n "$dscp_filter" ]; then
        return
    fi

    init_broute_rule

    config_get src_if "$sid" "ifname"
    config_get src_mac "$sid" "src_mac"
    config_get dst_mac "$sid" "dst_mac"
    config_get pcp_check "$sid" "pcp_check"
    config_get eth_type "$sid" "ethertype"
    config_get vid "$sid" "vid_check"
    config_get dhcp_type "$sid" "dhcp_type" # dhcpv4 or v6
    config_get src_vcid "$sid" "src_vendor_class_id" # dhcp option 60
    config_get dst_vcid "$sid" "dst_vendor_class_id" # dhcp option 60
    config_get src_clid "$sid" "src_client_id" # dhcp option 61
    config_get dst_clid "$sid" "dst_client_id" # dhcp option 61
    config_get src_ucid "$sid" "src_user_class_id" # dhcp option 77
    config_get dst_ucid "$sid" "dst_user_class_id" # dhcp option 77
    config_get traffic_class "$sid" "traffic_class"
    config_get protocol "$sid" "proto"
    config_get all_interfaces "$sid" "all_interfaces"
    config_get l3_ifname "$sid" "l3_ifname"
    config_get vid_mark "$sid" "vid_mark"

    if [ -n "$l3_ifname" ]; then
        broute_filter_on_l3_if "$l3_ifname"
        is_l2_rule=1
    fi

    if [ "$all_interfaces" == "1" ]; then
	is_l2_rule=1
    elif [ -n "$src_if" ]; then
	for interf in $(jsonfilter -i /etc/board.json -e @.network.lan.ports[*] -e @.network.lan.device -e @.network.wan.device | xargs); do
	    if [ "$src_if" == "$interf" ]; then
		src_if="$src_if+"
		broute_filter_on_src_if "$src_if"
		is_l2_rule=1
	    fi
	done
    fi

    if [ -n "$src_mac" ]; then
        broute_filter_on_src_mac "$src_mac"
        is_l2_rule=1
    fi

    if [ -n "$dst_mac" ]; then
        broute_filter_on_dst_mac "$dst_mac"
        is_l2_rule=1
    fi

    if [ -n "$pcp_check" ]; then
        broute_filter_on_pcp "$pcp_check"
        is_l2_rule=1
    fi

    if [ -n "$eth_type" ]; then
        broute_filter_on_ether_type "$eth_type"
        is_l2_rule=1
    fi

    if [ -n "$vid" ]; then
        broute_filter_on_vid "$vid"
        is_l2_rule=1
    fi

    case $(echo "$eth_type" | tr 'a-z' 'A-Z') in
    IPV4|0800|800|0X0800)
        ip_version=4
        ;;
    IPV6|86DD|0X86DD)
        ip_version=6
        ;;
    *)
        if [ -z "$eth_type" ]; then
            case "$src_ip$dst_ip" in
            *.*)
                ip_version=4
                broute_filter_on_ether_type "IPv4"
                ;;
            *:*)
                ip_version=6
                broute_filter_on_ether_type "IPv6"
                ;;
            *)
                if [ -n "$protocol" ] || [ -n "$dscp_filter" ]; then
                    # Neither ether_type nor ip address used,
                    # ethertype is not configured by user, so install
                    # both proto IPv4 and IPv6 rule (version 1)
                    ip_version=1
                    BR6_RULE="$BR_RULE"
                    broute_filter_on_ether_type "IPv4"
                    broute_filter_on_ether_type6 "IPv6"
                fi
            esac
        fi
    esac

    if [ "$ip_version" == "4" ] || [ "$ip_version" == "1" ]; then
        broute_ipv4_rule_options "$sid"
        is_l2_rule=1
    fi

    if [ "$ip_version" == "6" ] || [ "$ip_version" == "1" ]; then
        broute_ipv6_rule_options "$sid"
        is_l2_rule=1
    fi

    # first process options that will help figure our source mac address
    # dhcp option for "vendor class id"
    if [ -n "$src_vcid" ]; then
        src_dhcp_options="$src_dhcp_options vcid=$src_vcid"
        is_l2_rule=1
    fi

    # dhcp option for "client id"
    if [ -n "$src_clid" ]; then
        src_dhcp_options="$src_dhcp_options clid=$src_clid"
        is_l2_rule=1
    fi

    # dhcp option for "user class id"
    if [ -n "$src_ucid" ]; then
        src_dhcp_options="$src_dhcp_options ucid=$src_ucid"
        is_l2_rule=1
    fi

    # if src mac is already a classification criteria, then it
    # does not really make sense to add it as a criteria to
    # filter packets again based on source mac
    if [ -n "$src_dhcp_options" ] && [ -z "$src_mac" ]; then
        comp="$(grep -i "$src_dhcp_options" /tmp/dhcp.client.options)"
        if [ -n "$comp" ]; then
            s_mac_add="$(echo $comp | head -n1 | awk '{print $1;}')"
            if [ -n "$s_mac_add" ]; then
                broute_filter_on_src_mac "$s_mac_add"
            fi
        fi
    fi

    # Now process options that will help figure our destination mac address
    # dhcp option for "vendor class id"
    if [ -n "$dst_vcid" ]; then
        dst_dhcp_options="$dst_dhcp_options vcid=$dst_vcid"
        is_l2_rule=1
    fi

    # dhcp option for "client id"
    if [ -n "$dst_clid" ]; then
        dst_dhcp_options="$dst_dhcp_options clid=$dst_clid"
        is_l2_rule=1
    fi

    # dhcp option for "user class id"
    if [ -n "$dst_ucid" ]; then
        dst_dhcp_options="$dst_dhcp_options ucid=$dst_ucid"
        is_l2_rule=1
    fi

    # if dst mac is already a classification criteria, then it
    # does not really make sense to add it as a criteria to
    # filter packets again based on destination mac
    if [ -n "$dst_dhcp_options" ] && [ -z "$dst_mac" ] ; then
        comp="$(grep -i "$dst_dhcp_options" /tmp/dhcp.client.options)"
        if [ -n "$comp" ]; then
            d_mac_add="$(echo $comp | head -n1 | awk '{print $1;}')"
            if [ -n "$d_mac_add" ]; then
                broute_filter_on_dst_mac "$d_mac_add"
            fi
        fi
    fi

    if [ $is_l2_rule -eq 0 ]; then
        return
    fi

    [ -n "$traffic_class" ] && broute_rule_set_traffic_class "$traffic_class"

    if [ -n "$vid_mark" ] || [ -n "$pcp_mark" ]; then
        broute_rule_set_xlate_vid_pbit "$vid_mark" "$pcp_mark"
    fi

    if [ -n "$BR_RULE" ]; then
        if [ -n "$vid_mark" ] || [ -n "$pcp_mark" ]; then
            broute_append_rule "prevlanxlate" "$src_if"
        else
            broute_append_rule "qos" "$src_if"
        fi
    fi
}

handle_ebtables_dscp2pbit() {
    local in_if=$1
    local dscp_filter=""
    local pcp_mark=""
    local ifname=""
    local dscp2pbit_mapping_list=""
    local corder_file="/tmp/qos/classify.order"

    if [ -z "$in_if" ]; then
        return
    fi

    init_broute_dscp2pbit_rule
    while read -r line; do
        line_cid=${line#*_}
        config_get dscp_filter "$line_cid" "dscp_filter"
        config_get pcp_mark "$line_cid" "pcp_mark"

        # return if not a dscp to p-bit rule
        if [ -z "$dscp_filter" ] || [ -z "$pcp_mark" ]; then
            continue
        fi

        config_get ifname "$line_cid" "ifname"

        # return if this config is not for the currently processing interface (in_if)
        if [ -n "$ifname" ] && [ "$ifname" != "$in_if" ]; then
            continue
        fi

        dscp2pbit_mapping_list="$dscp2pbit_mapping_list,$dscp_filter=$pcp_mark"
    done < "$corder_file"

    # if not dscp2pbit config found for our interface, return
    [ -z "$dscp2pbit_mapping_list" ] && return

    # remove first character(comma) from the dscp2pbit_mapping_list, not required.
    dscp2pbit_mapping_list="${dscp2pbit_mapping_list:1}"

    # construct ebtables rule:
    BR_RULE_DSCP2PBIT=" -i $in_if -j dscp2pbit --dscp2pbit-mapping $dscp2pbit_mapping_list --dscp2pbit-target CONTINUE"
}

flush_ebtables_chains() {
    echo "ebtables --concurrent -t nat -F qos_output" > /tmp/qos/classify.ebtables
    echo "ebtables --concurrent -t broute -F qos" > /tmp/qos/classify.ebtables
    echo "ebtables --concurrent -t broute -F dscp2pbits" >> /tmp/qos/classify.ebtables
    echo "ebtables --concurrent -t broute -F prevlanxlate" >> /tmp/qos/classify.ebtables
}

