--- /dev/null
+#!/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