#!/bin/bash

# NFT_TEST_REQUIRES(NFT_TEST_HAVE_tcpdump)
# NFT_TEST_REQUIRES(NFT_TEST_HAVE_curl)
# NFT_TEST_REQUIRES(NFT_TEST_HAVE_vsftpd)

# modprobe does not work in fake root env
[ "$NFT_TEST_HAS_REALROOT" != y ] && exit 77

. $NFT_TEST_LIBRARY_FILE

cleanup()
{
	for i in $R $C $S;do
		kill $(ip netns pid $i) 2>/dev/null
		ip netns del $i
	done
	rm -rf $WORKDIR
}
trap cleanup EXIT

assert_failout()
{
	ip netns exec $R $NFT list ruleset
	tcpdump -nnr ${PCAP}
	test -r /proc/net/nf_conntrack && ip netns exec $R cat /proc/net/nf_conntrack
	ip netns exec $R conntrack -S
	ip netns exec $R conntrack -L
	ip netns exec $S ss -nitepal
}

rnd=$(mktemp -u XXXXXXXX)
R="natftp-router-$rnd"
C="natftp-client-$rnd"
S="natftp-server-$rnd"

WORKDIR="/tmp/nat_ftp_test-$rnd"
FTPCONF="$WORKDIR/ftp-conf"
INFILE="$WORKDIR/infile"
OUTFILE="$WORKDIR/outfile"
PCAP="$WORKDIR/tcpdump.pcap"

umask 022
mkdir -p $WORKDIR
assert_pass "mkdir $WORKDIR"

modprobe nf_nat_ftp
assert_pass "modprobe nf_nat_ftp. Needed for DNAT of data connection and active mode PORT change with SNAT"

ip_sr=2001:db8:ffff:22::1
ip_cr=2001:db8:ffff:21::2
ip_rs=2001:db8:ffff:22::fffe
ip_rc=2001:db8:ffff:21::fffe

ip netns add $R
ip netns add $S
ip netns add $C
ip -net $S link set lo up
ip -net $R link set lo up
ip -net $C link set lo up
ip netns exec $R sysctl -wq net.ipv6.conf.all.forwarding=1

ip link add s_r netns $S type veth peer name r_s netns $R
ip link add c_r netns $C type veth peer name r_c netns $R
ip -net $S link set s_r up
ip -net $R link set r_s up
ip -net $R link set r_c up
ip -net $C link set c_r up

ip -net $S addr add ${ip_sr}/64 dev s_r nodad
ip -net $C addr add ${ip_cr}/64 dev c_r nodad
ip -net $R addr add ${ip_rs}/64 dev r_s nodad
ip -net $R addr add ${ip_rc}/64 dev r_c nodad
ip -net $C route add ${ip_rs}/64 via ${ip_rc} dev c_r
ip -net $S route add ${ip_rc}/64 via ${ip_rs} dev s_r

ip netns exec $C ping -q -6 ${ip_sr} -c1 > /dev/null
assert_pass "topo initialization"

reload_ruleset_base()
{
	ip netns exec $R conntrack -F 2> /dev/null
	ip netns exec $R $NFT -f - <<-EOF
	flush ruleset
	table ip6 ftp_helper_nat_test {
		ct helper ftp-standard {
			type "ftp" protocol tcp;
		}

		chain PRE-aftnat {
			type filter hook prerouting priority 350; policy drop;
			iifname r_c tcp dport 21 ct state new ct helper set "ftp-standard" counter accept

			ip6 nexthdr tcp ct state related counter accept
			ip6 nexthdr tcp ct state established counter accept

			ip6 nexthdr icmpv6 counter accept

			counter log
		}

		chain forward {
			type filter hook forward priority filter; policy drop;
			ip6 daddr ${ip_sr} counter tcp dport 21 ct state new counter accept
			ip6 nexthdr tcp ct state established counter accept
			ip6 nexthdr tcp ct state related     counter log accept
		}
	}
	EOF
	assert_pass "apply ftp helper base ruleset"
}

load_dnat()
{
	ip netns exec $R $NFT -f - <<-EOF
	table ip6 ftp_helper_nat_test {
		chain PRE-dnat {
			type nat hook prerouting priority dstnat; policy accept;
			# Dnat the control connection, data connection will be automaticly NATed.
			ip6 daddr ${ip_rc} counter ip6 nexthdr tcp tcp dport 2121 counter dnat ip6 to [${ip_sr}]:21
		}
	}
	EOF
	assert_pass "apply ftp helper DNAT ruleset"
}

load_snat()
{
	ip netns exec $R $NFT -f - <<-EOF
	table ip6 ftp_helper_nat_test {
		chain POST-srcnat {
			type nat hook postrouting priority srcnat; policy accept;
			ip6 daddr ${ip_sr} ip6 nexthdr tcp tcp dport 21 counter snat ip6 to [${ip_rs}]:16500
		}
	}
	EOF
	assert_pass "apply ftp helper SNAT ruleset"
}

reload_ruleset()
{
	reload_ruleset_base
	load_dnat
	load_snat
}

reload_ruleset_dnat_only()
{
	reload_ruleset_base
	load_dnat
}

reload_ruleset_snat_only()
{
	reload_ruleset_base
	load_snat
}

dd if=/dev/urandom of="$INFILE" bs=4096 count=1 2>/dev/null
chmod 755 $INFILE
assert_pass "Prepare the file for FTP transmission"

cat > ${FTPCONF} <<-EOF
anonymous_enable=YES
anon_root=${WORKDIR}
local_enable=YES
connect_from_port_20=YES
listen=NO
listen_ipv6=YES
pam_service_name=vsftpd
background=YES
EOF
ip netns exec $S vsftpd ${FTPCONF}
wait_local_port_listen $S 21 tcp

ip netns exec $S ss -6ltnp | grep -q '*:21'
assert_pass "start vsftpd server"

test_case()
{
	tag=$1
	ftp_ip_and_port=$2
	client_ip_to_check=$3
	additional_curl_options=$4

	ip netns exec $S tcpdump -q --immediate-mode -Ui s_r -w ${PCAP} 2> /dev/null &
	pid=$!
	sleep 0.5
	ip netns exec $C curl ${additional_curl_options} --no-progress-meter --connect-timeout 5 ftp://${ftp_ip_and_port}/$(basename $INFILE) -o $OUTFILE
	assert_pass "curl ftp "${tag}

	cmp "$INFILE" "$OUTFILE"
	assert_pass "FTP "${tag}": The input and output files remain the same when traffic passes through NAT."

	kill $pid;
	tcpdump -nnr ${PCAP} src ${client_ip_to_check} and dst ${ip_sr} 2>&1 |grep -q FTP
	assert_pass "assert FTP traffic NATed"
}

# test passive mode
reload_ruleset
test_case "Passive mode" "[${ip_rc}]:2121" ${ip_rs}


# test passive mode DNAT only
reload_ruleset_dnat_only
test_case "Passive mode DNAT only" "[${ip_rc}]:2121" ${ip_cr}


# test passive mode SNAT only
reload_ruleset_snat_only
test_case "Passive mode SNAT only" "[${ip_sr}]:21" ${ip_rs}


# test active mode
reload_ruleset
test_case "Active mode" "[${ip_rc}]:2121" ${ip_rs} "-P -"


# test active mode DNAT only
reload_ruleset_dnat_only
test_case "Active mode DNAT only" "[${ip_rc}]:2121" ${ip_cr} "-P -"


# test active mode SNAT only
reload_ruleset_snat_only
test_case "Active mode SNAT only" "[${ip_sr}]:21" ${ip_rs} "-P -"

# trap calls cleanup
exit 0
