#!/bin/bash

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

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_pass()
{
	local ret=$?
	if [ $ret != 0 ]
	then
		echo "FAIL: ${@}"
		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
		exit 1
	else
		echo "PASS: ${@}"
	fi
}

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"

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()
{
	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-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
		}

		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
		}

		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 ruleset"
}

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}
sleep 1
ip netns exec $S ss -6ltnp | grep -q '*:21'
assert_pass "start vsftpd server"


# test passive mode
reload_ruleset
ip netns exec $S tcpdump -q --immediate-mode -Ui s_r -w ${PCAP} 2> /dev/null &
pid=$!
sleep 1
ip netns exec $C curl --no-progress-meter --connect-timeout 5 ftp://[${ip_rc}]:2121/$(basename $INFILE) -o $OUTFILE
assert_pass "curl ftp passive mode "

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

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


# test active mode
reload_ruleset

ip netns exec $S tcpdump -q --immediate-mode -Ui s_r -w ${PCAP} 2> /dev/null &
pid=$!
ip netns exec $C curl --no-progress-meter -P - --connect-timeout 5 ftp://[${ip_rc}]:2121/$(basename $INFILE) -o $OUTFILE
assert_pass "curl ftp active mode "

cmp "$INFILE" "$OUTFILE"
assert_pass "FTP Active mode: in and output files remain the same when FTP traffic passes through NAT."

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

# trap calls cleanup
exit 0
