#!/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((htonl(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((htonl(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(" %s %s %s" % (exttype, self.DecodeSadbSa(ext), attrs.encode("hex"))) print(" %s %s %s" % (exttype, ext, attrs.encode("hex"))) print("") if __name__ == "__main__": p = PfKey() p.DumpSaInfo()