diff options
author | Lorenzo Colitti <lorenzo@google.com> | 2017-05-10 21:43:54 +0900 |
---|---|---|
committer | Lorenzo Colitti <lorenzo@google.com> | 2017-05-11 17:37:44 +0900 |
commit | b1db0fad158c83a20a3a7ad3edc6cc744827a7d9 (patch) | |
tree | 555e2c425785fa198c2679025ea189eb6cf99eaf | |
parent | 2bff1d53542906a257a133366337a512c4408979 (diff) | |
download | tests-b1db0fad158c83a20a3a7ad3edc6cc744827a7d9.tar.gz |
Add code to use the PF_KEY interface.
Test: all_tests.sh passes on common and device kernels
Bug: 34114242
Change-Id: I353a14aa816531718854311a0e1868ff3d82bce5
-rwxr-xr-x | net/test/pf_key.py | 327 | ||||
-rwxr-xr-x | net/test/pf_key_test.py | 98 | ||||
-rwxr-xr-x | net/test/run_net_test.sh | 2 |
3 files changed, 426 insertions, 1 deletions
diff --git a/net/test/pf_key.py b/net/test/pf_key.py new file mode 100755 index 0000000..174ceb7 --- /dev/null +++ b/net/test/pf_key.py @@ -0,0 +1,327 @@ +#!/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. + +"""Partial implementation of the PFKEYv2 interface.""" + +# pylint: disable=g-bad-todo,bad-whitespace + +import os +from socket import * # pylint: disable=wildcard-import +import sys + +import cstruct +import net_test + + +# AF_KEY socket type. See include/linux/socket.h. +AF_KEY = 15 + +# PFKEYv2 constants. See include/uapi/linux/pfkeyv2.h. +PF_KEY_V2 = 2 + +# IPsec constants. See include/uapi/linux/ipsec.h. +IPSEC_MODE_ANY = 0 +IPSEC_MODE_TRANSPORT = 1 +IPSEC_MODE_TUNNEL = 2 +IPSEC_MODE_BEET = 3 + +# Operation types. +SADB_ADD = 3 +SADB_DELETE = 4 +SADB_DUMP = 10 + +# SA types. +SADB_TYPE_UNSPEC = 0 +SADB_TYPE_AH = 2 +SADB_TYPE_ESP = 3 + +# SA states. +SADB_SASTATE_LARVAL = 0 +SADB_SASTATE_MATURE = 1 +SADB_SASTATE_DYING = 2 +SADB_SASTATE_DEAD = 3 + +# Authentication algorithms. +SADB_AALG_NONE = 0 +SADB_AALG_MD5HMAC = 2 +SADB_AALG_SHA1HMAC = 3 +SADB_X_AALG_SHA2_256HMAC = 5 +SADB_X_AALG_SHA2_384HMAC = 6 +SADB_X_AALG_SHA2_512HMAC = 7 +SADB_X_AALG_RIPEMD160HMAC = 8 +SADB_X_AALG_AES_XCBC_MAC = 9 +SADB_X_AALG_NULL = 251 + +# Encryption algorithms. +SADB_EALG_NONE = 0 +SADB_EALG_DESCBC = 2 +SADB_EALG_3DESCBC = 3 +SADB_X_EALG_CASTCBC = 6 +SADB_X_EALG_BLOWFISHCBC = 7 +SADB_EALG_NULL = 11 +SADB_X_EALG_AESCBC = 12 +SADB_X_EALG_AESCTR = 13 +SADB_X_EALG_AES_CCM_ICV8 = 14 +SADB_X_EALG_AES_CCM_ICV12 = 15 +SADB_X_EALG_AES_CCM_ICV16 = 16 +SADB_X_EALG_AES_GCM_ICV8 = 18 +SADB_X_EALG_AES_GCM_ICV12 = 19 +SADB_X_EALG_AES_GCM_ICV16 = 20 +SADB_X_EALG_CAMELLIACBC = 22 +SADB_X_EALG_NULL_AES_GMAC = 23 +SADB_X_EALG_SERPENTCBC = 252 +SADB_X_EALG_TWOFISHCBC = 253 + +# Extension Header values. +SADB_EXT_RESERVED = 0 +SADB_EXT_SA = 1 +SADB_EXT_LIFETIME_CURRENT = 2 +SADB_EXT_LIFETIME_HARD = 3 +SADB_EXT_LIFETIME_SOFT = 4 +SADB_EXT_ADDRESS_SRC = 5 +SADB_EXT_ADDRESS_DST = 6 +SADB_EXT_ADDRESS_PROXY = 7 +SADB_EXT_KEY_AUTH = 8 +SADB_EXT_KEY_ENCRYPT = 9 +SADB_EXT_IDENTITY_SRC = 10 +SADB_EXT_IDENTITY_DST = 11 +SADB_EXT_SENSITIVITY = 12 +SADB_EXT_PROPOSAL = 13 +SADB_EXT_SUPPORTED_AUTH = 14 +SADB_EXT_SUPPORTED_ENCRYPT = 15 +SADB_EXT_SPIRANGE = 16 +SADB_X_EXT_KMPRIVATE = 17 +SADB_X_EXT_POLICY = 18 +SADB_X_EXT_SA2 = 19 +SADB_X_EXT_NAT_T_TYPE = 20 +SADB_X_EXT_NAT_T_SPORT = 21 +SADB_X_EXT_NAT_T_DPORT = 22 +SADB_X_EXT_NAT_T_OA = 23 +SADB_X_EXT_SEC_CTX = 24 +SADB_X_EXT_KMADDRESS = 25 +SADB_X_EXT_FILTER = 26 + +# Data structure formats. +# These aren't constants, they're classes. So, pylint: disable=invalid-name +SadbMsg = cstruct.Struct( + "SadbMsg", "=BBBBHHII", "version type errno satype len reserved seq pid") + +# Fake struct containing the common beginning of all extension structs. +SadbExt = cstruct.Struct("SadbExt", "=HH", "len exttype") + +SadbSa = cstruct.Struct( + "SadbSa", "=IBBBBI", "spi replay state auth encrypt flags") + +SadbLifetime = cstruct.Struct( + "SadbLifetime", "=IQQQ", "allocations bytes addtime usetime") + +SadbAddress = cstruct.Struct("SadbAddress", "=BB2x", "proto prefixlen") + +SadbKey = cstruct.Struct("SadbKey", "=H2x", "bits") + +SadbXSa2 = cstruct.Struct("SadbXSa2", "=B3xII", "mode sequence reqid") + +SadbXNatTType = cstruct.Struct("SadbXNatTType", "=B3x", "type") + +SadbXNatTPort = cstruct.Struct("SadbXNatTPort", "!H2x", "port") + + +def _GetConstantName(value, prefix): + """Translates a number to a constant of the same value in this file.""" + thismodule = sys.modules[__name__] + # Match shorter constant names first. This allows us to match SADB_DUMP and + # instead of, say, SADB_EXT_LIFETIME_HARD if we pass in a prefix of "SADB_" + # and a value of 3, and match SADB_EXT_LIFETIME_HARD just by specifying + # a longer prefix. + for name in sorted(dir(thismodule), key=len): + if (name.startswith(prefix) and + name.isupper() and getattr(thismodule, name) == value): + return name + return value + + +def _GetMultiConstantName(value, prefixes): + for prefix in prefixes: + name = _GetConstantName(value, prefix) + try: + int(name) + continue + except ValueError: + return name + + +# Converts extension blobs to a (name, struct, attrs) tuple. +def ParseExtension(exttype, data): + struct_type = None + if exttype == SADB_EXT_SA: + struct_type = SadbSa + elif exttype in [SADB_EXT_LIFETIME_CURRENT, SADB_EXT_LIFETIME_HARD, + SADB_EXT_LIFETIME_SOFT]: + struct_type = SadbLifetime + elif exttype in [SADB_EXT_ADDRESS_SRC, SADB_EXT_ADDRESS_DST, + SADB_EXT_ADDRESS_PROXY]: + struct_type = SadbAddress + elif exttype in [SADB_EXT_KEY_AUTH, SADB_EXT_KEY_ENCRYPT]: + struct_type = SadbKey + elif exttype == SADB_X_EXT_SA2: + struct_type = SadbXSa2 + elif exttype == SADB_X_EXT_NAT_T_TYPE: + struct_type = SadbXNatTType + elif exttype in [SADB_X_EXT_NAT_T_SPORT, SADB_X_EXT_NAT_T_DPORT]: + struct_type = SadbXNatTPort + + if struct_type: + ext, attrs = cstruct.Read(data, struct_type) + else: + ext, attrs, = data, "" + + return exttype, ext, attrs + +class PfKey(object): + + """PF_KEY interface to kernel IPsec implementation.""" + + def __init__(self): + self.sock = socket(AF_KEY, SOCK_RAW, PF_KEY_V2) + net_test.SetNonBlocking(self.sock) + self.seq = 0 + + def Recv(self): + reply = self.sock.recv(4096) + msg = SadbMsg(reply) + # print "RECV:", self.DecodeSadbMsg(msg) + if msg.errno != 0: + raise OSError(msg.errno, os.strerror(msg.errno)) + return reply + + def SendAndRecv(self, msg, extensions): + self.seq += 1 + msg.seq = self.seq + msg.pid = os.getpid() + msg.len = (len(SadbMsg) + len(extensions)) / 8 + self.sock.send(msg.Pack() + extensions) + # print "SEND:", self.DecodeSadbMsg(msg) + return self.Recv() + + def PackPfKeyExtensions(self, extlist): + extensions = "" + for exttype, extstruct, attrs in extlist: + extdata = extstruct.Pack() + ext = SadbExt(((len(extdata) + len(SadbExt) + len(attrs)) / 8, exttype)) + extensions += ext.Pack() + extdata + attrs + return extensions + + def MakeSadbMsg(self, msgtype, satype): + # errno is 0. seq, pid and len are filled in by SendAndRecv(). + return SadbMsg((PF_KEY_V2, msgtype, 0, satype, 0, 0, 0, 0)) + + def MakeSadbExtAddr(self, exttype, addr): + prefixlen = {AF_INET: 32, AF_INET6: 128}[addr.family] + packed = addr.Pack() + padbytes = (len(SadbExt) + len(SadbAddress) + len(packed)) % 8 + packed += "\x00" * padbytes + return (exttype, SadbAddress((0, prefixlen)), packed) + + def AddSa(self, src, dst, spi, satype, mode, reqid, encryption, + encryption_key, auth, auth_key): + """Adds a security association.""" + msg = self.MakeSadbMsg(SADB_ADD, satype) + replay = 4 + extlist = [ + (SADB_EXT_SA, SadbSa((spi, replay, SADB_SASTATE_MATURE, + auth, encryption, 0)), ""), + self.MakeSadbExtAddr(SADB_EXT_ADDRESS_SRC, src), + self.MakeSadbExtAddr(SADB_EXT_ADDRESS_DST, dst), + (SADB_X_EXT_SA2, SadbXSa2((mode, 0, reqid)), ""), + (SADB_EXT_KEY_AUTH, SadbKey((len(auth_key) * 8,)), auth_key), + (SADB_EXT_KEY_ENCRYPT, SadbKey((len(encryption_key) * 8,)), + encryption_key) + ] + self.SendAndRecv(msg, self.PackPfKeyExtensions(extlist)) + + def DelSa(self, src, dst, spi, satype): + """Deletes a security association.""" + msg = self.MakeSadbMsg(SADB_DELETE, satype) + extlist = [ + (SADB_EXT_SA, SadbSa((spi, 4, SADB_SASTATE_MATURE, 0, 0, 0)), ""), + self.MakeSadbExtAddr(SADB_EXT_ADDRESS_SRC, src), + self.MakeSadbExtAddr(SADB_EXT_ADDRESS_DST, dst), + ] + self.SendAndRecv(msg, self.PackPfKeyExtensions(extlist)) + + @staticmethod + def DecodeSadbMsg(msg): + msgtype = _GetConstantName(msg.type, "SADB_") + satype = _GetConstantName(msg.satype, "SADB_TYPE_") + return ("SadbMsg(version=%d, type=%s, errno=%d, satype=%s, " + "len=%d, reserved=%d, seq=%d, pid=%d)" % ( + msg.version, msgtype, msg.errno, satype, msg.len, + msg.reserved, msg.seq, msg.pid)) + + @staticmethod + def DecodeSadbSa(sa): + state = _GetConstantName(sa.state, "SADB_SASTATE_") + auth = _GetMultiConstantName(sa.auth, ["SADB_AALG_", "SADB_X_AALG"]) + encrypt = _GetMultiConstantName(sa.encrypt, ["SADB_EALG_", + "SADB_X_EALG_"]) + return ("SadbSa(spi=%x, replay=%d, state=%s, " + "auth=%s, encrypt=%s, flags=%x)" % ( + sa.spi, sa.replay, state, auth, encrypt, sa.flags)) + + @staticmethod + def ExtensionsLength(msg, struct_type): + return (msg.len * 8) - len(struct_type) + + @staticmethod + def ParseExtensions(data): + """Parses the extensions in a SADB message.""" + extensions = [] + while data: + ext, data = cstruct.Read(data, SadbExt) + datalen = PfKey.ExtensionsLength(ext, SadbExt) + extdata, data = data[:datalen], data[datalen:] + extensions.append(ParseExtension(ext.exttype, extdata)) + return extensions + + def DumpSaInfo(self): + """Returns a list of (SadbMsg, [(extension, attr), ...], ...) tuples.""" + dump = [] + msg = self.MakeSadbMsg(SADB_DUMP, SADB_TYPE_UNSPEC) + received = self.SendAndRecv(msg, "") + while received: + msg, data = cstruct.Read(received, SadbMsg) + extlen = self.ExtensionsLength(msg, SadbMsg) + extensions, data = data[:extlen], data[extlen:] + dump.append((msg, self.ParseExtensions(extensions))) + if msg.seq == 0: # End of dump. + break + received = self.Recv() + return dump + + def PrintSaInfos(self, dump): + for msg, extensions in dump: + print self.DecodeSadbMsg(msg) + for exttype, ext, attrs in extensions: + exttype = _GetMultiConstantName(exttype, ["SADB_EXT", "SADB_X_EXT"]) + if exttype == SADB_EXT_SA: + print " ", exttype, self.DecodeSadbSa(ext), attrs.encode("hex") + print " ", exttype, ext, attrs.encode("hex") + print + + +if __name__ == "__main__": + p = PfKey() + p.DumpSaInfo() diff --git a/net/test/pf_key_test.py b/net/test/pf_key_test.py new file mode 100755 index 0000000..52e9ee9 --- /dev/null +++ b/net/test/pf_key_test.py @@ -0,0 +1,98 @@ +#!/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 socket import * +import unittest + +import csocket +import pf_key +import xfrm + +ENCRYPTION_KEY = ("308146eb3bd84b044573d60f5a5fd159" + "57c7d4fe567a2120f35bae0f9869ec22".decode("hex")) +AUTH_KEY = "af442892cdcd0ef650e9c299f9a8436a".decode("hex") + + +class PfKeyTest(unittest.TestCase): + + def setUp(self): + self.pf_key = pf_key.PfKey() + self.xfrm = xfrm.Xfrm() + + def testAddDelSa(self): + src4 = csocket.Sockaddr(("192.0.2.1", 0)) + dst4 = csocket.Sockaddr(("192.0.2.2", 1)) + self.pf_key.AddSa(src4, dst4, 0xdeadbeef, pf_key.SADB_TYPE_ESP, + pf_key.IPSEC_MODE_TRANSPORT, 54321, + pf_key.SADB_X_EALG_AESCBC, ENCRYPTION_KEY, + pf_key.SADB_X_AALG_SHA2_256HMAC, ENCRYPTION_KEY) + + src6 = csocket.Sockaddr(("2001:db8::1", 0)) + dst6 = csocket.Sockaddr(("2001:db8::2", 0)) + self.pf_key.AddSa(src6, dst6, 0xbeefdead, pf_key.SADB_TYPE_ESP, + pf_key.IPSEC_MODE_TRANSPORT, 12345, + pf_key.SADB_X_EALG_AESCBC, ENCRYPTION_KEY, + pf_key.SADB_X_AALG_SHA2_256HMAC, ENCRYPTION_KEY) + + sainfos = self.xfrm.DumpSaInfo() + self.assertEquals(2, len(sainfos)) + state4, attrs4 = [(s, a) for s, a in sainfos if s.family == AF_INET][0] + state6, attrs6 = [(s, a) for s, a in sainfos if s.family == AF_INET6][0] + + pfkey_sainfos = self.pf_key.DumpSaInfo() + self.assertEquals(2, len(pfkey_sainfos)) + self.assertTrue(all(msg.satype == pf_key.SDB_TYPE_ESP) + for msg, _ in pfkey_sainfos) + + self.assertEquals(xfrm.IPPROTO_ESP, state4.id.proto) + self.assertEquals(xfrm.IPPROTO_ESP, state6.id.proto) + self.assertEquals(54321, state4.reqid) + self.assertEquals(12345, state6.reqid) + self.assertEquals(0xdeadbeef, state4.id.spi) + self.assertEquals(0xbeefdead, state6.id.spi) + + self.assertEquals(xfrm.PaddedAddress("192.0.2.1"), state4.saddr) + self.assertEquals(xfrm.PaddedAddress("192.0.2.2"), state4.id.daddr) + self.assertEquals(xfrm.PaddedAddress("2001:db8::1"), state6.saddr) + self.assertEquals(xfrm.PaddedAddress("2001:db8::2"), state6.id.daddr) + + # The algorithm names are null-terminated, but after that contain garbage. + # Kernel bug? + aes_name = "cbc(aes)\x00" + sha256_name = "hmac(sha256)\x00" + self.assertTrue(attrs4["XFRMA_ALG_CRYPT"].name.startswith(aes_name)) + self.assertTrue(attrs6["XFRMA_ALG_CRYPT"].name.startswith(aes_name)) + self.assertTrue(attrs4["XFRMA_ALG_AUTH"].name.startswith(sha256_name)) + self.assertTrue(attrs6["XFRMA_ALG_AUTH"].name.startswith(sha256_name)) + + self.assertEquals(256, attrs4["XFRMA_ALG_CRYPT"].key_len) + self.assertEquals(256, attrs4["XFRMA_ALG_CRYPT"].key_len) + self.assertEquals(256, attrs6["XFRMA_ALG_AUTH"].key_len) + self.assertEquals(256, attrs6["XFRMA_ALG_AUTH"].key_len) + self.assertEquals(256, attrs6["XFRMA_ALG_AUTH_TRUNC"].key_len) + self.assertEquals(256, attrs6["XFRMA_ALG_AUTH_TRUNC"].key_len) + + # TODO: check truncation lengths, once they are correct. + + self.pf_key.DelSa(src4, dst4, 0xdeadbeef, pf_key.SADB_TYPE_ESP) + self.assertEquals(1, len(self.xfrm.DumpSaInfo())) + self.pf_key.DelSa(src6, dst6, 0xbeefdead, pf_key.SADB_TYPE_ESP) + self.assertEquals(0, len(self.xfrm.DumpSaInfo())) + + +if __name__ == "__main__": + unittest.main() diff --git a/net/test/run_net_test.sh b/net/test/run_net_test.sh index ad25fe3..8d60bea 100755 --- a/net/test/run_net_test.sh +++ b/net/test/run_net_test.sh @@ -21,7 +21,7 @@ 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 XFRM_USER CRYPTO_CBC CRYPTO_CTR" +OPTIONS="$OPTIONS BPF_SYSCALL NET_KEY 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" |