#!/usr/bin/ksh

#
# This file and its contents are supplied under the terms of the
# Common Development and Distribution License ("CDDL"), version 1.0.
# You may only use this file in accordance with the terms of version
# 1.0 of the CDDL.
#
# A full copy of the text of the CDDL should have accompanied this
# source.  A copy of the CDDL is also available via the Internet at
# http://www.illumos.org/license/CDDL.
#

#
# Copyright 2019 Joyent, Inc.
#

# we can't presume /usr/bin/timeout is there
timeout_cmd() {
    $* &
    sleep 3
    kill $!
    # we want to pause a while to make sure the monitor log is
    # updated...
    sleep 2
}

if [[ `id -u` != 0 ]]; then
    echo "Error: need to be root or have effective UID of root." >&2
    exit 255
fi

if [[ ! -x "$(type -p curl)" ]]; then
    echo "Error: curl binary not found." >&2
    exit 255
fi

# NOTE: If multihomed, this may fail in interesting ways...
MY_IP=`netstat -in -f inet | egrep -v "Name|lo0" | awk '{print $4}' | head -1`
TEST_REMOTE_DST1=10.90.1.25
TEST_REMOTE_DST2=10.19.84.2
TEST_REMOTE_DST3=10.19.84.3
TEST_REMOTE_DST4=10.19.84.4

T1_SRC=10.21.12.4
T1_DST=10.21.12.5
T1_PREFIX=10.21.12.0/24
T2_SRC=10.51.50.4
T2_DST=10.51.50.5
T2_PREFIX=10.51.50.0/24

CURL_DST3_LPORT=10001
CURL_DST4_LPORT=10002
CURL_DST1_LPORT=10003
CURL_PORT=80

MONITOR_LOG=/tmp/ipseckey-monitor.$$

EACQ_PROG=/opt/os-tests/tests/pf_key/eacq-enabler

$EACQ_PROG &
eapid=$!

echo "Warning, this trashes IPsec policy."
ipsecconf -Fq

# Setup the IPsec policy...
ipsecconf -qa - << EOF
# Global policy...
# Remote-port-based policy.  Use different algorithms...
{ raddr $TEST_REMOTE_DST3 rport $CURL_PORT ulp tcp } ipsec { encr_algs aes encr_auth_algs sha512 }

# Unique policy...
{ raddr $TEST_REMOTE_DST4 rport $CURL_PORT ulp tcp } ipsec { encr_algs aes encr_auth_algs sha256 sa unique }

# Simple IP address policy.  Use an AH + ESP for it.
{ raddr $TEST_REMOTE_DST1 } ipsec { auth_algs sha512 encr_algs aes(256) }
{ raddr $TEST_REMOTE_DST2 } ipsec { auth_algs sha384 encr_algs aes(256) }

# Tunnel policy...
{ tunnel rush0 raddr $T1_PREFIX negotiate tunnel } ipsec { encr_algs aes-gcm(256) }
# NULL-encryption...
{ tunnel vh0 raddr $T2_PREFIX negotiate tunnel } ipsec {encr_auth_algs hmac-sha384 }
EOF

# Plumb the tunnels
dladm create-iptun -t -T ipv4 -a local=$MY_IP -a remote=$TEST_REMOTE_DST1 rush0
dladm create-iptun -t -T ipv4 -a local=$MY_IP -a remote=$TEST_REMOTE_DST2 vh0
ipadm create-addr -t -T static -a local=$T1_SRC,remote=$T1_DST rush0/v4
ipadm create-addr -t -T static -a local=$T2_SRC,remote=$T2_DST vh0/v4
route add $T1_PREFIX $T1_DST
route add $T2_PREFIX $T2_DST

ipseckey flush
ipseckey -np monitor > $MONITOR_LOG &
IPSECKEY_PID=$!

# give the monitor some time to get set up
sleep 3

# Launch pings to various addresses (each requiring an ACQUIRE).

timeout_cmd ping -svn $TEST_REMOTE_DST1 1024 1
timeout_cmd ping -svn $TEST_REMOTE_DST2 1024 1
timeout_cmd ping -svn $T1_DST 1024 1
timeout_cmd ping -svn $T2_DST 1024 1

# Now try some curls to trigger local port and unique policy.

# port-only for DST3
timeout_cmd curl --local-port $CURL_DST3_LPORT \
    http://$TEST_REMOTE_DST3:$CURL_PORT
# unique for DST4
timeout_cmd curl --local-port $CURL_DST4_LPORT \
    http://$TEST_REMOTE_DST4:$CURL_PORT
# Nothing specced for DST1
timeout_cmd curl --local-port $CURL_DST1_LPORT \
    http://$TEST_REMOTE_DST1:$CURL_PORT

# Clean up.
kill $IPSECKEY_PID
kill $eapid
# Unplumb the tunnels
route delete $T2_PREFIX $T2_DST
route delete $T1_PREFIX $T1_DST
ipadm delete-addr vh0/v4
ipadm delete-addr rush0/v4
ipadm delete-if vh0
ipadm delete-if rush0
dladm delete-iptun vh0
dladm delete-iptun rush0
# Flush policy
ipsecconf -Fq
# Use SMF to restore anything that may have been there.  "restart" on
# a disabled service is a NOP, but an enabled one will get
# /etc/inet/ipsecinit.conf reloaded.
svcadm restart ipsec/policy

# give the monitor some time to finish up
sleep 5

# Process MONITOR_LOG's output...
echo "Checking for unique local port only in one ACQUIRE case."
egrep "$CURL_DST3_LPORT|$CURL_DST4_LPORT|$CURL_DST1_LPORT" \
    $MONITOR_LOG > /tmp/egrep.$$
grep $CURL_DST4_LPORT $MONITOR_LOG > /tmp/grep.$$ || {
    echo "unique port $CURL_DST4_LPORT missing from monitor log."
    exit 1
}
diff /tmp/grep.$$ /tmp/egrep.$$
if [[ $? != 0 ]]; then
    echo "More than just the one unique port $CURL_DST4_LPORT found."
    exit 1
fi

# Split out extended (file.0) and regular (file.1) ACQUIREs.
# NOTE: "+7" is dependent on "ipseckey monitor"'s first output where it gets
# the "PROMISC" reply.

mkdir /tmp/raw.$$
savedir=$PWD
cd /tmp/raw.$$
tail +7 $MONITOR_LOG | \
    awk 'BEGIN { out=0; } /Read/ {out++;} { print >> (out % 2) }'
cd $savedir

# Pluck out the address extension from the two ACQUIRE types.
# NOTE: Add any new in-ACQUIRE address types here if more arrive.
egrep "DST:|SRC:|INS:|IND:" /tmp/raw.$$/0 > /tmp/extended-addresses.$$
egrep "DST:|SRC:|INS:|IND:" /tmp/raw.$$/1 > /tmp/regular-addresses.$$

# There should be NO differences between address fields from regular vs.
# extended ACQUIREs. If there are, it's a bug (or an older version of illumos).
diff /tmp/extended-addresses.$$ /tmp/regular-addresses.$$
if [[ $? != 0 ]]; then
    echo "Address fields in ACQUIRE differ."
    rc=1
else
    rc=0
fi

/bin/rm -rf /tmp/*-addresses.$$ /tmp/raw.$$
/bin/rm -f /tmp/grep.$$ /tmp/egrep.$$ /tmp/addrs.$$ $MONITOR_LOG

exit $rc
