summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorLorenzo Colitti <lorenzo@google.com>2017-02-23 09:04:07 +0000
committerandroid-build-merger <android-build-merger@google.com>2017-02-23 09:04:07 +0000
commit5ab9017646bed7d904152753053629e5b7c52574 (patch)
treedaa6d94fb4f941c691d8fb734e5639848a85022d
parentd9193c718c6f96662e780aae5a526286609e291e (diff)
parent2607aafb5a2fbbe7ab5eb2ca9d2d232b8817dd85 (diff)
downloadtests-5ab9017646bed7d904152753053629e5b7c52574.tar.gz
Initial tests for xfrm netlink interface and socket policies. am: cbcfcf0835 am: d571640957
am: 2607aafb5a Change-Id: I72137c3acc996695f48c101a1539cf5d58302b60
-rwxr-xr-xnet/test/run_net_test.sh11
-rwxr-xr-xnet/test/xfrm.py288
-rwxr-xr-xnet/test/xfrm_test.py321
3 files changed, 618 insertions, 2 deletions
diff --git a/net/test/run_net_test.sh b/net/test/run_net_test.sh
index a802205..cc276d8 100755
--- a/net/test/run_net_test.sh
+++ b/net/test/run_net_test.sh
@@ -10,13 +10,20 @@ 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_NETFILTER_XT_TARGET_NFLOG"
-OPTIONS="$OPTIONS CONFIG_NETFILTER_XT_MATCH_QUOTA CONFIG_NETFILTER_XT_MATCH_QUOTA2"
+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_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"
+OPTIONS="$OPTIONS BPF_SYSCALL XFRM_USER CRYPTO_CBC CRYPTO_CTR"
+OPTIONS="$OPTIONS CRYPTO_HMAC CRYPTO_AES CRYPTO_SHA1 CRYPTO_SHA256 CRYPTO_SHA12"
+OPTIONS="$OPTIONS CRYPTO_USER INET_AH INET_ESP INET_XFRM_MODE"
+OPTIONS="$OPTIONS TRANSPORT INET_XFRM_MODE_TUNNEL INET6_AH INET6_ESP"
+OPTIONS="$OPTIONS INET6_XFRM_MODE_TRANSPORT INET6_XFRM_MODE_TUNNEL"
+OPTIONS="$OPTIONS CRYPTO_SHA256 CRYPTO_SHA512 CRYPTO_AES_X86_64"
+OPTIONS="$OPTIONS CRYPTO_ECHAINIV"
# For 3.1 kernels, where devtmpfs is not on by default.
OPTIONS="$OPTIONS DEVTMPFS DEVTMPFS_MOUNT"
diff --git a/net/test/xfrm.py b/net/test/xfrm.py
new file mode 100755
index 0000000..e9b4fce
--- /dev/null
+++ b/net/test/xfrm.py
@@ -0,0 +1,288 @@
+#!/usr/bin/python
+#
+# Copyright 2016 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.
+
+"""Partial implementation of xfrm netlink code and socket options."""
+
+# pylint: disable=g-bad-todo
+
+import errno
+import os
+from socket import * # pylint: disable=wildcard-import
+import struct
+
+import csocket
+import cstruct
+import net_test
+import netlink
+
+# Base netlink constants. See include/uapi/linux/netlink.h.
+NETLINK_XFRM = 6
+
+# Netlink constants. See include/uapi/linux/xfrm.h.
+# Message types.
+XFRM_MSG_NEWSA = 16
+XFRM_MSG_DELSA = 17
+XFRM_MSG_GETSA = 18
+XFRM_MSG_NEWPOLICY = 19
+XFRM_MSG_DELPOLICY = 20
+XFRM_MSG_GETPOLICY = 21
+XFRM_MSG_ALLOCSPI = 22
+XFRM_MSG_ACQUIRE = 23
+XFRM_MSG_EXPIRE = 24
+XFRM_MSG_UPDPOLICY = 25
+XFRM_MSG_UPDSA = 26
+XFRM_MSG_POLEXPIRE = 27
+XFRM_MSG_FLUSHSA = 28
+XFRM_MSG_FLUSHPOLICY = 29
+XFRM_MSG_NEWAE = 30
+XFRM_MSG_GETAE = 31
+XFRM_MSG_REPORT = 32
+XFRM_MSG_MIGRATE = 33
+XFRM_MSG_NEWSADINFO = 34
+XFRM_MSG_GETSADINFO = 35
+XFRM_MSG_NEWSPDINFO = 36
+XFRM_MSG_GETSPDINFO = 37
+XFRM_MSG_MAPPING = 38
+
+# Attributes.
+XFRMA_UNSPEC = 0
+XFRMA_ALG_AUTH = 1
+XFRMA_ALG_CRYPT = 2
+XFRMA_ALG_COMP = 3
+XFRMA_ENCAP = 4
+XFRMA_TMPL = 5
+XFRMA_SA = 6
+XFRMA_POLICY = 7
+XFRMA_SEC_CTX = 8
+XFRMA_LTIME_VAL = 9
+XFRMA_REPLAY_VAL = 10
+XFRMA_REPLAY_THRESH = 11
+XFRMA_ETIMER_THRESH = 12
+XFRMA_SRCADDR = 13
+XFRMA_COADDR = 14
+XFRMA_LASTUSED = 15
+XFRMA_POLICY_TYPE = 16
+XFRMA_MIGRATE = 17
+XFRMA_ALG_AEAD = 18
+XFRMA_KMADDRESS = 19
+XFRMA_ALG_AUTH_TRUNC = 20
+XFRMA_MARK = 21
+XFRMA_TFCPAD = 22
+XFRMA_REPLAY_ESN_VAL = 23
+XFRMA_SA_EXTRA_FLAGS = 24
+XFRMA_PROTO = 25
+XFRMA_ADDRESS_FILTER = 26
+XFRMA_PAD = 27
+
+# Other netlink constants. See include/uapi/linux/xfrm.h.
+
+# Directions.
+XFRM_POLICY_IN = 0
+XFRM_POLICY_OUT = 1
+XFRM_POLICY_FWD = 2
+XFRM_POLICY_MASK = 3
+
+# Policy sharing.
+XFRM_SHARE_ANY = 0 # /* No limitations */
+XFRM_SHARE_SESSION = 1 # /* For this session only */
+XFRM_SHARE_USER = 2 # /* For this user only */
+XFRM_SHARE_UNIQUE = 3 # /* Use once */
+
+# Modes.
+XFRM_MODE_TRANSPORT = 0
+XFRM_MODE_TUNNEL = 1
+XFRM_MODE_ROUTEOPTIMIZATION = 2
+XFRM_MODE_IN_TRIGGER = 3
+XFRM_MODE_BEET = 4
+XFRM_MODE_MAX = 5
+
+# Actions.
+XFRM_POLICY_ALLOW = 0
+XFRM_POLICY_BLOCK = 1
+
+# Flags.
+XFRM_POLICY_LOCALOK = 1
+XFRM_POLICY_ICMP = 2
+
+# Data structure formats.
+# These aren't constants, they're classes. So, pylint: disable=invalid-name
+XfrmSelector = cstruct.Struct(
+ "XfrmSelector", "=16s16sHHHHHBBBxxxiI",
+ "daddr saddr dport dport_mask sport sport_mask "
+ "family prefixlen_d prefixlen_s proto ifindex user")
+
+XfrmLifetimeCfg = cstruct.Struct(
+ "XfrmLifetimeCfg", "=QQQQQQQQ",
+ "soft_byte hard_byte soft_packet hard_packet "
+ "soft_add_expires hard_add_expires soft_use_expires hard_use_expires")
+
+XfrmLifetimeCur = cstruct.Struct(
+ "XfrmLifetimeCur", "=QQQQ", "bytes packets add_time use_time")
+
+XfrmAlgo = cstruct.Struct("XfrmAlgo", "=64AI", "name key_len")
+
+XfrmAlgoAuth = cstruct.Struct("XfrmAlgo", "=64AII", "name key_len trunc_len")
+
+XfrmAlgoAead = cstruct.Struct("XfrmAlgoAead", "=64AII", "name key_len icv_len")
+
+XfrmStats = cstruct.Struct(
+ "XfrmStats", "=III", "replay_window replay integrity_failed")
+
+XfrmId = cstruct.Struct("XfrmId", "=16sIBxxx", "daddr spi proto")
+
+XfrmUserTmpl = cstruct.Struct(
+ "XfrmUserTmpl", "=SHxx16sIBBBxIII",
+ "id family saddr reqid mode share optional aalgos ealgos calgos",
+ [XfrmId])
+
+XfrmEncapTmpl = cstruct.Struct(
+ "XfrmEncapTmpl", "=HHHxx16s", "type sport dport oa")
+
+XfrmUsersaInfo = cstruct.Struct(
+ "XfrmUsersaInfo", "=SS16sSSSIIHBBB7x",
+ "sel id saddr lft curlft stats seq reqid family mode replay_window flags",
+ [XfrmSelector, XfrmId, XfrmLifetimeCfg, XfrmLifetimeCur, XfrmStats])
+
+XfrmUsersaId = cstruct.Struct(
+ "XfrmUsersaInfo", "=16sIHBx", "daddr spi family proto")
+
+XfrmUserpolicyInfo = cstruct.Struct(
+ "XfrmUserpolicyInfo", "=SSSIIBBBBxxxx",
+ "sel lft curlft priority index dir action flags share",
+ [XfrmSelector, XfrmLifetimeCfg, XfrmLifetimeCur])
+
+# Socket options. See include/uapi/linux/in.h.
+IP_IPSEC_POLICY = 16
+IP_XFRM_POLICY = 17
+IPV6_IPSEC_POLICY = 34
+IPV6_XFRM_POLICY = 35
+
+# UDP encapsulation constants. See include/uapi/linux/udp.h.
+UDP_ENCAP = 100
+UDP_ENCAP_ESPINUDP_NON_IKE = 1
+UDP_ENCAP_ESPINUDP = 2
+
+_INF = 2 ** 64 -1
+NO_LIFETIME_CFG = XfrmLifetimeCfg((_INF, _INF, _INF, _INF, 0, 0, 0, 0))
+NO_LIFETIME_CUR = "\x00" * len(XfrmLifetimeCur)
+
+
+def RawAddress(addr):
+ """Converts an IP address string to binary format."""
+ family = AF_INET6 if ":" in addr else AF_INET
+ return inet_pton(family, addr)
+
+
+def PaddedAddress(addr):
+ """Converts an IP address string to binary format for InetDiagSockId."""
+ padded = RawAddress(addr)
+ if len(padded) < 16:
+ padded += "\x00" * (16 - len(padded))
+ return padded
+
+
+class Xfrm(netlink.NetlinkSocket):
+ """Netlink interface to xfrm."""
+
+ FAMILY = NETLINK_XFRM
+ DEBUG = False
+
+ def __init__(self):
+ super(Xfrm, self).__init__()
+
+ def _GetConstantName(self, value, prefix):
+ return super(Xfrm, self)._GetConstantName(__name__, value, prefix)
+
+ def MaybeDebugCommand(self, command, flags, data):
+ if "ALL" not in self.NL_DEBUG and "XFRM" not in self.NL_DEBUG:
+ return
+
+ if command == XFRM_MSG_GETSA:
+ if flags & netlink.NLM_F_DUMP:
+ struct_type = XfrmUsersaInfo
+ else:
+ struct_type = XfrmUsersaId
+ elif command == XFRM_MSG_DELSA:
+ struct_type = XfrmUsersaId
+ else:
+ struct_type = None
+
+ cmdname = self._GetConstantName(command, "XFRM_MSG_")
+ if struct_type:
+ print "%s %s" % (cmdname, str(self._ParseNLMsg(data, struct_type)))
+ else:
+ print "%s" % cmdname
+
+ def _Decode(self, command, unused_msg, nla_type, nla_data):
+ """Decodes netlink attributes to Python types."""
+ name = self._GetConstantName(nla_type, "XFRMA_")
+
+ if name in ["XFRMA_ALG_CRYPT", "XFRMA_ALG_AUTH"]:
+ data = cstruct.Read(nla_data, XfrmAlgo)[0]
+ elif name == "XFRMA_ALG_AUTH_TRUNC":
+ data = cstruct.Read(nla_data, XfrmAlgoAuth)[0]
+ elif name == "XFRMA_ENCAP":
+ data = cstruct.Read(nla_data, XfrmEncapTmpl)[0]
+ else:
+ data = nla_data
+
+ return name, data
+
+ def AddSaInfo(self, selector, xfrm_id, saddr, lifetimes, reqid, family, mode,
+ replay_window, flags, nlattrs):
+ # The kernel ignores these on input.
+ cur = "\x00" * len(XfrmLifetimeCur)
+ stats = "\x00" * len(XfrmStats)
+ seq = 0
+ sa = XfrmUsersaInfo((selector, xfrm_id, saddr, lifetimes, cur, stats, seq,
+ reqid, family, mode, replay_window, flags))
+ msg = sa.Pack() + nlattrs
+ flags = netlink.NLM_F_REQUEST | netlink.NLM_F_ACK
+ self._SendNlRequest(XFRM_MSG_NEWSA, msg, flags)
+
+ def AddMinimalSaInfo(self, src, dst, spi, proto, mode, reqid,
+ encryption, encryption_key,
+ auth_trunc, auth_trunc_key, encap):
+ selector = XfrmSelector("\x00" * len(XfrmSelector))
+ xfrm_id = XfrmId((PaddedAddress(dst), spi, proto))
+ family = AF_INET6 if ":" in dst else AF_INET
+ nlattrs = self._NlAttr(XFRMA_ALG_CRYPT,
+ encryption.Pack() + encryption_key)
+ nlattrs += self._NlAttr(XFRMA_ALG_AUTH_TRUNC,
+ auth_trunc.Pack() + auth_trunc_key)
+ if encap is not None:
+ nlattrs += self._NlAttr(XFRMA_ENCAP, encap.Pack())
+ self.AddSaInfo(selector, xfrm_id, PaddedAddress(src), NO_LIFETIME_CFG,
+ reqid, family, mode, 4, 0, nlattrs)
+
+ def DeleteSaInfo(self, daddr, spi, proto):
+ # TODO: deletes take a mark as well.
+ family = AF_INET6 if ":" in daddr else AF_INET
+ usersa_id = XfrmUsersaId((PaddedAddress(daddr), spi, family, proto))
+ flags = netlink.NLM_F_REQUEST | netlink.NLM_F_ACK
+ self._SendNlRequest(XFRM_MSG_DELSA, usersa_id.Pack(), flags)
+
+ def DumpSaInfo(self):
+ return self._Dump(XFRM_MSG_GETSA, None, XfrmUsersaInfo, "")
+
+ def FindSaInfo(self, spi):
+ sainfo = [sa for sa, attrs in self.DumpSaInfo() if sa.id.spi == spi]
+ return sainfo[0] if sainfo else None
+
+
+if __name__ == "__main__":
+ x = Xfrm()
+ print x.DumpSaInfo()
diff --git a/net/test/xfrm_test.py b/net/test/xfrm_test.py
new file mode 100755
index 0000000..5700ad8
--- /dev/null
+++ b/net/test/xfrm_test.py
@@ -0,0 +1,321 @@
+#!/usr/bin/python
+#
+# Copyright 2017 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.
+
+# pylint: disable=g-bad-todo,g-bad-file-header,wildcard-import
+from errno import * # pylint: disable=wildcard-import
+import os
+import random
+import re
+from scapy import all as scapy
+from socket import * # pylint: disable=wildcard-import
+import struct
+import subprocess
+import time
+import unittest
+
+import multinetwork_base
+import net_test
+import netlink
+import packets
+import xfrm
+
+XFRM_ADDR_ANY = 16 * "\x00"
+LOOPBACK = 15 * "\x00" + "\x01"
+ENCRYPTED_PAYLOAD = ("b1c74998efd6326faebe2061f00f2c750e90e76001664a80c287b150"
+ "59e74bf949769cc6af71e51b539e7de3a2a14cb05a231b969e035174"
+ "d98c5aa0cef1937db98889ec0d08fa408fecf616")
+ENCRYPTION_KEY = ("308146eb3bd84b044573d60f5a5fd159"
+ "57c7d4fe567a2120f35bae0f9869ec22".decode("hex"))
+AUTH_TRUNC_KEY = "af442892cdcd0ef650e9c299f9a8436a".decode("hex")
+
+TEST_ADDR1 = "2001:4860:4860::8888"
+TEST_ADDR2 = "2001:4860:4860::8844"
+
+TEST_SPI = 0x1234
+
+ALL_ALGORITHMS = 0xffffffff
+ALGO_CBC_AES_256 = xfrm.XfrmAlgo(("cbc(aes)", 256))
+ALGO_HMAC_SHA1 = xfrm.XfrmAlgoAuth(("hmac(sha1)", 128, 96))
+
+class XfrmTest(multinetwork_base.MultiNetworkBaseTest):
+
+ @classmethod
+ def setUpClass(cls):
+ super(XfrmTest, cls).setUpClass()
+ cls.xfrm = xfrm.Xfrm()
+
+ def setUp(self):
+ # TODO: delete this when we're more diligent about deleting our SAs.
+ super(XfrmTest, self).setUp()
+ subprocess.call("ip xfrm state flush".split())
+
+ def expectIPv6EspPacketOn(self, netid, spi, seq, length):
+ packets = self.ReadAllPacketsOn(netid)
+ self.assertEquals(1, len(packets))
+ packet = packets[0]
+ self.assertEquals(IPPROTO_ESP, packet.nh)
+ spi_seq = struct.pack("!II", spi, seq)
+ self.assertEquals(spi_seq, str(packet.payload)[:len(spi_seq)])
+ self.assertEquals(length, len(packet.payload))
+
+ def assertIsUdpEncapEsp(self, packet, spi, seq, length):
+ self.assertEquals(IPPROTO_UDP, packet.proto)
+ self.assertEquals(4500, packet.dport)
+ # Skip UDP header. TODO: isn't there a better way to do this?
+ payload = str(packet.payload)[8:]
+ self.assertEquals(length, len(payload))
+ spi_seq = struct.pack("!II", ntohl(spi), seq)
+ self.assertEquals(spi_seq, str(payload)[:len(spi_seq)])
+
+ def testAddSa(self):
+ self.xfrm.AddMinimalSaInfo("::", TEST_ADDR1, htonl(TEST_SPI), IPPROTO_ESP,
+ xfrm.XFRM_MODE_TRANSPORT, 3320,
+ ALGO_CBC_AES_256, ENCRYPTION_KEY,
+ ALGO_HMAC_SHA1, AUTH_TRUNC_KEY, None)
+ expected = (
+ "src :: dst 2001:4860:4860::8888\n"
+ "\tproto esp spi 0x00001234 reqid 3320 mode transport\n"
+ "\treplay-window 4 \n"
+ "\tauth-trunc hmac(sha1) 0x%s 96\n"
+ "\tenc cbc(aes) 0x%s\n"
+ "\tsel src ::/0 dst ::/0 \n" % (
+ AUTH_TRUNC_KEY.encode("hex"), ENCRYPTION_KEY.encode("hex")))
+
+ actual = subprocess.check_output("ip xfrm state".split())
+ try:
+ self.assertMultiLineEqual(expected, actual)
+ finally:
+ self.xfrm.DeleteSaInfo(TEST_ADDR1, htonl(TEST_SPI), IPPROTO_ESP)
+
+
+ @unittest.skipUnless(net_test.LINUX_VERSION < (4, 4, 0), "regression")
+ def testSocketPolicy(self):
+ # Open an IPv6 UDP socket and connect it.
+ s = socket(AF_INET6, SOCK_DGRAM, 0)
+ netid = random.choice(self.NETIDS)
+ self.SelectInterface(s, netid, "mark")
+ s.connect((TEST_ADDR1, 53))
+ saddr, sport = s.getsockname()[:2]
+ daddr, dport = s.getpeername()[:2]
+
+ # Create a selector that matches all UDP packets. It's not actually used to
+ # select traffic, that will be done by the socket policy, which selects the
+ # SA entry (i.e., xfrm state) via the SPI and reqid.
+ sel = xfrm.XfrmSelector((XFRM_ADDR_ANY, XFRM_ADDR_ANY, 0, 0, 0, 0,
+ AF_INET6, 0, 0, IPPROTO_UDP, 0, 0))
+
+ # Create a user policy that specifies that all outbound packets matching the
+ # (essentially no-op) selector should be encrypted.
+ info = xfrm.XfrmUserpolicyInfo((sel,
+ xfrm.NO_LIFETIME_CFG, xfrm.NO_LIFETIME_CUR,
+ 100, 0,
+ xfrm.XFRM_POLICY_OUT,
+ xfrm.XFRM_POLICY_ALLOW,
+ xfrm.XFRM_POLICY_LOCALOK,
+ xfrm.XFRM_SHARE_UNIQUE))
+
+ # Create a template that specifies the SPI and the protocol.
+ xfrmid = xfrm.XfrmId((XFRM_ADDR_ANY, htonl(TEST_SPI), IPPROTO_ESP))
+ tmpl = xfrm.XfrmUserTmpl((xfrmid, AF_INET6, XFRM_ADDR_ANY, 0,
+ xfrm.XFRM_MODE_TRANSPORT, xfrm.XFRM_SHARE_UNIQUE,
+ 0, # require
+ ALL_ALGORITHMS, # auth algos
+ ALL_ALGORITHMS, # encryption algos
+ ALL_ALGORITHMS)) # compression algos
+
+ # Set the policy and template on our socket.
+ data = info.Pack() + tmpl.Pack()
+ s.setsockopt(IPPROTO_IPV6, xfrm.IPV6_XFRM_POLICY, data)
+
+ # Because the policy has level set to "require" (the default), attempting
+ # to send a packet results in an error, because there is no SA that
+ # matches the socket policy we set.
+ self.assertRaisesErrno(
+ EAGAIN,
+ s.sendto, net_test.UDP_PAYLOAD, (TEST_ADDR1, 53))
+
+ # 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
+ # the tunnel destination).
+ reqid = 0
+ self.xfrm.AddMinimalSaInfo("::", TEST_ADDR1, htonl(TEST_SPI), IPPROTO_ESP,
+ xfrm.XFRM_MODE_TRANSPORT, reqid,
+ ALGO_CBC_AES_256, ENCRYPTION_KEY,
+ ALGO_HMAC_SHA1, AUTH_TRUNC_KEY, None)
+
+ s.sendto(net_test.UDP_PAYLOAD, (TEST_ADDR1, 53))
+ self.expectIPv6EspPacketOn(netid, TEST_SPI, 1, 84)
+
+ # Sending to another destination doesn't work: again, no matching SA.
+ self.assertRaisesErrno(
+ EAGAIN,
+ s.sendto, net_test.UDP_PAYLOAD, (TEST_ADDR2, 53))
+
+ # Sending on another socket without the policy applied results in an
+ # unencrypted packet going out.
+ s2 = socket(AF_INET6, SOCK_DGRAM, 0)
+ self.SelectInterface(s2, netid, "mark")
+ s2.sendto(net_test.UDP_PAYLOAD, (TEST_ADDR1, 53))
+ packets = self.ReadAllPacketsOn(netid)
+ self.assertEquals(1, len(packets))
+ packet = packets[0]
+ self.assertEquals(IPPROTO_UDP, packet.nh)
+
+ # Deleting the SA causes the first socket to return errors again.
+ self.xfrm.DeleteSaInfo(TEST_ADDR1, htonl(TEST_SPI), IPPROTO_ESP)
+ self.assertRaisesErrno(
+ EAGAIN,
+ s.sendto, net_test.UDP_PAYLOAD, (TEST_ADDR1, 53))
+
+
+ def testUdpEncapWithSocketPolicy(self):
+ # TODO: test IPv6 instead of IPv4.
+ netid = random.choice(self.NETIDS)
+ myaddr = self.MyAddress(4, netid)
+ remoteaddr = self.GetRemoteAddress(4)
+
+ # Reserve a port on which to receive UDP encapsulated packets. Sending
+ # packets works without this (and potentially can send packets with a source
+ # port belonging to another application), but receiving requires the port to
+ # be bound and the encapsulation socket option enabled.
+ encap_socket = net_test.Socket(AF_INET, SOCK_DGRAM, 0)
+ encap_socket.bind((myaddr, 0))
+ encap_port = encap_socket.getsockname()[1]
+ encap_socket.setsockopt(IPPROTO_UDP, xfrm.UDP_ENCAP,
+ xfrm.UDP_ENCAP_ESPINUDP)
+
+ # Open a socket to send traffic.
+ s = socket(AF_INET, SOCK_DGRAM, 0)
+ self.SelectInterface(s, netid, "mark")
+ s.connect((remoteaddr, 53))
+
+ # Create a UDP encap policy and template inbound and outbound and apply
+ # them to s.
+ sel = xfrm.XfrmSelector((XFRM_ADDR_ANY, XFRM_ADDR_ANY, 0, 0, 0, 0,
+ AF_INET, 0, 0, IPPROTO_UDP, 0, 0))
+
+ # Use the same SPI both inbound and outbound because this lets us receive
+ # encrypted packets by simply replaying the packets the kernel sends.
+ in_reqid = 123
+ in_spi = htonl(TEST_SPI)
+ out_reqid = 456
+ out_spi = htonl(TEST_SPI)
+
+ # Start with the outbound policy.
+ # TODO: what happens without XFRM_SHARE_UNIQUE?
+ info = xfrm.XfrmUserpolicyInfo((sel,
+ xfrm.NO_LIFETIME_CFG, xfrm.NO_LIFETIME_CUR,
+ 100, 0,
+ xfrm.XFRM_POLICY_OUT,
+ xfrm.XFRM_POLICY_ALLOW,
+ xfrm.XFRM_POLICY_LOCALOK,
+ xfrm.XFRM_SHARE_UNIQUE))
+ xfrmid = xfrm.XfrmId((XFRM_ADDR_ANY, out_spi, IPPROTO_ESP))
+ usertmpl = xfrm.XfrmUserTmpl((xfrmid, AF_INET, XFRM_ADDR_ANY, out_reqid,
+ xfrm.XFRM_MODE_TRANSPORT, xfrm.XFRM_SHARE_UNIQUE,
+ 0, # require
+ ALL_ALGORITHMS, # auth algos
+ ALL_ALGORITHMS, # encryption algos
+ ALL_ALGORITHMS)) # compression algos
+
+ data = info.Pack() + usertmpl.Pack()
+ s.setsockopt(IPPROTO_IP, xfrm.IP_XFRM_POLICY, data)
+
+ # Uncomment for debugging.
+ # subprocess.call("ip xfrm policy".split())
+
+ # Create inbound and outbound SAs that specify UDP encapsulation.
+ encaptmpl = xfrm.XfrmEncapTmpl((xfrm.UDP_ENCAP_ESPINUDP, htons(encap_port),
+ htons(4500), 16 * "\x00"))
+ self.xfrm.AddMinimalSaInfo(myaddr, remoteaddr, out_spi, IPPROTO_ESP,
+ xfrm.XFRM_MODE_TRANSPORT, out_reqid,
+ ALGO_CBC_AES_256, ENCRYPTION_KEY,
+ ALGO_HMAC_SHA1, AUTH_TRUNC_KEY, encaptmpl)
+
+ # Add an encap template that's the mirror of the outbound one.
+ encaptmpl.sport, encaptmpl.dport = encaptmpl.dport, encaptmpl.sport
+ self.xfrm.AddMinimalSaInfo(remoteaddr, myaddr, in_spi, IPPROTO_ESP,
+ xfrm.XFRM_MODE_TRANSPORT, in_reqid,
+ ALGO_CBC_AES_256, ENCRYPTION_KEY,
+ ALGO_HMAC_SHA1, AUTH_TRUNC_KEY, encaptmpl)
+
+ # Uncomment for debugging.
+ # subprocess.call("ip xfrm state".split())
+
+ # Now send a packet.
+ s.sendto("foo", (remoteaddr, 53))
+ srcport = s.getsockname()[1]
+ # s.send("foo") # TODO: WHY DOES THIS NOT WORK?
+
+ # Expect to see an UDP encapsulated packet.
+ packets = self.ReadAllPacketsOn(netid)
+ self.assertEquals(1, len(packets))
+ packet = packets[0]
+ self.assertIsUdpEncapEsp(packet, out_spi, 1, 52)
+
+ # Now test the receive path. Because we don't know how to decrypt packets,
+ # we just play back the encrypted packet that kernel sent earlier. We swap
+ # the addresses in the IP header to make the packet look like it's bound for
+ # us, but we can't do that for the port numbers because the UDP header is
+ # part of the integrity protected payload, which we can only replay as is.
+ # So the source and destination ports are swapped and the packet appears to
+ # be sent from srcport to port 53. Open another socket on that port, and
+ # apply the inbound policy to it.
+ twisted_socket = socket(AF_INET, SOCK_DGRAM, 0)
+ net_test.SetSocketTimeout(twisted_socket, 100)
+ twisted_socket.bind(("0.0.0.0", 53))
+
+ # TODO: why does this work even without the per-socket policy applied? The
+ # received packet obviously matches an SA, but don't inbound packets need to
+ # match a policy as well?
+ info.dir = xfrm.XFRM_POLICY_IN
+ xfrmid.spi = in_spi
+ usertmpl.reqid = in_reqid
+ data = info.Pack() + usertmpl.Pack()
+ twisted_socket.setsockopt(IPPROTO_IP, xfrm.IP_XFRM_POLICY, data)
+
+ # Save the payload of the packet so we can replay it back to ourselves.
+ payload = str(packet.payload)[8:]
+ spi_seq = struct.pack("!II", ntohl(in_spi), 1)
+ payload = spi_seq + payload[len(spi_seq):]
+
+ # Tamper with the packet and check that it's dropped and counted as invalid.
+ sainfo = self.xfrm.FindSaInfo(in_spi)
+ self.assertEquals(0, sainfo.stats.integrity_failed)
+ broken = payload[:25] + chr((ord(payload[25]) + 1) % 256) + payload[26:]
+ incoming = (scapy.IP(src=remoteaddr, dst=myaddr) /
+ scapy.UDP(sport=4500, dport=encap_port) / broken)
+ self.ReceivePacketOn(netid, incoming)
+ sainfo = self.xfrm.FindSaInfo(in_spi)
+ self.assertEquals(1, sainfo.stats.integrity_failed)
+
+ # Now play back the valid packet and check that we receive it.
+ incoming = (scapy.IP(src=remoteaddr, dst=myaddr) /
+ scapy.UDP(sport=4500, dport=encap_port) / payload)
+ self.ReceivePacketOn(netid, incoming)
+ data, src = twisted_socket.recvfrom(4096)
+ self.assertEquals("foo", data)
+ self.assertEquals((remoteaddr, srcport), src)
+
+ # Check that unencrypted packets are not received.
+ unencrypted = (scapy.IP(src=remoteaddr, dst=myaddr) /
+ scapy.UDP(sport=srcport, dport=53) / "foo")
+ self.assertRaisesErrno(EAGAIN, twisted_socket.recv, 4096)
+
+
+if __name__ == "__main__":
+ unittest.main()