diff options
Diffstat (limited to 'src/pluto/kernel_netlink.c')
-rw-r--r-- | src/pluto/kernel_netlink.c | 1219 |
1 files changed, 1219 insertions, 0 deletions
diff --git a/src/pluto/kernel_netlink.c b/src/pluto/kernel_netlink.c new file mode 100644 index 000000000..1947ddbac --- /dev/null +++ b/src/pluto/kernel_netlink.c @@ -0,0 +1,1219 @@ +/* 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. + * + * RCSID $Id: kernel_netlink.c,v 1.24 2006/03/10 14:49:43 as Exp $ + */ + +#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 "kameipsec.h" +#include "linux26/rtnetlink.h" +#include "linux26/xfrm.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" + +/* 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_MD5_HMAC, "md5" }, + { SADB_AALG_SHA1_HMAC, "sha1" }, + { SADB_AALG_SHA2_256_HMAC, "sha256" }, + { SADB_AALG_SHA2_384_HMAC, "sha384" }, + { SADB_AALG_SHA2_512_HMAC, "sha512" }, + { SADB_AALG_RIPEMD_160_HMAC, "ripemd160" }, + { SADB_X_AALG_NULL, "null" }, + { 0, sparse_end } +}; + +/* Encryption algorithms */ +static sparse_names ealg_list = { + { SADB_EALG_NULL, "cipher_null" }, + { SADB_EALG_DES_CBC, "des" }, + { SADB_EALG_3DES_CBC, "des3_ede" }, + { SADB_EALG_IDEA_CBC, "idea" }, + { SADB_EALG_CAST_CBC, "cast128" }, + { SADB_EALG_BLOWFISH_CBC, "blowfish" }, + { SADB_EALG_AES_CBC, "aes" }, + { SADB_X_EALG_SERPENT_CBC, "serpent" }, + { SADB_X_EALG_TWOFISH_CBC, "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; + } 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].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; + + 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; + req.p.mode = (sa->encapsulation == ENCAPSULATION_MODE_TUNNEL); + 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) + { + struct xfrm_algo algo; + const char *name; + + name = sparse_name(aalg_list, sa->authalg); + if (!name) { + loglog(RC_LOG_SERIOUS, "unknown authentication algorithm: %u" + , sa->authalg); + return FALSE; + } + + 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); + } + + if (sa->encalg) + { + 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; + } + + strcpy(algo.alg_name, name); + algo.alg_key_len = sa->enckeylen * BITS_PER_BYTE; + + attr->rta_type = XFRMA_ALG_CRYPT; + attr->rta_len = RTA_LENGTH(sizeof(algo) + sa->enckeylen); + + memcpy(RTA_DATA(attr), &algo, sizeof(algo)); + memcpy((char *)RTA_DATA(attr) + sizeof(algo), sa->enckey + , sa->enckeylen); + + req.n.nlmsg_len += attr->rta_len; + 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 */ |