#!/bin/bash

mlxdev="enp5s0f0np0"
pcidev="0000:05:00.0"

export PATH=/usr/sbin:/usr/bin:/sbin:/bin


### common

bail(){
	echo "$@" >&2
	exit 1
}


### step01

setup_v21(){
	ip link add name v21 link $mlxdev type vlan id 21
	ip link set address 2a:11:22:33:21:11 dev v21
	ip link set mtu 9000 up dev v21
	ip addr add 10.53.21.1/27 dev v21
}

setup_v22(){
	ip link add name v22 link $mlxdev type vlan id 22
	ip link set address 2a:11:22:33:22:21 dev v22
	ip link set mtu 9000 up dev v22
	ip addr add 10.53.22.2/31 dev v22
	ip neigh add 10.53.22.3 lladdr 2a:11:22:33:22:22 nud permanent dev v22
	ip neigh replace 10.53.22.3 lladdr 2a:11:22:33:22:22 nud permanent dev v22
}

step01_basic(){
	ip link del v21
	ip link del v22
	ip link set mtu 9000 up dev "$mlxdev"
	setup_v21
	setup_v22
	ethtool -A "$mlxdev" autoneg off rx off tx off
	sysctl -w net.ipv4.conf.all.forwarding=1

	# https://files.fiberby.net/ast/2024/tc_skip_sw/basic_test.nft
	./basic_test.nft
}

### step02

wait_for_netdev(){
	local ifname="$1"
	sleep 5;
	for i in $(seq 1 30); do
		if [ -d "/sys/class/net/$ifname/" ] ; then
			return 0
		fi
		sleep 1
	done
	bail "lost hope while waiting for $ifname"
}

get_eswitch_mode_simple(){
	local busaddr="$1"
	devlink dev eswitch show "$busaddr" |
		grep -w mode |
		sed -e 's/^.* mode \([^ ]\+\) .*$/\1/g'
}

get_eswitch_mode(){
	local busaddr="$1"
	local mode="$(get_eswitch_mode_simple "$busaddr" 2>/dev/null)"
	if [ "$mode" = '' ] ; then
		echo legacy
	else
		echo "$mode"
	fi
}

set_eswitch_mode(){
	local busaddr="$1"
	local mode="$2"

	devlink dev eswitch set "$busaddr" mode "$mode"
}

ensure_switchdev(){
	local busaddr="$1"

	local current_mode="$(get_eswitch_mode "$busaddr")"

	if [ "$current_mode" = 'legacy' ] ; then
		set_eswitch_mode "$busaddr" 'switchdev'
		wait_for_netdev "$mlxdev"
	fi

	local new_mode="$(get_eswitch_mode "$busaddr")"
	if [ "$new_mode" != "switchdev" ] ; then
		bail "error changing eswitch mode"
	fi

	return 0
}

step02_eswitch(){
	ensure_switchdev 'pci/'"$pcidev"
	ethtool -L "$mlxdev" combined 8
	ethtool -K "$mlxdev" hw-tc-offload on
	ip link set mtu 9000 up dev "$mlxdev"
}


### step03

step03_clean_and_add_queue(){
	# flush existing rules
	tc qdisc del dev "$mlxdev" ingress > /dev/null 2>&1

	# setup qdisc
	tc qdisc add dev "$mlxdev" ingress
}

### step04

a_dst_vlan=22
a_src_mac='2a:11:22:33:22:21'
a_dst_mac='2a:11:22:33:22:22'

tc_mode=skip_sw

tc_offload_basics_ipv4(){
	local chain_no="$1"
	local pref="$2"
	local dst_vlan="$3"
	local src_mac="$4"
	local dst_mac="$5"

	# forward packet
	tc filter add dev "$mlxdev" ingress chain "$chain_no" pref "$pref" \
		protocol 802.1Q flower "$tc_mode" \
		vlan_ethtype ipv4 dst_ip 10.53.22.3 \
		action vlan modify id "$dst_vlan" pipe \
		action pedit ex \
		munge eth dst set "$dst_mac" \
		munge eth src set "$src_mac" \
		munge ip ttl dec pipe \
		action mirred egress redirect dev "$mlxdev"
}

step04_single_matching(){
	step03_clean_and_add_queue
	tc_offload_basics_ipv4 0 1 "$a_dst_vlan" "$a_src_mac" "$a_dst_mac"
}


### step05

tc_match_goto(){
	local chain_no="$1"
	local pref="$2"
	local net="$3"
	local goto_chain="$4"

	tc filter add dev "$mlxdev" ingress chain "$chain_no" pref $pref \
		protocol 802.1Q flower "$tc_mode" \
		vlan_ethtype ipv4 dst_ip "$net" \
		action goto chain "$goto_chain"
}

tc_n_non_matching(){
	local n=$1
	for i in $(seq 1 $n); do
		tc_match_goto 0 $i 10.51.$i.0/25 42
	done
}

step_n_non_matching_and_matching(){
	if [ $# -ne 1 ] ; then
		echo "missing n argument" >&2
		exit 1
	fi
	local n=$1
	step03_clean_and_add_queue
	tc_n_non_matching $n
	tc_offload_basics_ipv4 0 $((n+1)) "$a_dst_vlan" "$a_src_mac" "$a_dst_mac"
}

step_n_non_matching(){
	if [ $# -ne 1 ] ; then
		echo "missing n argument" >&2
		exit 1
	fi
	local n=$1
	step03_clean_and_add_queue
	tc_n_non_matching $n
}

lsnet(){
	ls -l /sys/bus/*/devices/*/net/*/device/driver 2>/dev/null |
		sed -e 's/^[^/]\+\(.*\) -> .*\/\([^/]\+\)$/\1\/\2/g' |
		cut -d/ -f4,6,8,11 |
		tr / ' ' |
		awk '{ print $1 "/" $2 "/" $4 "/" $3}'
}

### main

main(){
	if [ $# -lt 1 ] ; then
		echo "$0: invalid cmd" >&2
		exit 1
	fi

	local cmd="$1"
	shift
	case "$cmd" in
	step01)
		# basic forwarding + notrack
		step01_basic
		;;
	step02)
		# switch to eswitch mode
		step02_eswitch
		;;
	step03)
		# add ingress qdisc
		step03_clean_and_add_queue
		;;
	step04)
		# 1 maching rule
		step04_single_matching
		;;
	step05)
		# <n> non-matching rule
		step_n_non_matching "$@"
		;;
	step06)
		# <n> non-matching rule and one matching
		step_n_non_matching_and_matching "$@"
		;;
	lsnet)
		# list interfaces with PCI bus and drivers
		lsnet
		;;
	*)
		echo "$0: unknown cmd" >&2
		exit 1
		;;
	esac
}

main "$@"
