summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorMaciej Żenczykowski <maze@google.com>2019-02-06 15:14:52 -0800
committerMaciej Żenczykowski <maze@google.com>2019-02-06 15:15:48 -0800
commit6bf416b74b3c2652b33dd42c01edc0989052a18d (patch)
treede1f6e56d8aee89570b9bd4fde9ba179dbd66fd2
parent60317ba07e58a16ffe300b963a0b6fcbb8e17c07 (diff)
parentd8cd1253af486f5129cb52fb503d616dffc92794 (diff)
downloadtests-pie-dev.tar.gz
Merge master into pie-devpie-dev
This pulls in: net-test: try to detect vsyscall=none uml and warn about it. Add tests for unset output marks on floating policies Revert "Test Updating OUTPUT_MARK on Active SAs" Test Updating OUTPUT_MARK on Active SAs anycast_test.py: increase waiting time to 3 sec to wait CloseFileDescriptorThread finished anycast_test.py: increase waiting time to 3 sec to wait CloseFileDescriptorThread finished Test to check tcp initial rwnd size net_test: fix job control in console's bash terminal net_test: workaround for 3.18 and 4.4 late urandom init Revert "Implement a workaround for clang + PARAVIRT failure." net-test: deflake 4.19 entropy installation anycast_test.py: change to use thread.join to wait CloseFileDescriptorThread finished Use blocking sockets with timeout for xfrm_tunnel_test Close the socket in socketCreateTest before exist Test to verify cgroup socket filter net_test - extra debugging for ReadProcNetSocket() regexp match failures net/test/OWNERS: passing the torch from ek@ to maze@ run_net_test - further boost UML entropy run_net_test.sh: Add support for arm64 build_rootfs.sh: Add support for arm64 Improve xfrm net test Improve xfrm net test Filter neighbour dumps by interface. run_net_test: UML - fix insufficient entropy problems run_net_test: handle UML's tendency to leave stdout in non-blocking mode run_net_test.sh: switch to readonly by default run_net_test: add 'no_test' test Filter neighbour dumps by interface. run_net_test.sh: fix UML --readonly flag Enable virtio rng device for net tests on qemu/kvm. Add lspci & lsusb commands to stretch image. Remove mutable default parameter in tunnel_test Document/enforce a bug in udp_dump_one. Allow ROOTFS to use environment variables Check xfrm state to delete embryonic SA Fix sysfs mount in net_test.sh. net_test: fix sock_diag_test.py to handle ipv5 correctly Add tests for netfilter reject policies Add tests for VTI rekey procedure Always test UDP_DIAG for 4.9 kernel Refactor VTI tests to support null encryption Add tunnel input tests to net_tests Refactor parameterization logic in net tests Fix nobuild runs of run_net_test.sh without KERNEL_BINARY env var set Add scripts for building the net tests rootfs. anycast_test.py: change to use thread.join to wait CloseFileDescriptorThread finished Fix net tests for 32-bit kernel Fix net tests for 32-bit kernel Enable FHANDLE to support systemd Implement a workaround for clang + PARAVIRT failure. Add support for running the harness with QEMU. Annotate non-common kernel config options. Fix some invalid config options. Drop unnecessary CONFIG_ prefixes. Fix selection of bpf syscall number with COMPAT_UTS_MACHINE. Add __NR_bpf constant for i686. Fix the flaky cgroup uid bpf test Test for getFirstMapKey of bpf maps Test experimental xfrm interfaces if supported. Set SA mark to unused for Tunnel Mode Be flexible about TCP RST and SOCK_DESTROY poll return values. Verify VTI Modification using RTM_NEWLINK Disable qtaguid tests if qtaguid is not present. Enable algorithm net tests for 3.18 kernels Fix algorithm tests to ensure no lingering sockets Signed-off-by: Maciej Żenczykowski <maze@google.com> Change-Id: Ic43271c0349d57e09e9b8709d03b4e3165914824
-rw-r--r--net/test/OWNERS2
-rwxr-xr-xnet/test/all_tests.py1
-rw-r--r--[-rwxr-xr-x]net/test/anycast_test.py9
-rwxr-xr-xnet/test/bpf.py18
-rwxr-xr-xnet/test/bpf_test.py62
-rwxr-xr-xnet/test/build_rootfs.sh139
-rw-r--r--net/test/csocket.py7
-rw-r--r--net/test/iproute.py32
-rwxr-xr-xnet/test/neighbour_test.py6
-rwxr-xr-xnet/test/net_test.py14
-rwxr-xr-xnet/test/net_test.sh135
-rwxr-xr-xnet/test/nf_test.py86
-rwxr-xr-xnet/test/no_test1
-rwxr-xr-xnet/test/parallel_tests.sh2
-rwxr-xr-xnet/test/parameterization_test.py83
-rwxr-xr-xnet/test/qtaguid_test.py3
-rw-r--r--net/test/rootfs/common.sh57
-rwxr-xr-xnet/test/rootfs/net_test.sh34
-rw-r--r--net/test/rootfs/stretch.list33
-rwxr-xr-xnet/test/rootfs/stretch.sh150
-rw-r--r--net/test/rootfs/wheezy.list33
-rwxr-xr-xnet/test/rootfs/wheezy.sh50
-rwxr-xr-xnet/test/run_net_test.sh314
-rwxr-xr-xnet/test/sock_diag_test.py128
-rw-r--r--net/test/util.py57
-rwxr-xr-xnet/test/xfrm.py108
-rwxr-xr-xnet/test/xfrm_algorithm_test.py109
-rwxr-xr-xnet/test/xfrm_test.py15
-rwxr-xr-xnet/test/xfrm_tunnel_test.py889
29 files changed, 2190 insertions, 387 deletions
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 @@ test_modules = [
'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
index 82130db..6222580 100755..100644
--- a/net/test/anycast_test.py
+++ b/net/test/anycast_test.py
@@ -93,7 +93,14 @@ class AnycastTest(multinetwork_base.MultiNetworkBaseTest):
# This will hang if the kernel has the bug.
thread = CloseFileDescriptorThread(self.tuns[netid])
thread.start()
- time.sleep(0.1)
+ # Wait up to 3 seconds for the thread to finish, but
+ # continue and fail the test if the thread hangs.
+
+ # 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 c9ad264..43502bd 100755
--- a/net/test/bpf.py
+++ b/net/test/bpf.py
@@ -21,13 +21,24 @@ import csocket
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,
- "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
@@ -141,6 +152,7 @@ BPF_FUNC_unspec = 0
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 @@ import sock_diag
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 @@ class BpfTest(net_test.NetworkTest):
+ 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 @@ class BpfCgroupTest(net_test.NetworkTest):
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 @@ class BpfCgroupTest(net_test.NetworkTest):
+ 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/csocket.py b/net/test/csocket.py
index bdd501c..ccabf4a 100644
--- a/net/test/csocket.py
+++ b/net/test/csocket.py
@@ -17,6 +17,7 @@
import ctypes
import ctypes.util
import os
+import re
import socket
import struct
@@ -78,9 +79,9 @@ libc = ctypes.CDLL(ctypes.util.find_library("c"), use_errno=True)
# TODO: Unlike most of this file, these functions aren't specific to wrapping C
# library calls. Move them to a utils.py or constants.py file, once we have one.
def LinuxVersion():
- # Example: "3.4.67-00753-gb7a556f".
- # Get the part before the dash.
- version = os.uname()[2].split("-")[0]
+ # Example: "3.4.67-00753-gb7a556f", "4.4.135+".
+ # Get the prefix consisting of digits and dots.
+ version = re.search("^[0-9.]*", os.uname()[2]).group()
# Convert it into a tuple such as (3, 4, 67). That allows comparing versions
# using < and >, since tuples are compared lexicographically.
version = tuple(int(i) for i in version.split("."))
diff --git a/net/test/iproute.py b/net/test/iproute.py
index 9cfafc6..8376eb6 100644
--- a/net/test/iproute.py
+++ b/net/test/iproute.py
@@ -147,6 +147,7 @@ NDA_DST = 1
NDA_LLADDR = 2
NDA_CACHEINFO = 3
NDA_PROBES = 4
+NDA_IFINDEX = 8
# Neighbour cache entry states.
NUD_PERMANENT = 0x80
@@ -208,13 +209,17 @@ IFLA_PAD = 42
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
@@ -635,9 +640,10 @@ class IPRoute(netlink.NetlinkSocket):
self._Neighbour(version, True, addr, lladdr, dev, state,
flags=netlink.NLM_F_REPLACE)
- def DumpNeighbours(self, version):
+ def DumpNeighbours(self, version, ifindex):
ndmsg = NdMsg((self._AddressFamily(version), 0, 0, 0, 0))
- return self._Dump(RTM_GETNEIGH, ndmsg, NdMsg, "")
+ attrs = self._NlAttrU32(NDA_IFINDEX, ifindex) if ifindex else ""
+ return self._Dump(RTM_GETNEIGH, ndmsg, NdMsg, attrs)
def ParseNeighbourMessage(self, msg):
msg, _ = self._ParseNLMsg(msg, NdMsg)
@@ -680,7 +686,7 @@ class IPRoute(netlink.NetlinkSocket):
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)
@@ -743,6 +749,22 @@ class IPRoute(netlink.NetlinkSocket):
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/neighbour_test.py b/net/test/neighbour_test.py
index 2caba44..caf2e6e 100755
--- a/net/test/neighbour_test.py
+++ b/net/test/neighbour_test.py
@@ -87,14 +87,14 @@ class NeighbourTest(multinetwork_base.MultiNetworkBaseTest):
self.netid = random.choice(self.tuns.keys())
self.ifindex = self.ifindices[self.netid]
- def GetNeighbour(self, addr):
+ def GetNeighbour(self, addr, ifindex):
version = csocket.AddressVersion(addr)
- for msg, args in self.iproute.DumpNeighbours(version):
+ for msg, args in self.iproute.DumpNeighbours(version, ifindex):
if args["NDA_DST"] == addr:
return msg, args
def GetNdEntry(self, addr):
- return self.GetNeighbour(addr)
+ return self.GetNeighbour(addr, self.ifindex)
def CheckNoNdEvents(self):
self.assertRaisesErrno(errno.EAGAIN, self.sock.recvfrom, 4096, MSG_PEEK)
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 @@ class RunAsUidGid(object):
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 @@ class RunAsUid(RunAsUidGid):
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 @@ class NetworkTest(unittest.TestCase):
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 @@ class NetworkTest(unittest.TestCase):
# 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 @@ fi
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 @@ function runtests() {
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 @@ import tcp_test
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..a81ad33 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 @@ DISABLE_OPTIONS="$DISABLE_OPTIONS CONFIG_SYSVIPC"
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 @@ SCRIPT_DIR=$(dirname $(readlink -f $0))
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 @@ function isBuildOnly() {
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 @@ cd -
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,184 @@ fi
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.
+ 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.
- [ -f $CONFIG_FILE ] || (cd $KERNEL_DIR && $MAKE defconfig ARCH=um SUBARCH=x86_64)
+ [ -n "$DEFCONFIG" ] || DEFCONFIG=defconfig
+ [ -f $CONFIG_FILE ] || (cd $KERNEL_DIR && $MAKE $make_flags $DEFCONFIG)
# Enable the kernel config options listed in $OPTIONS.
- cmdline=${OPTIONS// / -e }
- $CONFIG_SCRIPT --file $CONFIG_FILE $cmdline
+ $CONFIG_SCRIPT --file $CONFIG_FILE ${OPTIONS// / -e }
# 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.
+ $CONFIG_SCRIPT --file $CONFIG_FILE ${DISABLE_OPTIONS// / -d }
-EOF
- fi
+ $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
+
+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
+
+ exitcode=0
+ $KERNEL_BINARY >&2 umid=net_test mem=512M \
+ $blockdevice=$SCRIPT_DIR/$ROOTFS $netconfig $consolemode $cmdline \
+ || exitcode=$?
+
+ # UML is kind of crazy in how guest syscalls work. It requires host kernel
+ # to not be in vsyscall=none mode.
+ if [[ "${exitcode}" != '0' ]]; then
+ {
+ # Hopefully one of these exists
+ cat /proc/config || :
+ zcat /proc/config.gz || :
+ cat "/boot/config-$(uname -r)" || :
+ zcat "/boot/config-$(uname -r).gz" || :
+ } 2>/dev/null \
+ | egrep -q '^CONFIG_LEGACY_VSYSCALL_NONE=y' \
+ && ! egrep -q '(^| )vsyscall=(native|emulate)( |$)' /proc/cmdline \
+ && {
+ echo '-----=====-----'
+ echo 'If above you saw a "net_test.sh[1]: segfault at ..." followed by'
+ echo '"Kernel panic - not syncing: Attempted to kill init!" then please'
+ echo 'set "vsyscall=emulate" on *host* kernel command line.'
+ echo '(for example via GRUB_CMDLINE_LINUX in /etc/default/grub)'
+ echo '-----=====-----'
+ }
+ fi
+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 || :
-# 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
+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 e25035b..daa2fa4 100755
--- a/net/test/sock_diag_test.py
+++ b/net/test/sock_diag_test.py
@@ -25,24 +25,42 @@ import threading
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
def HaveUdpDiag():
- # There is no way to tell whether a dump succeeded: if the appropriate handler
- # wasn't found, __inet_diag_dump just returns an empty result instead of an
- # error. So, just check to see if a UDP dump returns no sockets when we know
- # it should return one.
+ """Checks if the current kernel has config CONFIG_INET_UDP_DIAG enabled.
+
+ This config is required for device running 4.9 kernel that ship with P, In
+ this case always assume the config is there and use the tests to check if the
+ config is enabled as required.
+
+ For all ther other kernel version, there is no way to tell whether a dump
+ succeeded: if the appropriate handler wasn't found, __inet_diag_dump just
+ returns an empty result instead of an error. So, just check to see if a UDP
+ dump returns no sockets when we know it should return one. If not, some tests
+ will be skipped.
+
+ Returns:
+ True if the kernel is 4.9 or above, or the CONFIG_INET_UDP_DIAG is enabled.
+ False otherwise.
+ """
+ if LINUX_4_9_OR_ABOVE:
+ return True;
s = socket(AF_INET6, SOCK_DGRAM, 0)
s.bind(("::", 0))
s.connect((s.getsockname()))
@@ -192,10 +210,10 @@ class SockDiagTest(SockDiagBaseTest):
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.
@@ -225,9 +243,21 @@ class SockDiagTest(SockDiagBaseTest):
# 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 = [
@@ -360,11 +390,45 @@ class SockDiagTest(SockDiagBaseTest):
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.
@@ -487,6 +551,50 @@ class SockDiagTcpTest(tcp_test.TcpBaseTest, SockDiagBaseTest):
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):
@@ -877,7 +985,7 @@ class SockDestroyUdpTest(SockDiagBaseTest):
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_ADDRESS_FILTER = 26
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_CFG = XfrmLifetimeCfg((_INF, _INF, _INF, _INF, 0, 0, 0, 0))
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 @@ EspHdr = cstruct.Struct("EspHdr", "!II", "spi seqnum")
_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 @@ class Xfrm(netlink.NetlinkSocket):
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 @@ class Xfrm(netlink.NetlinkSocket):
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 @@ class Xfrm(netlink.NetlinkSocket):
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 @@ class Xfrm(netlink.NetlinkSocket):
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 @@ class Xfrm(netlink.NetlinkSocket):
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 @@ class Xfrm(netlink.NetlinkSocket):
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 @@ class Xfrm(netlink.NetlinkSocket):
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 @@ class Xfrm(netlink.NetlinkSocket):
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 @@ class Xfrm(netlink.NetlinkSocket):
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 @@ class Xfrm(netlink.NetlinkSocket):
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 @@ class Xfrm(netlink.NetlinkSocket):
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 @@ class Xfrm(netlink.NetlinkSocket):
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, xfrm_if_id):
+ if mark is not None:
+ mark = ExactMatchMark(mark)
- def DeleteTunnel(self, direction, selector, dst, spi, mark):
- self.DeleteSaInfo(dst, spi, IPPROTO_ESP, 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 unittest
import multinetwork_base
import net_test
from tun_twister import TapTwister
+import util
import xfrm
import xfrm_base
@@ -72,49 +73,26 @@ AEAD_ALGOS = [
]
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)
-
- @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})
+ param_list = itertools.product(VERSIONS, TYPES, [None], [None], AEAD_ALGOS)
+ util.InjectParameterizedTest(cls, param_list, cls.TestNameGenerator)
+ @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 @@ class XfrmAlgorithmTest(xfrm_base.XfrmLazyTest):
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 @@ class XfrmAlgorithmTest(xfrm_base.XfrmLazyTest):
# 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 @@ class XfrmAlgorithmTest(xfrm_base.XfrmLazyTest):
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 @@ class XfrmAlgorithmTest(xfrm_base.XfrmLazyTest):
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 93c66f4..3a3d9b0 100755
--- a/net/test/xfrm_test.py
+++ b/net/test/xfrm_test.py
@@ -132,6 +132,14 @@ class XfrmFunctionalTest(xfrm_base.XfrmLazyTest):
EAGAIN,
s.sendto, net_test.UDP_PAYLOAD, (remotesockaddr, 53))
+ # If there is a user space key manager, calling sendto() after applying the socket policy
+ # creates an SA whose state is XFRM_STATE_ACQ. So this just deletes it.
+ # If there is no user space key manager, deleting SA returns ESRCH as the error code.
+ try:
+ self.xfrm.DeleteSaInfo(self.GetRemoteAddress(xfrm_version), TEST_SPI, IPPROTO_ESP)
+ except IOError as e:
+ self.assertEquals(ESRCH, e.errno, "Unexpected error when deleting ACQ SA")
+
# Adding a matching SA causes the packet to go out encrypted. The SA's
# SPI must match the one in our template, and the destination address must
# match the packet's destination address (in tunnel mode, it has to match
@@ -139,6 +147,7 @@ class XfrmFunctionalTest(xfrm_base.XfrmLazyTest):
self.CreateNewSa(
net_test.GetWildcardAddress(xfrm_version),
self.GetRemoteAddress(xfrm_version), TEST_SPI, reqid, None)
+
s.sendto(net_test.UDP_PAYLOAD, (remotesockaddr, 53))
expected_length = xfrm_base.GetEspPacketLength(xfrm.XFRM_MODE_TRANSPORT,
version, False,
@@ -631,14 +640,14 @@ class XfrmFunctionalTest(xfrm_base.XfrmLazyTest):
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 ac340d9..eb1a46e 100755
--- a/net/test/xfrm_tunnel_test.py
+++ b/net/test/xfrm_tunnel_test.py
@@ -19,32 +19,59 @@ from errno import * # pylint: disable=wildcard-import
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,162 @@ def _GetRemoteOuterAddress(version):
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"""
+ def _CheckTunnelOutput(self, inner_version, outer_version, underlying_netid,
+ netid, local_inner, remote_inner, local_outer,
+ remote_outer, write_sock):
+
+ write_sock.sendto(net_test.UDP_PAYLOAD, (remote_inner, 53))
+ self._ExpectEspPacketOn(underlying_netid, _TEST_OUT_SPI, 1, None,
+ local_outer, remote_outer)
+
+ def _CheckTunnelInput(self, inner_version, outer_version, underlying_netid,
+ netid, local_inner, remote_inner, local_outer,
+ remote_outer, read_sock):
+
+ # The second parameter of the tuple is the port number regardless of AF.
+ local_port = read_sock.getsockname()[1]
+
+ # 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)
+
+ # 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 _TestTunnel(self, inner_version, outer_version, func, direction,
+ test_output_mark_unset):
+ """Test a unidirectional 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()
+ u_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)
+ # 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, underlying_netid)
+ local_outer = self.MyAddress(outer_version, u_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.
+ output_mark = u_netid
+ if test_output_mark_unset:
+ output_mark = None
+ self.SetDefaultNetwork(u_netid)
- def testIpv4InIpv4TunnelOutput(self):
- self._CheckTunnelOutput(4, 4)
+ try:
+ # 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, output_mark, 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)
+ finally:
+ if test_output_mark_unset:
+ self.ClearDefaultNetwork()
- def testIpv4InIpv6TunnelOutput(self):
- self._CheckTunnelOutput(4, 6)
+ def ParamTestTunnelInput(self, inner_version, outer_version):
+ self._TestTunnel(inner_version, outer_version, self._CheckTunnelInput,
+ xfrm.XFRM_POLICY_IN, False)
- def testIpv6InIpv4TunnelOutput(self):
- self._CheckTunnelOutput(6, 4)
+ def ParamTestTunnelOutput(self, inner_version, outer_version):
+ self._TestTunnel(inner_version, outer_version, self._CheckTunnelOutput,
+ xfrm.XFRM_POLICY_OUT, False)
- def testIpv6InIpv6TunnelOutput(self):
- self._CheckTunnelOutput(6, 6)
+ def ParamTestTunnelOutputNoSetMark(self, inner_version, outer_version):
+ self._TestTunnel(inner_version, outer_version, self._CheckTunnelOutput,
+ xfrm.XFRM_POLICY_OUT, True)
@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 +248,37 @@ class XfrmAddDeleteVtiTest(xfrm_base.XfrmBaseTest):
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 +289,276 @@ class XfrmAddDeleteVtiTest(xfrm_base.XfrmBaseTest):
def tearDown(self):
super(XfrmAddDeleteVtiTest, self).tearDown()
- self._QuietDeleteLink(_VTI_IFNAME)
+ self._QuietDeleteLink(_TEST_XFRM_IFNAME)
+
+class SaInfo(object):
-class VtiInterface(object):
+ def __init__(self, spi):
+ self.spi = spi
+ self.seq_num = 1
- def __init__(self, iface, netid, underlying_netid, local, remote):
+
+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(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)
-@unittest.skipUnless(net_test.LINUX_VERSION >= (3, 18, 0), "VTI Unsupported")
-class XfrmVtiTest(xfrm_base.XfrmBaseTest):
+ 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 +579,23 @@ class XfrmVtiTest(xfrm_base.XfrmBaseTest):
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 +603,30 @@ class XfrmVtiTest(xfrm_base.XfrmBaseTest):
# 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 +634,320 @@ class XfrmVtiTest(xfrm_base.XfrmBaseTest):
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 assertSentPacket(self, vti):
- vti.tx += 1
- self.assertEquals((vti.rx, vti.tx), self.iproute.GetRxTxPackets(vti.iface))
-
- # 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
-
- # 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)
-
- # 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))
+ 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, tunnel, sa_info):
+ tunnel.tx += 1
+ self.assertEquals((tunnel.rx, tunnel.tx),
+ self.iproute.GetRxTxPackets(tunnel.iface))
+ sa_info.seq_num += 1
+
+ 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)
+
+ 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)
+
+ 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)
-
- # 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)
+ pkt = self._ExpectEspPacketOn(tunnel.underlying_netid, sa_info.spi,
+ sa_info.seq_num, None, tunnel.local,
+ tunnel.remote)
+
+ # 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.
+ # 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)
- self.ReceivePacketOn(vti.underlying_netid, pkt)
- self.assertReceivedPacket(vti)
- # Receive the decrypted packet on the dest port number.
- read_packet = read_sock.recv(4096)
- self.assertEquals(read_packet, net_test.UDP_PAYLOAD)
- finally:
- # Unwind the switcheroo
- self._SwapInterfaceAddress(vti.iface, new_addr=local, old_addr=remote)
+ 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]
+
+ 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)
- 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)
+ 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()