diff options
author | Lorenzo Colitti <lorenzo@google.com> | 2017-02-23 09:04:07 +0000 |
---|---|---|
committer | android-build-merger <android-build-merger@google.com> | 2017-02-23 09:04:07 +0000 |
commit | 5ab9017646bed7d904152753053629e5b7c52574 (patch) | |
tree | daa6d94fb4f941c691d8fb734e5639848a85022d | |
parent | d9193c718c6f96662e780aae5a526286609e291e (diff) | |
parent | 2607aafb5a2fbbe7ab5eb2ca9d2d232b8817dd85 (diff) | |
download | tests-5ab9017646bed7d904152753053629e5b7c52574.tar.gz |
Initial tests for xfrm netlink interface and socket policies. am: cbcfcf0835 am: d571640957
am: 2607aafb5a
Change-Id: I72137c3acc996695f48c101a1539cf5d58302b60
-rwxr-xr-x | net/test/run_net_test.sh | 11 | ||||
-rwxr-xr-x | net/test/xfrm.py | 288 | ||||
-rwxr-xr-x | net/test/xfrm_test.py | 321 |
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() |