#!/bin/bash
set -ex

lock_file=/var/lock/openshift-sdn.lock

action=$1
net_container=$2
tenant_id=$3
ingress_bw=$4
egress_bw=$5
macvlan=$6

lockwrap() {
    (
    flock 200
    "$@"
    ) 200>${lock_file}
}

# Retrieve the name of the host-local member of the veth pair that
# connects the container (identified by pid) to the docker bridge.
get_veth_host() {
    local pid=$1

    local veth_ifindex=$(nsenter -n -t $pid -- ethtool -S eth0 | sed -n -e 's/.*peer_ifindex: //p')
    # Strip a suffix starting with '@' from the interface name.
    # The suffixed interface name won't be recognized by brctl or ovs-*
    ip link show | sed -ne "s/^$veth_ifindex: \([^:@]*\).*/\1/p"
}

get_container_mac() {
    local pid=$1

    nsenter -n -t $pid -- ip link show dev eth0 | sed -n -e 's/.*link.ether \([^ ]*\).*/\1/p'
}

get_ipaddr_pid_veth() {
    network_mode=$(docker inspect --format "{{.HostConfig.NetworkMode}}" ${net_container})
    if [ "${network_mode}" == "host" ]; then
      # quit, nothing for the SDN here
      exit 0
    elif [[ "${network_mode}" =~ container:.* ]]; then
      # Get pod infra container
      net_container=$(echo ${network_mode} | cut -d ":" -f 2)
    fi
    ipaddr=$(docker inspect --format "{{.NetworkSettings.IPAddress}}" ${net_container})
    pid=$(docker inspect --format "{{.State.Pid}}" ${net_container})
    veth_host=$(get_veth_host $pid)
    macaddr=$(get_container_mac $pid)
}

add_ovs_port() {
    brctl delif lbr0 $veth_host
    ovs-vsctl add-port br0 ${veth_host}
}

ensure_ovs_port() {
    ovs-vsctl --may-exist add-port br0 ${veth_host}
}

del_ovs_port() {
    ovs-vsctl --if-exists del-port $veth_host
}

add_ovs_flows() {
    ovs_port=$(ovs-vsctl get Interface ${veth_host} ofport)

    # from container
    ovs-ofctl -O OpenFlow13 add-flow br0 "table=2, priority=100, in_port=${ovs_port}, arp, nw_src=${ipaddr}, arp_sha=${macaddr}, actions=load:${tenant_id}->NXM_NX_REG0[], goto_table:5"
    ovs-ofctl -O OpenFlow13 add-flow br0 "table=2, priority=100, in_port=${ovs_port}, ip, nw_src=${ipaddr}, actions=load:${tenant_id}->NXM_NX_REG0[], goto_table:3"

    # arp request/response to container (not isolated)
    ovs-ofctl -O OpenFlow13 add-flow br0 "table=6, priority=100, arp, nw_dst=${ipaddr}, actions=output:${ovs_port}"

    # IP to container
    if [ $tenant_id = "0" ]; then
	ovs-ofctl -O OpenFlow13 add-flow br0 "table=7, priority=100, ip, nw_dst=${ipaddr}, actions=output:${ovs_port}"
    else
	ovs-ofctl -O OpenFlow13 add-flow br0 "table=7, priority=100, reg0=0, ip, nw_dst=${ipaddr}, actions=output:${ovs_port}"
	ovs-ofctl -O OpenFlow13 add-flow br0 "table=7, priority=100, reg0=${tenant_id}, ip, nw_dst=${ipaddr}, actions=output:${ovs_port}"
    fi

    # Pod ingress == OVS bridge egress
    # linux-htb used here since that's the Kubernetes default traffic shaper too
    if [ -n "${ingress_bw}" ]; then
        qos=$(ovs-vsctl create qos type=linux-htb other-config:max-rate=${ingress_bw})
        ovs-vsctl set port ${veth_host} qos=${qos}
    fi

    # Pod egress == OVS bridge ingress
    if [ -n "${egress_bw}" ]; then
        # OVS ingress_policing specified in Kbps
        ovs-vsctl set interface ${veth_host} ingress_policing_rate=$((${egress_bw}/1000))
    fi
}

del_ovs_flows() {
    ovs-ofctl -O OpenFlow13 del-flows br0 "ip,nw_dst=${ipaddr}"
    ovs-ofctl -O OpenFlow13 del-flows br0 "ip,nw_src=${ipaddr}"
    ovs-ofctl -O OpenFlow13 del-flows br0 "arp,nw_dst=${ipaddr}"
    ovs-ofctl -O OpenFlow13 del-flows br0 "arp,nw_src=${ipaddr}"

    qos=$(ovs-vsctl get port ${veth_host} qos)
    if [ "$qos" != "[]" ]; then
        ovs-vsctl clear port ${veth_host} qos
        ovs-vsctl --if-exists destroy qos ${qos}
    fi
}

add_subnet_route() {
    nsenter -n -t $pid -- ip route add $OPENSHIFT_CLUSTER_SUBNET dev eth0 proto kernel scope link src $ipaddr
}

ensure_subnet_route() {
    nsenter -n -t $pid -- ip route del $OPENSHIFT_CLUSTER_SUBNET dev eth0 || true
    add_subnet_route
}

add_macvlan() {
    default_dev=$(ip route show | sed -ne 's/^default .* dev \([^ ]*\) .*/\1/p')
    if [ -z "$default_dev" ]; then
	echo "Could not find default network interface"
	exit 1
    fi
    ip link add link $default_dev name macvlan0 type macvlan mode private
    ip link set macvlan0 netns $pid
}

run() {
    get_ipaddr_pid_veth
    source /run/openshift-sdn/config.env

    case "$action" in
	setup)
	    add_ovs_port
	    add_ovs_flows
	    add_subnet_route
	    if [ "$macvlan" = true ]; then
		add_macvlan
	    fi
	    ;;

	update)
	    ensure_ovs_port
	    del_ovs_flows
	    add_ovs_flows
	    ensure_subnet_route
	    ;;

	teardown)
	    del_ovs_flows
	    # Delete ovs port in the end, del_ovs_flows needs the port to delete qos record
	    del_ovs_port
	    ;;

	*)
            echo "Bad input: $@"
            exit 1
    esac
}

lockwrap run
