822 lines
27 KiB
C++
822 lines
27 KiB
C++
/*
|
|
*
|
|
* Copyright (C) 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.
|
|
*/
|
|
|
|
#include <string>
|
|
#include <vector>
|
|
|
|
#include <ctype.h>
|
|
#include <errno.h>
|
|
#include <fcntl.h>
|
|
#include <getopt.h>
|
|
#include <stdio.h>
|
|
#include <stdlib.h>
|
|
#include <string.h>
|
|
|
|
#define __STDC_FORMAT_MACROS
|
|
#include <inttypes.h>
|
|
|
|
#include <arpa/inet.h>
|
|
#include <netinet/in.h>
|
|
|
|
#include <sys/socket.h>
|
|
#include <sys/stat.h>
|
|
#include <sys/types.h>
|
|
#include <sys/wait.h>
|
|
|
|
#include <linux/in.h>
|
|
#include <linux/netlink.h>
|
|
#include <linux/xfrm.h>
|
|
|
|
#include "android-base/stringprintf.h"
|
|
#include "android-base/strings.h"
|
|
#include "android-base/unique_fd.h"
|
|
#define LOG_TAG "XfrmController"
|
|
#include "NetdConstants.h"
|
|
#include "NetlinkCommands.h"
|
|
#include "ResponseCode.h"
|
|
#include "XfrmController.h"
|
|
#include <cutils/log.h>
|
|
#include <cutils/properties.h>
|
|
#include <logwrap/logwrap.h>
|
|
|
|
#define VDBG 1 // STOPSHIP if true
|
|
|
|
namespace android {
|
|
namespace net {
|
|
|
|
namespace {
|
|
|
|
constexpr uint32_t ALGO_MASK_AUTH_ALL = ~0;
|
|
constexpr uint32_t ALGO_MASK_CRYPT_ALL = ~0;
|
|
|
|
constexpr uint8_t REPLAY_WINDOW_SIZE = 4;
|
|
|
|
constexpr uint32_t RAND_SPI_MIN = 1;
|
|
constexpr uint32_t RAND_SPI_MAX = 0xFFFFFFFE;
|
|
|
|
constexpr uint32_t INVALID_SPI = 0;
|
|
|
|
#define XFRM_MSG_TRANS(x) \
|
|
case x: \
|
|
return #x;
|
|
|
|
const char* xfrmMsgTypeToString(uint16_t msg) {
|
|
switch (msg) {
|
|
XFRM_MSG_TRANS(XFRM_MSG_NEWSA)
|
|
XFRM_MSG_TRANS(XFRM_MSG_DELSA)
|
|
XFRM_MSG_TRANS(XFRM_MSG_GETSA)
|
|
XFRM_MSG_TRANS(XFRM_MSG_NEWPOLICY)
|
|
XFRM_MSG_TRANS(XFRM_MSG_DELPOLICY)
|
|
XFRM_MSG_TRANS(XFRM_MSG_GETPOLICY)
|
|
XFRM_MSG_TRANS(XFRM_MSG_ALLOCSPI)
|
|
XFRM_MSG_TRANS(XFRM_MSG_ACQUIRE)
|
|
XFRM_MSG_TRANS(XFRM_MSG_EXPIRE)
|
|
XFRM_MSG_TRANS(XFRM_MSG_UPDPOLICY)
|
|
XFRM_MSG_TRANS(XFRM_MSG_UPDSA)
|
|
XFRM_MSG_TRANS(XFRM_MSG_POLEXPIRE)
|
|
XFRM_MSG_TRANS(XFRM_MSG_FLUSHSA)
|
|
XFRM_MSG_TRANS(XFRM_MSG_FLUSHPOLICY)
|
|
XFRM_MSG_TRANS(XFRM_MSG_NEWAE)
|
|
XFRM_MSG_TRANS(XFRM_MSG_GETAE)
|
|
XFRM_MSG_TRANS(XFRM_MSG_REPORT)
|
|
XFRM_MSG_TRANS(XFRM_MSG_MIGRATE)
|
|
XFRM_MSG_TRANS(XFRM_MSG_NEWSADINFO)
|
|
XFRM_MSG_TRANS(XFRM_MSG_GETSADINFO)
|
|
XFRM_MSG_TRANS(XFRM_MSG_GETSPDINFO)
|
|
XFRM_MSG_TRANS(XFRM_MSG_NEWSPDINFO)
|
|
XFRM_MSG_TRANS(XFRM_MSG_MAPPING)
|
|
default:
|
|
return "XFRM_MSG UNKNOWN";
|
|
}
|
|
}
|
|
|
|
// actually const but cannot be declared as such for reasons
|
|
uint8_t kPadBytesArray[] = {0, 0, 0};
|
|
void* kPadBytes = static_cast<void*>(kPadBytesArray);
|
|
|
|
#if VDBG
|
|
#define LOG_HEX(__desc16__, __buf__, __len__) \
|
|
do { \
|
|
logHex(__desc16__, __buf__, __len__); \
|
|
} while (0)
|
|
#define LOG_IOV(__iov__, __iov_len__) \
|
|
do { \
|
|
logIov(__iov__, __iov_len__); \
|
|
} while (0)
|
|
|
|
void logHex(const char* desc16, const char* buf, size_t len) {
|
|
char* printBuf = new char[len * 2 + 1 + 26]; // len->ascii, +newline, +prefix strlen
|
|
int offset = 0;
|
|
if (desc16) {
|
|
sprintf(printBuf, "{%-16s}", desc16);
|
|
offset += 18; // prefix string length
|
|
}
|
|
sprintf(printBuf + offset, "[%4.4u]: ", (len > 9999) ? 9999 : (unsigned)len);
|
|
offset += 8;
|
|
|
|
for (uint32_t j = 0; j < (uint32_t)len; j++) {
|
|
sprintf(&printBuf[j * 2 + offset], "%0.2x", buf[j]);
|
|
}
|
|
ALOGD("%s", printBuf);
|
|
delete[] printBuf;
|
|
}
|
|
|
|
void logIov(const iovec* iov, size_t iovLen) {
|
|
for (uint32_t i = 0; i < (uint32_t)iovLen; i++) {
|
|
const iovec* row = &iov[i];
|
|
logHex(0, reinterpret_cast<char*>(row->iov_base), row->iov_len);
|
|
}
|
|
}
|
|
|
|
#else
|
|
#define LOG_HEX(__desc16__, __buf__, __len__)
|
|
#define LOG_IOV(__iov__, __iov_len__)
|
|
#endif
|
|
|
|
class XfrmSocketImpl : public XfrmSocket {
|
|
private:
|
|
static constexpr int NLMSG_DEFAULTSIZE = 8192;
|
|
|
|
union NetlinkResponse {
|
|
nlmsghdr hdr;
|
|
struct _err_ {
|
|
nlmsghdr hdr;
|
|
nlmsgerr err;
|
|
} err;
|
|
|
|
struct _buf_ {
|
|
nlmsghdr hdr;
|
|
char buf[NLMSG_DEFAULTSIZE];
|
|
} buf;
|
|
};
|
|
|
|
public:
|
|
virtual bool open() {
|
|
mSock = openNetlinkSocket(NETLINK_XFRM);
|
|
if (mSock <= 0) {
|
|
ALOGW("Could not get a new socket, line=%d", __LINE__);
|
|
return false;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
static int validateResponse(NetlinkResponse response, size_t len) {
|
|
if (len < sizeof(nlmsghdr)) {
|
|
ALOGW("Invalid response message received over netlink");
|
|
return -EBADMSG;
|
|
}
|
|
|
|
switch (response.hdr.nlmsg_type) {
|
|
case NLMSG_NOOP:
|
|
case NLMSG_DONE:
|
|
return 0;
|
|
case NLMSG_OVERRUN:
|
|
ALOGD("Netlink request overran kernel buffer");
|
|
return -EBADMSG;
|
|
case NLMSG_ERROR:
|
|
if (len < sizeof(NetlinkResponse::_err_)) {
|
|
ALOGD("Netlink message received malformed error response");
|
|
return -EBADMSG;
|
|
}
|
|
return response.err.err.error; // Netlink errors are negative errno.
|
|
case XFRM_MSG_NEWSA:
|
|
break;
|
|
}
|
|
|
|
if (response.hdr.nlmsg_type < XFRM_MSG_BASE /*== NLMSG_MIN_TYPE*/ ||
|
|
response.hdr.nlmsg_type > XFRM_MSG_MAX) {
|
|
ALOGD("Netlink message responded with an out-of-range message ID");
|
|
return -EBADMSG;
|
|
}
|
|
|
|
// TODO Add more message validation here
|
|
return 0;
|
|
}
|
|
|
|
virtual int sendMessage(uint16_t nlMsgType, uint16_t nlMsgFlags, uint16_t nlMsgSeqNum,
|
|
iovec* iov, int iovLen) const {
|
|
nlmsghdr nlMsg = {
|
|
.nlmsg_type = nlMsgType, .nlmsg_flags = nlMsgFlags, .nlmsg_seq = nlMsgSeqNum,
|
|
};
|
|
|
|
iov[0].iov_base = &nlMsg;
|
|
iov[0].iov_len = NLMSG_HDRLEN;
|
|
for (int i = 0; i < iovLen; ++i) {
|
|
nlMsg.nlmsg_len += iov[i].iov_len;
|
|
}
|
|
|
|
ALOGD("Sending Netlink XFRM Message: %s", xfrmMsgTypeToString(nlMsgType));
|
|
if (VDBG)
|
|
LOG_IOV(iov, iovLen);
|
|
|
|
int ret;
|
|
|
|
if (writev(mSock, iov, iovLen) < 0) {
|
|
ALOGE("netlink socket writev failed (%s)", strerror(errno));
|
|
return -errno;
|
|
}
|
|
|
|
NetlinkResponse* response = new NetlinkResponse{};
|
|
|
|
if ((ret = recv(mSock, response, sizeof(*response), 0)) < 0) {
|
|
ALOGE("netlink response contains error (%s)", strerror(errno));
|
|
delete response;
|
|
return -errno;
|
|
}
|
|
|
|
LOG_HEX("netlink msg resp", reinterpret_cast<char*>(response), ret);
|
|
|
|
ret = validateResponse(*response, ret);
|
|
delete response;
|
|
if (ret < 0)
|
|
ALOGE("netlink response contains error (%s)", strerror(-ret));
|
|
return ret;
|
|
}
|
|
};
|
|
|
|
int convertToXfrmAddr(const std::string& strAddr, xfrm_address_t* xfrmAddr) {
|
|
if (strAddr.length() == 0) {
|
|
memset(xfrmAddr, 0, sizeof(*xfrmAddr));
|
|
return AF_UNSPEC;
|
|
}
|
|
|
|
if (inet_pton(AF_INET6, strAddr.c_str(), reinterpret_cast<void*>(xfrmAddr))) {
|
|
return AF_INET6;
|
|
} else if (inet_pton(AF_INET, strAddr.c_str(), reinterpret_cast<void*>(xfrmAddr))) {
|
|
return AF_INET;
|
|
} else {
|
|
return -EAFNOSUPPORT;
|
|
}
|
|
}
|
|
|
|
void fillXfrmNlaHdr(nlattr* hdr, uint16_t type, uint16_t len) {
|
|
hdr->nla_type = type;
|
|
hdr->nla_len = len;
|
|
}
|
|
|
|
void fillXfrmCurLifetimeDefaults(xfrm_lifetime_cur* cur) {
|
|
memset(reinterpret_cast<char*>(cur), 0, sizeof(*cur));
|
|
}
|
|
void fillXfrmLifetimeDefaults(xfrm_lifetime_cfg* cfg) {
|
|
cfg->soft_byte_limit = XFRM_INF;
|
|
cfg->hard_byte_limit = XFRM_INF;
|
|
cfg->soft_packet_limit = XFRM_INF;
|
|
cfg->hard_packet_limit = XFRM_INF;
|
|
}
|
|
|
|
/*
|
|
* Allocate SPIs within an (inclusive) range of min-max.
|
|
* returns 0 (INVALID_SPI) once the entire range has been parsed.
|
|
*/
|
|
class RandomSpi {
|
|
public:
|
|
RandomSpi(int min, int max) : mMin(min) {
|
|
time_t t;
|
|
srand((unsigned int)time(&t));
|
|
// TODO: more random random
|
|
mNext = rand();
|
|
mSize = max - min + 1;
|
|
mCount = mSize;
|
|
}
|
|
|
|
uint32_t next() {
|
|
if (!mCount)
|
|
return 0;
|
|
mCount--;
|
|
return (mNext++ % mSize) + mMin;
|
|
}
|
|
|
|
private:
|
|
uint32_t mNext;
|
|
uint32_t mSize;
|
|
uint32_t mMin;
|
|
uint32_t mCount;
|
|
};
|
|
|
|
} // namespace
|
|
|
|
//
|
|
// Begin XfrmController Impl
|
|
//
|
|
//
|
|
XfrmController::XfrmController(void) {}
|
|
|
|
int XfrmController::ipSecAllocateSpi(int32_t transformId, int32_t direction,
|
|
const std::string& localAddress,
|
|
const std::string& remoteAddress, int32_t inSpi,
|
|
int32_t* outSpi) {
|
|
ALOGD("XfrmController:%s, line=%d", __FUNCTION__, __LINE__);
|
|
ALOGD("transformId=%d", transformId);
|
|
ALOGD("direction=%d", direction);
|
|
ALOGD("localAddress=%s", localAddress.c_str());
|
|
ALOGD("remoteAddress=%s", remoteAddress.c_str());
|
|
ALOGD("inSpi=%0.8x", inSpi);
|
|
|
|
XfrmSaInfo saInfo{};
|
|
int ret;
|
|
|
|
if ((ret = fillXfrmSaId(direction, localAddress, remoteAddress, INVALID_SPI, &saInfo)) < 0) {
|
|
return ret;
|
|
}
|
|
|
|
XfrmSocketImpl sock;
|
|
if (!sock.open()) {
|
|
ALOGD("Sock open failed for XFRM, line=%d", __LINE__);
|
|
return -1; // TODO: return right error; for whatever reason the sock
|
|
// failed to open
|
|
}
|
|
|
|
int minSpi = RAND_SPI_MIN, maxSpi = RAND_SPI_MAX;
|
|
|
|
if (inSpi)
|
|
minSpi = maxSpi = inSpi;
|
|
ret = allocateSpi(saInfo, minSpi, maxSpi, reinterpret_cast<uint32_t*>(outSpi), sock);
|
|
if (ret < 0) {
|
|
ALOGD("Failed to Allocate an SPI, line=%d", __LINE__);
|
|
*outSpi = INVALID_SPI;
|
|
}
|
|
|
|
return ret;
|
|
}
|
|
|
|
int XfrmController::ipSecAddSecurityAssociation(
|
|
int32_t transformId, int32_t mode, int32_t direction, const std::string& localAddress,
|
|
const std::string& remoteAddress, int64_t underlyingNetworkHandle, int32_t spi,
|
|
const std::string& authAlgo, const std::vector<uint8_t>& authKey, int32_t authTruncBits,
|
|
const std::string& cryptAlgo, const std::vector<uint8_t>& cryptKey, int32_t cryptTruncBits,
|
|
int32_t encapType, int32_t encapLocalPort, int32_t encapRemotePort) {
|
|
android::RWLock::AutoWLock lock(mLock);
|
|
|
|
ALOGD("XfrmController::%s, line=%d", __FUNCTION__, __LINE__);
|
|
ALOGD("transformId=%d", transformId);
|
|
ALOGD("mode=%d", mode);
|
|
ALOGD("direction=%d", direction);
|
|
ALOGD("localAddress=%s", localAddress.c_str());
|
|
ALOGD("remoteAddress=%s", remoteAddress.c_str());
|
|
ALOGD("underlyingNetworkHandle=%" PRIx64, underlyingNetworkHandle);
|
|
ALOGD("spi=%0.8x", spi);
|
|
ALOGD("authAlgo=%s", authAlgo.c_str());
|
|
ALOGD("authTruncBits=%d", authTruncBits);
|
|
ALOGD("cryptAlgo=%s", cryptAlgo.c_str());
|
|
ALOGD("cryptTruncBits=%d,", cryptTruncBits);
|
|
ALOGD("encapType=%d", encapType);
|
|
ALOGD("encapLocalPort=%d", encapLocalPort);
|
|
ALOGD("encapRemotePort=%d", encapRemotePort);
|
|
|
|
XfrmSaInfo saInfo{};
|
|
int ret;
|
|
|
|
if ((ret = fillXfrmSaId(direction, localAddress, remoteAddress, spi, &saInfo)) < 0) {
|
|
return ret;
|
|
}
|
|
|
|
saInfo.transformId = transformId;
|
|
|
|
// STOPSHIP : range check the key lengths to prevent puncturing and overflow
|
|
saInfo.auth = XfrmAlgo{
|
|
.name = authAlgo, .key = authKey, .truncLenBits = static_cast<uint16_t>(authTruncBits)};
|
|
|
|
saInfo.crypt = XfrmAlgo{
|
|
.name = cryptAlgo, .key = cryptKey, .truncLenBits = static_cast<uint16_t>(cryptTruncBits)};
|
|
|
|
saInfo.direction = static_cast<XfrmDirection>(direction);
|
|
|
|
switch (static_cast<XfrmMode>(mode)) {
|
|
case XfrmMode::TRANSPORT:
|
|
case XfrmMode::TUNNEL:
|
|
saInfo.mode = static_cast<XfrmMode>(mode);
|
|
break;
|
|
default:
|
|
return -EINVAL;
|
|
}
|
|
|
|
XfrmSocketImpl sock;
|
|
if (!sock.open()) {
|
|
ALOGD("Sock open failed for XFRM, line=%d", __LINE__);
|
|
return -1; // TODO: return right error; for whatever reason the sock
|
|
// failed to open
|
|
}
|
|
|
|
switch (static_cast<XfrmEncapType>(encapType)) {
|
|
case XfrmEncapType::ESPINUDP:
|
|
case XfrmEncapType::ESPINUDP_NON_IKE:
|
|
if (saInfo.addrFamily != AF_INET) {
|
|
return -EAFNOSUPPORT;
|
|
}
|
|
switch (saInfo.direction) {
|
|
case XfrmDirection::IN:
|
|
saInfo.encap.srcPort = encapRemotePort;
|
|
saInfo.encap.dstPort = encapLocalPort;
|
|
break;
|
|
case XfrmDirection::OUT:
|
|
saInfo.encap.srcPort = encapLocalPort;
|
|
saInfo.encap.dstPort = encapRemotePort;
|
|
break;
|
|
default:
|
|
return -EINVAL;
|
|
}
|
|
// fall through
|
|
case XfrmEncapType::NONE:
|
|
saInfo.encap.type = static_cast<XfrmEncapType>(encapType);
|
|
break;
|
|
default:
|
|
return -EINVAL;
|
|
}
|
|
|
|
ret = createTransportModeSecurityAssociation(saInfo, sock);
|
|
if (ret < 0) {
|
|
ALOGD("Failed creating a Security Association, line=%d", __LINE__);
|
|
return ret; // something went wrong creating the SA
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
int XfrmController::ipSecDeleteSecurityAssociation(int32_t transformId, int32_t direction,
|
|
const std::string& localAddress,
|
|
const std::string& remoteAddress, int32_t spi) {
|
|
ALOGD("XfrmController:%s, line=%d", __FUNCTION__, __LINE__);
|
|
ALOGD("transformId=%d", transformId);
|
|
ALOGD("direction=%d", direction);
|
|
ALOGD("localAddress=%s", localAddress.c_str());
|
|
ALOGD("remoteAddress=%s", remoteAddress.c_str());
|
|
ALOGD("spi=%0.8x", spi);
|
|
|
|
XfrmSaId saId;
|
|
int ret;
|
|
|
|
if ((ret = fillXfrmSaId(direction, localAddress, remoteAddress, spi, &saId)) < 0) {
|
|
return ret;
|
|
}
|
|
|
|
XfrmSocketImpl sock;
|
|
if (!sock.open()) {
|
|
ALOGD("Sock open failed for XFRM, line=%d", __LINE__);
|
|
return -1; // TODO: return right error; for whatever reason the sock
|
|
// failed to open
|
|
}
|
|
|
|
ret = deleteSecurityAssociation(saId, sock);
|
|
if (ret < 0) {
|
|
ALOGD("Failed to delete Security Association, line=%d", __LINE__);
|
|
return ret; // something went wrong deleting the SA
|
|
}
|
|
|
|
return ret;
|
|
}
|
|
|
|
int XfrmController::fillXfrmSaId(int32_t direction, const std::string& localAddress,
|
|
const std::string& remoteAddress, int32_t spi, XfrmSaId* xfrmId) {
|
|
xfrm_address_t localXfrmAddr{}, remoteXfrmAddr{};
|
|
|
|
int addrFamilyLocal, addrFamilyRemote;
|
|
addrFamilyRemote = convertToXfrmAddr(remoteAddress, &remoteXfrmAddr);
|
|
addrFamilyLocal = convertToXfrmAddr(localAddress, &localXfrmAddr);
|
|
if (addrFamilyRemote < 0 || addrFamilyLocal < 0) {
|
|
return -EINVAL;
|
|
}
|
|
|
|
if (addrFamilyRemote == AF_UNSPEC ||
|
|
(addrFamilyLocal != AF_UNSPEC && addrFamilyLocal != addrFamilyRemote)) {
|
|
ALOGD("Invalid or Mismatched Address Families, %d != %d, line=%d", addrFamilyLocal,
|
|
addrFamilyRemote, __LINE__);
|
|
return -EINVAL;
|
|
}
|
|
|
|
xfrmId->addrFamily = addrFamilyRemote;
|
|
|
|
xfrmId->spi = htonl(spi);
|
|
|
|
switch (static_cast<XfrmDirection>(direction)) {
|
|
case XfrmDirection::IN:
|
|
xfrmId->dstAddr = localXfrmAddr;
|
|
xfrmId->srcAddr = remoteXfrmAddr;
|
|
break;
|
|
|
|
case XfrmDirection::OUT:
|
|
xfrmId->dstAddr = remoteXfrmAddr;
|
|
xfrmId->srcAddr = localXfrmAddr;
|
|
break;
|
|
|
|
default:
|
|
ALOGD("Invalid XFRM direction, line=%d", __LINE__);
|
|
// Invalid direction for Transport mode transform: time to bail
|
|
return -EINVAL;
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
int XfrmController::ipSecApplyTransportModeTransform(const android::base::unique_fd& socket,
|
|
int32_t transformId, int32_t direction,
|
|
const std::string& localAddress,
|
|
const std::string& remoteAddress,
|
|
int32_t spi) {
|
|
ALOGD("XfrmController::%s, line=%d", __FUNCTION__, __LINE__);
|
|
ALOGD("transformId=%d", transformId);
|
|
ALOGD("direction=%d", direction);
|
|
ALOGD("localAddress=%s", localAddress.c_str());
|
|
ALOGD("remoteAddress=%s", remoteAddress.c_str());
|
|
ALOGD("spi=%0.8x", spi);
|
|
|
|
struct sockaddr_storage saddr;
|
|
|
|
socklen_t len = sizeof(saddr);
|
|
int err;
|
|
int userSocket = socket.get();
|
|
|
|
if ((err = getsockname(userSocket, reinterpret_cast<struct sockaddr*>(&saddr), &len)) < 0) {
|
|
ALOGE("Failed to get socket info in %s", __FUNCTION__);
|
|
return -err;
|
|
}
|
|
|
|
XfrmSaInfo saInfo{};
|
|
saInfo.transformId = transformId;
|
|
saInfo.direction = static_cast<XfrmDirection>(direction);
|
|
saInfo.spi = spi;
|
|
|
|
if ((err = fillXfrmSaId(direction, localAddress, remoteAddress, spi, &saInfo)) < 0) {
|
|
ALOGE("Couldn't build SA ID %s", __FUNCTION__);
|
|
return -err;
|
|
}
|
|
|
|
if (saInfo.addrFamily != saddr.ss_family) {
|
|
ALOGE("Transform address family(%d) differs from socket address "
|
|
"family(%d)!",
|
|
saInfo.addrFamily, saddr.ss_family);
|
|
return -EINVAL;
|
|
}
|
|
|
|
struct {
|
|
xfrm_userpolicy_info info;
|
|
xfrm_user_tmpl tmpl;
|
|
} policy{};
|
|
|
|
fillTransportModeUserSpInfo(saInfo, &policy.info);
|
|
fillUserTemplate(saInfo, &policy.tmpl);
|
|
|
|
LOG_HEX("XfrmUserPolicy", reinterpret_cast<char*>(&policy), sizeof(policy));
|
|
|
|
int sockOpt, sockLayer;
|
|
switch (saInfo.addrFamily) {
|
|
case AF_INET:
|
|
sockOpt = IP_XFRM_POLICY;
|
|
sockLayer = SOL_IP;
|
|
break;
|
|
case AF_INET6:
|
|
sockOpt = IPV6_XFRM_POLICY;
|
|
sockLayer = SOL_IPV6;
|
|
break;
|
|
default:
|
|
return -EAFNOSUPPORT;
|
|
}
|
|
|
|
err = setsockopt(userSocket, sockLayer, sockOpt, &policy, sizeof(policy));
|
|
if (err < 0) {
|
|
err = errno;
|
|
ALOGE("Error setting socket option for XFRM! (%s)", strerror(err));
|
|
}
|
|
|
|
return -err;
|
|
}
|
|
|
|
int XfrmController::ipSecRemoveTransportModeTransform(const android::base::unique_fd& socket) {
|
|
(void)socket;
|
|
return 0;
|
|
}
|
|
|
|
void XfrmController::fillTransportModeSelector(const XfrmSaInfo& record, xfrm_selector* selector) {
|
|
selector->family = record.addrFamily;
|
|
selector->proto = AF_UNSPEC; // TODO: do we need to match the protocol? it's
|
|
// possible via the socket
|
|
selector->ifindex = record.netId; // TODO : still need to sort this out
|
|
}
|
|
|
|
int XfrmController::createTransportModeSecurityAssociation(const XfrmSaInfo& record,
|
|
const XfrmSocket& sock) {
|
|
xfrm_usersa_info usersa{};
|
|
nlattr_algo_crypt crypt{};
|
|
nlattr_algo_auth auth{};
|
|
nlattr_encap_tmpl encap{};
|
|
|
|
enum {
|
|
NLMSG_HDR,
|
|
USERSA,
|
|
USERSA_PAD,
|
|
CRYPT,
|
|
CRYPT_PAD,
|
|
AUTH,
|
|
AUTH_PAD,
|
|
ENCAP,
|
|
ENCAP_PAD,
|
|
iovLen
|
|
};
|
|
|
|
iovec iov[] = {
|
|
{NULL, 0}, // reserved for the eventual addition of a NLMSG_HDR
|
|
{&usersa, 0}, // main usersa_info struct
|
|
{kPadBytes, 0}, // up to NLMSG_ALIGNTO pad bytes of padding
|
|
{&crypt, 0}, // adjust size if crypt algo is present
|
|
{kPadBytes, 0}, // up to NLATTR_ALIGNTO pad bytes
|
|
{&auth, 0}, // adjust size if auth algo is present
|
|
{kPadBytes, 0}, // up to NLATTR_ALIGNTO pad bytes
|
|
{&encap, 0}, // adjust size if auth algo is present
|
|
{kPadBytes, 0}, // up to NLATTR_ALIGNTO pad bytes
|
|
};
|
|
|
|
int len;
|
|
len = iov[USERSA].iov_len = fillUserSaInfo(record, &usersa);
|
|
iov[USERSA_PAD].iov_len = NLMSG_ALIGN(len) - len;
|
|
|
|
len = iov[CRYPT].iov_len = fillNlAttrXfrmAlgoEnc(record.crypt, &crypt);
|
|
iov[CRYPT_PAD].iov_len = NLA_ALIGN(len) - len;
|
|
|
|
len = iov[AUTH].iov_len = fillNlAttrXfrmAlgoAuth(record.auth, &auth);
|
|
iov[AUTH_PAD].iov_len = NLA_ALIGN(len) - len;
|
|
|
|
len = iov[ENCAP].iov_len = fillNlAttrXfrmEncapTmpl(record, &encap);
|
|
iov[ENCAP_PAD].iov_len = NLA_ALIGN(len) - len;
|
|
return sock.sendMessage(XFRM_MSG_UPDSA, NETLINK_REQUEST_FLAGS, 0, iov, iovLen);
|
|
}
|
|
|
|
int XfrmController::fillNlAttrXfrmAlgoEnc(const XfrmAlgo& inAlgo, nlattr_algo_crypt* algo) {
|
|
int len = NLA_HDRLEN + sizeof(xfrm_algo);
|
|
strncpy(algo->crypt.alg_name, inAlgo.name.c_str(), sizeof(algo->crypt.alg_name));
|
|
algo->crypt.alg_key_len = inAlgo.key.size() * 8; // bits
|
|
memcpy(algo->key, &inAlgo.key[0], inAlgo.key.size()); // FIXME :safety checks
|
|
len += inAlgo.key.size();
|
|
fillXfrmNlaHdr(&algo->hdr, XFRMA_ALG_CRYPT, len);
|
|
return len;
|
|
}
|
|
|
|
int XfrmController::fillNlAttrXfrmAlgoAuth(const XfrmAlgo& inAlgo, nlattr_algo_auth* algo) {
|
|
int len = NLA_HDRLEN + sizeof(xfrm_algo_auth);
|
|
strncpy(algo->auth.alg_name, inAlgo.name.c_str(), sizeof(algo->auth.alg_name));
|
|
algo->auth.alg_key_len = inAlgo.key.size() * 8; // bits
|
|
|
|
// This is the extra field for ALG_AUTH_TRUNC
|
|
algo->auth.alg_trunc_len = inAlgo.truncLenBits;
|
|
|
|
memcpy(algo->key, &inAlgo.key[0], inAlgo.key.size()); // FIXME :safety checks
|
|
len += inAlgo.key.size();
|
|
|
|
fillXfrmNlaHdr(&algo->hdr, XFRMA_ALG_AUTH_TRUNC, len);
|
|
return len;
|
|
}
|
|
|
|
int XfrmController::fillNlAttrXfrmEncapTmpl(const XfrmSaInfo& record, nlattr_encap_tmpl* tmpl) {
|
|
if (record.encap.type == XfrmEncapType::NONE) {
|
|
return 0;
|
|
}
|
|
|
|
int len = NLA_HDRLEN + sizeof(xfrm_encap_tmpl);
|
|
tmpl->tmpl.encap_type = static_cast<uint16_t>(record.encap.type);
|
|
tmpl->tmpl.encap_sport = htons(record.encap.srcPort);
|
|
tmpl->tmpl.encap_dport = htons(record.encap.dstPort);
|
|
fillXfrmNlaHdr(&tmpl->hdr, XFRMA_ENCAP, len);
|
|
return len;
|
|
}
|
|
|
|
int XfrmController::fillUserSaInfo(const XfrmSaInfo& record, xfrm_usersa_info* usersa) {
|
|
fillTransportModeSelector(record, &usersa->sel);
|
|
|
|
usersa->id.proto = IPPROTO_ESP;
|
|
usersa->id.spi = record.spi;
|
|
usersa->id.daddr = record.dstAddr;
|
|
|
|
usersa->saddr = record.srcAddr;
|
|
|
|
fillXfrmLifetimeDefaults(&usersa->lft);
|
|
fillXfrmCurLifetimeDefaults(&usersa->curlft);
|
|
memset(&usersa->stats, 0, sizeof(usersa->stats)); // leave stats zeroed out
|
|
usersa->reqid = record.transformId;
|
|
usersa->family = record.addrFamily;
|
|
usersa->mode = static_cast<uint8_t>(record.mode);
|
|
usersa->replay_window = REPLAY_WINDOW_SIZE;
|
|
usersa->flags = 0; // TODO: should we actually set flags, XFRM_SA_XFLAG_DONT_ENCAP_DSCP?
|
|
return sizeof(*usersa);
|
|
}
|
|
|
|
int XfrmController::fillUserSaId(const XfrmSaId& record, xfrm_usersa_id* said) {
|
|
said->daddr = record.dstAddr;
|
|
said->spi = record.spi;
|
|
said->family = record.addrFamily;
|
|
said->proto = IPPROTO_ESP;
|
|
|
|
return sizeof(*said);
|
|
}
|
|
|
|
int XfrmController::deleteSecurityAssociation(const XfrmSaId& record, const XfrmSocket& sock) {
|
|
xfrm_usersa_id said{};
|
|
|
|
enum { NLMSG_HDR, USERSAID, USERSAID_PAD, iovLen };
|
|
|
|
iovec iov[] = {
|
|
{NULL, 0}, // reserved for the eventual addition of a NLMSG_HDR
|
|
{&said, 0}, // main usersa_info struct
|
|
{kPadBytes, 0}, // up to NLMSG_ALIGNTO pad bytes of padding
|
|
};
|
|
|
|
int len;
|
|
len = iov[USERSAID].iov_len = fillUserSaId(record, &said);
|
|
iov[USERSAID_PAD].iov_len = NLMSG_ALIGN(len) - len;
|
|
|
|
return sock.sendMessage(XFRM_MSG_DELSA, NETLINK_REQUEST_FLAGS, 0, iov, iovLen);
|
|
}
|
|
|
|
int XfrmController::allocateSpi(const XfrmSaInfo& record, uint32_t minSpi, uint32_t maxSpi,
|
|
uint32_t* outSpi, const XfrmSocket& sock) {
|
|
xfrm_userspi_info spiInfo{};
|
|
|
|
enum { NLMSG_HDR, USERSAID, USERSAID_PAD, iovLen };
|
|
|
|
iovec iov[] = {
|
|
{NULL, 0}, // reserved for the eventual addition of a NLMSG_HDR
|
|
{&spiInfo, 0}, // main userspi_info struct
|
|
{kPadBytes, 0}, // up to NLMSG_ALIGNTO pad bytes of padding
|
|
};
|
|
|
|
int len;
|
|
if (fillUserSaInfo(record, &spiInfo.info) == 0) {
|
|
ALOGE("Failed to fill transport SA Info");
|
|
}
|
|
|
|
len = iov[USERSAID].iov_len = sizeof(spiInfo);
|
|
iov[USERSAID_PAD].iov_len = NLMSG_ALIGN(len) - len;
|
|
|
|
RandomSpi spiGen = RandomSpi(minSpi, maxSpi);
|
|
int spi, ret;
|
|
while ((spi = spiGen.next()) != INVALID_SPI) {
|
|
spiInfo.min = spi;
|
|
spiInfo.max = spi;
|
|
ret = sock.sendMessage(XFRM_MSG_ALLOCSPI, NETLINK_REQUEST_FLAGS, 0, iov, iovLen);
|
|
|
|
/* If the SPI is in use, we'll get ENOENT */
|
|
if (ret == -ENOENT)
|
|
continue;
|
|
|
|
if (ret == 0) {
|
|
*outSpi = spi;
|
|
ALOGD("Allocated an SPI: %x", *outSpi);
|
|
} else {
|
|
*outSpi = INVALID_SPI;
|
|
ALOGE("SPI Allocation Failed with error %d", ret);
|
|
}
|
|
|
|
return ret;
|
|
}
|
|
|
|
// Should always be -ENOENT if we get here
|
|
return ret;
|
|
}
|
|
|
|
int XfrmController::fillTransportModeUserSpInfo(const XfrmSaInfo& record,
|
|
xfrm_userpolicy_info* usersp) {
|
|
fillTransportModeSelector(record, &usersp->sel);
|
|
fillXfrmLifetimeDefaults(&usersp->lft);
|
|
fillXfrmCurLifetimeDefaults(&usersp->curlft);
|
|
/* if (index) index & 0x3 == dir -- must be true
|
|
* xfrm_user.c:verify_newpolicy_info() */
|
|
usersp->index = 0;
|
|
usersp->dir = static_cast<uint8_t>(record.direction);
|
|
usersp->action = XFRM_POLICY_ALLOW;
|
|
usersp->flags = XFRM_POLICY_LOCALOK;
|
|
usersp->share = XFRM_SHARE_UNIQUE;
|
|
return sizeof(*usersp);
|
|
}
|
|
|
|
int XfrmController::fillUserTemplate(const XfrmSaInfo& record, xfrm_user_tmpl* tmpl) {
|
|
tmpl->id.daddr = record.dstAddr;
|
|
tmpl->id.spi = record.spi;
|
|
tmpl->id.proto = IPPROTO_ESP;
|
|
|
|
tmpl->family = record.addrFamily;
|
|
tmpl->saddr = record.srcAddr;
|
|
tmpl->reqid = record.transformId;
|
|
tmpl->mode = static_cast<uint8_t>(record.mode);
|
|
tmpl->share = XFRM_SHARE_UNIQUE;
|
|
tmpl->optional = 0; // if this is true, then a failed state lookup will be considered OK:
|
|
// http://lxr.free-electrons.com/source/net/xfrm/xfrm_policy.c#L1492
|
|
tmpl->aalgos = ALGO_MASK_AUTH_ALL; // TODO: if there's a bitmask somewhere of
|
|
// algos, we should find it and apply it.
|
|
// I can't find one.
|
|
tmpl->ealgos = ALGO_MASK_CRYPT_ALL; // TODO: if there's a bitmask somewhere...
|
|
return 0;
|
|
}
|
|
|
|
} // namespace net
|
|
} // namespace android
|