[automerger skipped] anycast_test.py: change to use thread.join to wait CloseFileDescriptorThread finished
am: 2eacd48327  -s ours

Change-Id: I262df7a0e863e1b44997837be1db22b969a20add
diff --git a/net/test/OWNERS b/net/test/OWNERS
index f002a84..cbbfa70 100644
--- a/net/test/OWNERS
+++ b/net/test/OWNERS
@@ -1,2 +1,2 @@
-ek@google.com
 lorenzo@google.com
+maze@google.com
diff --git a/net/test/all_tests.py b/net/test/all_tests.py
index 72d3c4e..bfba0e5 100755
--- a/net/test/all_tests.py
+++ b/net/test/all_tests.py
@@ -27,6 +27,7 @@
     'leak_test',
     'multinetwork_test',
     'neighbour_test',
+    'nf_test',
     'pf_key_test',
     'ping6_test',
     'qtaguid_test',
diff --git a/net/test/anycast_test.py b/net/test/anycast_test.py
old mode 100755
new mode 100644
index 62d874e..6222580
--- a/net/test/anycast_test.py
+++ b/net/test/anycast_test.py
@@ -93,9 +93,14 @@
     # This will hang if the kernel has the bug.
     thread = CloseFileDescriptorThread(self.tuns[netid])
     thread.start()
-    # Wait up to 0.5 seconds for the thread to finish, but
+    # Wait up to 3 seconds for the thread to finish, but
     # continue and fail the test if the thread hangs.
-    thread.join(0.5)
+
+    # For kernels with MPTCP ported, closing tun interface need more
+    # than 0.5 sec. DAD procedure within MPTCP fullmesh module takes
+    # more time, because duplicate address-timer takes a refcount
+    # on the IPv6-address, preventing it from getting closed.
+    thread.join(3)
 
     # Make teardown work.
     del self.tuns[netid]
diff --git a/net/test/bpf.py b/net/test/bpf.py
index 5e90daa..43502bd 100755
--- a/net/test/bpf.py
+++ b/net/test/bpf.py
@@ -21,14 +21,24 @@
 import cstruct
 import net_test
 import socket
+import platform
 
 # __NR_bpf syscall numbers for various architectures.
+# NOTE: If python inherited COMPAT_UTS_MACHINE, uname's 'machine' field will
+# return the 32-bit architecture name, even if python itself is 64-bit. To work
+# around this problem and pick the right syscall nr, we can additionally check
+# the bitness of the python interpreter. Assume that the 64-bit architectures
+# are not running with COMPAT_UTS_MACHINE and must be 64-bit at all times.
 # TODO: is there a better way of doing this?
 __NR_bpf = {
-    "aarch64": 280,
-    "armv7l": 386,
-    "armv8l": 386,
-    "x86_64": 321}[os.uname()[4]]
+    "aarch64-64bit": 280,
+    "armv7l-32bit": 386,
+    "armv8l-32bit": 386,
+    "armv8l-64bit": 280,
+    "i686-32bit": 357,
+    "i686-64bit": 321,
+    "x86_64-64bit": 321,
+}[os.uname()[4] + "-" + platform.architecture()[0]]
 
 LOG_LEVEL = 1
 LOG_SIZE = 65536
@@ -142,6 +152,7 @@
 BPF_FUNC_map_lookup_elem = 1
 BPF_FUNC_map_update_elem = 2
 BPF_FUNC_map_delete_elem = 3
+BPF_FUNC_get_current_uid_gid = 15
 BPF_FUNC_get_socket_cookie = 46
 BPF_FUNC_get_socket_uid = 47
 
diff --git a/net/test/bpf_test.py b/net/test/bpf_test.py
index e7a4edb..ea3e56b 100755
--- a/net/test/bpf_test.py
+++ b/net/test/bpf_test.py
@@ -30,9 +30,12 @@
 
 libc = ctypes.CDLL(ctypes.util.find_library("c"), use_errno=True)
 HAVE_EBPF_ACCOUNTING = net_test.LINUX_VERSION >= (4, 9, 0)
+HAVE_EBPF_SOCKET = net_test.LINUX_VERSION >= (4, 14, 0)
 KEY_SIZE = 8
 VALUE_SIZE = 4
 TOTAL_ENTRIES = 20
+TEST_UID = 54321
+TEST_GID = 12345
 # Offset to store the map key in stack register REG10
 key_offset = -8
 # Offset to store the map value in stack register REG10
@@ -300,7 +303,7 @@
                      + INS_SK_FILTER_ACCEPT)
     self.prog_fd = BpfProgLoad(BPF_PROG_TYPE_SOCKET_FILTER, instructions)
     packet_count = 10
-    uid = 12345
+    uid = TEST_UID
     with net_test.RunAsUid(uid):
       self.assertRaisesErrno(errno.ENOENT, LookupMap, self.map_fd, uid)
       SocketUDPLoopBack(packet_count, 4, self.prog_fd)
@@ -349,6 +352,10 @@
       BpfProgDetach(self._cg_fd, BPF_CGROUP_INET_INGRESS)
     except socket.error:
       pass
+    try:
+      BpfProgDetach(self._cg_fd, BPF_CGROUP_INET_SOCK_CREATE)
+    except socket.error:
+      pass
 
   def testCgroupBpfAttach(self):
     self.prog_fd = BpfProgLoad(BPF_PROG_TYPE_CGROUP_SKB, INS_BPF_EXIT_BLOCK)
@@ -385,14 +392,55 @@
                      + INS_CGROUP_ACCEPT + INS_PACK_COUNT_UPDATE + INS_CGROUP_ACCEPT)
     self.prog_fd = BpfProgLoad(BPF_PROG_TYPE_CGROUP_SKB, instructions)
     BpfProgAttach(self.prog_fd, self._cg_fd, BPF_CGROUP_INET_INGRESS)
-    uid = os.getuid()
     packet_count = 20
-    SocketUDPLoopBack(packet_count, 4, None)
-    self.assertEquals(packet_count, LookupMap(self.map_fd, uid).value)
-    DeleteMap(self.map_fd, uid);
-    SocketUDPLoopBack(packet_count, 6, None)
-    self.assertEquals(packet_count, LookupMap(self.map_fd, uid).value)
+    uid = TEST_UID
+    with net_test.RunAsUid(uid):
+      self.assertRaisesErrno(errno.ENOENT, LookupMap, self.map_fd, uid)
+      SocketUDPLoopBack(packet_count, 4, None)
+      self.assertEquals(packet_count, LookupMap(self.map_fd, uid).value)
+      DeleteMap(self.map_fd, uid)
+      SocketUDPLoopBack(packet_count, 6, None)
+      self.assertEquals(packet_count, LookupMap(self.map_fd, uid).value)
     BpfProgDetach(self._cg_fd, BPF_CGROUP_INET_INGRESS)
 
+  def checkSocketCreate(self, family, socktype, success):
+    try:
+      sock = socket.socket(family, socktype, 0)
+      sock.close()
+    except socket.error, e:
+      if success:
+        self.fail("Failed to create socket family=%d type=%d err=%s" %
+                  (family, socktype, os.strerror(e.errno)))
+      return;
+    if not success:
+      self.fail("unexpected socket family=%d type=%d created, should be blocked" %
+                (family, socktype))
+
+
+  def trySocketCreate(self, success):
+      for family in [socket.AF_INET, socket.AF_INET6]:
+        for socktype in [socket.SOCK_DGRAM, socket.SOCK_STREAM]:
+          self.checkSocketCreate(family, socktype, success)
+
+  @unittest.skipUnless(HAVE_EBPF_SOCKET,
+                     "Cgroup BPF socket is not supported")
+  def testCgroupSocketCreateBlock(self):
+    instructions = [
+        BpfFuncCall(BPF_FUNC_get_current_uid_gid),
+        BpfAlu64Imm(BPF_AND, BPF_REG_0, 0xfffffff),
+        BpfJumpImm(BPF_JNE, BPF_REG_0, TEST_UID, 2),
+    ]
+    instructions += INS_BPF_EXIT_BLOCK + INS_CGROUP_ACCEPT;
+    self.prog_fd = BpfProgLoad(BPF_PROG_TYPE_CGROUP_SOCK, instructions)
+    BpfProgAttach(self.prog_fd, self._cg_fd, BPF_CGROUP_INET_SOCK_CREATE)
+    with net_test.RunAsUid(TEST_UID):
+      # Socket creation with target uid should fail
+      self.trySocketCreate(False);
+    # Socket create with different uid should success
+    self.trySocketCreate(True)
+    BpfProgDetach(self._cg_fd, BPF_CGROUP_INET_SOCK_CREATE)
+    with net_test.RunAsUid(TEST_UID):
+      self.trySocketCreate(True)
+
 if __name__ == "__main__":
   unittest.main()
diff --git a/net/test/build_rootfs.sh b/net/test/build_rootfs.sh
new file mode 100755
index 0000000..ce09da1
--- /dev/null
+++ b/net/test/build_rootfs.sh
@@ -0,0 +1,139 @@
+#!/bin/bash
+#
+# Copyright (C) 2018 The Android Open Source Project
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#      http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+
+set -e
+
+SCRIPT_DIR=$(CDPATH= cd -- "$(dirname -- "$0")" && pwd -P)
+
+usage() {
+  echo -n "usage: $0 [-h] [-s wheezy|stretch] [-a amd64|arm64] "
+  echo "[-m http://mirror/debian] [-n net_test.rootfs.`date +%Y%m%d`]"
+  exit 1
+}
+
+mirror=http://ftp.debian.org/debian
+debootstrap=debootstrap
+suite=stretch
+arch=amd64
+
+while getopts ":hs:a:m:n:" opt; do
+  case $opt in
+    h)
+      usage
+      ;;
+    s)
+      if [ "$OPTARG" != "wheezy" -a "$OPTARG" != "stretch" ]; then
+        echo "Invalid suite: $OPTARG" >&2
+        usage
+      fi
+      suite=$OPTARG
+      ;;
+    a)
+      if [ "$OPTARG" != "amd64" -a "$OPTARG" != "arm64" ]; then
+        echo "Invalid arch: $OPTARG" >&2
+        usage
+      fi
+      arch=$OPTARG
+      ;;
+    m)
+      mirror=$OPTARG
+      ;;
+    n)
+      name=$OPTARG
+      ;;
+    \?)
+      echo "Invalid option: $OPTARG" >&2
+      usage
+      ;;
+    :)
+      echo "Invalid option: $OPTARG requires an argument" >&2
+      usage
+      ;;
+  esac
+done
+
+name=net_test.rootfs.$arch.`date +%Y%m%d`
+
+# Switch to qemu-debootstrap for incompatible architectures
+if [ "$arch" = "arm64" ]; then
+  debootstrap=qemu-debootstrap
+fi
+
+# Sometimes it isn't obvious when the script fails
+failure() {
+  echo "Filesystem generation process failed." >&2
+}
+trap failure ERR
+
+# Import the package list for this release
+packages=`cat $SCRIPT_DIR/rootfs/$suite.list | xargs | tr -s ' ' ','`
+
+# For the debootstrap intermediates
+workdir=`mktemp -d`
+workdir_remove() {
+  echo "Removing temporary files.." >&2
+  sudo rm -rf $workdir
+}
+trap workdir_remove EXIT
+
+# Run the debootstrap first
+cd $workdir
+sudo $debootstrap --arch=$arch --variant=minbase --include=$packages \
+                  $suite . $mirror
+# Workarounds for bugs in the debootstrap suite scripts
+for mount in `cat /proc/mounts | cut -d' ' -f2 | grep -e ^$workdir`; do
+  echo "Unmounting mountpoint $mount.." >&2
+  sudo umount $mount
+done
+# Copy the chroot preparation scripts, and enter the chroot
+for file in $suite.sh common.sh net_test.sh; do
+  sudo cp -a $SCRIPT_DIR/rootfs/$file root/$file
+  sudo chown root:root root/$file
+done
+sudo chroot . /root/$suite.sh
+
+# Leave the workdir, to build the filesystem
+cd -
+
+# For the final image mount
+mount=`mktemp -d`
+mount_remove() {
+ rmdir $mount
+ workdir_remove
+}
+trap mount_remove EXIT
+
+# Create a 1G empty ext3 filesystem
+truncate -s 1G $name
+mke2fs -F -t ext3 -L ROOT $name
+
+# Mount the new filesystem locally
+sudo mount -o loop -t ext3 $name $mount
+image_unmount() {
+  sudo umount $mount
+  mount_remove
+}
+trap image_unmount EXIT
+
+# Copy the patched debootstrap results into the new filesystem
+sudo cp -a $workdir/* $mount
+
+# Fill the rest of the space with zeroes, to optimize compression
+sudo dd if=/dev/zero of=$mount/sparse bs=1M 2>/dev/null || true
+sudo rm -f $mount/sparse
+
+echo "Debian $suite for $arch filesystem generated at '$name'."
diff --git a/net/test/iproute.py b/net/test/iproute.py
index a3310a2..8376eb6 100644
--- a/net/test/iproute.py
+++ b/net/test/iproute.py
@@ -209,13 +209,17 @@
 IFLA_XDP = 43
 IFLA_EVENT = 44
 
-# linux/include/uapi/if_link.h
+# include/uapi/linux/if_link.h
 IFLA_INFO_UNSPEC = 0
 IFLA_INFO_KIND = 1
 IFLA_INFO_DATA = 2
 IFLA_INFO_XSTATS = 3
 
-# linux/if_tunnel.h
+IFLA_XFRM_UNSPEC = 0
+IFLA_XFRM_LINK = 1
+IFLA_XFRM_IF_ID = 2
+
+# include/uapi/linux/if_tunnel.h
 IFLA_VTI_UNSPEC = 0
 IFLA_VTI_LINK = 1
 IFLA_VTI_IKEY = 2
@@ -682,7 +686,7 @@
     attrs = self._ParseAttributes(RTM_NEWLINK, IfinfoMsg, attrs)
     return attrs["IFLA_STATS64"]
 
-  def GetVtiInfoData(self, dev_name):
+  def GetIfinfoData(self, dev_name):
     """Returns an IFLA_INFO_DATA dict object for the specified interface."""
     _, attrs = self.GetIfinfo(dev_name)
     attrs = self._ParseAttributes(RTM_NEWLINK, IfinfoMsg, attrs)
@@ -745,6 +749,22 @@
       flags |= netlink.NLM_F_EXCL
     return self._SendNlRequest(RTM_NEWLINK, ifinfo, flags)
 
+  def CreateXfrmInterface(self, dev_name, xfrm_if_id, underlying_ifindex):
+    """Creates an XFRM interface with the specified parameters."""
+    # The netlink attribute structure is essentially identical to the one
+    # for VTI above (q.v).
+    ifdata = self._NlAttrU32(IFLA_XFRM_LINK, underlying_ifindex)
+    ifdata += self._NlAttrU32(IFLA_XFRM_IF_ID, xfrm_if_id)
+
+    linkinfo = self._NlAttrStr(IFLA_INFO_KIND, "xfrm")
+    linkinfo += self._NlAttr(IFLA_INFO_DATA, ifdata)
+
+    msg = IfinfoMsg().Pack()
+    msg += self._NlAttrStr(IFLA_IFNAME, dev_name)
+    msg += self._NlAttr(IFLA_LINKINFO, linkinfo)
+
+    return self._SendNlRequest(RTM_NEWLINK, msg)
+
 
 if __name__ == "__main__":
   iproute = IPRoute()
diff --git a/net/test/net_test.py b/net/test/net_test.py
index 6b19f54..1c7f32f 100755
--- a/net/test/net_test.py
+++ b/net/test/net_test.py
@@ -369,17 +369,17 @@
 
   def __enter__(self):
     if self.uid:
-      self.saved_uid = os.geteuid()
+      self.saved_uids = os.getresuid()
       self.saved_groups = os.getgroups()
       os.setgroups(self.saved_groups + [AID_INET])
-      os.seteuid(self.uid)
+      os.setresuid(self.uid, self.uid, self.saved_uids[0])
     if self.gid:
       self.saved_gid = os.getgid()
       os.setgid(self.gid)
 
   def __exit__(self, unused_type, unused_value, unused_traceback):
     if self.uid:
-      os.seteuid(self.saved_uid)
+      os.setresuid(*self.saved_uids)
       os.setgroups(self.saved_groups)
     if self.gid:
       os.setgid(self.saved_gid)
@@ -390,7 +390,6 @@
   def __init__(self, uid):
     RunAsUidGid.__init__(self, uid, 0)
 
-
 class NetworkTest(unittest.TestCase):
 
   def assertRaisesErrno(self, err_num, f=None, *args):
@@ -433,7 +432,7 @@
 
     if protocol.startswith("tcp"):
       # Real sockets have 5 extra numbers, timewait sockets have none.
-      end_regexp = "(| +[0-9]+ [0-9]+ [0-9]+ [0-9]+ -?[0-9]+|)$"
+      end_regexp = "(| +[0-9]+ [0-9]+ [0-9]+ [0-9]+ -?[0-9]+)$"
     elif re.match("icmp|udp|raw", protocol):
       # Drops.
       end_regexp = " +([0-9]+) *$"
@@ -458,8 +457,11 @@
     # TODO: consider returning a dict or namedtuple instead.
     out = []
     for line in lines:
+      m = regexp.match(line)
+      if m is None:
+        raise ValueError("Failed match on [%s]" % line)
       (_, src, dst, state, mem,
-       _, _, uid, _, _, refcnt, _, extra) = regexp.match(line).groups()
+       _, _, uid, _, _, refcnt, _, extra) = m.groups()
       out.append([src, dst, state, mem, uid, refcnt, extra])
     return out
 
diff --git a/net/test/net_test.sh b/net/test/net_test.sh
index bade6de..72c67a9 100755
--- a/net/test/net_test.sh
+++ b/net/test/net_test.sh
@@ -1,4 +1,135 @@
 #!/bin/bash
+if [[ -n "${verbose}" ]]; then
+  echo 'Current working directory:'
+  echo " - according to builtin:  [$(pwd)]"
+  echo " - according to /bin/pwd: [$(/bin/pwd)]"
+  echo
+
+  echo 'Shell environment:'
+  env
+  echo
+
+  echo -n "net_test.sh (pid $$, parent ${PPID}, tty $(tty)) running [$0] with args:"
+  for arg in "$@"; do
+    echo -n " [${arg}]"
+  done
+  echo
+  echo
+fi
+
+if [[ "$(tty)" == '/dev/console' ]]; then
+  ARCH="$(uname -m)"
+  # Underscore is illegal in hostname, replace with hyphen
+  ARCH="${ARCH//_/-}"
+
+  # setsid + /dev/tty{,AMA,S}0 allows bash's job control to work, ie. Ctrl+C/Z
+  if [[ -c '/dev/tty0' ]]; then
+    # exists in UML, does not exist on graphics/vga/curses-less QEMU
+    CON='/dev/tty0'
+    hostname "uml-${ARCH}"
+  elif [[ -c '/dev/ttyAMA0' ]]; then
+    # Qemu for arm (note: /dev/ttyS0 also exists for exitcode)
+    CON='/dev/ttyAMA0'
+    hostname "qemu-${ARCH}"
+  elif [[ -c '/dev/ttyS0' ]]; then
+    # Qemu for x86 (note: /dev/ttyS1 also exists for exitcode)
+    CON='/dev/ttyS0'
+    hostname "qemu-${ARCH}"
+  else
+    # Can't figure it out, job control won't work, tough luck
+    echo 'Unable to figure out proper console - job control will not work.' >&2
+    CON=''
+    hostname "local-${ARCH}"
+  fi
+
+  unset ARCH
+
+  echo -n "$(hostname): Currently tty[/dev/console], but it should be [${CON}]..."
+
+  if [[ -n "${CON}" ]]; then
+    # Redirect std{in,out,err} to the console equivalent tty
+    # which actually supports all standard tty ioctls
+    exec <"${CON}" >&"${CON}"
+
+    # Bash wants to be session leader, hence need for setsid
+    echo " re-executing..."
+    exec /usr/bin/setsid "$0" "$@"
+    # If the above exec fails, we just fall through...
+    # (this implies failure to *find* setsid, not error return from bash,
+    #  in practice due to image construction this cannot happen)
+  else
+    echo
+  fi
+
+  # In case we fall through, clean up
+  unset CON
+fi
+
+if [[ -n "${verbose}" ]]; then
+  echo 'TTY settings:'
+  stty
+  echo
+
+  echo 'TTY settings (verbose):'
+  stty -a
+  echo
+
+  echo 'Restoring TTY sanity...'
+fi
+
+stty sane
+stty 115200
+[[ -z "${console_cols}" ]] || stty columns "${console_cols}"
+[[ -z "${console_rows}" ]] || stty rows    "${console_rows}"
+
+if [[ -n "${verbose}" ]]; then
+  echo
+
+  echo 'TTY settings:'
+  stty
+  echo
+
+  echo 'TTY settings (verbose):'
+  stty -a
+  echo
+fi
+
+# By the time we get here we should have a sane console:
+#  - 115200 baud rate
+#  - appropriate (and known) width and height (note: this assumes
+#    that the terminal doesn't get further resized)
+#  - it is no longer /dev/console, so job control should function
+#    (this means working ctrl+c [abort] and ctrl+z [suspend])
+
+
+# This defaults to 60 which is needlessly long during boot
+# (we will reset it back to the default later)
+echo 0 > /proc/sys/kernel/random/urandom_min_reseed_secs
+
+if [[ -n "${entropy}" ]]; then
+  echo "adding entropy from hex string [${entropy}]" >&2
+
+  # In kernel/include/uapi/linux/random.h RNDADDENTROPY is defined as
+  # _IOW('R', 0x03, int[2]) =(R is 0x52)= 0x40085203 = 1074287107
+  /usr/bin/python 3>/dev/random <<EOF
+import fcntl, struct
+rnd = '${entropy}'.decode('base64')
+fcntl.ioctl(3, 0x40085203, struct.pack('ii', len(rnd) * 8, len(rnd)) + rnd)
+EOF
+
+fi
+
+# Make sure the urandom pool has a chance to initialize before we reset
+# the reseed timer back to 60 seconds.  One timer tick should be enough.
+sleep 1.1
+
+# By this point either 'random: crng init done' (newer kernels)
+# or 'random: nonblocking pool is initialized' (older kernels)
+# should have been printed out to dmesg/console.
+
+# Reset it back to boot time default
+echo 60 > /proc/sys/kernel/random/urandom_min_reseed_secs
+
 
 # In case IPv6 is compiled as a module.
 [ -f /proc/net/if_inet6 ] || insmod $DIR/kernel/net-next/net/ipv6/ipv6.ko
@@ -22,6 +153,6 @@
 echo -e "Running $net_test $net_test_args\n"
 $net_test $net_test_args
 
-# Write exit code of net_test to /proc/exitcode so that the builder can use it
+# Write exit code of net_test to a file so that the builder can use it
 # to signal failure if any tests fail.
-echo $? >/proc/exitcode
+echo $? >$net_test_exitcode
diff --git a/net/test/nf_test.py b/net/test/nf_test.py
new file mode 100755
index 0000000..cd6c976
--- /dev/null
+++ b/net/test/nf_test.py
@@ -0,0 +1,86 @@
+#!/usr/bin/python
+#
+# Copyright 2018 The Android Open Source Project
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+import unittest
+
+import errno
+from socket import *
+
+import multinetwork_base
+import net_test
+
+_TEST_IP4_ADDR = "192.0.2.1"
+_TEST_IP6_ADDR = "2001:db8::"
+
+
+# Regression tests for interactions between kernel networking and netfilter
+#
+# These tests were added to ensure that the lookup path for local-ICMP errors
+# do not cause failures. Specifically, local-ICMP packets do not have a
+# net_device in the skb, and has been known to trigger bugs in surrounding code.
+class NetilterRejectTargetTest(multinetwork_base.MultiNetworkBaseTest):
+
+  def setUp(self):
+    multinetwork_base.MultiNetworkBaseTest.setUp(self)
+    net_test.RunIptablesCommand(4, "-A OUTPUT -d " + _TEST_IP4_ADDR + " -j REJECT")
+    net_test.RunIptablesCommand(6, "-A OUTPUT -d " + _TEST_IP6_ADDR + " -j REJECT")
+
+  def tearDown(self):
+    net_test.RunIptablesCommand(4, "-D OUTPUT -d " + _TEST_IP4_ADDR + " -j REJECT")
+    net_test.RunIptablesCommand(6, "-D OUTPUT -d " + _TEST_IP6_ADDR + " -j REJECT")
+    multinetwork_base.MultiNetworkBaseTest.tearDown(self)
+
+  # Test a rejected TCP connect. The responding ICMP may not have skb->dev set.
+  # This tests the local-ICMP output-input path.
+  def CheckRejectedTcp(self, version, addr):
+    sock = net_test.TCPSocket(net_test.GetAddressFamily(version))
+    netid = self.RandomNetid()
+    self.SelectInterface(sock, netid, "mark")
+
+    # Expect this to fail with ICMP unreachable
+    try:
+      sock.connect((addr, 53))
+    except IOError:
+      pass
+
+  def testRejectTcp4(self):
+    self.CheckRejectedTcp(4, _TEST_IP4_ADDR)
+
+  def testRejectTcp6(self):
+    self.CheckRejectedTcp(6, _TEST_IP6_ADDR)
+
+  # Test a rejected UDP connect. The responding ICMP may not have skb->dev set.
+  # This tests the local-ICMP output-input path.
+  def CheckRejectedUdp(self, version, addr):
+    sock = net_test.UDPSocket(net_test.GetAddressFamily(version))
+    netid = self.RandomNetid()
+    self.SelectInterface(sock, netid, "mark")
+
+    # Expect this to fail with ICMP unreachable
+    try:
+      sock.sendto(net_test.UDP_PAYLOAD, (addr, 53))
+    except IOError:
+      pass
+
+  def testRejectUdp4(self):
+    self.CheckRejectedUdp(4, _TEST_IP4_ADDR)
+
+  def testRejectUdp6(self):
+    self.CheckRejectedUdp(6, _TEST_IP6_ADDR)
+
+
+if __name__ == "__main__":
+  unittest.main()
\ No newline at end of file
diff --git a/net/test/no_test b/net/test/no_test
new file mode 100755
index 0000000..b23e556
--- /dev/null
+++ b/net/test/no_test
@@ -0,0 +1 @@
+#!/bin/true
diff --git a/net/test/parallel_tests.sh b/net/test/parallel_tests.sh
index 93a43c8..eb67421 100755
--- a/net/test/parallel_tests.sh
+++ b/net/test/parallel_tests.sh
@@ -15,7 +15,7 @@
   local test=$3
   local j=0
   while ((j < runs)); do
-    $DIR/run_net_test.sh --readonly --builder --nobuild $test \
+    $DIR/run_net_test.sh --builder --nobuild $test \
         > /dev/null 2> $RESULTSDIR/results.$worker.$j
     j=$((j + 1))
     echo -n "." >&2
diff --git a/net/test/parameterization_test.py b/net/test/parameterization_test.py
new file mode 100755
index 0000000..8f9e130
--- /dev/null
+++ b/net/test/parameterization_test.py
@@ -0,0 +1,83 @@
+#!/usr/bin/python
+#
+# Copyright 2018 The Android Open Source Project
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+import itertools
+import unittest
+
+import net_test
+import util
+
+
+def InjectTests():
+  ParmeterizationTest.InjectTests()
+
+
+# This test class ensures that the Parameterized Test generator in utils.py
+# works properly. It injects test methods into itself, and ensures that they
+# are generated as expected, and that the TestClosures being run are properly
+# defined, and running different parameterized tests each time.
+class ParmeterizationTest(net_test.NetworkTest):
+  tests_run_list = []
+
+  @staticmethod
+  def NameGenerator(a, b, c):
+    return str(a) + "_" + str(b) + "_" + str(c)
+
+  @classmethod
+  def InjectTests(cls):
+    PARAMS_A = (1, 2)
+    PARAMS_B = (3, 4)
+    PARAMS_C = (5, 6)
+
+    param_list = itertools.product(PARAMS_A, PARAMS_B, PARAMS_C)
+    util.InjectParameterizedTest(cls, param_list, cls.NameGenerator)
+
+  def ParamTestDummyFunc(self, a, b, c):
+    self.tests_run_list.append(
+        "testDummyFunc_" + ParmeterizationTest.NameGenerator(a, b, c))
+
+  def testParameterization(self):
+    expected = [
+        "testDummyFunc_1_3_5",
+        "testDummyFunc_1_3_6",
+        "testDummyFunc_1_4_5",
+        "testDummyFunc_1_4_6",
+        "testDummyFunc_2_3_5",
+        "testDummyFunc_2_3_6",
+        "testDummyFunc_2_4_5",
+        "testDummyFunc_2_4_6",
+    ]
+
+    actual = [name for name in dir(self) if name.startswith("testDummyFunc")]
+
+    # Check that name and contents are equal
+    self.assertEqual(len(expected), len(actual))
+    self.assertEqual(sorted(expected), sorted(actual))
+
+    # Start a clean list, and run all the tests.
+    self.tests_run_list = list()
+    for test_name in expected:
+      test_method = getattr(self, test_name)
+      test_method()
+
+    # Make sure all tests have been run with the correct parameters
+    for test_name in expected:
+      self.assertTrue(test_name in self.tests_run_list)
+
+
+if __name__ == "__main__":
+  ParmeterizationTest.InjectTests()
+  unittest.main()
diff --git a/net/test/qtaguid_test.py b/net/test/qtaguid_test.py
index ad99a57..c121df2 100755
--- a/net/test/qtaguid_test.py
+++ b/net/test/qtaguid_test.py
@@ -27,7 +27,10 @@
 
 CTRL_PROCPATH = "/proc/net/xt_qtaguid/ctrl"
 OTHER_UID_GID = 12345
+HAVE_QTAGUID = os.path.exists(CTRL_PROCPATH)
 
+
+@unittest.skipUnless(HAVE_QTAGUID, "xt_qtaguid not supported")
 class QtaguidTest(tcp_test.TcpBaseTest):
 
   def RunIptablesCommand(self, args):
diff --git a/net/test/rootfs/common.sh b/net/test/rootfs/common.sh
new file mode 100644
index 0000000..172d9b6
--- /dev/null
+++ b/net/test/rootfs/common.sh
@@ -0,0 +1,57 @@
+#!/bin/sh
+#
+# Copyright (C) 2018 The Android Open Source Project
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#      http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+
+chroot_sanity_check() {
+  if [ ! -f /var/log/bootstrap.log ]; then
+    echo "Do not run this script directly!"
+    echo "This is supposed to be run from inside a debootstrap chroot!"
+    echo "Aborting."
+    exit 1
+  fi
+}
+
+chroot_cleanup() {
+  # Read-only root breaks booting via init
+  cat >/etc/fstab << EOF
+tmpfs /tmp     tmpfs defaults 0 0
+tmpfs /var/log tmpfs defaults 0 0
+tmpfs /var/tmp tmpfs defaults 0 0
+EOF
+
+  # systemd will attempt to re-create this symlink if it does not exist,
+  # which fails if it is booting from a read-only root filesystem (which
+  # is normally the case). The syslink must be relative, not absolute,
+  # and it must point to /proc/self/mounts, not /proc/mounts.
+  ln -sf ../proc/self/mounts /etc/mtab
+
+  # Remove contaminants coming from the debootstrap process
+  echo vm >/etc/hostname
+  echo "nameserver 127.0.0.1" >/etc/resolv.conf
+
+  # Put the helper net_test.sh script into place
+  mv /root/net_test.sh /sbin/net_test.sh
+
+  # Make sure the /host mountpoint exists for net_test.sh
+  mkdir /host
+
+  # Disable the root password
+  passwd -d root
+
+  # Clean up any junk created by the imaging process
+  rm -rf /var/lib/apt/lists/* /var/log/bootstrap.log /root/* /tmp/*
+  find /var/log -type f -exec rm -f '{}' ';'
+}
diff --git a/net/test/rootfs/net_test.sh b/net/test/rootfs/net_test.sh
new file mode 100755
index 0000000..9c94d06
--- /dev/null
+++ b/net/test/rootfs/net_test.sh
@@ -0,0 +1,34 @@
+#!/bin/bash
+#
+# Copyright (C) 2018 The Android Open Source Project
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#      http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+
+mount -t proc none /proc
+mount -t sysfs none /sys
+mount -t tmpfs tmpfs /tmp
+mount -t tmpfs tmpfs /run
+
+# If this system was booted under UML, it will always have a /proc/exitcode
+# file. If it was booted natively or under QEMU, it will not have this file.
+if [ -e /proc/exitcode ]; then
+  mount -t hostfs hostfs /host
+else
+  mount -t 9p -o trans=virtio,version=9p2000.L host /host
+fi
+
+test=$(cat /proc/cmdline | sed -re 's/.*net_test=([^ ]*).*/\1/g')
+cd $(dirname $test)
+./net_test.sh
+poweroff -f
diff --git a/net/test/rootfs/stretch.list b/net/test/rootfs/stretch.list
new file mode 100644
index 0000000..fbeddde
--- /dev/null
+++ b/net/test/rootfs/stretch.list
@@ -0,0 +1,33 @@
+apt
+apt-utils
+bash-completion
+bsdmainutils
+ca-certificates
+file
+gpgv
+ifupdown
+insserv
+iputils-ping
+less
+libnetfilter-conntrack3
+libnfnetlink0
+mime-support
+netbase
+netcat-openbsd
+netcat-traditional
+net-tools
+openssl
+pciutils
+procps
+psmisc
+python
+python-scapy
+strace
+systemd-sysv
+tcpdump
+traceroute
+udev
+udhcpc
+usbutils
+vim-tiny
+wget
diff --git a/net/test/rootfs/stretch.sh b/net/test/rootfs/stretch.sh
new file mode 100755
index 0000000..6d8a9a4
--- /dev/null
+++ b/net/test/rootfs/stretch.sh
@@ -0,0 +1,150 @@
+#!/bin/bash
+#
+# Copyright (C) 2018 The Android Open Source Project
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#      http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+
+set -e
+
+SCRIPT_DIR=$(CDPATH= cd -- "$(dirname -- "$0")" && pwd -P)
+
+. $SCRIPT_DIR/common.sh
+
+chroot_sanity_check
+
+cd /root
+
+# Add the needed debian sources
+cat >/etc/apt/sources.list <<EOF
+deb http://ftp.debian.org/debian stretch main
+deb-src http://ftp.debian.org/debian stretch main
+deb http://ftp.debian.org/debian stretch-backports main
+deb-src http://ftp.debian.org/debian stretch-backports main
+deb http://ftp.debian.org/debian buster main
+deb-src http://ftp.debian.org/debian buster main
+EOF
+
+# Make sure apt doesn't want to install from buster by default
+cat >/etc/apt/apt.conf.d/80default <<EOF
+APT::Default-Release "stretch";
+EOF
+
+# Disable the automatic installation of recommended packages
+cat >/etc/apt/apt.conf.d/90recommends <<EOF
+APT::Install-Recommends "0";
+EOF
+
+# Deprioritize buster, so it must be specified manually
+cat >/etc/apt/preferences.d/90buster <<EOF
+Package: *
+Pin: release a=buster
+Pin-Priority: 90
+EOF
+
+# Update for the above changes
+apt-get update
+
+# Install python-scapy from buster, because stretch's version is broken
+apt-get install -y -t buster python-scapy
+
+# Note what we have installed; we will go back to this
+LANG=C dpkg --get-selections | sort >originally-installed
+
+# Install everything needed from stretch to build iptables
+apt-get install -y \
+  build-essential \
+  autoconf \
+  automake \
+  bison \
+  debhelper \
+  devscripts \
+  fakeroot \
+  flex \
+  libmnl-dev \
+  libnetfilter-conntrack-dev \
+  libnfnetlink-dev \
+  libnftnl-dev \
+  libtool
+
+# Install newer linux-libc headers (these are from 4.16)
+apt-get install -y -t stretch-backports linux-libc-dev
+
+# We are done with apt; reclaim the disk space
+apt-get clean
+
+# Construct the iptables source package to build
+iptables=iptables-1.6.1
+mkdir -p /usr/src/$iptables
+
+cd /usr/src/$iptables
+# Download a specific revision of iptables from AOSP
+aosp_iptables=android-wear-p-preview-2
+wget -qO - \
+  https://android.googlesource.com/platform/external/iptables/+archive/$aosp_iptables.tar.gz | \
+  tar -zxf -
+# Download a compatible 'debian' overlay from Debian salsa
+# We don't want all of the sources, just the Debian modifications
+debian_iptables=1.6.1-2_bpo9+1
+debian_iptables_dir=pkg-iptables-debian-$debian_iptables
+wget -qO - \
+  https://salsa.debian.org/pkg-netfilter-team/pkg-iptables/-/archive/debian/$debian_iptables/$debian_iptables_dir.tar.gz | \
+  tar --strip-components 1 -zxf - \
+  $debian_iptables_dir/debian
+cd -
+
+cd /usr/src
+# Generate a source package to leave in the filesystem. This is done for license
+# compliance and build reproducibility.
+tar --exclude=debian -cf - $iptables | \
+  xz -9 >`echo $iptables | tr -s '-' '_'`.orig.tar.xz
+cd -
+
+cd /usr/src/$iptables
+# Build debian packages from the integrated iptables source
+dpkg-buildpackage -F -us -uc
+cd -
+
+# Record the list of packages we have installed now
+LANG=C dpkg --get-selections | sort >installed
+
+# Compute the difference, and remove anything installed between the snapshots
+dpkg -P `comm -3 originally-installed installed | sed -e 's,install,,' -e 's,\t,,' | xargs`
+
+cd /usr/src
+# Find any packages generated, resolve to the debian package name, then
+# exclude any compat, header or symbol packages
+packages=`find -maxdepth 1 -name '*.deb' | colrm 1 2 | cut -d'_' -f1 |
+          grep -ve '-compat$\|-dbg$\|-dbgsym$\|-dev$' | xargs`
+# Install the patched iptables packages, and 'hold' then so
+# "apt-get dist-upgrade" doesn't replace them
+dpkg -i `
+for package in $packages; do
+  echo ${package}_*.deb
+done | xargs`
+for package in $packages; do
+  echo "$package hold" | dpkg --set-selections
+done
+# Tidy up the mess we left behind, leaving just the source tarballs
+rm -rf $iptables *.buildinfo *.changes *.deb *.dsc
+cd -
+
+# Ensure a getty is spawned on ttyS0, if booting the image manually
+ln -s /lib/systemd/system/serial-getty\@.service \
+  /etc/systemd/system/getty.target.wants/serial-getty\@ttyS0.service
+
+# systemd needs some directories to be created
+mkdir -p /var/lib/systemd/coredump /var/lib/systemd/rfkill
+
+# Finalize and tidy up the created image
+chroot_cleanup
diff --git a/net/test/rootfs/wheezy.list b/net/test/rootfs/wheezy.list
new file mode 100644
index 0000000..44e3d85
--- /dev/null
+++ b/net/test/rootfs/wheezy.list
@@ -0,0 +1,33 @@
+adduser
+apt
+apt-utils
+bash-completion
+binutils
+bsdmainutils
+ca-certificates
+file
+gpgv
+ifupdown
+insserv
+iptables
+iputils-ping
+less
+libpopt0
+mime-support
+netbase
+netcat6
+netcat-traditional
+net-tools
+module-init-tools
+openssl
+procps
+psmisc
+python2.7
+python-scapy
+strace
+tcpdump
+traceroute
+udev
+udhcpc
+vim-tiny
+wget
diff --git a/net/test/rootfs/wheezy.sh b/net/test/rootfs/wheezy.sh
new file mode 100755
index 0000000..81cfad7
--- /dev/null
+++ b/net/test/rootfs/wheezy.sh
@@ -0,0 +1,50 @@
+#!/bin/bash
+#
+# Copyright (C) 2018 The Android Open Source Project
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#      http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+
+# NOTE: It is highly recommended that you do not create new wheezy rootfs
+#       images. This script is here for forensic purposes only, to understand
+#       how the original rootfs was created.
+
+set -e
+
+SCRIPT_DIR=$(CDPATH= cd -- "$(dirname -- "$0")" && pwd -P)
+
+. $SCRIPT_DIR/common.sh
+
+chroot_sanity_check
+
+# Remove things pulled in by debootstrap that we do not need
+dpkg -P \
+  debconf-i18n \
+  liblocale-gettext-perl \
+  libtext-charwidth-perl \
+  libtext-iconv-perl \
+  libtext-wrapi18n-perl \
+  python2.6 \
+  python2.6-minimal \
+  xz-utils
+
+# We are done with apt; reclaim the disk space
+apt-get clean
+
+# Ensure a getty is spawned on ttyS0, if booting the image manually
+# This also removes the vt gettys, as we may have no vt
+sed -i '/tty[123456]/d' /etc/inittab
+echo "s0:1235:respawn:/sbin/getty 115200 ttyS0 linux" >>/etc/inittab
+
+# Finalize and tidy up the created image
+chroot_cleanup
diff --git a/net/test/run_net_test.sh b/net/test/run_net_test.sh
index 8c256b4..17b44d9 100755
--- a/net/test/run_net_test.sh
+++ b/net/test/run_net_test.sh
@@ -1,47 +1,72 @@
 #!/bin/bash
 
-# Kernel configuration options.
+# Builds mysteriously fail if stdout is non-blocking.
+fixup_ptys() {
+  python << 'EOF'
+import fcntl, os, sys
+fd = sys.stdout.fileno()
+flags = fcntl.fcntl(fd, fcntl.F_GETFL)
+flags &= ~(fcntl.FASYNC | os.O_NONBLOCK | os.O_APPEND)
+fcntl.fcntl(fd, fcntl.F_SETFL, flags)
+EOF
+}
+
+# Common kernel options
 OPTIONS=" DEBUG_SPINLOCK DEBUG_ATOMIC_SLEEP DEBUG_MUTEXES DEBUG_RT_MUTEXES"
+OPTIONS="$OPTIONS DEVTMPFS DEVTMPFS_MOUNT FHANDLE"
 OPTIONS="$OPTIONS IPV6 IPV6_ROUTER_PREF IPV6_MULTIPLE_TABLES IPV6_ROUTE_INFO"
 OPTIONS="$OPTIONS TUN SYN_COOKIES IP_ADVANCED_ROUTER IP_MULTIPLE_TABLES"
 OPTIONS="$OPTIONS NETFILTER NETFILTER_ADVANCED NETFILTER_XTABLES"
 OPTIONS="$OPTIONS NETFILTER_XT_MARK NETFILTER_XT_TARGET_MARK"
 OPTIONS="$OPTIONS IP_NF_IPTABLES IP_NF_MANGLE IP_NF_FILTER"
 OPTIONS="$OPTIONS IP6_NF_IPTABLES IP6_NF_MANGLE IP6_NF_FILTER INET6_IPCOMP"
-OPTIONS="$OPTIONS IPV6_PRIVACY IPV6_OPTIMISTIC_DAD"
-OPTIONS="$OPTIONS CONFIG_IPV6_ROUTE_INFO CONFIG_IPV6_ROUTER_PREF"
-OPTIONS="$OPTIONS CONFIG_NETFILTER_XT_TARGET_NFLOG"
-OPTIONS="$OPTIONS CONFIG_NETFILTER_XT_MATCH_QUOTA"
-OPTIONS="$OPTIONS CONFIG_NETFILTER_XT_MATCH_QUOTA2"
-OPTIONS="$OPTIONS CONFIG_NETFILTER_XT_MATCH_QUOTA2_LOG"
-OPTIONS="$OPTIONS CONFIG_NETFILTER_TPROXY"
-OPTIONS="$OPTIONS CONFIG_NETFILTER_XT_MATCH_SOCKET"
-OPTIONS="$OPTIONS CONFIG_NETFILTER_XT_MATCH_QTAGUID"
-# For 4.14 CONFIG_NETFILTER_XT_MATCH_SOCKET demands CONFIG_NF_SOCKET_IPV4/6
-OPTIONS="$OPTIONS CONFIG_NF_SOCKET_IPV4 CONFIG_NF_SOCKET_IPV6"
-OPTIONS="$OPTIONS CONFIG_INET_UDP_DIAG CONFIG_INET_DIAG_DESTROY"
-OPTIONS="$OPTIONS IP_SCTP INET_SCTP_DIAG"
-OPTIONS="$OPTIONS CONFIG_IP_NF_TARGET_REJECT CONFIG_IP_NF_TARGET_REJECT_SKERR"
-OPTIONS="$OPTIONS CONFIG_IP6_NF_TARGET_REJECT CONFIG_IP6_NF_TARGET_REJECT_SKERR"
-OPTIONS="$OPTIONS BPF_SYSCALL NET_KEY XFRM_USER XFRM_STATISTICS CRYPTO_CBC"
-OPTIONS="$OPTIONS CRYPTO_CTR CRYPTO_HMAC CRYPTO_AES CRYPTO_SHA1 CRYPTO_SHA256"
-OPTIONS="$OPTIONS CRYPTO_SHA12 CRYPTO_USER INET_AH INET_ESP INET_XFRM_MODE"
-OPTIONS="$OPTIONS TRANSPORT INET_XFRM_MODE_TUNNEL INET6_AH INET6_ESP"
+OPTIONS="$OPTIONS IPV6_OPTIMISTIC_DAD"
+OPTIONS="$OPTIONS IPV6_ROUTE_INFO IPV6_ROUTER_PREF"
+OPTIONS="$OPTIONS NETFILTER_XT_TARGET_NFLOG"
+OPTIONS="$OPTIONS NETFILTER_XT_MATCH_QUOTA"
+OPTIONS="$OPTIONS NETFILTER_XT_MATCH_QUOTA2"
+OPTIONS="$OPTIONS NETFILTER_XT_MATCH_QUOTA2_LOG"
+OPTIONS="$OPTIONS NETFILTER_XT_MATCH_SOCKET"
+OPTIONS="$OPTIONS NETFILTER_XT_MATCH_QTAGUID"
+OPTIONS="$OPTIONS INET_UDP_DIAG INET_DIAG_DESTROY"
+OPTIONS="$OPTIONS IP_SCTP"
+OPTIONS="$OPTIONS IP_NF_TARGET_REJECT IP_NF_TARGET_REJECT_SKERR"
+OPTIONS="$OPTIONS IP6_NF_TARGET_REJECT IP6_NF_TARGET_REJECT_SKERR"
+OPTIONS="$OPTIONS NET_KEY XFRM_USER XFRM_STATISTICS CRYPTO_CBC"
+OPTIONS="$OPTIONS CRYPTO_CTR CRYPTO_HMAC CRYPTO_AES CRYPTO_SHA1"
+OPTIONS="$OPTIONS CRYPTO_USER INET_ESP INET_XFRM_MODE_TRANSPORT"
+OPTIONS="$OPTIONS INET_XFRM_MODE_TUNNEL INET6_ESP"
 OPTIONS="$OPTIONS INET6_XFRM_MODE_TRANSPORT INET6_XFRM_MODE_TUNNEL"
 OPTIONS="$OPTIONS CRYPTO_SHA256 CRYPTO_SHA512 CRYPTO_AES_X86_64 CRYPTO_NULL"
-OPTIONS="$OPTIONS CRYPTO_GCM CRYPTO_ECHAINIV NET_IPVTI IPV6_VTI"
-OPTIONS="$OPTIONS SOCK_CGROUP_DATA CGROUP_BPF"
+OPTIONS="$OPTIONS CRYPTO_GCM CRYPTO_ECHAINIV NET_IPVTI"
 
-# For 4.14 kernels, where UBD and HOSTFS are not set
-OPTIONS="$OPTIONS CONFIG_BLK_DEV_UBD CONFIG_HOSTFS"
+# Kernel version specific options
+OPTIONS="$OPTIONS XFRM_INTERFACE"                # Various device kernels
+OPTIONS="$OPTIONS CGROUP_BPF"                    # Added in android-4.9
+OPTIONS="$OPTIONS NF_SOCKET_IPV4 NF_SOCKET_IPV6" # Added in 4.9
+OPTIONS="$OPTIONS INET_SCTP_DIAG"                # Added in 4.7
+OPTIONS="$OPTIONS SOCK_CGROUP_DATA"              # Added in 4.5
+OPTIONS="$OPTIONS CRYPTO_ECHAINIV"               # Added in 4.1
+OPTIONS="$OPTIONS BPF_SYSCALL"                   # Added in 3.18
+OPTIONS="$OPTIONS IPV6_VTI"                      # Added in 3.13
+OPTIONS="$OPTIONS IPV6_PRIVACY"                  # Removed in 3.12
+OPTIONS="$OPTIONS NETFILTER_TPROXY"              # Removed in 3.11
 
-# For 3.1 kernels, where devtmpfs is not on by default.
-OPTIONS="$OPTIONS DEVTMPFS DEVTMPFS_MOUNT"
+# UML specific options
+OPTIONS="$OPTIONS BLK_DEV_UBD HOSTFS"
+
+# QEMU specific options
+OPTIONS="$OPTIONS VIRTIO VIRTIO_PCI VIRTIO_BLK NET_9P NET_9P_VIRTIO 9P_FS"
+OPTIONS="$OPTIONS SERIAL_8250 SERIAL_8250_PCI"
+
+# Obsolete options present at some time in Android kernels
+OPTIONS="$OPTIONS IP_NF_TARGET_REJECT_SKERR IP6_NF_TARGET_REJECT_SKERR"
 
 # These two break the flo kernel due to differences in -Werror on recent GCC.
-DISABLE_OPTIONS=" CONFIG_REISERFS_FS CONFIG_ANDROID_PMEM"
+DISABLE_OPTIONS=" REISERFS_FS ANDROID_PMEM"
+
 # This one breaks the fugu kernel due to a nonexistent sem_wait_array.
-DISABLE_OPTIONS="$DISABLE_OPTIONS CONFIG_SYSVIPC"
+DISABLE_OPTIONS="$DISABLE_OPTIONS SYSVIPC"
 
 # How many TAP interfaces to create to provide the VM with real network access
 # via the host. This requires privileges (e.g., root access) on the host.
@@ -56,11 +81,12 @@
 NUMTAPINTERFACES=0
 
 # The root filesystem disk image we'll use.
-ROOTFS=net_test.rootfs.20150203
+ROOTFS=${ROOTFS:-net_test.rootfs.20150203}
 COMPRESSED_ROOTFS=$ROOTFS.xz
 URL=https://dl.google.com/dl/android/$COMPRESSED_ROOTFS
 
 # Parse arguments and figure out which test to run.
+ARCH=${ARCH:-um}
 J=${J:-64}
 MAKE="make"
 OUT_DIR=$(readlink -f ${OUT_DIR:-.})
@@ -72,25 +98,42 @@
 CONFIG_SCRIPT=${KERNEL_DIR}/scripts/config
 CONFIG_FILE=${OUT_DIR}/.config
 consolemode=
+netconfig=
 testmode=
-blockdevice=ubda
+cmdline=
+nowrite=1
 nobuild=0
 norun=0
 
-while [ -n "$1" ]; do
-  if [ "$1" = "--builder" ]; then
+if tty >/dev/null; then
+  verbose=
+else
+  verbose=1
+fi
+
+while [[ -n "$1" ]]; do
+  if [[ "$1" == "--builder" ]]; then
     consolemode="con=null,fd:1"
     testmode=builder
     shift
-  elif [ "$1" == "--readonly" ]; then
-    blockdevice="${blockdevice}r"
+  elif [[ "$1" == "--readwrite" || "$1" == "--rw" ]]; then
+    nowrite=0
     shift
-  elif [ "$1" == "--nobuild" ]; then
+  elif [[ "$1" == "--readonly" ||  "$1" == "--ro" ]]; then
+    nowrite=1
+    shift
+  elif [[ "$1" == "--nobuild" ]]; then
     nobuild=1
     shift
-  elif [ "$1" == "--norun" ]; then
+  elif [[ "$1" == "--norun" ]]; then
     norun=1
     shift
+  elif [[ "$1" == "--verbose" ]]; then
+    verbose=1
+    shift
+  elif [[ "$1" == "--noverbose" ]]; then
+    verbose=
+    shift
   else
     test=$1
     break  # Arguments after the test file are passed to the test itself.
@@ -122,7 +165,7 @@
 
 if ! isRunningTest && ! isBuildOnly; then
   echo "Usage:" >&2
-  echo "  $0 [--builder] [--readonly] [--nobuild] <test>" >&2
+  echo "  $0 [--builder] [--readonly|--ro|--readwrite|--rw] [--nobuild] [--verbose] <test>" >&2
   echo "  $0 --norun" >&2
   exit 1
 fi
@@ -152,12 +195,16 @@
 if (( $NUMTAPINTERFACES > 0 )); then
   user=${USER:0:10}
   tapinterfaces=
-  netconfig=
   for id in $(seq 0 $(( NUMTAPINTERFACES - 1 )) ); do
     tap=${user}TAP$id
     tapinterfaces="$tapinterfaces $tap"
     mac=$(printf fe:fd:00:00:00:%02x $id)
-    netconfig="$netconfig eth$id=tuntap,$tap,$mac"
+    if [ "$ARCH" == "um" ]; then
+      netconfig="$netconfig eth$id=tuntap,$tap,$mac"
+    else
+      netconfig="$netconfig -netdev tap,id=hostnet$id,ifname=$tap,script=no,downscript=no"
+      netconfig="$netconfig -device virtio-net-pci,netdev=hostnet$id,id=net$id,mac=$mac"
+    fi
   done
 
   for tap in $tapinterfaces; do
@@ -172,49 +219,161 @@
 if [ -n "$KERNEL_BINARY" ]; then
   nobuild=1
 else
-  KERNEL_BINARY=./linux
+  # Set default KERNEL_BINARY location if it was not provided.
+  if [ "$ARCH" == "um" ]; then
+    KERNEL_BINARY=./linux
+  elif [ "$ARCH" == "i386" -o "$ARCH" == "x86_64" -o "$ARCH" == "x86" ]; then
+    KERNEL_BINARY=./arch/x86/boot/bzImage
+  elif [ "$ARCH" == "arm64" ]; then
+    KERNEL_BINARY=./arch/arm64/boot/Image.gz
+  fi
 fi
 
 if ((nobuild == 0)); then
-  # Exporting ARCH=um SUBARCH=x86_64 doesn't seem to work, as it "sometimes"
-  # (?) results in a 32-bit kernel.
-
-  # If there's no kernel config at all, create one or UML won't work.
-  [ -f $CONFIG_FILE ] || (cd $KERNEL_DIR && $MAKE defconfig ARCH=um SUBARCH=x86_64)
-
-  # Enable the kernel config options listed in $OPTIONS.
-  cmdline=${OPTIONS// / -e }
-  $CONFIG_SCRIPT --file $CONFIG_FILE $cmdline
-
-  # Disable the kernel config options listed in $DISABLE_OPTIONS.
-  cmdline=${DISABLE_OPTIONS// / -d }
-  $CONFIG_SCRIPT --file $CONFIG_FILE $cmdline
-
-  # olddefconfig doesn't work on old kernels.
-  if ! $MAKE olddefconfig ARCH=um SUBARCH=x86_64 CROSS_COMPILE= ; then
-    cat >&2 << EOF
-
-Warning: "make olddefconfig" failed.
-Perhaps this kernel is too old to support it.
-You may get asked lots of questions.
-Keep enter pressed to accept the defaults.
-
-EOF
+  make_flags=
+  if [ "$ARCH" == "um" ]; then
+    # Exporting ARCH=um SUBARCH=x86_64 doesn't seem to work, as it
+    # "sometimes" (?) results in a 32-bit kernel.
+    make_flags="$make_flags ARCH=$ARCH SUBARCH=x86_64 CROSS_COMPILE= "
+  fi
+  if [ -n "$CC" ]; then
+    # The CC flag is *not* inherited from the environment, so it must be
+    # passed in on the command line.
+    make_flags="$make_flags CC=$CC"
   fi
 
+  # If there's no kernel config at all, create one or UML won't work.
+  [ -n "$DEFCONFIG" ] || DEFCONFIG=defconfig
+  [ -f $CONFIG_FILE ] || (cd $KERNEL_DIR && $MAKE $make_flags $DEFCONFIG)
+
+  # Enable the kernel config options listed in $OPTIONS.
+  $CONFIG_SCRIPT --file $CONFIG_FILE ${OPTIONS// / -e }
+
+  # Disable the kernel config options listed in $DISABLE_OPTIONS.
+  $CONFIG_SCRIPT --file $CONFIG_FILE ${DISABLE_OPTIONS// / -d }
+
+  $MAKE $make_flags olddefconfig
+
   # Compile the kernel.
-  $MAKE -j$J linux ARCH=um SUBARCH=x86_64 CROSS_COMPILE=
+  if [ "$ARCH" == "um" ]; then
+    $MAKE -j$J $make_flags linux
+  else
+    $MAKE -j$J $make_flags
+  fi
 fi
 
 if (( norun == 1 )); then
   exit 0
 fi
 
-# Get the absolute path to the test file that's being run.
-dir=/host$SCRIPT_DIR
+if (( nowrite == 1 )); then
+  cmdline="ro"
+fi
 
-# Start the VM.
-exec $KERNEL_BINARY umid=net_test $blockdevice=$SCRIPT_DIR/$ROOTFS \
-    mem=512M init=/sbin/net_test.sh net_test=$dir/$test \
-    net_test_args=\"$test_args\" \
-    net_test_mode=$testmode $netconfig $consolemode >&2
+if (( verbose == 1 )); then
+  cmdline="$cmdline verbose=1"
+fi
+
+cmdline="$cmdline init=/sbin/net_test.sh"
+cmdline="$cmdline net_test_args=\"$test_args\" net_test_mode=$testmode"
+
+if [ "$ARCH" == "um" ]; then
+  # Get the absolute path to the test file that's being run.
+  cmdline="$cmdline net_test=/host$SCRIPT_DIR/$test"
+
+  # Use UML's /proc/exitcode feature to communicate errors on test failure
+  cmdline="$cmdline net_test_exitcode=/proc/exitcode"
+
+  # Experience shows that we need at least 128 bits of entropy for the
+  # kernel's crng init to complete (before it fully initializes stuff behaves
+  # *weirdly* and there's plenty of kernel warnings and some tests even fail),
+  # hence net_test.sh needs at least 32 hex chars (which is the amount of hex
+  # in a single random UUID) provided to it on the kernel cmdline.
+  #
+  # Just to be safe, we'll pass in 384 bits, and we'll do this as a random
+  # 64 character base64 seed (because this is shorter than base16).
+  # We do this by getting *three* random UUIDs and concatenating their hex
+  # digits into an *even* length hex encoded string, which we then convert
+  # into base64.
+  entropy="$(cat /proc/sys/kernel/random{/,/,/}uuid | tr -d '\n-')"
+  entropy="$(xxd -r -p <<< "${entropy}" | base64 -w 0)"
+  cmdline="${cmdline} entropy=${entropy}"
+
+  # Map the --readonly flag to UML block device names
+  if ((nowrite == 0)); then
+    blockdevice=ubda
+  else
+    blockdevice=ubdar
+  fi
+
+  $KERNEL_BINARY >&2 umid=net_test mem=512M \
+    $blockdevice=$SCRIPT_DIR/$ROOTFS $netconfig $consolemode $cmdline
+  exitcode=$?
+else
+  # We boot into the filesystem image directly in all cases
+  cmdline="$cmdline root=/dev/vda"
+
+  # The path is stripped by the 9p export; we don't need SCRIPT_DIR
+  cmdline="$cmdline net_test=/host/$test"
+
+  # Map the --readonly flag to a QEMU block device flag
+  if ((nowrite > 0)); then
+    blockdevice=",readonly"
+  else
+    blockdevice=
+  fi
+  blockdevice="-drive file=$SCRIPT_DIR/$ROOTFS,format=raw,if=none,id=drive-virtio-disk0$blockdevice"
+  blockdevice="$blockdevice -device virtio-blk-pci,drive=drive-virtio-disk0"
+
+  # Pass through our current console/screen size to inner shell session
+  read rows cols < <(stty size 2>/dev/null)
+  [[ -z "${rows}" ]] || cmdline="${cmdline} console_rows=${rows}"
+  [[ -z "${cols}" ]] || cmdline="${cmdline} console_cols=${cols}"
+  unset rows cols
+
+  # QEMU has no way to modify its exitcode; simulate it with a serial port.
+  #
+  # Choose to do it this way over writing a file to /host, because QEMU will
+  # initialize the 'exitcode' file for us, it avoids unnecessary writes to the
+  # host filesystem (which is normally not written to) and it allows us to
+  # communicate an exit code back in cases we do not have /host mounted.
+  #
+  if [ "$ARCH" == "i386" -o "$ARCH" == "x86_64" -o "$ARCH" == "x86" ]; then
+    # Assume we have hardware-accelerated virtualization support for amd64
+    qemu="qemu-system-x86_64 -machine pc,accel=kvm -cpu host"
+
+    # The assignment of 'ttyS1' here is magical -- we know 'ttyS0' will be our
+    # serial port from the hard-coded '-serial stdio' flag below, and so this
+    # second serial port will be 'ttyS1'.
+    cmdline="$cmdline net_test_exitcode=/dev/ttyS1"
+  elif [ "$ARCH" == "arm64" ]; then
+    # This uses a software model CPU, based on cortex-a57
+    qemu="qemu-system-aarch64 -machine virt -cpu cortex-a57"
+
+    # The kernel will print messages via a virtual ARM serial port (ttyAMA0),
+    # but for command line consistency with x86, we put the exitcode serial
+    # port on the PCI bus, and it will be the only one.
+    cmdline="$cmdline net_test_exitcode=/dev/ttyS0"
+  fi
+
+  $qemu >&2 -name net_test -m 512 \
+    -kernel $KERNEL_BINARY \
+    -no-user-config -nodefaults -no-reboot \
+    -display none -nographic -serial mon:stdio -parallel none \
+    -smp 4,sockets=4,cores=1,threads=1 \
+    -device virtio-rng-pci \
+    -chardev file,id=exitcode,path=exitcode \
+    -device pci-serial,chardev=exitcode \
+    -fsdev local,security_model=mapped-xattr,id=fsdev0,fmode=0644,dmode=0755,path=$SCRIPT_DIR \
+    -device virtio-9p-pci,id=fs0,fsdev=fsdev0,mount_tag=host \
+    $blockdevice $netconfig -append "$cmdline"
+  [[ -s exitcode ]] && exitcode=`cat exitcode | tr -d '\r'` || exitcode=1
+  rm -f exitcode
+fi
+
+# UML reliably screws up the ptys, QEMU probably can as well...
+fixup_ptys
+stty sane || :
+
+echo "Returning exit code ${exitcode}." 1>&2
+exit "${exitcode}"
diff --git a/net/test/sock_diag_test.py b/net/test/sock_diag_test.py
index 4b1d055..daa2fa4 100755
--- a/net/test/sock_diag_test.py
+++ b/net/test/sock_diag_test.py
@@ -25,16 +25,20 @@
 import time
 import unittest
 
+import cstruct
 import multinetwork_base
 import net_test
 import packets
 import sock_diag
 import tcp_test
 
+# Mostly empty structure definition containing only the fields we currently use.
+TcpInfo = cstruct.Struct("TcpInfo", "64xI", "tcpi_rcv_ssthresh")
 
 NUM_SOCKETS = 30
 NO_BYTECODE = ""
-HAVE_SO_COOKIE_SUPPORT = net_test.LINUX_VERSION >= (4, 9, 0)
+LINUX_4_9_OR_ABOVE = net_test.LINUX_VERSION >= (4, 9, 0)
+LINUX_4_19_OR_ABOVE = net_test.LINUX_VERSION >= (4, 19, 0)
 
 IPPROTO_SCTP = 132
 
@@ -55,7 +59,7 @@
     True if the kernel is 4.9 or above, or the CONFIG_INET_UDP_DIAG is enabled.
     False otherwise.
   """
-  if HAVE_SO_COOKIE_SUPPORT:
+  if LINUX_4_9_OR_ABOVE:
       return True;
   s = socket(AF_INET6, SOCK_DGRAM, 0)
   s.bind(("::", 0))
@@ -206,10 +210,10 @@
       self.sock_diag.GetSockInfo(diag_req)
       # No errors? Good.
 
-  def testFindsAllMySockets(self):
+  def CheckFindsAllMySockets(self, socktype, proto):
     """Tests that basic socket dumping works."""
-    self.socketpairs = self._CreateLotsOfSockets(SOCK_STREAM)
-    sockets = self.sock_diag.DumpAllInetSockets(IPPROTO_TCP, NO_BYTECODE)
+    self.socketpairs = self._CreateLotsOfSockets(socktype)
+    sockets = self.sock_diag.DumpAllInetSockets(proto, NO_BYTECODE)
     self.assertGreaterEqual(len(sockets), NUM_SOCKETS)
 
     # Find the cookies for all of our sockets.
@@ -239,9 +243,21 @@
         # Check that we can find a diag_msg once we know the cookie.
         req = self.sock_diag.DiagReqFromSocket(sock)
         req.id.cookie = cookie
+        if proto == IPPROTO_UDP:
+          # Kernel bug: for UDP sockets, the order of arguments must be swapped.
+          # See testDemonstrateUdpGetSockIdBug.
+          req.id.sport, req.id.dport = req.id.dport, req.id.sport
+          req.id.src, req.id.dst = req.id.dst, req.id.src
         info = self.sock_diag.GetSockInfo(req)
         self.assertSockInfoMatchesSocket(sock, info)
 
+  def testFindsAllMySocketsTcp(self):
+    self.CheckFindsAllMySockets(SOCK_STREAM, IPPROTO_TCP)
+
+  @unittest.skipUnless(HAVE_UDP_DIAG, "INET_UDP_DIAG not enabled")
+  def testFindsAllMySocketsUdp(self):
+    self.CheckFindsAllMySockets(SOCK_DGRAM, IPPROTO_UDP)
+
   def testBytecodeCompilation(self):
     # pylint: disable=bad-whitespace
     instructions = [
@@ -374,11 +390,45 @@
       cookie = sock.getsockopt(net_test.SOL_SOCKET, net_test.SO_COOKIE, 8)
       self.assertEqual(diag_msg.id.cookie, cookie)
 
-  @unittest.skipUnless(HAVE_SO_COOKIE_SUPPORT, "SO_COOKIE not supported")
+  @unittest.skipUnless(LINUX_4_9_OR_ABOVE, "SO_COOKIE not supported")
   def testGetsockoptcookie(self):
     self.CheckSocketCookie(AF_INET, "127.0.0.1")
     self.CheckSocketCookie(AF_INET6, "::1")
 
+  @unittest.skipUnless(HAVE_UDP_DIAG, "INET_UDP_DIAG not enabled")
+  def testDemonstrateUdpGetSockIdBug(self):
+    # TODO: this is because udp_dump_one mistakenly uses __udp[46]_lib_lookup
+    # by passing the source address as the source address argument.
+    # Unfortunately those functions are intended to match local sockets based
+    # on received packets, and the argument that ends up being compared with
+    # e.g., sk_daddr is actually saddr, not daddr. udp_diag_destroy does not
+    # have this bug.  Upstream has confirmed that this will not be fixed:
+    # https://www.mail-archive.com/netdev@vger.kernel.org/msg248638.html
+    """Documents a bug: getting UDP sockets requires swapping src and dst."""
+    for version in [4, 5, 6]:
+      family = net_test.GetAddressFamily(version)
+      s = socket(family, SOCK_DGRAM, 0)
+      self.SelectInterface(s, self.RandomNetid(), "mark")
+      s.connect((self.GetRemoteSocketAddress(version), 53))
+
+      # Create a fully-specified diag req from our socket, including cookie if
+      # we can get it.
+      req = self.sock_diag.DiagReqFromSocket(s)
+      if LINUX_4_9_OR_ABOVE:
+        req.id.cookie = s.getsockopt(net_test.SOL_SOCKET, net_test.SO_COOKIE, 8)
+      else:
+        req.id.cookie = "\xff" * 16  # INET_DIAG_NOCOOKIE[2]
+
+      # As is, this request does not find anything.
+      with self.assertRaisesErrno(ENOENT):
+        self.sock_diag.GetSockInfo(req)
+
+      # But if we swap src and dst, the kernel finds our socket.
+      req.id.sport, req.id.dport = req.id.dport, req.id.sport
+      req.id.src, req.id.dst = req.id.dst, req.id.src
+
+      self.assertSockInfoMatchesSocket(s, self.sock_diag.GetSockInfo(req))
+
 
 class SockDestroyTest(SockDiagBaseTest):
   """Tests that SOCK_DESTROY works correctly.
@@ -501,6 +551,50 @@
                        child.id.src)
 
 
+class TcpRcvWindowTest(tcp_test.TcpBaseTest, SockDiagBaseTest):
+
+  RWND_SIZE = 64000 if LINUX_4_19_OR_ABOVE else 42000
+  TCP_DEFAULT_INIT_RWND = "/proc/sys/net/ipv4/tcp_default_init_rwnd"
+
+  def setUp(self):
+    super(TcpRcvWindowTest, self).setUp()
+    if LINUX_4_19_OR_ABOVE:
+      self.assertRaisesErrno(ENOENT, open, self.TCP_DEFAULT_INIT_RWND, "w")
+      return
+
+    f = open(self.TCP_DEFAULT_INIT_RWND, "w")
+    f.write("60")
+
+  def checkInitRwndSize(self, version, netid):
+    self.IncomingConnection(version, tcp_test.TCP_ESTABLISHED, netid)
+    tcpInfo = TcpInfo(self.accepted.getsockopt(net_test.SOL_TCP,
+                                               net_test.TCP_INFO, len(TcpInfo)))
+    self.assertLess(self.RWND_SIZE, tcpInfo.tcpi_rcv_ssthresh,
+                    "Tcp rwnd of netid=%d, version=%d is not enough. "
+                    "Expect: %d, actual: %d" % (netid, version, self.RWND_SIZE,
+                                                tcpInfo.tcpi_rcv_ssthresh))
+
+  def checkSynPacketWindowSize(self, version, netid):
+    s = self.BuildSocket(version, net_test.TCPSocket, netid, "mark")
+    myaddr = self.MyAddress(version, netid)
+    dstaddr = self.GetRemoteAddress(version)
+    dstsockaddr = self.GetRemoteSocketAddress(version)
+    desc, expected = packets.SYN(53, version, myaddr, dstaddr,
+                                 sport=None, seq=None)
+    self.assertRaisesErrno(EINPROGRESS, s.connect, (dstsockaddr, 53))
+    msg = "IPv%s TCP connect: expected %s on %s" % (
+        version, desc, self.GetInterfaceName(netid))
+    syn = self.ExpectPacketOn(netid, msg, expected)
+    self.assertLess(self.RWND_SIZE, syn.window)
+    s.close()
+
+  def testTcpCwndSize(self):
+    for version in [4, 5, 6]:
+      for netid in self.NETIDS:
+        self.checkInitRwndSize(version, netid)
+        self.checkSynPacketWindowSize(version, netid)
+
+
 class SockDestroyTcpTest(tcp_test.TcpBaseTest, SockDiagBaseTest):
 
   def setUp(self):
@@ -891,7 +985,7 @@
       family = {4: AF_INET, 5: AF_INET6, 6: AF_INET6}[version]
       s = net_test.UDPSocket(family)
       self.SelectInterface(s, random.choice(self.NETIDS), "mark")
-      addr = self.GetRemoteAddress(version)
+      addr = self.GetRemoteSocketAddress(version)
 
       # Check that reads on connected sockets are interrupted.
       s.connect((addr, 53))
diff --git a/net/test/util.py b/net/test/util.py
index bed3e1d..cbcd2d0 100644
--- a/net/test/util.py
+++ b/net/test/util.py
@@ -13,4 +13,59 @@
 # limitations under the License.
 
 def GetPadLength(block_size, length):
-  return (block_size - (length % block_size)) % block_size
\ No newline at end of file
+  return (block_size - (length % block_size)) % block_size
+
+
+def InjectParameterizedTest(cls, param_list, name_generator):
+  """Injects parameterized tests into the provided class
+
+  This method searches for all tests that start with the name "ParamTest",
+  and injects a test method for each set of parameters in param_list. Names
+  are generated via the use of the name_generator.
+
+  Args:
+    cls: the class for which to inject all parameterized tests
+    param_list: a list of tuples, where each tuple is a combination of
+        of parameters to test (i.e. representing a single test case)
+    name_generator: A function that takes a combination of parameters and
+        returns a string that identifies the test case.
+  """
+  param_test_names = [name for name in dir(cls) if name.startswith("ParamTest")]
+
+  # Force param_list to an actual list; otherwise itertools.Product will hit
+  # the end, resulting in only the first ParamTest* method actually being
+  # parameterized
+  param_list = list(param_list)
+
+  # Parameterize each test method starting with "ParamTest"
+  for test_name in param_test_names:
+    func = getattr(cls, test_name)
+
+    for params in param_list:
+      # Give the test method a readable, debuggable name.
+      param_string = name_generator(*params)
+      new_name = "%s_%s" % (func.__name__.replace("ParamTest", "test"),
+                            param_string)
+      new_name = new_name.replace("(", "-").replace(")", "")  # remove parens
+
+      # Inject the test method
+      setattr(cls, new_name, _GetTestClosure(func, params))
+
+
+def _GetTestClosure(func, params):
+  """ Creates a no-argument test method for the given function and parameters.
+
+  This is required to be separate from the InjectParameterizedTest method, due
+  to some interesting scoping issues with internal function declarations. If
+  left in InjectParameterizedTest, all the tests end up using the same
+  instance of TestClosure
+
+  Args:
+    func: the function for which this test closure should run
+    params: the parameters for the run of this test function
+  """
+
+  def TestClosure(self):
+    func(self, *params)
+
+  return TestClosure
diff --git a/net/test/xfrm.py b/net/test/xfrm.py
index 1bd10da..acdfd4f 100755
--- a/net/test/xfrm.py
+++ b/net/test/xfrm.py
@@ -85,6 +85,8 @@
 XFRMA_PAD = 27
 XFRMA_OFFLOAD_DEV = 28
 XFRMA_OUTPUT_MARK = 29
+XFRMA_INPUT_MARK = 30
+XFRMA_IF_ID = 31
 
 # Other netlink constants. See include/uapi/linux/xfrm.h.
 
@@ -206,7 +208,7 @@
 NO_LIFETIME_CUR = "\x00" * len(XfrmLifetimeCur)
 
 # IPsec constants.
-IPSEC_PROTO_ANY	= 255
+IPSEC_PROTO_ANY = 255
 
 # ESP header, not technically XFRM but we need a place for a protocol
 # header and this is the only one we have.
@@ -217,6 +219,11 @@
 _DEFAULT_REPLAY_WINDOW = 4
 ALL_ALGORITHMS = 0xffffffff
 
+# Policy-SA match method (for VTI/XFRM-I).
+MATCH_METHOD_ALL = "all"
+MATCH_METHOD_MARK = "mark"
+MATCH_METHOD_IFID = "ifid"
+
 
 def RawAddress(addr):
   """Converts an IP address string to binary format."""
@@ -369,21 +376,25 @@
       data = struct.unpack("=I", nla_data)[0]
     elif name == "XFRMA_TMPL":
       data = cstruct.Read(nla_data, XfrmUserTmpl)[0]
+    elif name == "XFRMA_IF_ID":
+      data = struct.unpack("=I", nla_data)[0]
     else:
       data = nla_data
 
     return name, data
 
-  def _UpdatePolicyInfo(self, msg, policy, tmpl, mark):
+  def _UpdatePolicyInfo(self, msg, policy, tmpl, mark, xfrm_if_id):
     """Send a policy to the Security Policy Database"""
     nlattrs = []
     if tmpl is not None:
       nlattrs.append((XFRMA_TMPL, tmpl))
     if mark is not None:
       nlattrs.append((XFRMA_MARK, mark))
+    if xfrm_if_id is not None:
+      nlattrs.append((XFRMA_IF_ID, struct.pack("=I", xfrm_if_id)))
     self.SendXfrmNlRequest(msg, policy, nlattrs)
 
-  def AddPolicyInfo(self, policy, tmpl, mark):
+  def AddPolicyInfo(self, policy, tmpl, mark, xfrm_if_id=None):
     """Add a new policy to the Security Policy Database
 
     If the policy exists, then return an error (EEXIST).
@@ -392,10 +403,11 @@
       policy: an unpacked XfrmUserpolicyInfo
       tmpl: an unpacked XfrmUserTmpl
       mark: an unpacked XfrmMark
+      xfrm_if_id: the XFRM interface ID as an integer, or None
     """
-    self._UpdatePolicyInfo(XFRM_MSG_NEWPOLICY, policy, tmpl, mark)
+    self._UpdatePolicyInfo(XFRM_MSG_NEWPOLICY, policy, tmpl, mark, xfrm_if_id)
 
-  def UpdatePolicyInfo(self, policy, tmpl, mark):
+  def UpdatePolicyInfo(self, policy, tmpl, mark, xfrm_if_id):
     """Update an existing policy in the Security Policy Database
 
     If the policy does not exist, then create it; otherwise, update the
@@ -405,10 +417,11 @@
       policy: an unpacked XfrmUserpolicyInfo
       tmpl: an unpacked XfrmUserTmpl to update
       mark: an unpacked XfrmMark to match the existing policy or None
+      xfrm_if_id: an XFRM interface ID or None
     """
-    self._UpdatePolicyInfo(XFRM_MSG_UPDPOLICY, policy, tmpl, mark)
+    self._UpdatePolicyInfo(XFRM_MSG_UPDPOLICY, policy, tmpl, mark, xfrm_if_id)
 
-  def DeletePolicyInfo(self, selector, direction, mark):
+  def DeletePolicyInfo(self, selector, direction, mark, xfrm_if_id=None):
     """Delete a policy from the Security Policy Database
 
     Args:
@@ -419,6 +432,8 @@
     nlattrs = []
     if mark is not None:
       nlattrs.append((XFRMA_MARK, mark))
+    if xfrm_if_id is not None:
+      nlattrs.append((XFRMA_IF_ID, struct.pack("=I", xfrm_if_id)))
     self.SendXfrmNlRequest(XFRM_MSG_DELPOLICY,
                            XfrmUserpolicyId(sel=selector, dir=direction),
                            nlattrs)
@@ -440,11 +455,35 @@
     if nlattrs is None:
       nlattrs = []
     for attr_type, attr_msg in nlattrs:
-      msg += self._NlAttr(attr_type, attr_msg.Pack())
+      # TODO: find a better way to deal with the fact that many XFRM messages
+      # use nlattrs that aren't cstructs.
+      #
+      # This code allows callers to pass in either something that has a Pack()
+      # method or a packed netlink attr, but not other types of attributes.
+      # Alternatives include:
+      #
+      # 1. Require callers to marshal netlink attributes themselves and call
+      #    _SendNlRequest directly. Delete this method.
+      # 2. Rename this function to _SendXfrmNlRequestCstructOnly (or other name
+      #    that makes it clear that this only takes cstructs). Switch callers
+      #    that need non-cstruct elements to calling _SendNlRequest directly.
+      # 3. Make this function somehow automatically detect what to do for
+      #    all types of XFRM attributes today and in the future. This may be
+      #    feasible because all XFRM attributes today occupy the same number
+      #    space, but what about nested attributes? It is unlikley feasible via
+      #    things like "if isinstance(attr_msg, str): ...", because that would
+      #    not be able to determine the right size or byte order for non-struct
+      #    types such as int.
+      # 4. Define fictitious cstructs which have no correspondence to actual
+      #    kernel structs such as the following to represent a raw integer.
+      #    XfrmAttrOutputMark = cstruct.Struct("=I", mark)
+      if hasattr(attr_msg, "Pack"):
+        attr_msg = attr_msg.Pack()
+      msg += self._NlAttr(attr_type, attr_msg)
     return self._SendNlRequest(msg_type, msg, flags)
 
   def AddSaInfo(self, src, dst, spi, mode, reqid, encryption, auth_trunc, aead,
-                encap, mark, output_mark, is_update=False):
+                encap, mark, output_mark, is_update=False, xfrm_if_id=None):
     """Adds an IPsec security association.
 
     Args:
@@ -463,6 +502,7 @@
       output_mark: An integer, the output mark. 0 means unset.
       is_update: If true, update an existing SA otherwise create a new SA. For
         compatibility reasons, this value defaults to False.
+      xfrm_if_id: The XFRM interface ID, or None.
     """
     proto = IPPROTO_ESP
     xfrm_id = XfrmId((PaddedAddress(dst), spi, proto))
@@ -488,6 +528,8 @@
       nlattrs += self._NlAttr(XFRMA_ENCAP, encap.Pack())
     if output_mark is not None:
       nlattrs += self._NlAttrU32(XFRMA_OUTPUT_MARK, output_mark)
+    if xfrm_if_id is not None:
+      nlattrs += self._NlAttrU32(XFRMA_IF_ID, xfrm_if_id)
 
     # The kernel ignores these on input, so make them empty.
     cur = XfrmLifetimeCur()
@@ -519,7 +561,7 @@
     nl_msg_type = XFRM_MSG_UPDSA if is_update else XFRM_MSG_NEWSA
     self._SendNlRequest(nl_msg_type, msg, flags)
 
-  def DeleteSaInfo(self, dst, spi, proto, mark=None):
+  def DeleteSaInfo(self, dst, spi, proto, mark=None, xfrm_if_id=None):
     """Delete an SA from the SAD
 
     Args:
@@ -530,12 +572,13 @@
       mark: A mark match specifier, such as returned by ExactMatchMark(), or
         None for an SA without a Mark attribute.
     """
-    # TODO: deletes take a mark as well.
     family = AF_INET6 if ":" in dst else AF_INET
     usersa_id = XfrmUsersaId((PaddedAddress(dst), spi, family, proto))
     nlattrs = []
     if mark is not None:
       nlattrs.append((XFRMA_MARK, mark))
+    if xfrm_if_id is not None:
+      nlattrs.append((XFRMA_IF_ID, struct.pack("=I", xfrm_if_id)))
     self.SendXfrmNlRequest(XFRM_MSG_DELSA, usersa_id, nlattrs)
 
   def AllocSpi(self, dst, proto, min_spi, max_spi):
@@ -592,7 +635,7 @@
     self._SendNlRequest(XFRM_MSG_FLUSHSA, usersa_flush.Pack(), flags)
 
   def CreateTunnel(self, direction, selector, src, dst, spi, encryption,
-                   auth_trunc, mark, output_mark):
+                   auth_trunc, mark, output_mark, xfrm_if_id, match_method):
     """Create an XFRM Tunnel Consisting of a Policy and an SA.
 
     Create a unidirectional XFRM tunnel, which entails one Policy and one
@@ -610,15 +653,37 @@
       encryption: A tuple (XfrmAlgo, key), the encryption parameters.
       auth_trunc: A tuple (XfrmAlgoAuth, key), the authentication parameters.
       mark: An XfrmMark, the mark used for selecting packets to be tunneled, and
-        for matching the security policy and security association. None means
-        unspecified.
+        for matching the security policy. None means unspecified.
       output_mark: The mark used to select the underlying network for packets
         outbound from xfrm. None means unspecified.
+      xfrm_if_id: The ID of the XFRM interface to use or None.
+      match_method: One of MATCH_METHOD_[MARK | ALL | IFID]. This determines how
+        SAs and policies are matched.
     """
     outer_family = net_test.GetAddressFamily(net_test.GetAddressVersion(dst))
 
+    # SA mark is currently unused due to UPDSA not updating marks.
+    # Kept as documentation of ideal/desired behavior.
+    if match_method == MATCH_METHOD_MARK:
+      # sa_mark = mark
+      tmpl_spi = 0
+      if_id = None
+    elif match_method == MATCH_METHOD_ALL:
+      # sa_mark = mark
+      tmpl_spi = spi
+      if_id = xfrm_if_id
+    elif match_method == MATCH_METHOD_IFID:
+      # sa_mark = None
+      tmpl_spi = 0
+      if_id = xfrm_if_id
+    else:
+      raise ValueError("Unknown match_method supplied: %s" % match_method)
+
+    # Device code does not use mark; during AllocSpi, the mark is unset, and
+    # UPDSA does not update marks at this time. Actual use case will have no
+    # mark set. Test this use case.
     self.AddSaInfo(src, dst, spi, XFRM_MODE_TUNNEL, 0, encryption, auth_trunc,
-                   None, None, mark, output_mark)
+                   None, None, None, output_mark, xfrm_if_id=xfrm_if_id)
 
     if selector is None:
       selectors = [EmptySelector(AF_INET), EmptySelector(AF_INET6)]
@@ -627,17 +692,20 @@
 
     for selector in selectors:
       policy = UserPolicy(direction, selector)
-      tmpl = UserTemplate(outer_family, spi, 0, (src, dst))
-      self.AddPolicyInfo(policy, tmpl, mark)
+      tmpl = UserTemplate(outer_family, tmpl_spi, 0, (src, dst))
+      self.AddPolicyInfo(policy, tmpl, mark, xfrm_if_id=xfrm_if_id)
 
-  def DeleteTunnel(self, direction, selector, dst, spi, mark):
-    self.DeleteSaInfo(dst, spi, IPPROTO_ESP, ExactMatchMark(mark))
+  def DeleteTunnel(self, direction, selector, dst, spi, mark, xfrm_if_id):
+    if mark is not None:
+      mark = ExactMatchMark(mark)
+
+    self.DeleteSaInfo(dst, spi, IPPROTO_ESP, mark, xfrm_if_id)
     if selector is None:
       selectors = [EmptySelector(AF_INET), EmptySelector(AF_INET6)]
     else:
       selectors = [selector]
     for selector in selectors:
-      self.DeletePolicyInfo(selector, direction, ExactMatchMark(mark))
+      self.DeletePolicyInfo(selector, direction, mark, xfrm_if_id)
 
 
 if __name__ == "__main__":
diff --git a/net/test/xfrm_algorithm_test.py b/net/test/xfrm_algorithm_test.py
index 6adc461..0176265 100755
--- a/net/test/xfrm_algorithm_test.py
+++ b/net/test/xfrm_algorithm_test.py
@@ -27,6 +27,7 @@
 import multinetwork_base
 import net_test
 from tun_twister import TapTwister
+import util
 import xfrm
 import xfrm_base
 
@@ -72,49 +73,26 @@
 ]
 
 def InjectTests():
-    XfrmAlgorithmTest.InjectTests()
+  XfrmAlgorithmTest.InjectTests()
+
 
 class XfrmAlgorithmTest(xfrm_base.XfrmLazyTest):
   @classmethod
   def InjectTests(cls):
-    """Inject parameterized test cases into this class.
-
-    Because a library for parameterized testing is not availble in
-    net_test.rootfs.20150203, this does a minimal parameterization.
-
-    This finds methods named like "ParamTestFoo" and replaces them with several
-    "testFoo(*)" methods taking different parameter dicts. A set of test
-    parameters is generated from every combination of encryption,
-    authentication, IP version, and TCP/UDP.
-
-    The benefit of this approach is that an individually failing tests have a
-    clearly separated stack trace, and one failed test doesn't prevent the rest
-    from running.
-    """
-    param_test_names = [
-        name for name in dir(cls) if name.startswith("ParamTest")
-    ]
     VERSIONS = (4, 6)
     TYPES = (SOCK_DGRAM, SOCK_STREAM)
 
     # Tests all combinations of auth & crypt. Mutually exclusive with aead.
-    for crypt, auth, version, proto, name in itertools.product(
-        CRYPT_ALGOS, AUTH_ALGOS, VERSIONS, TYPES, param_test_names):
-      XfrmAlgorithmTest.InjectSingleTest(name, version, proto, crypt=crypt, auth=auth)
+    param_list = itertools.product(VERSIONS, TYPES, AUTH_ALGOS, CRYPT_ALGOS,
+                                   [None])
+    util.InjectParameterizedTest(cls, param_list, cls.TestNameGenerator)
 
     # Tests all combinations of aead. Mutually exclusive with auth/crypt.
-    for aead, version, proto, name in itertools.product(
-        AEAD_ALGOS, VERSIONS, TYPES, param_test_names):
-      XfrmAlgorithmTest.InjectSingleTest(name, version, proto, aead=aead)
+    param_list = itertools.product(VERSIONS, TYPES, [None], [None], AEAD_ALGOS)
+    util.InjectParameterizedTest(cls, param_list, cls.TestNameGenerator)
 
-  @classmethod
-  def InjectSingleTest(cls, name, version, proto, crypt=None, auth=None, aead=None):
-    func = getattr(cls, name)
-
-    def TestClosure(self):
-      func(self, {"crypt": crypt, "auth": auth, "aead": aead,
-          "version": version, "proto": proto})
-
+  @staticmethod
+  def TestNameGenerator(version, proto, auth, crypt, aead):
     # Produce a unique and readable name for each test. e.g.
     #     testSocketPolicySimple_cbc-aes_256_hmac-sha512_512_256_IPv6_UDP
     param_string = ""
@@ -131,12 +109,9 @@
 
     param_string += "%s_%s" % ("IPv4" if version == 4 else "IPv6",
         "UDP" if proto == SOCK_DGRAM else "TCP")
-    new_name = "%s_%s" % (func.__name__.replace("ParamTest", "test"),
-                          param_string)
-    new_name = new_name.replace("(", "-").replace(")", "")  # remove parens
-    setattr(cls, new_name, TestClosure)
+    return param_string
 
-  def ParamTestSocketPolicySimple(self, params):
+  def ParamTestSocketPolicySimple(self, version, proto, auth, crypt, aead):
     """Test two-way traffic using transport mode and socket policies."""
 
     def AssertEncrypted(packet):
@@ -153,37 +128,21 @@
     # other using transport mode ESP. Because of TapTwister, both sockets
     # perceive each other as owning "remote_addr".
     netid = self.RandomNetid()
-    family = net_test.GetAddressFamily(params["version"])
-    local_addr = self.MyAddress(params["version"], netid)
-    remote_addr = self.GetRemoteSocketAddress(params["version"])
-    crypt_left = (xfrm.XfrmAlgo((
-        params["crypt"].name,
-        params["crypt"].key_len)),
-        os.urandom(params["crypt"].key_len / 8)) if params["crypt"] else None
-    crypt_right = (xfrm.XfrmAlgo((
-        params["crypt"].name,
-        params["crypt"].key_len)),
-        os.urandom(params["crypt"].key_len / 8)) if params["crypt"] else None
-    auth_left = (xfrm.XfrmAlgoAuth((
-        params["auth"].name,
-        params["auth"].key_len,
-        params["auth"].trunc_len)),
-        os.urandom(params["auth"].key_len / 8)) if params["auth"] else None
-    auth_right = (xfrm.XfrmAlgoAuth((
-        params["auth"].name,
-        params["auth"].key_len,
-        params["auth"].trunc_len)),
-        os.urandom(params["auth"].key_len / 8)) if params["auth"] else None
-    aead_left = (xfrm.XfrmAlgoAead((
-        params["aead"].name,
-        params["aead"].key_len,
-        params["aead"].icv_len)),
-        os.urandom(params["aead"].key_len / 8)) if params["aead"] else None
-    aead_right = (xfrm.XfrmAlgoAead((
-        params["aead"].name,
-        params["aead"].key_len,
-        params["aead"].icv_len)),
-        os.urandom(params["aead"].key_len / 8)) if params["aead"] else None
+    family = net_test.GetAddressFamily(version)
+    local_addr = self.MyAddress(version, netid)
+    remote_addr = self.GetRemoteSocketAddress(version)
+    auth_left = (xfrm.XfrmAlgoAuth((auth.name, auth.key_len, auth.trunc_len)),
+                 os.urandom(auth.key_len / 8)) if auth else None
+    auth_right = (xfrm.XfrmAlgoAuth((auth.name, auth.key_len, auth.trunc_len)),
+                  os.urandom(auth.key_len / 8)) if auth else None
+    crypt_left = (xfrm.XfrmAlgo((crypt.name, crypt.key_len)),
+                  os.urandom(crypt.key_len / 8)) if crypt else None
+    crypt_right = (xfrm.XfrmAlgo((crypt.name, crypt.key_len)),
+                   os.urandom(crypt.key_len / 8)) if crypt else None
+    aead_left = (xfrm.XfrmAlgoAead((aead.name, aead.key_len, aead.icv_len)),
+                 os.urandom(aead.key_len / 8)) if aead else None
+    aead_right = (xfrm.XfrmAlgoAead((aead.name, aead.key_len, aead.icv_len)),
+                  os.urandom(aead.key_len / 8)) if aead else None
     spi_left = 0xbeefface
     spi_right = 0xcafed00d
     req_ids = [100, 200, 300, 400]  # Used to match templates and SAs.
@@ -242,20 +201,20 @@
         output_mark=None)
 
     # Make two sockets.
-    sock_left = socket(family, params["proto"], 0)
+    sock_left = socket(family, proto, 0)
     sock_left.settimeout(2.0)
     sock_left.setsockopt(SOL_SOCKET, SO_REUSEADDR, 1)
     self.SelectInterface(sock_left, netid, "mark")
-    sock_right = socket(family, params["proto"], 0)
+    sock_right = socket(family, proto, 0)
     sock_right.settimeout(2.0)
     sock_right.setsockopt(SOL_SOCKET, SO_REUSEADDR, 1)
     self.SelectInterface(sock_right, netid, "mark")
 
     # For UDP, set SO_LINGER to 0, to prevent TCP sockets from hanging around
     # in a TIME_WAIT state.
-    if params["proto"] == SOCK_STREAM:
-        net_test.DisableFinWait(sock_left)
-        net_test.DisableFinWait(sock_right)
+    if proto == SOCK_STREAM:
+      net_test.DisableFinWait(sock_left)
+      net_test.DisableFinWait(sock_right)
 
     # Apply the left outbound socket policy.
     xfrm_base.ApplySocketPolicy(sock_left, family, xfrm.XFRM_POLICY_OUT,
@@ -302,14 +261,14 @@
         sock.close()
 
     # Server and client need to know each other's port numbers in advance.
-    wildcard_addr = net_test.GetWildcardAddress(params["version"])
+    wildcard_addr = net_test.GetWildcardAddress(version)
     sock_left.bind((wildcard_addr, 0))
     sock_right.bind((wildcard_addr, 0))
     left_port = sock_left.getsockname()[1]
     right_port = sock_right.getsockname()[1]
 
     # Start the appropriate server type on sock_right.
-    target = TcpServer if params["proto"] == SOCK_STREAM else UdpServer
+    target = TcpServer if proto == SOCK_STREAM else UdpServer
     server = threading.Thread(
         target=target,
         args=(sock_right, left_port),
diff --git a/net/test/xfrm_test.py b/net/test/xfrm_test.py
index 24f9edc..3a3d9b0 100755
--- a/net/test/xfrm_test.py
+++ b/net/test/xfrm_test.py
@@ -640,14 +640,14 @@
       self.assertEquals(attributes['XFRMA_TMPL'], tmpl)
 
     # Create a new policy using update.
-    self.xfrm.UpdatePolicyInfo(policy, tmpl1, mark)
+    self.xfrm.UpdatePolicyInfo(policy, tmpl1, mark, None)
     # NEWPOLICY will not update the existing policy. This checks both that
     # UPDPOLICY created a policy and that NEWPOLICY will not perform updates.
     _CheckTemplateMatch(tmpl1)
     with self.assertRaisesErrno(EEXIST):
-      self.xfrm.AddPolicyInfo(policy, tmpl2, mark)
+      self.xfrm.AddPolicyInfo(policy, tmpl2, mark, None)
     # Update the policy using UPDPOLICY.
-    self.xfrm.UpdatePolicyInfo(policy, tmpl2, mark)
+    self.xfrm.UpdatePolicyInfo(policy, tmpl2, mark, None)
     # There should only be one policy after update, and it should have the
     # updated template.
     _CheckTemplateMatch(tmpl2)
diff --git a/net/test/xfrm_tunnel_test.py b/net/test/xfrm_tunnel_test.py
index 77cc8b3..bf32ffc 100755
--- a/net/test/xfrm_tunnel_test.py
+++ b/net/test/xfrm_tunnel_test.py
@@ -19,32 +19,59 @@
 from socket import *  # pylint: disable=wildcard-import
 
 import random
+import itertools
 import struct
 import unittest
 
+from scapy import all as scapy
 from tun_twister import TunTwister
 import csocket
 import iproute
 import multinetwork_base
 import net_test
 import packets
+import util
 import xfrm
 import xfrm_base
 
-# Parameters to Set up VTI as a special network
-_BASE_VTI_NETID = {4: 40, 6: 60}
+_LOOPBACK_IFINDEX = 1
+_TEST_XFRM_IFNAME = "ipsec42"
+_TEST_XFRM_IF_ID = 42
+
+# Does the kernel support xfrmi interfaces?
+def HaveXfrmInterfaces():
+  try:
+    i = iproute.IPRoute()
+    i.CreateXfrmInterface(_TEST_XFRM_IFNAME, _TEST_XFRM_IF_ID,
+                          _LOOPBACK_IFINDEX)
+    i.DeleteLink(_TEST_XFRM_IFNAME)
+    try:
+      i.GetIfIndex(_TEST_XFRM_IFNAME)
+      assert "Deleted interface %s still exists!" % _TEST_XFRM_IFNAME
+    except IOError:
+      pass
+    return True
+  except IOError:
+    return False
+
+HAVE_XFRM_INTERFACES = HaveXfrmInterfaces()
+
+# Parameters to setup tunnels as special networks
+_TUNNEL_NETID_OFFSET = 0xFC00  # Matches reserved netid range for IpSecService
+_BASE_TUNNEL_NETID = {4: 40, 6: 60}
 _BASE_VTI_OKEY = 2000000100
 _BASE_VTI_IKEY = 2000000200
 
-_VTI_NETID = 50
-_VTI_IFNAME = "test_vti"
-
 _TEST_OUT_SPI = 0x1234
 _TEST_IN_SPI = _TEST_OUT_SPI
 
 _TEST_OKEY = 2000000100
 _TEST_IKEY = 2000000200
 
+_TEST_REMOTE_PORT = 1234
+
+_SCAPY_IP_TYPE = {4: scapy.IP, 6: scapy.IPv6}
+
 
 def _GetLocalInnerAddress(version):
   return {4: "10.16.5.15", 6: "2001:db8:1::1"}[version]
@@ -58,61 +85,148 @@
   return {4: net_test.IPV4_ADDR, 6: net_test.IPV6_ADDR}[version]
 
 
+def _GetNullAuthCryptTunnelModePkt(inner_version, src_inner, src_outer,
+                                   src_port, dst_inner, dst_outer,
+                                   dst_port, spi, seq_num, ip_hdr_options=None):
+  if ip_hdr_options is None:
+    ip_hdr_options = {}
+
+  ip_hdr_options.update({'src': src_inner, 'dst': dst_inner})
+
+  # Build and receive an ESP packet destined for the inner socket
+  IpType = {4: scapy.IP, 6: scapy.IPv6}[inner_version]
+  input_pkt = (
+      IpType(**ip_hdr_options) / scapy.UDP(sport=src_port, dport=dst_port) /
+      net_test.UDP_PAYLOAD)
+  input_pkt = IpType(str(input_pkt))  # Compute length, checksum.
+  input_pkt = xfrm_base.EncryptPacketWithNull(input_pkt, spi, seq_num,
+                                              (src_outer, dst_outer))
+
+  return input_pkt
+
+
+def _CreateReceiveSock(version, port=0):
+  # Create a socket to receive packets.
+  read_sock = socket(net_test.GetAddressFamily(version), SOCK_DGRAM, 0)
+  read_sock.bind((net_test.GetWildcardAddress(version), port))
+  # The second parameter of the tuple is the port number regardless of AF.
+  local_port = read_sock.getsockname()[1]
+  # Guard against the eventuality of the receive failing.
+  csocket.SetSocketTimeout(read_sock, 500)
+
+  return read_sock, local_port
+
+
+def _SendPacket(testInstance, netid, version, remote, remote_port):
+  # Send a packet out via the tunnel-backed network, bound for the port number
+  # of the input socket.
+  write_sock = socket(net_test.GetAddressFamily(version), SOCK_DGRAM, 0)
+  testInstance.SelectInterface(write_sock, netid, "mark")
+  write_sock.sendto(net_test.UDP_PAYLOAD, (remote, remote_port))
+  local_port = write_sock.getsockname()[1]
+
+  return local_port
+
+
+def InjectTests():
+  InjectParameterizedTests(XfrmTunnelTest)
+  InjectParameterizedTests(XfrmInterfaceTest)
+  InjectParameterizedTests(XfrmVtiTest)
+
+
+def InjectParameterizedTests(cls):
+  VERSIONS = (4, 6)
+  param_list = itertools.product(VERSIONS, VERSIONS)
+
+  def NameGenerator(*args):
+    return "IPv%d_in_IPv%d" % tuple(args)
+
+  util.InjectParameterizedTest(cls, param_list, NameGenerator)
+
+
 class XfrmTunnelTest(xfrm_base.XfrmLazyTest):
 
-  def _CheckTunnelOutput(self, inner_version, outer_version):
-    """Test a bi-directional XFRM Tunnel with explicit selectors"""
-    # Select the underlying netid, which represents the external
-    # interface from/to which to route ESP packets.
-    underlying_netid = self.RandomNetid()
-    # Select a random netid that will originate traffic locally and
-    # which represents the logical tunnel network.
-    netid = self.RandomNetid(exclude=underlying_netid)
+  def _CheckTunnelOutput(self, inner_version, outer_version, underlying_netid,
+                         netid, local_inner, remote_inner, local_outer,
+                         remote_outer, write_sock):
 
-    local_inner = self.MyAddress(inner_version, netid)
-    remote_inner = _GetRemoteInnerAddress(inner_version)
-    local_outer = self.MyAddress(outer_version, underlying_netid)
-    remote_outer = _GetRemoteOuterAddress(outer_version)
-
-    self.xfrm.CreateTunnel(xfrm.XFRM_POLICY_OUT,
-                           xfrm.SrcDstSelector(local_inner, remote_inner),
-                           local_outer, remote_outer, _TEST_OUT_SPI,
-                           xfrm_base._ALGO_CBC_AES_256,
-                           xfrm_base._ALGO_HMAC_SHA1,
-                           None, underlying_netid)
-
-    write_sock = socket(net_test.GetAddressFamily(inner_version), SOCK_DGRAM, 0)
-    # Select an interface, which provides the source address of the inner
-    # packet.
-    self.SelectInterface(write_sock, netid, "mark")
     write_sock.sendto(net_test.UDP_PAYLOAD, (remote_inner, 53))
     self._ExpectEspPacketOn(underlying_netid, _TEST_OUT_SPI, 1, None,
                             local_outer, remote_outer)
 
-  # TODO: Add support for the input path.
+  def _CheckTunnelInput(self, inner_version, outer_version, underlying_netid,
+                        netid, local_inner, remote_inner, local_outer,
+                        remote_outer, read_sock):
 
-  def testIpv4InIpv4TunnelOutput(self):
-    self._CheckTunnelOutput(4, 4)
+    # The second parameter of the tuple is the port number regardless of AF.
+    local_port = read_sock.getsockname()[1]
 
-  def testIpv4InIpv6TunnelOutput(self):
-    self._CheckTunnelOutput(4, 6)
+    # Build and receive an ESP packet destined for the inner socket
+    input_pkt = _GetNullAuthCryptTunnelModePkt(
+        inner_version, remote_inner, remote_outer, _TEST_REMOTE_PORT,
+        local_inner, local_outer, local_port, _TEST_IN_SPI, 1)
+    self.ReceivePacketOn(underlying_netid, input_pkt)
 
-  def testIpv6InIpv4TunnelOutput(self):
-    self._CheckTunnelOutput(6, 4)
+    # Verify that the packet data and src are correct
+    data, src = read_sock.recvfrom(4096)
+    self.assertEquals(net_test.UDP_PAYLOAD, data)
+    self.assertEquals((remote_inner, _TEST_REMOTE_PORT), src[:2])
 
-  def testIpv6InIpv6TunnelOutput(self):
-    self._CheckTunnelOutput(6, 6)
+  def _TestTunnel(self, inner_version, outer_version, func, direction):
+    """Test a unidirectional XFRM Tunnel with explicit selectors"""
+    # Select the underlying netid, which represents the external
+    # interface from/to which to route ESP packets.
+    u_netid = self.RandomNetid()
+    # Select a random netid that will originate traffic locally and
+    # which represents the netid on which the plaintext is sent
+    netid = self.RandomNetid(exclude=u_netid)
+
+    local_inner = self.MyAddress(inner_version, netid)
+    remote_inner = _GetRemoteInnerAddress(inner_version)
+    local_outer = self.MyAddress(outer_version, u_netid)
+    remote_outer = _GetRemoteOuterAddress(outer_version)
+
+    # Create input/ouput SPs, SAs and sockets to simulate a more realistic
+    # environment.
+    self.xfrm.CreateTunnel(
+        xfrm.XFRM_POLICY_IN, xfrm.SrcDstSelector(remote_inner, local_inner),
+        remote_outer, local_outer, _TEST_IN_SPI, xfrm_base._ALGO_CRYPT_NULL,
+        xfrm_base._ALGO_AUTH_NULL, None, None, None, xfrm.MATCH_METHOD_ALL)
+
+    self.xfrm.CreateTunnel(
+        xfrm.XFRM_POLICY_OUT, xfrm.SrcDstSelector(local_inner, remote_inner),
+        local_outer, remote_outer, _TEST_OUT_SPI, xfrm_base._ALGO_CBC_AES_256,
+        xfrm_base._ALGO_HMAC_SHA1, None, u_netid, None, xfrm.MATCH_METHOD_ALL)
+
+    write_sock = socket(net_test.GetAddressFamily(inner_version), SOCK_DGRAM, 0)
+    self.SelectInterface(write_sock, netid, "mark")
+    read_sock, _ = _CreateReceiveSock(inner_version)
+
+    sock = write_sock if direction == xfrm.XFRM_POLICY_OUT else read_sock
+    func(inner_version, outer_version, u_netid, netid, local_inner,
+         remote_inner, local_outer, remote_outer, sock)
+
+  def ParamTestTunnelInput(self, inner_version, outer_version):
+    self._TestTunnel(inner_version, outer_version, self._CheckTunnelInput,
+                     xfrm.XFRM_POLICY_IN)
+
+  def ParamTestTunnelOutput(self, inner_version, outer_version):
+    self._TestTunnel(inner_version, outer_version, self._CheckTunnelOutput,
+                     xfrm.XFRM_POLICY_OUT)
 
 
 @unittest.skipUnless(net_test.LINUX_VERSION >= (3, 18, 0), "VTI Unsupported")
 class XfrmAddDeleteVtiTest(xfrm_base.XfrmBaseTest):
-  def verifyVtiInfoData(self, vti_info_data, version, local_addr, remote_addr, ikey, okey):
+  def _VerifyVtiInfoData(self, vti_info_data, version, local_addr, remote_addr,
+                         ikey, okey):
     self.assertEquals(vti_info_data["IFLA_VTI_IKEY"], ikey)
     self.assertEquals(vti_info_data["IFLA_VTI_OKEY"], okey)
 
     family = AF_INET if version == 4 else AF_INET6
-    self.assertEquals(inet_ntop(family, vti_info_data["IFLA_VTI_LOCAL"]), local_addr)
-    self.assertEquals(inet_ntop(family, vti_info_data["IFLA_VTI_REMOTE"]), remote_addr)
+    self.assertEquals(inet_ntop(family, vti_info_data["IFLA_VTI_LOCAL"]),
+                      local_addr)
+    self.assertEquals(inet_ntop(family, vti_info_data["IFLA_VTI_REMOTE"]),
+                      remote_addr)
 
   def testAddVti(self):
     """Test the creation of a Virtual Tunnel Interface."""
@@ -120,37 +234,37 @@
       netid = self.RandomNetid()
       local_addr = self.MyAddress(version, netid)
       self.iproute.CreateVirtualTunnelInterface(
-          dev_name=_VTI_IFNAME,
+          dev_name=_TEST_XFRM_IFNAME,
           local_addr=local_addr,
           remote_addr=_GetRemoteOuterAddress(version),
           o_key=_TEST_OKEY,
           i_key=_TEST_IKEY)
-      self.verifyVtiInfoData(self.iproute.GetVtiInfoData(_VTI_IFNAME),
-                             version, local_addr, _GetRemoteOuterAddress(version),
-                             _TEST_IKEY, _TEST_OKEY)
+      self._VerifyVtiInfoData(
+          self.iproute.GetIfinfoData(_TEST_XFRM_IFNAME), version, local_addr,
+          _GetRemoteOuterAddress(version), _TEST_IKEY, _TEST_OKEY)
 
       new_remote_addr = {4: net_test.IPV4_ADDR2, 6: net_test.IPV6_ADDR2}
-      new_okey = _TEST_OKEY + _VTI_NETID
-      new_ikey = _TEST_IKEY + _VTI_NETID
+      new_okey = _TEST_OKEY + _TEST_XFRM_IF_ID
+      new_ikey = _TEST_IKEY + _TEST_XFRM_IF_ID
       self.iproute.CreateVirtualTunnelInterface(
-          dev_name=_VTI_IFNAME,
+          dev_name=_TEST_XFRM_IFNAME,
           local_addr=local_addr,
           remote_addr=new_remote_addr[version],
           o_key=new_okey,
           i_key=new_ikey,
           is_update=True)
 
-      self.verifyVtiInfoData(self.iproute.GetVtiInfoData(_VTI_IFNAME),
-                             version, local_addr, new_remote_addr[version],
-                             new_ikey, new_okey)
+      self._VerifyVtiInfoData(
+          self.iproute.GetIfinfoData(_TEST_XFRM_IFNAME), version, local_addr,
+          new_remote_addr[version], new_ikey, new_okey)
 
-      if_index = self.iproute.GetIfIndex(_VTI_IFNAME)
+      if_index = self.iproute.GetIfIndex(_TEST_XFRM_IFNAME)
 
       # Validate that the netlink interface matches the ioctl interface.
-      self.assertEquals(net_test.GetInterfaceIndex(_VTI_IFNAME), if_index)
-      self.iproute.DeleteLink(_VTI_IFNAME)
+      self.assertEquals(net_test.GetInterfaceIndex(_TEST_XFRM_IFNAME), if_index)
+      self.iproute.DeleteLink(_TEST_XFRM_IFNAME)
       with self.assertRaises(IOError):
-        self.iproute.GetIfIndex(_VTI_IFNAME)
+        self.iproute.GetIfIndex(_TEST_XFRM_IFNAME)
 
   def _QuietDeleteLink(self, ifname):
     try:
@@ -161,100 +275,276 @@
 
   def tearDown(self):
     super(XfrmAddDeleteVtiTest, self).tearDown()
-    self._QuietDeleteLink(_VTI_IFNAME)
+    self._QuietDeleteLink(_TEST_XFRM_IFNAME)
 
 
-class VtiInterface(object):
+class SaInfo(object):
 
-  def __init__(self, iface, netid, underlying_netid, local, remote):
+  def __init__(self, spi):
+    self.spi = spi
+    self.seq_num = 1
+
+
+class IpSecBaseInterface(object):
+
+  def __init__(self, iface, netid, underlying_netid, local, remote, version):
     self.iface = iface
     self.netid = netid
     self.underlying_netid = underlying_netid
     self.local, self.remote = local, remote
+
+    # XFRM interfaces technically do not have a version. This keeps track of
+    # the IP version of the local and remote addresses.
+    self.version = version
     self.rx = self.tx = 0
-    self.ikey = _TEST_IKEY + netid
-    self.okey = _TEST_OKEY + netid
-    self.out_spi = self.in_spi = random.randint(0, 0x7fffffff)
+    self.addrs = {}
 
     self.iproute = iproute.IPRoute()
     self.xfrm = xfrm.Xfrm()
 
-    self.SetupInterface()
-    self.SetupXfrm()
-    self.addrs = {}
-
   def Teardown(self):
     self.TeardownXfrm()
     self.TeardownInterface()
 
-  def SetupInterface(self):
-    self.iproute.CreateVirtualTunnelInterface(
-        self.iface, self.local, self.remote, self.ikey, self.okey)
-
   def TeardownInterface(self):
     self.iproute.DeleteLink(self.iface)
 
-  def SetupXfrm(self):
+  def SetupXfrm(self, use_null_crypt):
+    rand_spi = random.randint(0, 0x7fffffff)
+    self.in_sa = SaInfo(rand_spi)
+    self.out_sa = SaInfo(rand_spi)
+
+    # Select algorithms:
+    if use_null_crypt:
+      auth, crypt = xfrm_base._ALGO_AUTH_NULL, xfrm_base._ALGO_CRYPT_NULL
+    else:
+      auth, crypt = xfrm_base._ALGO_HMAC_SHA1, xfrm_base._ALGO_CBC_AES_256
+
+    self._SetupXfrmByType(auth, crypt)
+
+  def Rekey(self, outer_family, new_out_sa, new_in_sa):
+    """Rekeys the Tunnel Interface
+
+    Creates new SAs and updates the outbound security policy to use new SAs.
+
+    Args:
+      outer_family: AF_INET or AF_INET6
+      new_out_sa: An SaInfo struct representing the new outbound SA's info
+      new_in_sa: An SaInfo struct representing the new inbound SA's info
+    """
+    self._Rekey(outer_family, new_out_sa, new_in_sa)
+
+    # Update Interface object
+    self.out_sa = new_out_sa
+    self.in_sa = new_in_sa
+
+  def TeardownXfrm(self):
+    raise NotImplementedError("Subclasses should implement this")
+
+  def _SetupXfrmByType(self, auth_algo, crypt_algo):
+    raise NotImplementedError("Subclasses should implement this")
+
+  def _Rekey(self, outer_family, new_out_sa, new_in_sa):
+    raise NotImplementedError("Subclasses should implement this")
+
+
+class VtiInterface(IpSecBaseInterface):
+
+  def __init__(self, iface, netid, underlying_netid, _, local, remote, version):
+    super(VtiInterface, self).__init__(iface, netid, underlying_netid, local,
+                                       remote, version)
+
+    self.ikey = _TEST_IKEY + netid
+    self.okey = _TEST_OKEY + netid
+
+    self.SetupInterface()
+    self.SetupXfrm(False)
+
+  def SetupInterface(self):
+    return self.iproute.CreateVirtualTunnelInterface(
+        self.iface, self.local, self.remote, self.ikey, self.okey)
+
+  def _SetupXfrmByType(self, auth_algo, crypt_algo):
     # For the VTI, the selectors are wildcard since packets will only
     # be selected if they have the appropriate mark, hence the inner
     # addresses are wildcard.
     self.xfrm.CreateTunnel(xfrm.XFRM_POLICY_OUT, None, self.local, self.remote,
-                           self.out_spi, xfrm_base._ALGO_CBC_AES_256,
-                           xfrm_base._ALGO_HMAC_SHA1,
+                           self.out_sa.spi, crypt_algo, auth_algo,
                            xfrm.ExactMatchMark(self.okey),
-                           self.underlying_netid)
+                           self.underlying_netid, None, xfrm.MATCH_METHOD_ALL)
 
     self.xfrm.CreateTunnel(xfrm.XFRM_POLICY_IN, None, self.remote, self.local,
-                           self.in_spi, xfrm_base._ALGO_CBC_AES_256,
-                           xfrm_base._ALGO_HMAC_SHA1,
-                           xfrm.ExactMatchMark(self.ikey), None)
+                           self.in_sa.spi, crypt_algo, auth_algo,
+                           xfrm.ExactMatchMark(self.ikey), None, None,
+                           xfrm.MATCH_METHOD_MARK)
 
   def TeardownXfrm(self):
     self.xfrm.DeleteTunnel(xfrm.XFRM_POLICY_OUT, None, self.remote,
-                           self.out_spi, self.okey)
+                           self.out_sa.spi, self.okey, None)
     self.xfrm.DeleteTunnel(xfrm.XFRM_POLICY_IN, None, self.local,
-                           self.in_spi, self.ikey)
+                           self.in_sa.spi, self.ikey, None)
+
+  def _Rekey(self, outer_family, new_out_sa, new_in_sa):
+    # TODO: Consider ways to share code with xfrm.CreateTunnel(). It's mostly
+    #       the same, but rekeys are asymmetric, and only update the outbound
+    #       policy.
+    self.xfrm.AddSaInfo(self.local, self.remote, new_out_sa.spi,
+                        xfrm.XFRM_MODE_TUNNEL, 0, xfrm_base._ALGO_CRYPT_NULL,
+                        xfrm_base._ALGO_AUTH_NULL, None, None,
+                        xfrm.ExactMatchMark(self.okey), self.underlying_netid)
+
+    self.xfrm.AddSaInfo(self.remote, self.local, new_in_sa.spi,
+                        xfrm.XFRM_MODE_TUNNEL, 0, xfrm_base._ALGO_CRYPT_NULL,
+                        xfrm_base._ALGO_AUTH_NULL, None, None,
+                        xfrm.ExactMatchMark(self.ikey), None)
+
+    # Create new policies for IPv4 and IPv6.
+    for sel in [xfrm.EmptySelector(AF_INET), xfrm.EmptySelector(AF_INET6)]:
+      # Add SPI-specific output policy to enforce using new outbound SPI
+      policy = xfrm.UserPolicy(xfrm.XFRM_POLICY_OUT, sel)
+      tmpl = xfrm.UserTemplate(outer_family, new_out_sa.spi, 0,
+                                    (self.local, self.remote))
+      self.xfrm.UpdatePolicyInfo(policy, tmpl, xfrm.ExactMatchMark(self.okey),
+                                 0)
+
+  def DeleteOldSaInfo(self, outer_family, old_in_spi, old_out_spi):
+    self.xfrm.DeleteSaInfo(self.local, old_in_spi, IPPROTO_ESP,
+                           xfrm.ExactMatchMark(self.ikey))
+    self.xfrm.DeleteSaInfo(self.remote, old_out_spi, IPPROTO_ESP,
+                           xfrm.ExactMatchMark(self.okey))
 
 
-@unittest.skipUnless(net_test.LINUX_VERSION >= (3, 18, 0), "VTI Unsupported")
-class XfrmVtiTest(xfrm_base.XfrmBaseTest):
+@unittest.skipUnless(HAVE_XFRM_INTERFACES, "XFRM interfaces unsupported")
+class XfrmAddDeleteXfrmInterfaceTest(xfrm_base.XfrmBaseTest):
+  """Test the creation of an XFRM Interface."""
+
+  def testAddXfrmInterface(self):
+    self.iproute.CreateXfrmInterface(_TEST_XFRM_IFNAME, _TEST_XFRM_IF_ID,
+                                     _LOOPBACK_IFINDEX)
+    if_index = self.iproute.GetIfIndex(_TEST_XFRM_IFNAME)
+    net_test.SetInterfaceUp(_TEST_XFRM_IFNAME)
+
+    # Validate that the netlink interface matches the ioctl interface.
+    self.assertEquals(net_test.GetInterfaceIndex(_TEST_XFRM_IFNAME), if_index)
+    self.iproute.DeleteLink(_TEST_XFRM_IFNAME)
+    with self.assertRaises(IOError):
+      self.iproute.GetIfIndex(_TEST_XFRM_IFNAME)
+
+
+class XfrmInterface(IpSecBaseInterface):
+
+  def __init__(self, iface, netid, underlying_netid, ifindex, local, remote,
+               version):
+    super(XfrmInterface, self).__init__(iface, netid, underlying_netid, local,
+                                        remote, version)
+
+    self.ifindex = ifindex
+    self.xfrm_if_id = netid
+
+    self.SetupInterface()
+    self.SetupXfrm(False)
+
+  def SetupInterface(self):
+    """Create an XFRM interface."""
+    return self.iproute.CreateXfrmInterface(self.iface, self.netid, self.ifindex)
+
+  def _SetupXfrmByType(self, auth_algo, crypt_algo):
+    self.xfrm.CreateTunnel(xfrm.XFRM_POLICY_OUT, None, self.local, self.remote,
+                           self.out_sa.spi, crypt_algo, auth_algo, None,
+                           self.underlying_netid, self.xfrm_if_id,
+                           xfrm.MATCH_METHOD_ALL)
+    self.xfrm.CreateTunnel(xfrm.XFRM_POLICY_IN, None, self.remote, self.local,
+                           self.in_sa.spi, crypt_algo, auth_algo, None, None,
+                           self.xfrm_if_id, xfrm.MATCH_METHOD_IFID)
+
+  def TeardownXfrm(self):
+    self.xfrm.DeleteTunnel(xfrm.XFRM_POLICY_OUT, None, self.remote,
+                           self.out_sa.spi, None, self.xfrm_if_id)
+    self.xfrm.DeleteTunnel(xfrm.XFRM_POLICY_IN, None, self.local,
+                           self.in_sa.spi, None, self.xfrm_if_id)
+
+  def _Rekey(self, outer_family, new_out_sa, new_in_sa):
+    # TODO: Consider ways to share code with xfrm.CreateTunnel(). It's mostly
+    #       the same, but rekeys are asymmetric, and only update the outbound
+    #       policy.
+    self.xfrm.AddSaInfo(
+        self.local, self.remote, new_out_sa.spi, xfrm.XFRM_MODE_TUNNEL, 0,
+        xfrm_base._ALGO_CRYPT_NULL, xfrm_base._ALGO_AUTH_NULL, None, None,
+        None, self.underlying_netid, xfrm_if_id=self.xfrm_if_id)
+
+    self.xfrm.AddSaInfo(
+        self.remote, self.local, new_in_sa.spi, xfrm.XFRM_MODE_TUNNEL, 0,
+        xfrm_base._ALGO_CRYPT_NULL, xfrm_base._ALGO_AUTH_NULL, None, None,
+        None, None, xfrm_if_id=self.xfrm_if_id)
+
+    # Create new policies for IPv4 and IPv6.
+    for sel in [xfrm.EmptySelector(AF_INET), xfrm.EmptySelector(AF_INET6)]:
+      # Add SPI-specific output policy to enforce using new outbound SPI
+      policy = xfrm.UserPolicy(xfrm.XFRM_POLICY_OUT, sel)
+      tmpl = xfrm.UserTemplate(outer_family, new_out_sa.spi, 0,
+                                    (self.local, self.remote))
+      self.xfrm.UpdatePolicyInfo(policy, tmpl, None, self.xfrm_if_id)
+
+  def DeleteOldSaInfo(self, outer_family, old_in_spi, old_out_spi):
+    self.xfrm.DeleteSaInfo(self.local, old_in_spi, IPPROTO_ESP, None,
+                           self.xfrm_if_id)
+    self.xfrm.DeleteSaInfo(self.remote, old_out_spi, IPPROTO_ESP, None,
+                           self.xfrm_if_id)
+
+
+class XfrmTunnelBase(xfrm_base.XfrmBaseTest):
 
   @classmethod
   def setUpClass(cls):
     xfrm_base.XfrmBaseTest.setUpClass()
-    # VTI interfaces use marks extensively, so configure realistic packet
+    # Tunnel interfaces use marks extensively, so configure realistic packet
     # marking rules to make the test representative, make PMTUD work, etc.
     cls.SetInboundMarks(True)
     cls.SetMarkReflectSysctls(1)
 
-    cls.vtis = {}
+    # Group by tunnel version to ensure that we test at least one IPv4 and one
+    # IPv6 tunnel
+    cls.tunnelsV4 = {}
+    cls.tunnelsV6 = {}
     for i, underlying_netid in enumerate(cls.tuns):
       for version in 4, 6:
-        netid = _BASE_VTI_NETID[version] + i
+        netid = _BASE_TUNNEL_NETID[version] + _TUNNEL_NETID_OFFSET + i
         iface = "ipsec%s" % netid
         local = cls.MyAddress(version, underlying_netid)
         if version == 4:
-          remote = net_test.IPV4_ADDR2 if (i % 2) else net_test.IPV4_ADDR
+          remote = (net_test.IPV4_ADDR if (i % 2) else net_test.IPV4_ADDR2)
         else:
-          remote = net_test.IPV6_ADDR2 if (i % 2) else net_test.IPV6_ADDR
-        vti = VtiInterface(iface, netid, underlying_netid, local, remote)
+          remote = (net_test.IPV6_ADDR if (i % 2) else net_test.IPV6_ADDR2)
+
+        ifindex = cls.ifindices[underlying_netid]
+        tunnel = cls.INTERFACE_CLASS(iface, netid, underlying_netid, ifindex,
+                                   local, remote, version)
         cls._SetInboundMarking(netid, iface, True)
-        cls._SetupVtiNetwork(vti, True)
-        cls.vtis[netid] = vti
+        cls._SetupTunnelNetwork(tunnel, True)
+
+        if version == 4:
+          cls.tunnelsV4[netid] = tunnel
+        else:
+          cls.tunnelsV6[netid] = tunnel
 
   @classmethod
   def tearDownClass(cls):
     # The sysctls are restored by MultinetworkBaseTest.tearDownClass.
     cls.SetInboundMarks(False)
-    for vti in cls.vtis.values():
-      cls._SetInboundMarking(vti.netid, vti.iface, False)
-      cls._SetupVtiNetwork(vti, False)
-      vti.Teardown()
+    for tunnel in cls.tunnelsV4.values() + cls.tunnelsV6.values():
+      cls._SetInboundMarking(tunnel.netid, tunnel.iface, False)
+      cls._SetupTunnelNetwork(tunnel, False)
+      tunnel.Teardown()
     xfrm_base.XfrmBaseTest.tearDownClass()
 
+  def randomTunnel(self, outer_version):
+    version_dict = self.tunnelsV4 if outer_version == 4 else self.tunnelsV6
+    return random.choice(version_dict.values())
+
   def setUp(self):
     multinetwork_base.MultiNetworkBaseTest.setUp(self)
     self.iproute = iproute.IPRoute()
+    self.xfrm = xfrm.Xfrm()
 
   def tearDown(self):
     multinetwork_base.MultiNetworkBaseTest.tearDown(self)
@@ -275,16 +565,23 @@
                             net_test.AddressLengthBits(version), ifindex)
 
   @classmethod
-  def _SetupVtiNetwork(cls, vti, is_add):
-    """Setup rules and routes for a VTI Network.
+  def _GetLocalAddress(cls, version, netid):
+    if version == 4:
+      return cls._MyIPv4Address(netid - _TUNNEL_NETID_OFFSET)
+    else:
+      return cls.OnlinkPrefix(6, netid - _TUNNEL_NETID_OFFSET) + "1"
+
+  @classmethod
+  def _SetupTunnelNetwork(cls, tunnel, is_add):
+    """Setup rules and routes for a tunnel Network.
 
     Takes an interface and depending on the boolean
     value of is_add, either adds or removes the rules
-    and routes for a VTI to behave like an Android
-    Network for purposes of testing.
+    and routes for a tunnel interface to behave like an
+    Android Network for purposes of testing.
 
     Args:
-      vti: A VtiInterface, the VTI to set up.
+      tunnel: A VtiInterface or XfrmInterface, the tunnel to set up.
       is_add: Boolean that causes this method to perform setup if True or
         teardown if False
     """
@@ -292,32 +589,30 @@
       # Disable router solicitations to avoid occasional spurious packets
       # arriving on the underlying network; there are two possible behaviors
       # when that occurred: either only the RA packet is read, and when it
-      # is echoed back to the VTI, it causes the test to fail by not receiving
-      # the UDP_PAYLOAD; or, two packets may arrive on the underlying
-      # network which fails the assertion that only one ESP packet is received.
+      # is echoed back to the tunnel, it causes the test to fail by not
+      # receiving # the UDP_PAYLOAD; or, two packets may arrive on the
+      # underlying # network which fails the assertion that only one ESP packet
+      # is received.
       cls.SetSysctl(
-          "/proc/sys/net/ipv6/conf/%s/router_solicitations" % vti.iface, 0)
-      net_test.SetInterfaceUp(vti.iface)
+          "/proc/sys/net/ipv6/conf/%s/router_solicitations" % tunnel.iface, 0)
+      net_test.SetInterfaceUp(tunnel.iface)
 
     for version in [4, 6]:
-      ifindex = net_test.GetInterfaceIndex(vti.iface)
-      table = vti.netid
+      ifindex = net_test.GetInterfaceIndex(tunnel.iface)
+      table = tunnel.netid
 
       # Set up routing rules.
-      start, end = cls.UidRangeForNetid(vti.netid)
+      start, end = cls.UidRangeForNetid(tunnel.netid)
       cls.iproute.UidRangeRule(version, is_add, start, end, table,
                                 cls.PRIORITY_UID)
-      cls.iproute.OifRule(version, is_add, vti.iface, table, cls.PRIORITY_OIF)
-      cls.iproute.FwmarkRule(version, is_add, vti.netid, cls.NETID_FWMASK,
+      cls.iproute.OifRule(version, is_add, tunnel.iface, table, cls.PRIORITY_OIF)
+      cls.iproute.FwmarkRule(version, is_add, tunnel.netid, cls.NETID_FWMASK,
                               table, cls.PRIORITY_FWMARK)
 
       # Configure IP addresses.
-      if version == 4:
-        addr = cls._MyIPv4Address(vti.netid)
-      else:
-        addr = cls.OnlinkPrefix(6, vti.netid) + "1"
+      addr = cls._GetLocalAddress(version, tunnel.netid)
       prefixlen = net_test.AddressLengthBits(version)
-      vti.addrs[version] = addr
+      tunnel.addrs[version] = addr
       if is_add:
         cls.iproute.AddAddress(addr, prefixlen, ifindex)
         cls.iproute.AddRoute(version, table, "default", 0, None, ifindex)
@@ -325,100 +620,320 @@
         cls.iproute.DelRoute(version, table, "default", 0, None, ifindex)
         cls.iproute.DelAddress(addr, prefixlen, ifindex)
 
-  def assertReceivedPacket(self, vti):
-    vti.rx += 1
-    self.assertEquals((vti.rx, vti.tx), self.iproute.GetRxTxPackets(vti.iface))
+  def assertReceivedPacket(self, tunnel, sa_info):
+    tunnel.rx += 1
+    self.assertEquals((tunnel.rx, tunnel.tx),
+                      self.iproute.GetRxTxPackets(tunnel.iface))
+    sa_info.seq_num += 1
 
-  def assertSentPacket(self, vti):
-    vti.tx += 1
-    self.assertEquals((vti.rx, vti.tx), self.iproute.GetRxTxPackets(vti.iface))
+  def assertSentPacket(self, tunnel, sa_info):
+    tunnel.tx += 1
+    self.assertEquals((tunnel.rx, tunnel.tx),
+                      self.iproute.GetRxTxPackets(tunnel.iface))
+    sa_info.seq_num += 1
 
-  # TODO: Should we completely re-write this using null encryption and null
-  # authentication? We could then assemble and disassemble packets for each
-  # direction individually. This approach would improve debuggability, avoid the
-  # complexity of the twister, and allow the test to more-closely validate
-  # deployable configurations.
-  def _CheckVtiInputOutput(self, vti, inner_version):
-    local_outer = vti.local
-    remote_outer = vti.remote
+  def _CheckTunnelInput(self, tunnel, inner_version, local_inner, remote_inner,
+                        sa_info=None, expect_fail=False):
+    """Test null-crypt input path over an IPsec interface."""
+    if sa_info is None:
+      sa_info = tunnel.in_sa
+    read_sock, local_port = _CreateReceiveSock(inner_version)
 
-    # Create a socket to receive packets.
-    read_sock = socket(
-        net_test.GetAddressFamily(inner_version), SOCK_DGRAM, 0)
-    read_sock.bind((net_test.GetWildcardAddress(inner_version), 0))
-    # The second parameter of the tuple is the port number regardless of AF.
-    port = read_sock.getsockname()[1]
-    # Guard against the eventuality of the receive failing.
-    csocket.SetSocketTimeout(read_sock, 100)
+    input_pkt = _GetNullAuthCryptTunnelModePkt(
+        inner_version, remote_inner, tunnel.remote, _TEST_REMOTE_PORT,
+        local_inner, tunnel.local, local_port, sa_info.spi, sa_info.seq_num)
+    self.ReceivePacketOn(tunnel.underlying_netid, input_pkt)
 
-    # Send a packet out via the vti-backed network, bound for the port number
-    # of the input socket.
-    write_sock = socket(
-        net_test.GetAddressFamily(inner_version), SOCK_DGRAM, 0)
-    self.SelectInterface(write_sock, vti.netid, "mark")
-    write_sock.sendto(net_test.UDP_PAYLOAD,
-                      (_GetRemoteInnerAddress(inner_version), port))
+    if expect_fail:
+      self.assertRaisesErrno(EAGAIN, read_sock.recv, 4096)
+    else:
+      # Verify that the packet data and src are correct
+      data, src = read_sock.recvfrom(4096)
+      self.assertReceivedPacket(tunnel, sa_info)
+      self.assertEquals(net_test.UDP_PAYLOAD, data)
+      self.assertEquals((remote_inner, _TEST_REMOTE_PORT), src[:2])
+
+  def _CheckTunnelOutput(self, tunnel, inner_version, local_inner,
+                         remote_inner, sa_info=None):
+    """Test null-crypt output path over an IPsec interface."""
+    if sa_info is None:
+      sa_info = tunnel.out_sa
+    local_port = _SendPacket(self, tunnel.netid, inner_version, remote_inner,
+                             _TEST_REMOTE_PORT)
 
     # Read a tunneled IP packet on the underlying (outbound) network
     # verifying that it is an ESP packet.
-    self.assertSentPacket(vti)
-    pkt = self._ExpectEspPacketOn(vti.underlying_netid, vti.out_spi, vti.tx, None,
-                                  local_outer, remote_outer)
+    pkt = self._ExpectEspPacketOn(tunnel.underlying_netid, sa_info.spi,
+                                  sa_info.seq_num, None, tunnel.local,
+                                  tunnel.remote)
 
-    # Perform an address switcheroo so that the inner address of the remote
-    # end of the tunnel is now the address on the local VTI interface; this
-    # way, the twisted inner packet finds a destination via the VTI once
-    # decrypted.
-    remote = _GetRemoteInnerAddress(inner_version)
-    local = vti.addrs[inner_version]
-    self._SwapInterfaceAddress(vti.iface, new_addr=remote, old_addr=local)
+    # Get and update the IP headers on the inner payload so that we can do a simple
+    # comparison of byte data. Unfortunately, due to the scapy version this runs on,
+    # we cannot parse past the ESP header to the inner IP header, and thus have to
+    # workaround in this manner
+    if inner_version == 4:
+      ip_hdr_options = {
+        'id': scapy.IP(str(pkt.payload)[8:]).id,
+        'flags': scapy.IP(str(pkt.payload)[8:]).flags
+      }
+    else:
+      ip_hdr_options = {'fl': scapy.IPv6(str(pkt.payload)[8:]).fl}
+
+    expected = _GetNullAuthCryptTunnelModePkt(
+        inner_version, local_inner, tunnel.local, local_port, remote_inner,
+        tunnel.remote, _TEST_REMOTE_PORT, sa_info.spi, sa_info.seq_num,
+        ip_hdr_options)
+
+    # Check outer header manually (Avoids having to overwrite outer header's
+    # id, flags or flow label)
+    self.assertSentPacket(tunnel, sa_info)
+    self.assertEquals(expected.src, pkt.src)
+    self.assertEquals(expected.dst, pkt.dst)
+    self.assertEquals(len(expected), len(pkt))
+
+    # Check everything else
+    self.assertEquals(str(expected.payload), str(pkt.payload))
+
+  def _CheckTunnelEncryption(self, tunnel, inner_version, local_inner,
+                             remote_inner):
+    """Test both input and output paths over an encrypted IPsec interface.
+
+    This tests specifically makes sure that the both encryption and decryption
+    work together, as opposed to the _CheckTunnel(Input|Output) where the
+    input and output paths are tested separately, and using null encryption.
+    """
+    src_port = _SendPacket(self, tunnel.netid, inner_version, remote_inner,
+                           _TEST_REMOTE_PORT)
+
+    # Make sure it appeared on the underlying interface
+    pkt = self._ExpectEspPacketOn(tunnel.underlying_netid, tunnel.out_sa.spi,
+                                  tunnel.out_sa.seq_num, None, tunnel.local,
+                                  tunnel.remote)
+
+    # Check that packet is not sent in plaintext
+    self.assertTrue(str(net_test.UDP_PAYLOAD) not in str(pkt))
+
+    # Check src/dst
+    self.assertEquals(tunnel.local, pkt.src)
+    self.assertEquals(tunnel.remote, pkt.dst)
+
+    # Check that the interface statistics recorded the outbound packet
+    self.assertSentPacket(tunnel, tunnel.out_sa)
+
     try:
-      # Swap the packet's IP headers and write it back to the
-      # underlying network.
-      pkt = TunTwister.TwistPacket(pkt)
-      self.ReceivePacketOn(vti.underlying_netid, pkt)
-      # Receive the decrypted packet on the dest port number.
-      read_packet = read_sock.recv(4096)
-      self.assertEquals(read_packet, net_test.UDP_PAYLOAD)
-      self.assertReceivedPacket(vti)
-    finally:
-      # Unwind the switcheroo
-      self._SwapInterfaceAddress(vti.iface, new_addr=local, old_addr=remote)
+      # Swap the interface addresses to pretend we are the remote
+      self._SwapInterfaceAddress(
+          tunnel.iface, new_addr=remote_inner, old_addr=local_inner)
 
+      # Swap the packet's IP headers and write it back to the underlying
+      # network.
+      pkt = TunTwister.TwistPacket(pkt)
+      read_sock, local_port = _CreateReceiveSock(inner_version,
+                                                 _TEST_REMOTE_PORT)
+      self.ReceivePacketOn(tunnel.underlying_netid, pkt)
+
+      # Verify that the packet data and src are correct
+      data, src = read_sock.recvfrom(4096)
+      self.assertEquals(net_test.UDP_PAYLOAD, data)
+      self.assertEquals((local_inner, src_port), src[:2])
+
+      # Check that the interface statistics recorded the inbound packet
+      self.assertReceivedPacket(tunnel, tunnel.in_sa)
+    finally:
+      # Swap the interface addresses to pretend we are the remote
+      self._SwapInterfaceAddress(
+          tunnel.iface, new_addr=local_inner, old_addr=remote_inner)
+
+  def _CheckTunnelIcmp(self, tunnel, inner_version, local_inner, remote_inner,
+                       sa_info=None):
+    """Test ICMP error path over an IPsec interface."""
+    if sa_info is None:
+      sa_info = tunnel.out_sa
     # Now attempt to provoke an ICMP error.
     # TODO: deduplicate with multinetwork_test.py.
-    version = net_test.GetAddressVersion(vti.remote)
     dst_prefix, intermediate = {
         4: ("172.19.", "172.16.9.12"),
         6: ("2001:db8::", "2001:db8::1")
-    }[version]
+    }[tunnel.version]
 
-    write_sock.sendto(net_test.UDP_PAYLOAD,
-                      (_GetRemoteInnerAddress(inner_version), port))
-    self.assertSentPacket(vti)
-    pkt = self._ExpectEspPacketOn(vti.underlying_netid, vti.out_spi, vti.tx, None,
-                                  local_outer, remote_outer)
-    myaddr = self.MyAddress(version, vti.underlying_netid)
-    _, toobig = packets.ICMPPacketTooBig(version, intermediate, myaddr, pkt)
-    self.ReceivePacketOn(vti.underlying_netid, toobig)
+    local_port = _SendPacket(self, tunnel.netid, inner_version, remote_inner,
+                             _TEST_REMOTE_PORT)
+    pkt = self._ExpectEspPacketOn(tunnel.underlying_netid, sa_info.spi,
+                                  sa_info.seq_num, None, tunnel.local,
+                                  tunnel.remote)
+    self.assertSentPacket(tunnel, sa_info)
+
+    myaddr = self.MyAddress(tunnel.version, tunnel.underlying_netid)
+    _, toobig = packets.ICMPPacketTooBig(tunnel.version, intermediate, myaddr,
+                                         pkt)
+    self.ReceivePacketOn(tunnel.underlying_netid, toobig)
 
     # Check that the packet too big reduced the MTU.
-    routes = self.iproute.GetRoutes(vti.remote, 0, vti.underlying_netid, None)
+    routes = self.iproute.GetRoutes(tunnel.remote, 0, tunnel.underlying_netid, None)
     self.assertEquals(1, len(routes))
     rtmsg, attributes = routes[0]
     self.assertEquals(iproute.RTN_UNICAST, rtmsg.type)
     self.assertEquals(packets.PTB_MTU, attributes["RTA_METRICS"]["RTAX_MTU"])
 
     # Clear PMTU information so that future tests don't have to worry about it.
-    self.InvalidateDstCache(version, vti.underlying_netid)
+    self.InvalidateDstCache(tunnel.version, tunnel.underlying_netid)
 
-  def testVtiInputOutput(self):
+  def _CheckTunnelEncryptionWithIcmp(self, tunnel, inner_version, local_inner,
+                                     remote_inner):
+    """Test combined encryption path with ICMP errors over an IPsec tunnel"""
+    self._CheckTunnelEncryption(tunnel, inner_version, local_inner,
+                                remote_inner)
+    self._CheckTunnelIcmp(tunnel, inner_version, local_inner, remote_inner)
+    self._CheckTunnelEncryption(tunnel, inner_version, local_inner,
+                                remote_inner)
+
+  def _TestTunnel(self, inner_version, outer_version, func, use_null_crypt):
+    """Bootstrap method to setup and run tests for the given parameters."""
+    tunnel = self.randomTunnel(outer_version)
+
+    try:
+      # Some tests require that the out_seq_num and in_seq_num are the same
+      # (Specifically encrypted tests), rebuild SAs to ensure seq_num is 1
+      #
+      # Until we get better scapy support, the only way we can build an
+      # encrypted packet is to send it out, and read the packet from the wire.
+      # We then generally use this as the "inbound" encrypted packet, injecting
+      # it into the interface for which it is expected on.
+      #
+      # As such, this is required to ensure that encrypted packets (which we
+      # currently have no way to easily modify) are not considered replay
+      # attacks by the inbound SA.  (eg: received 3 packets, seq_num_in = 3,
+      # sent only 1, # seq_num_out = 1, inbound SA would consider this a replay
+      # attack)
+      tunnel.TeardownXfrm()
+      tunnel.SetupXfrm(use_null_crypt)
+
+      local_inner = tunnel.addrs[inner_version]
+      remote_inner = _GetRemoteInnerAddress(inner_version)
+
+      for i in range(2):
+        func(tunnel, inner_version, local_inner, remote_inner)
+    finally:
+      if use_null_crypt:
+        tunnel.TeardownXfrm()
+        tunnel.SetupXfrm(False)
+
+  def _CheckTunnelRekey(self, tunnel, inner_version, local_inner, remote_inner):
+    old_out_sa = tunnel.out_sa
+    old_in_sa = tunnel.in_sa
+
+    # Check to make sure that both directions work before rekey
+    self._CheckTunnelInput(tunnel, inner_version, local_inner, remote_inner,
+                           old_in_sa)
+    self._CheckTunnelOutput(tunnel, inner_version, local_inner, remote_inner,
+                            old_out_sa)
+
+    # Rekey
+    outer_family = net_test.GetAddressFamily(tunnel.version)
+
+    # Create new SA
+    # Distinguish the new SAs with new SPIs.
+    new_out_sa = SaInfo(old_out_sa.spi + 1)
+    new_in_sa = SaInfo(old_in_sa.spi + 1)
+
+    # Perform Rekey
+    tunnel.Rekey(outer_family, new_out_sa, new_in_sa)
+
+    # Expect that the old SPI still works for inbound packets
+    self._CheckTunnelInput(tunnel, inner_version, local_inner, remote_inner,
+                           old_in_sa)
+
+    # Test both paths with new SPIs, expect outbound to use new SPI
+    self._CheckTunnelInput(tunnel, inner_version, local_inner, remote_inner,
+                           new_in_sa)
+    self._CheckTunnelOutput(tunnel, inner_version, local_inner, remote_inner,
+                            new_out_sa)
+
+    # Delete old SAs
+    tunnel.DeleteOldSaInfo(outer_family, old_in_sa.spi, old_out_sa.spi)
+
+    # Test both paths with new SPIs; should still work
+    self._CheckTunnelInput(tunnel, inner_version, local_inner, remote_inner,
+                           new_in_sa)
+    self._CheckTunnelOutput(tunnel, inner_version, local_inner, remote_inner,
+                            new_out_sa)
+
+    # Expect failure upon trying to receive a packet with the deleted SPI
+    self._CheckTunnelInput(tunnel, inner_version, local_inner, remote_inner,
+                           old_in_sa, True)
+
+  def _TestTunnelRekey(self, inner_version, outer_version):
     """Test packet input and output over a Virtual Tunnel Interface."""
-    for i in xrange(3 * len(self.vtis.values())):
-      vti = random.choice(self.vtis.values())
-      self._CheckVtiInputOutput(vti, 4)
-      self._CheckVtiInputOutput(vti, 6)
+    tunnel = self.randomTunnel(outer_version)
+
+    try:
+      # Always use null_crypt, so we can check input and output separately
+      tunnel.TeardownXfrm()
+      tunnel.SetupXfrm(True)
+
+      local_inner = tunnel.addrs[inner_version]
+      remote_inner = _GetRemoteInnerAddress(inner_version)
+
+      self._CheckTunnelRekey(tunnel, inner_version, local_inner, remote_inner)
+    finally:
+      tunnel.TeardownXfrm()
+      tunnel.SetupXfrm(False)
+
+
+@unittest.skipUnless(net_test.LINUX_VERSION >= (3, 18, 0), "VTI Unsupported")
+class XfrmVtiTest(XfrmTunnelBase):
+
+  INTERFACE_CLASS = VtiInterface
+
+  def ParamTestVtiInput(self, inner_version, outer_version):
+    self._TestTunnel(inner_version, outer_version, self._CheckTunnelInput, True)
+
+  def ParamTestVtiOutput(self, inner_version, outer_version):
+    self._TestTunnel(inner_version, outer_version, self._CheckTunnelOutput,
+                     True)
+
+  def ParamTestVtiInOutEncrypted(self, inner_version, outer_version):
+    self._TestTunnel(inner_version, outer_version, self._CheckTunnelEncryption,
+                     False)
+
+  def ParamTestVtiIcmp(self, inner_version, outer_version):
+    self._TestTunnel(inner_version, outer_version, self._CheckTunnelIcmp, False)
+
+  def ParamTestVtiEncryptionWithIcmp(self, inner_version, outer_version):
+    self._TestTunnel(inner_version, outer_version,
+                     self._CheckTunnelEncryptionWithIcmp, False)
+
+  def ParamTestVtiRekey(self, inner_version, outer_version):
+    self._TestTunnelRekey(inner_version, outer_version)
+
+
+@unittest.skipUnless(HAVE_XFRM_INTERFACES, "XFRM interfaces unsupported")
+class XfrmInterfaceTest(XfrmTunnelBase):
+
+  INTERFACE_CLASS = XfrmInterface
+
+  def ParamTestXfrmIntfInput(self, inner_version, outer_version):
+    self._TestTunnel(inner_version, outer_version, self._CheckTunnelInput, True)
+
+  def ParamTestXfrmIntfOutput(self, inner_version, outer_version):
+    self._TestTunnel(inner_version, outer_version, self._CheckTunnelOutput,
+                     True)
+
+  def ParamTestXfrmIntfInOutEncrypted(self, inner_version, outer_version):
+    self._TestTunnel(inner_version, outer_version, self._CheckTunnelEncryption,
+                     False)
+
+  def ParamTestXfrmIntfIcmp(self, inner_version, outer_version):
+    self._TestTunnel(inner_version, outer_version, self._CheckTunnelIcmp, False)
+
+  def ParamTestXfrmIntfEncryptionWithIcmp(self, inner_version, outer_version):
+    self._TestTunnel(inner_version, outer_version,
+                     self._CheckTunnelEncryptionWithIcmp, False)
+
+  def ParamTestXfrmIntfRekey(self, inner_version, outer_version):
+    self._TestTunnelRekey(inner_version, outer_version)
 
 
 if __name__ == "__main__":
+  InjectTests()
   unittest.main()