1320 lines
32 KiB
C
1320 lines
32 KiB
C
/* netlink interface to the kernel's IPsec mechanism
|
|
* Copyright (C) 2003 Herbert Xu.
|
|
*
|
|
* This program is free software; you can redistribute it and/or modify it
|
|
* under the terms of the GNU General Public License as published by the
|
|
* Free Software Foundation; either version 2 of the License, or (at your
|
|
* option) any later version. See <http://www.fsf.org/copyleft/gpl.txt>.
|
|
*
|
|
* This program is distributed in the hope that it will be useful, but
|
|
* WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
|
|
* or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
|
|
* for more details.
|
|
*/
|
|
|
|
#if defined(linux) && defined(KERNEL26_SUPPORT)
|
|
|
|
#include <errno.h>
|
|
#include <fcntl.h>
|
|
#include <string.h>
|
|
#include <sys/queue.h>
|
|
#include <sys/socket.h>
|
|
#include <sys/types.h>
|
|
#include <sys/queue.h>
|
|
#include <unistd.h>
|
|
#include <linux/xfrm.h>
|
|
#include <linux/rtnetlink.h>
|
|
|
|
#include "kameipsec.h"
|
|
|
|
#include <freeswan.h>
|
|
#include <pfkeyv2.h>
|
|
#include <pfkey.h>
|
|
|
|
#include "constants.h"
|
|
#include "defs.h"
|
|
#include "kernel.h"
|
|
#include "kernel_netlink.h"
|
|
#include "kernel_pfkey.h"
|
|
#include "log.h"
|
|
#include "whack.h" /* for RC_LOG_SERIOUS */
|
|
#include "kernel_alg.h"
|
|
|
|
/** required for Linux 2.6.26 kernel and later */
|
|
#ifndef XFRM_STATE_AF_UNSPEC
|
|
#define XFRM_STATE_AF_UNSPEC 32
|
|
#endif
|
|
|
|
/* Minimum priority number in SPD used by pluto. */
|
|
#define MIN_SPD_PRIORITY 1024
|
|
|
|
static int netlinkfd = NULL_FD;
|
|
static int netlink_bcast_fd = NULL_FD;
|
|
|
|
#define NE(x) { x, #x } /* Name Entry -- shorthand for sparse_names */
|
|
|
|
static sparse_names xfrm_type_names = {
|
|
NE(NLMSG_NOOP),
|
|
NE(NLMSG_ERROR),
|
|
NE(NLMSG_DONE),
|
|
NE(NLMSG_OVERRUN),
|
|
|
|
NE(XFRM_MSG_NEWSA),
|
|
NE(XFRM_MSG_DELSA),
|
|
NE(XFRM_MSG_GETSA),
|
|
|
|
NE(XFRM_MSG_NEWPOLICY),
|
|
NE(XFRM_MSG_DELPOLICY),
|
|
NE(XFRM_MSG_GETPOLICY),
|
|
|
|
NE(XFRM_MSG_ALLOCSPI),
|
|
NE(XFRM_MSG_ACQUIRE),
|
|
NE(XFRM_MSG_EXPIRE),
|
|
|
|
NE(XFRM_MSG_UPDPOLICY),
|
|
NE(XFRM_MSG_UPDSA),
|
|
|
|
NE(XFRM_MSG_POLEXPIRE),
|
|
|
|
NE(XFRM_MSG_MAX),
|
|
|
|
{ 0, sparse_end }
|
|
};
|
|
|
|
#undef NE
|
|
|
|
/* Authentication algorithms */
|
|
static sparse_names aalg_list = {
|
|
{ SADB_X_AALG_NULL, "digest_null" },
|
|
{ SADB_AALG_MD5HMAC, "md5" },
|
|
{ SADB_AALG_SHA1HMAC, "sha1" },
|
|
{ SADB_X_AALG_SHA2_256_96HMAC, "sha256" },
|
|
{ SADB_X_AALG_SHA2_256HMAC, "hmac(sha256)" },
|
|
{ SADB_X_AALG_SHA2_384HMAC, "hmac(sha384)" },
|
|
{ SADB_X_AALG_SHA2_512HMAC, "hmac(sha512)" },
|
|
{ SADB_X_AALG_RIPEMD160HMAC, "ripemd160" },
|
|
{ SADB_X_AALG_AES_XCBC_MAC, "xcbc(aes)"},
|
|
{ 0, sparse_end }
|
|
};
|
|
|
|
/* Encryption algorithms */
|
|
static sparse_names ealg_list = {
|
|
{ SADB_EALG_NULL, "cipher_null" },
|
|
{ SADB_EALG_DESCBC, "des" },
|
|
{ SADB_EALG_3DESCBC, "des3_ede" },
|
|
{ SADB_X_EALG_CASTCBC, "cast128" },
|
|
{ SADB_X_EALG_BLOWFISHCBC, "blowfish" },
|
|
{ SADB_X_EALG_AESCBC, "aes" },
|
|
{ SADB_X_EALG_AESCTR, "rfc3686(ctr(aes))" },
|
|
{ SADB_X_EALG_AES_CCM_ICV8, "rfc4309(ccm(aes))" },
|
|
{ SADB_X_EALG_AES_CCM_ICV12, "rfc4309(ccm(aes))" },
|
|
{ SADB_X_EALG_AES_CCM_ICV16, "rfc4309(ccm(aes))" },
|
|
{ SADB_X_EALG_AES_GCM_ICV8, "rfc4106(gcm(aes))" },
|
|
{ SADB_X_EALG_AES_GCM_ICV12, "rfc4106(gcm(aes))" },
|
|
{ SADB_X_EALG_AES_GCM_ICV16, "rfc4106(gcm(aes))" },
|
|
{ SADB_X_EALG_NULL_AES_GMAC, "rfc4543(gcm(aes))" },
|
|
{ SADB_X_EALG_CAMELLIACBC, "cbc(camellia)" },
|
|
{ SADB_X_EALG_SERPENTCBC, "serpent" },
|
|
{ SADB_X_EALG_TWOFISHCBC, "twofish" },
|
|
{ 0, sparse_end }
|
|
};
|
|
|
|
/* Compression algorithms */
|
|
static sparse_names calg_list = {
|
|
{ SADB_X_CALG_DEFLATE, "deflate" },
|
|
{ SADB_X_CALG_LZS, "lzs" },
|
|
{ SADB_X_CALG_LZJH, "lzjh" },
|
|
{ 0, sparse_end }
|
|
};
|
|
|
|
/** ip2xfrm - Take an IP address and convert to an xfrm.
|
|
*
|
|
* @param addr ip_address
|
|
* @param xaddr xfrm_address_t - IPv[46] Address from addr is copied here.
|
|
*/
|
|
static void ip2xfrm(const ip_address *addr, xfrm_address_t *xaddr)
|
|
{
|
|
if (addr->u.v4.sin_family == AF_INET)
|
|
{
|
|
xaddr->a4 = addr->u.v4.sin_addr.s_addr;
|
|
}
|
|
else
|
|
{
|
|
memcpy(xaddr->a6, &addr->u.v6.sin6_addr, sizeof(xaddr->a6));
|
|
}
|
|
}
|
|
|
|
/** init_netlink - Initialize the netlink inferface. Opens the sockets and
|
|
* then binds to the broadcast socket.
|
|
*/
|
|
static void init_netlink(void)
|
|
{
|
|
struct sockaddr_nl addr;
|
|
|
|
netlinkfd = socket(AF_NETLINK, SOCK_DGRAM, NETLINK_XFRM);
|
|
|
|
if (netlinkfd < 0)
|
|
{
|
|
exit_log_errno((e, "socket() in init_netlink()"));
|
|
}
|
|
if (fcntl(netlinkfd, F_SETFD, FD_CLOEXEC) != 0)
|
|
{
|
|
exit_log_errno((e, "fcntl(FD_CLOEXEC) in init_netlink()"));
|
|
}
|
|
netlink_bcast_fd = socket(AF_NETLINK, SOCK_DGRAM, NETLINK_XFRM);
|
|
|
|
if (netlink_bcast_fd < 0)
|
|
{
|
|
exit_log_errno((e, "socket() for bcast in init_netlink()"));
|
|
}
|
|
if (fcntl(netlink_bcast_fd, F_SETFD, FD_CLOEXEC) != 0)
|
|
{
|
|
exit_log_errno((e, "fcntl(FD_CLOEXEC) for bcast in init_netlink()"));
|
|
}
|
|
if (fcntl(netlink_bcast_fd, F_SETFL, O_NONBLOCK) != 0)
|
|
{
|
|
exit_log_errno((e, "fcntl(O_NONBLOCK) for bcast in init_netlink()"));
|
|
}
|
|
addr.nl_family = AF_NETLINK;
|
|
addr.nl_pid = getpid();
|
|
addr.nl_groups = XFRMGRP_ACQUIRE | XFRMGRP_EXPIRE;
|
|
if (bind(netlink_bcast_fd, (struct sockaddr *)&addr, sizeof(addr)) != 0)
|
|
{
|
|
exit_log_errno((e, "Failed to bind bcast socket in init_netlink()"));
|
|
}
|
|
}
|
|
|
|
/** send_netlink_msg
|
|
*
|
|
* @param hdr - Data to be sent.
|
|
* @param rbuf - Return Buffer - contains data returned from the send.
|
|
* @param rbuf_len - Length of rbuf
|
|
* @param description - String - user friendly description of what is
|
|
* being attempted. Used for diagnostics
|
|
* @param text_said - String
|
|
* @return bool True if the message was succesfully sent.
|
|
*/
|
|
static bool send_netlink_msg(struct nlmsghdr *hdr, struct nlmsghdr *rbuf,
|
|
size_t rbuf_len, const char *description,
|
|
const char *text_said)
|
|
{
|
|
struct {
|
|
struct nlmsghdr n;
|
|
struct nlmsgerr e;
|
|
char data[1024];
|
|
} rsp;
|
|
|
|
size_t len;
|
|
ssize_t r;
|
|
struct sockaddr_nl addr;
|
|
static uint32_t seq;
|
|
|
|
if (no_klips)
|
|
{
|
|
return TRUE;
|
|
}
|
|
|
|
hdr->nlmsg_seq = ++seq;
|
|
len = hdr->nlmsg_len;
|
|
do {
|
|
r = write(netlinkfd, hdr, len);
|
|
}
|
|
while (r < 0 && errno == EINTR);
|
|
|
|
if (r < 0)
|
|
{
|
|
log_errno((e
|
|
, "netlink write() of %s message"
|
|
" for %s %s failed"
|
|
, sparse_val_show(xfrm_type_names, hdr->nlmsg_type)
|
|
, description, text_said));
|
|
return FALSE;
|
|
}
|
|
else if ((size_t)r != len)
|
|
{
|
|
loglog(RC_LOG_SERIOUS
|
|
, "ERROR: netlink write() of %s message"
|
|
" for %s %s truncated: %ld instead of %lu"
|
|
, sparse_val_show(xfrm_type_names, hdr->nlmsg_type)
|
|
, description, text_said
|
|
, (long)r, (unsigned long)len);
|
|
return FALSE;
|
|
}
|
|
|
|
for (;;)
|
|
{
|
|
socklen_t alen;
|
|
|
|
alen = sizeof(addr);
|
|
r = recvfrom(netlinkfd, &rsp, sizeof(rsp), 0
|
|
, (struct sockaddr *)&addr, &alen);
|
|
if (r < 0)
|
|
{
|
|
if (errno == EINTR)
|
|
{
|
|
continue;
|
|
}
|
|
log_errno((e
|
|
, "netlink recvfrom() of response to our %s message"
|
|
" for %s %s failed"
|
|
, sparse_val_show(xfrm_type_names, hdr->nlmsg_type)
|
|
, description, text_said));
|
|
return FALSE;
|
|
}
|
|
else if ((size_t) r < sizeof(rsp.n))
|
|
{
|
|
plog("netlink read truncated message: %ld bytes; ignore message"
|
|
, (long) r);
|
|
continue;
|
|
}
|
|
else if (addr.nl_pid != 0)
|
|
{
|
|
/* not for us: ignore */
|
|
DBG(DBG_KLIPS,
|
|
DBG_log("netlink: ignoring %s message from process %u"
|
|
, sparse_val_show(xfrm_type_names, rsp.n.nlmsg_type)
|
|
, addr.nl_pid));
|
|
continue;
|
|
}
|
|
else if (rsp.n.nlmsg_seq != seq)
|
|
{
|
|
DBG(DBG_KLIPS,
|
|
DBG_log("netlink: ignoring out of sequence (%u/%u) message %s"
|
|
, rsp.n.nlmsg_seq, seq
|
|
, sparse_val_show(xfrm_type_names, rsp.n.nlmsg_type)));
|
|
continue;
|
|
}
|
|
break;
|
|
}
|
|
|
|
if (rsp.n.nlmsg_len > (size_t) r)
|
|
{
|
|
loglog(RC_LOG_SERIOUS
|
|
, "netlink recvfrom() of response to our %s message"
|
|
" for %s %s was truncated: %ld instead of %lu"
|
|
, sparse_val_show(xfrm_type_names, hdr->nlmsg_type)
|
|
, description, text_said
|
|
, (long) len, (unsigned long) rsp.n.nlmsg_len);
|
|
return FALSE;
|
|
}
|
|
else if (rsp.n.nlmsg_type != NLMSG_ERROR
|
|
&& (rbuf && rsp.n.nlmsg_type != rbuf->nlmsg_type))
|
|
{
|
|
loglog(RC_LOG_SERIOUS
|
|
, "netlink recvfrom() of response to our %s message"
|
|
" for %s %s was of wrong type (%s)"
|
|
, sparse_val_show(xfrm_type_names, hdr->nlmsg_type)
|
|
, description, text_said
|
|
, sparse_val_show(xfrm_type_names, rsp.n.nlmsg_type));
|
|
return FALSE;
|
|
}
|
|
else if (rbuf)
|
|
{
|
|
if ((size_t) r > rbuf_len)
|
|
{
|
|
loglog(RC_LOG_SERIOUS
|
|
, "netlink recvfrom() of response to our %s message"
|
|
" for %s %s was too long: %ld > %lu"
|
|
, sparse_val_show(xfrm_type_names, hdr->nlmsg_type)
|
|
, description, text_said
|
|
, (long)r, (unsigned long)rbuf_len);
|
|
return FALSE;
|
|
}
|
|
memcpy(rbuf, &rsp, r);
|
|
return TRUE;
|
|
}
|
|
else if (rsp.n.nlmsg_type == NLMSG_ERROR && rsp.e.error)
|
|
{
|
|
loglog(RC_LOG_SERIOUS
|
|
, "ERROR: netlink response for %s %s included errno %d: %s"
|
|
, description, text_said
|
|
, -rsp.e.error
|
|
, strerror(-rsp.e.error));
|
|
return FALSE;
|
|
}
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
/** netlink_policy -
|
|
*
|
|
* @param hdr - Data to check
|
|
* @param enoent_ok - Boolean - OK or not OK.
|
|
* @param text_said - String
|
|
* @return boolean
|
|
*/
|
|
static bool netlink_policy(struct nlmsghdr *hdr, bool enoent_ok,
|
|
const char *text_said)
|
|
{
|
|
struct {
|
|
struct nlmsghdr n;
|
|
struct nlmsgerr e;
|
|
char data[1024];
|
|
} rsp;
|
|
int error;
|
|
|
|
rsp.n.nlmsg_type = NLMSG_ERROR;
|
|
if (!send_netlink_msg(hdr, &rsp.n, sizeof(rsp), "policy", text_said))
|
|
{
|
|
return FALSE;
|
|
}
|
|
|
|
error = -rsp.e.error;
|
|
if (!error)
|
|
{
|
|
return TRUE;
|
|
}
|
|
|
|
if (error == ENOENT && enoent_ok)
|
|
{
|
|
return TRUE;
|
|
}
|
|
|
|
loglog(RC_LOG_SERIOUS
|
|
, "ERROR: netlink %s response for flow %s included errno %d: %s"
|
|
, sparse_val_show(xfrm_type_names, hdr->nlmsg_type)
|
|
, text_said
|
|
, error
|
|
, strerror(error));
|
|
return FALSE;
|
|
}
|
|
|
|
/** netlink_raw_eroute
|
|
*
|
|
* @param this_host ip_address
|
|
* @param this_client ip_subnet
|
|
* @param that_host ip_address
|
|
* @param that_client ip_subnet
|
|
* @param spi
|
|
* @param proto int (Currently unused) Contains protocol (u=tcp, 17=udp, etc...)
|
|
* @param transport_proto int (Currently unused) 0=tunnel, 1=transport
|
|
* @param satype int
|
|
* @param proto_info
|
|
* @param lifetime (Currently unused)
|
|
* @param ip int
|
|
* @return boolean True if successful
|
|
*/
|
|
static bool netlink_raw_eroute(const ip_address *this_host
|
|
, const ip_subnet *this_client
|
|
, const ip_address *that_host
|
|
, const ip_subnet *that_client
|
|
, ipsec_spi_t spi
|
|
, unsigned int satype
|
|
, unsigned int transport_proto
|
|
, const struct pfkey_proto_info *proto_info
|
|
, time_t use_lifetime UNUSED
|
|
, unsigned int op
|
|
, const char *text_said)
|
|
{
|
|
struct {
|
|
struct nlmsghdr n;
|
|
union {
|
|
struct xfrm_userpolicy_info p;
|
|
struct xfrm_userpolicy_id id;
|
|
} u;
|
|
char data[1024];
|
|
} req;
|
|
int shift;
|
|
int dir;
|
|
int family;
|
|
int policy;
|
|
bool ok;
|
|
bool enoent_ok;
|
|
|
|
policy = IPSEC_POLICY_IPSEC;
|
|
|
|
if (satype == SADB_X_SATYPE_INT)
|
|
{
|
|
/* shunt route */
|
|
switch (ntohl(spi))
|
|
{
|
|
case SPI_PASS:
|
|
policy = IPSEC_POLICY_NONE;
|
|
break;
|
|
case SPI_DROP:
|
|
case SPI_REJECT:
|
|
default:
|
|
policy = IPSEC_POLICY_DISCARD;
|
|
break;
|
|
case SPI_TRAP:
|
|
case SPI_TRAPSUBNET:
|
|
case SPI_HOLD:
|
|
if (op & (SADB_X_SAFLAGS_INFLOW << ERO_FLAG_SHIFT))
|
|
{
|
|
return TRUE;
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
|
|
memset(&req, 0, sizeof(req));
|
|
req.n.nlmsg_flags = NLM_F_REQUEST | NLM_F_ACK;
|
|
|
|
family = that_client->addr.u.v4.sin_family;
|
|
shift = (family == AF_INET) ? 5 : 7;
|
|
|
|
req.u.p.sel.sport = portof(&this_client->addr);
|
|
req.u.p.sel.dport = portof(&that_client->addr);
|
|
req.u.p.sel.sport_mask = (req.u.p.sel.sport) ? ~0:0;
|
|
req.u.p.sel.dport_mask = (req.u.p.sel.dport) ? ~0:0;
|
|
ip2xfrm(&this_client->addr, &req.u.p.sel.saddr);
|
|
ip2xfrm(&that_client->addr, &req.u.p.sel.daddr);
|
|
req.u.p.sel.prefixlen_s = this_client->maskbits;
|
|
req.u.p.sel.prefixlen_d = that_client->maskbits;
|
|
req.u.p.sel.proto = transport_proto;
|
|
req.u.p.sel.family = family;
|
|
|
|
dir = XFRM_POLICY_OUT;
|
|
if (op & (SADB_X_SAFLAGS_INFLOW << ERO_FLAG_SHIFT))
|
|
{
|
|
dir = XFRM_POLICY_IN;
|
|
}
|
|
|
|
if ((op & ERO_MASK) == ERO_DELETE)
|
|
{
|
|
req.u.id.dir = dir;
|
|
req.n.nlmsg_type = XFRM_MSG_DELPOLICY;
|
|
req.n.nlmsg_len = NLMSG_ALIGN(NLMSG_LENGTH(sizeof(req.u.id)));
|
|
}
|
|
else
|
|
{
|
|
int src, dst;
|
|
|
|
req.u.p.dir = dir;
|
|
|
|
src = req.u.p.sel.prefixlen_s;
|
|
dst = req.u.p.sel.prefixlen_d;
|
|
if (dir != XFRM_POLICY_OUT) {
|
|
src = req.u.p.sel.prefixlen_d;
|
|
dst = req.u.p.sel.prefixlen_s;
|
|
}
|
|
req.u.p.priority = MIN_SPD_PRIORITY
|
|
+ (((2 << shift) - src) << shift)
|
|
+ (2 << shift) - dst;
|
|
|
|
req.u.p.action = XFRM_POLICY_ALLOW;
|
|
if (policy == IPSEC_POLICY_DISCARD)
|
|
{
|
|
req.u.p.action = XFRM_POLICY_BLOCK;
|
|
}
|
|
req.u.p.lft.soft_use_expires_seconds = use_lifetime;
|
|
req.u.p.lft.soft_byte_limit = XFRM_INF;
|
|
req.u.p.lft.soft_packet_limit = XFRM_INF;
|
|
req.u.p.lft.hard_byte_limit = XFRM_INF;
|
|
req.u.p.lft.hard_packet_limit = XFRM_INF;
|
|
|
|
req.n.nlmsg_type = XFRM_MSG_NEWPOLICY;
|
|
if (op & (SADB_X_SAFLAGS_REPLACEFLOW << ERO_FLAG_SHIFT))
|
|
{
|
|
req.n.nlmsg_type = XFRM_MSG_UPDPOLICY;
|
|
}
|
|
req.n.nlmsg_len = NLMSG_ALIGN(NLMSG_LENGTH(sizeof(req.u.p)));
|
|
}
|
|
|
|
if (policy == IPSEC_POLICY_IPSEC && (op & ERO_MASK) != ERO_DELETE)
|
|
{
|
|
struct rtattr *attr;
|
|
struct xfrm_user_tmpl tmpl[4];
|
|
int i;
|
|
|
|
memset(tmpl, 0, sizeof(tmpl));
|
|
for (i = 0; proto_info[i].proto; i++)
|
|
{
|
|
tmpl[i].reqid = proto_info[i].reqid;
|
|
tmpl[i].id.proto = proto_info[i].proto;
|
|
tmpl[i].optional =
|
|
proto_info[i].proto == IPPROTO_COMP && dir != XFRM_POLICY_OUT;
|
|
tmpl[i].aalgos = tmpl[i].ealgos = tmpl[i].calgos = ~0;
|
|
tmpl[i].family = that_host->u.v4.sin_family;
|
|
tmpl[i].mode =
|
|
proto_info[i].encapsulation == ENCAPSULATION_MODE_TUNNEL;
|
|
if (!tmpl[i].mode)
|
|
{
|
|
continue;
|
|
}
|
|
|
|
ip2xfrm(this_host, &tmpl[i].saddr);
|
|
ip2xfrm(that_host, &tmpl[i].id.daddr);
|
|
}
|
|
|
|
attr = (struct rtattr *)((char *)&req + req.n.nlmsg_len);
|
|
attr->rta_type = XFRMA_TMPL;
|
|
attr->rta_len = i * sizeof(tmpl[0]);
|
|
memcpy(RTA_DATA(attr), tmpl, attr->rta_len);
|
|
attr->rta_len = RTA_LENGTH(attr->rta_len);
|
|
req.n.nlmsg_len += attr->rta_len;
|
|
}
|
|
|
|
enoent_ok = FALSE;
|
|
if (op == ERO_DEL_INBOUND)
|
|
{
|
|
enoent_ok = TRUE;
|
|
}
|
|
else if (op == ERO_DELETE && ntohl(spi) == SPI_HOLD)
|
|
{
|
|
enoent_ok = TRUE;
|
|
}
|
|
|
|
ok = netlink_policy(&req.n, enoent_ok, text_said);
|
|
switch (dir)
|
|
{
|
|
case XFRM_POLICY_IN:
|
|
if (req.n.nlmsg_type == XFRM_MSG_DELPOLICY)
|
|
{
|
|
req.u.id.dir = XFRM_POLICY_FWD;
|
|
}
|
|
else if (!ok)
|
|
{
|
|
break;
|
|
}
|
|
else if (proto_info[0].encapsulation != ENCAPSULATION_MODE_TUNNEL
|
|
&& satype != SADB_X_SATYPE_INT)
|
|
{
|
|
break;
|
|
}
|
|
else
|
|
{
|
|
req.u.p.dir = XFRM_POLICY_FWD;
|
|
}
|
|
ok &= netlink_policy(&req.n, enoent_ok, text_said);
|
|
break;
|
|
}
|
|
|
|
return ok;
|
|
}
|
|
|
|
/** netlink_add_sa - Add an SA into the kernel SPDB via netlink
|
|
*
|
|
* @param sa Kernel SA to add/modify
|
|
* @param replace boolean - true if this replaces an existing SA
|
|
* @return bool True if successfull
|
|
*/
|
|
static bool netlink_add_sa(const struct kernel_sa *sa, bool replace)
|
|
{
|
|
struct {
|
|
struct nlmsghdr n;
|
|
struct xfrm_usersa_info p;
|
|
char data[1024];
|
|
} req;
|
|
struct rtattr *attr;
|
|
u_int16_t icv_size = 64;
|
|
|
|
memset(&req, 0, sizeof(req));
|
|
req.n.nlmsg_flags = NLM_F_REQUEST | NLM_F_ACK;
|
|
req.n.nlmsg_type = replace ? XFRM_MSG_UPDSA : XFRM_MSG_NEWSA;
|
|
|
|
ip2xfrm(sa->src, &req.p.saddr);
|
|
ip2xfrm(sa->dst, &req.p.id.daddr);
|
|
|
|
req.p.id.spi = sa->spi;
|
|
req.p.id.proto = satype2proto(sa->satype);
|
|
req.p.family = sa->src->u.v4.sin_family;
|
|
if (sa->encapsulation == ENCAPSULATION_MODE_TUNNEL)
|
|
{
|
|
req.p.mode = XFRM_MODE_TUNNEL;
|
|
req.p.flags |= XFRM_STATE_AF_UNSPEC;
|
|
}
|
|
else
|
|
{
|
|
req.p.mode = XFRM_MODE_TRANSPORT;
|
|
}
|
|
req.p.replay_window = sa->replay_window;
|
|
req.p.reqid = sa->reqid;
|
|
req.p.lft.soft_byte_limit = XFRM_INF;
|
|
req.p.lft.soft_packet_limit = XFRM_INF;
|
|
req.p.lft.hard_byte_limit = XFRM_INF;
|
|
req.p.lft.hard_packet_limit = XFRM_INF;
|
|
|
|
req.n.nlmsg_len = NLMSG_ALIGN(NLMSG_LENGTH(sizeof(req.p)));
|
|
|
|
attr = (struct rtattr *)((char *)&req + req.n.nlmsg_len);
|
|
|
|
if (sa->authalg)
|
|
{
|
|
const char *name;
|
|
|
|
name = sparse_name(aalg_list, sa->authalg);
|
|
if (!name)
|
|
{
|
|
loglog(RC_LOG_SERIOUS, "unknown authentication algorithm: %u"
|
|
, sa->authalg);
|
|
return FALSE;
|
|
}
|
|
DBG(DBG_CRYPT,
|
|
DBG_log("configured authentication algorithm %s with key size %d",
|
|
enum_show(&auth_alg_names, sa->authalg),
|
|
sa->authkeylen * BITS_PER_BYTE)
|
|
)
|
|
|
|
if (sa->authalg == SADB_X_AALG_SHA2_256HMAC)
|
|
{
|
|
struct xfrm_algo_auth algo;
|
|
|
|
/* the kernel uses SHA256 with 96 bit truncation by default,
|
|
* use specified truncation size supported by newer kernels */
|
|
strcpy(algo.alg_name, name);
|
|
algo.alg_key_len = sa->authkeylen * BITS_PER_BYTE;
|
|
algo.alg_trunc_len = 128;
|
|
|
|
attr->rta_type = XFRMA_ALG_AUTH_TRUNC;
|
|
attr->rta_len = RTA_LENGTH(sizeof(algo) + sa->authkeylen);
|
|
|
|
memcpy(RTA_DATA(attr), &algo, sizeof(algo));
|
|
memcpy((char *)RTA_DATA(attr) + sizeof(algo), sa->authkey
|
|
, sa->authkeylen);
|
|
}
|
|
else
|
|
{
|
|
struct xfrm_algo algo;
|
|
|
|
strcpy(algo.alg_name, name);
|
|
algo.alg_key_len = sa->authkeylen * BITS_PER_BYTE;
|
|
|
|
attr->rta_type = XFRMA_ALG_AUTH;
|
|
attr->rta_len = RTA_LENGTH(sizeof(algo) + sa->authkeylen);
|
|
|
|
memcpy(RTA_DATA(attr), &algo, sizeof(algo));
|
|
memcpy((char *)RTA_DATA(attr) + sizeof(algo), sa->authkey
|
|
, sa->authkeylen);
|
|
}
|
|
req.n.nlmsg_len += attr->rta_len;
|
|
attr = (struct rtattr *)((char *)attr + attr->rta_len);
|
|
}
|
|
|
|
switch (sa->encalg)
|
|
{
|
|
case SADB_EALG_NONE:
|
|
/* no encryption */
|
|
break;
|
|
case SADB_X_EALG_AES_CCM_ICV16:
|
|
case SADB_X_EALG_AES_GCM_ICV16:
|
|
case SADB_X_EALG_NULL_AES_GMAC:
|
|
icv_size += 32;
|
|
/* FALL */
|
|
case SADB_X_EALG_AES_CCM_ICV12:
|
|
case SADB_X_EALG_AES_GCM_ICV12:
|
|
icv_size += 32;
|
|
/* FALL */
|
|
case SADB_X_EALG_AES_CCM_ICV8:
|
|
case SADB_X_EALG_AES_GCM_ICV8:
|
|
{
|
|
struct xfrm_algo_aead *algo;
|
|
const char *name;
|
|
|
|
name = sparse_name(ealg_list, sa->encalg);
|
|
if (!name)
|
|
{
|
|
loglog(RC_LOG_SERIOUS, "unknown encryption algorithm: %u",
|
|
sa->encalg);
|
|
return FALSE;
|
|
}
|
|
DBG(DBG_CRYPT,
|
|
DBG_log("configured esp encryption algorithm %s with key size %d",
|
|
enum_show(&esp_transform_names, sa->encalg),
|
|
sa->enckeylen * BITS_PER_BYTE)
|
|
)
|
|
attr->rta_type = XFRMA_ALG_AEAD;
|
|
attr->rta_len = RTA_LENGTH(sizeof(struct xfrm_algo_aead) + sa->enckeylen);
|
|
req.n.nlmsg_len += attr->rta_len;
|
|
|
|
algo = (struct xfrm_algo_aead*)RTA_DATA(attr);
|
|
algo->alg_key_len = sa->enckeylen * BITS_PER_BYTE;
|
|
algo->alg_icv_len = icv_size;
|
|
strcpy(algo->alg_name, name);
|
|
memcpy(algo->alg_key, sa->enckey, sa->enckeylen);
|
|
|
|
attr = (struct rtattr *)((char *)attr + attr->rta_len);
|
|
break;
|
|
}
|
|
default:
|
|
{
|
|
struct xfrm_algo *algo;
|
|
const char *name;
|
|
|
|
name = sparse_name(ealg_list, sa->encalg);
|
|
if (!name)
|
|
{
|
|
loglog(RC_LOG_SERIOUS, "unknown encryption algorithm: %u",
|
|
sa->encalg);
|
|
return FALSE;
|
|
}
|
|
DBG(DBG_CRYPT,
|
|
DBG_log("configured esp encryption algorithm %s with key size %d",
|
|
enum_show(&esp_transform_names, sa->encalg),
|
|
sa->enckeylen * BITS_PER_BYTE)
|
|
)
|
|
attr->rta_type = XFRMA_ALG_CRYPT;
|
|
attr->rta_len = RTA_LENGTH(sizeof(struct xfrm_algo) + sa->enckeylen);
|
|
req.n.nlmsg_len += attr->rta_len;
|
|
|
|
algo = (struct xfrm_algo*)RTA_DATA(attr);
|
|
algo->alg_key_len = sa->enckeylen * BITS_PER_BYTE;
|
|
strcpy(algo->alg_name, name);
|
|
memcpy(algo->alg_key, sa->enckey, sa->enckeylen);
|
|
|
|
attr = (struct rtattr *)((char *)attr + attr->rta_len);
|
|
}
|
|
}
|
|
|
|
if (sa->compalg)
|
|
{
|
|
struct xfrm_algo algo;
|
|
const char *name;
|
|
|
|
name = sparse_name(calg_list, sa->compalg);
|
|
if (!name)
|
|
{
|
|
loglog(RC_LOG_SERIOUS, "unknown compression algorithm: %u"
|
|
, sa->compalg);
|
|
return FALSE;
|
|
}
|
|
|
|
strcpy(algo.alg_name, name);
|
|
algo.alg_key_len = 0;
|
|
|
|
attr->rta_type = XFRMA_ALG_COMP;
|
|
attr->rta_len = RTA_LENGTH(sizeof(algo));
|
|
|
|
memcpy(RTA_DATA(attr), &algo, sizeof(algo));
|
|
|
|
req.n.nlmsg_len += attr->rta_len;
|
|
attr = (struct rtattr *)((char *)attr + attr->rta_len);
|
|
}
|
|
|
|
if (sa->natt_type)
|
|
{
|
|
struct xfrm_encap_tmpl natt;
|
|
|
|
natt.encap_type = sa->natt_type;
|
|
natt.encap_sport = ntohs(sa->natt_sport);
|
|
natt.encap_dport = ntohs(sa->natt_dport);
|
|
memset (&natt.encap_oa, 0, sizeof (natt.encap_oa));
|
|
|
|
attr->rta_type = XFRMA_ENCAP;
|
|
attr->rta_len = RTA_LENGTH(sizeof(natt));
|
|
|
|
memcpy(RTA_DATA(attr), &natt, sizeof(natt));
|
|
|
|
req.n.nlmsg_len += attr->rta_len;
|
|
attr = (struct rtattr *)((char *)attr + attr->rta_len);
|
|
}
|
|
|
|
return send_netlink_msg(&req.n, NULL, 0, "Add SA", sa->text_said);
|
|
}
|
|
|
|
/** netlink_del_sa - Delete an SA from the Kernel
|
|
*
|
|
* @param sa Kernel SA to be deleted
|
|
* @return bool True if successfull
|
|
*/
|
|
static bool netlink_del_sa(const struct kernel_sa *sa)
|
|
{
|
|
struct {
|
|
struct nlmsghdr n;
|
|
struct xfrm_usersa_id id;
|
|
char data[1024];
|
|
} req;
|
|
|
|
memset(&req, 0, sizeof(req));
|
|
req.n.nlmsg_flags = NLM_F_REQUEST | NLM_F_ACK;
|
|
req.n.nlmsg_type = XFRM_MSG_DELSA;
|
|
|
|
ip2xfrm(sa->dst, &req.id.daddr);
|
|
|
|
req.id.spi = sa->spi;
|
|
req.id.family = sa->src->u.v4.sin_family;
|
|
req.id.proto = sa->proto;
|
|
|
|
req.n.nlmsg_len = NLMSG_ALIGN(NLMSG_LENGTH(sizeof(req.id)));
|
|
|
|
return send_netlink_msg(&req.n, NULL, 0, "Del SA", sa->text_said);
|
|
}
|
|
|
|
static bool netlink_error(const char *req_type, const struct nlmsghdr *n,
|
|
const struct nlmsgerr *e, int rsp_size)
|
|
{
|
|
if (n->nlmsg_type == NLMSG_ERROR)
|
|
{
|
|
DBG(DBG_KLIPS,
|
|
DBG_log("%s returned with errno %d: %s"
|
|
, req_type
|
|
, -e->error
|
|
, strerror(-e->error))
|
|
)
|
|
return TRUE;
|
|
}
|
|
if (n->nlmsg_len < NLMSG_LENGTH(rsp_size))
|
|
{
|
|
plog("%s returned message with length %lu < %lu bytes"
|
|
, req_type
|
|
, (unsigned long) n->nlmsg_len
|
|
, (unsigned long) rsp_size);
|
|
return TRUE;
|
|
}
|
|
return FALSE;
|
|
}
|
|
|
|
static bool netlink_get_policy(const struct kernel_sa *sa, bool inbound,
|
|
time_t *use_time)
|
|
{
|
|
struct {
|
|
struct nlmsghdr n;
|
|
struct xfrm_userpolicy_id id;
|
|
} req;
|
|
|
|
struct {
|
|
struct nlmsghdr n;
|
|
union {
|
|
struct nlmsgerr e;
|
|
struct xfrm_userpolicy_info info;
|
|
} u;
|
|
char data[1024];
|
|
} rsp;
|
|
|
|
memset(&req, 0, sizeof(req));
|
|
req.n.nlmsg_flags = NLM_F_REQUEST;
|
|
req.n.nlmsg_type = XFRM_MSG_GETPOLICY;
|
|
|
|
req.id.sel.sport = portof(&sa->src_client->addr);
|
|
req.id.sel.dport = portof(&sa->dst_client->addr);
|
|
req.id.sel.sport_mask = (req.id.sel.sport) ? ~0:0;
|
|
req.id.sel.dport_mask = (req.id.sel.dport) ? ~0:0;
|
|
ip2xfrm(&sa->src_client->addr, &req.id.sel.saddr);
|
|
ip2xfrm(&sa->dst_client->addr, &req.id.sel.daddr);
|
|
req.id.sel.prefixlen_s = sa->src_client->maskbits;
|
|
req.id.sel.prefixlen_d = sa->dst_client->maskbits;
|
|
req.id.sel.proto = sa->transport_proto;
|
|
req.id.sel.family = sa->dst_client->addr.u.v4.sin_family;
|
|
|
|
req.n.nlmsg_len = NLMSG_ALIGN(NLMSG_LENGTH(sizeof(req.id)));
|
|
rsp.n.nlmsg_type = XFRM_MSG_NEWPOLICY;
|
|
|
|
req.id.dir = (inbound)? XFRM_POLICY_IN:XFRM_POLICY_OUT;
|
|
|
|
if (!send_netlink_msg(&req.n, &rsp.n, sizeof(rsp), "Get policy", "?"))
|
|
{
|
|
return FALSE;
|
|
}
|
|
if (netlink_error("XFRM_MSG_GETPOLICY", &rsp.n, &rsp.u.e, sizeof(rsp.u.info)))
|
|
{
|
|
return FALSE;
|
|
}
|
|
*use_time = (time_t)rsp.u.info.curlft.use_time;
|
|
|
|
if (inbound && sa->encapsulation == ENCAPSULATION_MODE_TUNNEL)
|
|
{
|
|
time_t use_time_fwd;
|
|
|
|
req.id.dir = XFRM_POLICY_FWD;
|
|
|
|
if (!send_netlink_msg(&req.n, &rsp.n, sizeof(rsp), "Get policy", "?"))
|
|
{
|
|
return FALSE;
|
|
}
|
|
if (netlink_error("XFRM_MSG_GETPOLICY", &rsp.n, &rsp.u.e, sizeof(rsp.u.info)))
|
|
{
|
|
return FALSE;
|
|
}
|
|
use_time_fwd = (time_t)rsp.u.info.curlft.use_time;
|
|
*use_time = (*use_time > use_time_fwd)? *use_time : use_time_fwd;
|
|
}
|
|
return TRUE;
|
|
}
|
|
|
|
|
|
/** netlink_get_sa - Get information about an SA from the Kernel
|
|
*
|
|
* @param sa Kernel SA to be queried
|
|
* @return bool True if successfull
|
|
*/
|
|
static bool netlink_get_sa(const struct kernel_sa *sa, u_int *bytes)
|
|
{
|
|
struct {
|
|
struct nlmsghdr n;
|
|
struct xfrm_usersa_id id;
|
|
} req;
|
|
|
|
struct {
|
|
struct nlmsghdr n;
|
|
union {
|
|
struct nlmsgerr e;
|
|
struct xfrm_usersa_info info;
|
|
} u;
|
|
char data[1024];
|
|
} rsp;
|
|
|
|
memset(&req, 0, sizeof(req));
|
|
req.n.nlmsg_flags = NLM_F_REQUEST;
|
|
req.n.nlmsg_type = XFRM_MSG_GETSA;
|
|
|
|
ip2xfrm(sa->dst, &req.id.daddr);
|
|
|
|
req.id.spi = sa->spi;
|
|
req.id.family = sa->src->u.v4.sin_family;
|
|
req.id.proto = sa->proto;
|
|
|
|
req.n.nlmsg_len = NLMSG_ALIGN(NLMSG_LENGTH(sizeof(req.id)));
|
|
rsp.n.nlmsg_type = XFRM_MSG_NEWSA;
|
|
|
|
if (!send_netlink_msg(&req.n, &rsp.n, sizeof(rsp), "Get SA", sa->text_said))
|
|
{
|
|
return FALSE;
|
|
}
|
|
if (netlink_error("XFRM_MSG_GETSA", &rsp.n, &rsp.u.e, sizeof(rsp.u.info)))
|
|
{
|
|
return FALSE;
|
|
}
|
|
*bytes = (u_int) rsp.u.info.curlft.bytes;
|
|
return TRUE;
|
|
}
|
|
|
|
static void linux_pfkey_register_response(const struct sadb_msg *msg)
|
|
{
|
|
switch (msg->sadb_msg_satype)
|
|
{
|
|
case SADB_SATYPE_ESP:
|
|
#ifndef NO_KERNEL_ALG
|
|
kernel_alg_register_pfkey(msg, msg->sadb_msg_len * IPSEC_PFKEYv2_ALIGN);
|
|
#endif
|
|
break;
|
|
case SADB_X_SATYPE_IPCOMP:
|
|
can_do_IPcomp = TRUE;
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
}
|
|
|
|
/** linux_pfkey_register - Register via PFKEY our capabilities
|
|
*
|
|
*/
|
|
static void linux_pfkey_register(void)
|
|
{
|
|
pfkey_register_proto(SADB_SATYPE_AH, "AH");
|
|
pfkey_register_proto(SADB_SATYPE_ESP, "ESP");
|
|
pfkey_register_proto(SADB_X_SATYPE_IPCOMP, "IPCOMP");
|
|
pfkey_close();
|
|
}
|
|
|
|
/** Create ip_address out of xfrm_address_t.
|
|
*
|
|
* @param family
|
|
* @param src xfrm formatted IP address
|
|
* @param dst ip_address formatted destination
|
|
* @return err_t NULL if okay, otherwise an error
|
|
*/
|
|
static err_t xfrm_to_ip_address(unsigned family, const xfrm_address_t *src,
|
|
ip_address *dst)
|
|
{
|
|
switch (family)
|
|
{
|
|
case AF_INET: /* IPv4 */
|
|
case AF_UNSPEC: /* Unspecified, we assume IPv4 */
|
|
initaddr((const void *) &src->a4, sizeof(src->a4), AF_INET, dst);
|
|
return NULL;
|
|
case AF_INET6: /* IPv6 */
|
|
initaddr((const void *) &src->a6, sizeof(src->a6), AF_INET6, dst);
|
|
return NULL;
|
|
default:
|
|
return "unknown address family";
|
|
}
|
|
}
|
|
|
|
/* Create a pair of ip_address's out of xfrm_sel.
|
|
*
|
|
* @param sel xfrm selector
|
|
* @param src ip_address formatted source
|
|
* @param dst ip_address formatted destination
|
|
* @return err_t NULL if okay, otherwise an error
|
|
*/
|
|
static err_t xfrm_sel_to_ip_pair(const struct xfrm_selector *sel,
|
|
ip_address *src, ip_address *dst)
|
|
{
|
|
int family;
|
|
err_t ugh;
|
|
|
|
family = sel->family;
|
|
|
|
if ((ugh = xfrm_to_ip_address(family, &sel->saddr, src))
|
|
|| (ugh = xfrm_to_ip_address(family, &sel->daddr, dst)))
|
|
{
|
|
return ugh;
|
|
}
|
|
|
|
/* family has been verified in xfrm_to_ip_address. */
|
|
if (family == AF_INET)
|
|
{
|
|
src->u.v4.sin_port = sel->sport;
|
|
dst->u.v4.sin_port = sel->dport;
|
|
}
|
|
else
|
|
{
|
|
src->u.v6.sin6_port = sel->sport;
|
|
dst->u.v6.sin6_port = sel->dport;
|
|
}
|
|
|
|
return NULL;
|
|
}
|
|
|
|
static void netlink_acquire(struct nlmsghdr *n)
|
|
{
|
|
struct xfrm_user_acquire *acquire;
|
|
ip_address src, dst;
|
|
ip_subnet ours, his;
|
|
unsigned transport_proto;
|
|
err_t ugh = NULL;
|
|
|
|
if (n->nlmsg_len < NLMSG_LENGTH(sizeof(*acquire)))
|
|
{
|
|
plog("netlink_acquire got message with length %lu < %lu bytes; ignore message"
|
|
, (unsigned long) n->nlmsg_len
|
|
, (unsigned long) sizeof(*acquire));
|
|
return;
|
|
}
|
|
|
|
acquire = NLMSG_DATA(n);
|
|
transport_proto = acquire->sel.proto;
|
|
|
|
/* XXX also the type of src/dst should be checked to make sure
|
|
* that they aren't v4 to v6 or something goofy
|
|
*/
|
|
|
|
if (!(ugh = xfrm_sel_to_ip_pair(&acquire->sel, &src, &dst))
|
|
&& !(ugh = addrtosubnet(&src, &ours))
|
|
&& !(ugh = addrtosubnet(&dst, &his)))
|
|
{
|
|
record_and_initiate_opportunistic(&ours, &his, transport_proto
|
|
, "%acquire-netlink");
|
|
}
|
|
if (ugh != NULL)
|
|
{
|
|
plog("XFRM_MSG_ACQUIRE message from kernel malformed: %s", ugh);
|
|
}
|
|
}
|
|
|
|
static void netlink_shunt_expire(struct xfrm_userpolicy_info *pol)
|
|
{
|
|
ip_address src, dst;
|
|
unsigned transport_proto;
|
|
err_t ugh = NULL;
|
|
|
|
transport_proto = pol->sel.proto;
|
|
|
|
if (!(ugh = xfrm_sel_to_ip_pair(&pol->sel, &src, &dst)))
|
|
{
|
|
plog("XFRM_MSG_POLEXPIRE message from kernel malformed: %s", ugh);
|
|
return;
|
|
}
|
|
|
|
replace_bare_shunt(&src, &dst, BOTTOM_PRIO, SPI_PASS, FALSE, transport_proto
|
|
, "delete expired bare shunt");
|
|
}
|
|
|
|
static void netlink_policy_expire(struct nlmsghdr *n)
|
|
{
|
|
struct xfrm_user_polexpire *upe;
|
|
struct {
|
|
struct nlmsghdr n;
|
|
struct xfrm_userpolicy_id id;
|
|
} req;
|
|
|
|
struct {
|
|
struct nlmsghdr n;
|
|
union {
|
|
struct nlmsgerr e;
|
|
struct xfrm_userpolicy_info pol;
|
|
} u;
|
|
char data[1024];
|
|
} rsp;
|
|
|
|
if (n->nlmsg_len < NLMSG_LENGTH(sizeof(*upe)))
|
|
{
|
|
plog("netlink_policy_expire got message with length %lu < %lu bytes; ignore message"
|
|
, (unsigned long) n->nlmsg_len
|
|
, (unsigned long) sizeof(*upe));
|
|
return;
|
|
}
|
|
|
|
upe = NLMSG_DATA(n);
|
|
req.id.dir = upe->pol.dir;
|
|
req.id.index = upe->pol.index;
|
|
req.n.nlmsg_flags = NLM_F_REQUEST;
|
|
req.n.nlmsg_type = XFRM_MSG_GETPOLICY;
|
|
req.n.nlmsg_len = NLMSG_ALIGN(NLMSG_LENGTH(sizeof(req.id)));
|
|
|
|
rsp.n.nlmsg_type = XFRM_MSG_NEWPOLICY;
|
|
|
|
if (!send_netlink_msg(&req.n, &rsp.n, sizeof(rsp), "Get policy", "?"))
|
|
{
|
|
return;
|
|
}
|
|
if (netlink_error("XFRM_MSG_GETPOLICY", &rsp.n, &rsp.u.e, sizeof(rsp.u.pol)))
|
|
{
|
|
return;
|
|
}
|
|
if (req.id.index != rsp.u.pol.index)
|
|
{
|
|
DBG(DBG_KLIPS,
|
|
DBG_log("netlink_policy_expire: policy was replaced: "
|
|
"dir=%d, oldindex=%d, newindex=%d"
|
|
, req.id.dir, req.id.index, rsp.u.pol.index));
|
|
return;
|
|
}
|
|
|
|
if (upe->pol.curlft.add_time != rsp.u.pol.curlft.add_time)
|
|
{
|
|
DBG(DBG_KLIPS,
|
|
DBG_log("netlink_policy_expire: policy was replaced "
|
|
" and you have won the lottery: "
|
|
"dir=%d, index=%d"
|
|
, req.id.dir, req.id.index));
|
|
return;
|
|
}
|
|
|
|
switch (upe->pol.dir)
|
|
{
|
|
case XFRM_POLICY_OUT:
|
|
netlink_shunt_expire(&rsp.u.pol);
|
|
break;
|
|
}
|
|
}
|
|
|
|
static bool netlink_get(void)
|
|
{
|
|
struct {
|
|
struct nlmsghdr n;
|
|
char data[1024];
|
|
} rsp;
|
|
ssize_t r;
|
|
struct sockaddr_nl addr;
|
|
socklen_t alen;
|
|
|
|
alen = sizeof(addr);
|
|
r = recvfrom(netlink_bcast_fd, &rsp, sizeof(rsp), 0
|
|
, (struct sockaddr *)&addr, &alen);
|
|
if (r < 0)
|
|
{
|
|
if (errno == EAGAIN)
|
|
return FALSE;
|
|
if (errno != EINTR)
|
|
log_errno((e, "recvfrom() failed in netlink_get"));
|
|
return TRUE;
|
|
}
|
|
else if ((size_t) r < sizeof(rsp.n))
|
|
{
|
|
plog("netlink_get read truncated message: %ld bytes; ignore message"
|
|
, (long) r);
|
|
return TRUE;
|
|
}
|
|
else if (addr.nl_pid != 0)
|
|
{
|
|
/* not for us: ignore */
|
|
DBG(DBG_KLIPS,
|
|
DBG_log("netlink_get: ignoring %s message from process %u"
|
|
, sparse_val_show(xfrm_type_names, rsp.n.nlmsg_type)
|
|
, addr.nl_pid));
|
|
return TRUE;
|
|
}
|
|
else if ((size_t) r != rsp.n.nlmsg_len)
|
|
{
|
|
plog("netlink_get read message with length %ld that doesn't equal nlmsg_len %lu bytes; ignore message"
|
|
, (long) r
|
|
, (unsigned long) rsp.n.nlmsg_len);
|
|
return TRUE;
|
|
}
|
|
|
|
DBG(DBG_KLIPS,
|
|
DBG_log("netlink_get: %s message"
|
|
, sparse_val_show(xfrm_type_names, rsp.n.nlmsg_type)));
|
|
|
|
switch (rsp.n.nlmsg_type)
|
|
{
|
|
case XFRM_MSG_ACQUIRE:
|
|
netlink_acquire(&rsp.n);
|
|
break;
|
|
case XFRM_MSG_POLEXPIRE:
|
|
netlink_policy_expire(&rsp.n);
|
|
break;
|
|
default:
|
|
/* ignored */
|
|
break;
|
|
}
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
static void netlink_process_msg(void)
|
|
{
|
|
while (netlink_get());
|
|
}
|
|
|
|
static ipsec_spi_t netlink_get_spi(const ip_address *src, const ip_address *dst,
|
|
int proto, bool tunnel_mode, unsigned reqid,
|
|
ipsec_spi_t min, ipsec_spi_t max,
|
|
const char *text_said)
|
|
{
|
|
struct {
|
|
struct nlmsghdr n;
|
|
struct xfrm_userspi_info spi;
|
|
} req;
|
|
|
|
struct {
|
|
struct nlmsghdr n;
|
|
union {
|
|
struct nlmsgerr e;
|
|
struct xfrm_usersa_info sa;
|
|
} u;
|
|
char data[1024];
|
|
} rsp;
|
|
|
|
memset(&req, 0, sizeof(req));
|
|
req.n.nlmsg_flags = NLM_F_REQUEST;
|
|
req.n.nlmsg_type = XFRM_MSG_ALLOCSPI;
|
|
|
|
ip2xfrm(src, &req.spi.info.saddr);
|
|
ip2xfrm(dst, &req.spi.info.id.daddr);
|
|
req.spi.info.mode = tunnel_mode;
|
|
req.spi.info.reqid = reqid;
|
|
req.spi.info.id.proto = proto;
|
|
req.spi.info.family = src->u.v4.sin_family;
|
|
req.spi.min = min;
|
|
req.spi.max = max;
|
|
|
|
req.n.nlmsg_len = NLMSG_ALIGN(NLMSG_LENGTH(sizeof(req.spi)));
|
|
rsp.n.nlmsg_type = XFRM_MSG_NEWSA;
|
|
|
|
if (!send_netlink_msg(&req.n, &rsp.n, sizeof(rsp), "Get SPI", text_said))
|
|
{
|
|
return 0;
|
|
}
|
|
if (netlink_error("XFRM_MSG_ALLOCSPI", &rsp.n, &rsp.u.e, sizeof(rsp.u.sa)))
|
|
{
|
|
return 0;
|
|
}
|
|
DBG(DBG_KLIPS,
|
|
DBG_log("netlink_get_spi: allocated 0x%x for %s"
|
|
, ntohl(rsp.u.sa.id.spi), text_said));
|
|
return rsp.u.sa.id.spi;
|
|
}
|
|
|
|
const struct kernel_ops linux_kernel_ops = {
|
|
type: KERNEL_TYPE_LINUX,
|
|
inbound_eroute: 1,
|
|
policy_lifetime: 1,
|
|
async_fdp: &netlink_bcast_fd,
|
|
|
|
init: init_netlink,
|
|
pfkey_register: linux_pfkey_register,
|
|
pfkey_register_response: linux_pfkey_register_response,
|
|
process_msg: netlink_process_msg,
|
|
raw_eroute: netlink_raw_eroute,
|
|
get_policy: netlink_get_policy,
|
|
add_sa: netlink_add_sa,
|
|
del_sa: netlink_del_sa,
|
|
get_sa: netlink_get_sa,
|
|
process_queue: NULL,
|
|
grp_sa: NULL,
|
|
get_spi: netlink_get_spi,
|
|
};
|
|
#endif /* linux && KLIPS */
|