/* 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 . * * 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 #include #include #include #include #include #include #include #include #include #include "kameipsec.h" #include #include #include #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_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: 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 */