+
+static struct mbuf *
+key_setdumpsastats (u_int32_t dir,
+ struct sastat *stats,
+ u_int32_t max_stats,
+ u_int64_t session_ids[],
+ u_int32_t seq,
+ u_int32_t pid)
+{
+ struct mbuf *result = NULL, *m = NULL;
+
+ m = key_setsadbmsg(SADB_GETSASTAT, 0, 0, seq, pid, 0);
+ if (!m) {
+ goto fail;
+ }
+ result = m;
+
+ m = key_setsadbsession_id(session_ids);
+ if (!m) {
+ goto fail;
+ }
+ m_cat(result, m);
+
+ m = key_setsadbsastat(dir,
+ stats,
+ max_stats);
+ if (!m) {
+ goto fail;
+ }
+ m_cat(result, m);
+
+ if ((result->m_flags & M_PKTHDR) == 0) {
+ goto fail;
+ }
+
+ if (result->m_len < sizeof(struct sadb_msg)) {
+ result = m_pullup(result, sizeof(struct sadb_msg));
+ if (result == NULL) {
+ goto fail;
+ }
+ }
+
+ result->m_pkthdr.len = 0;
+ for (m = result; m; m = m->m_next) {
+ result->m_pkthdr.len += m->m_len;
+ }
+
+ mtod(result, struct sadb_msg *)->sadb_msg_len =
+ PFKEY_UNIT64(result->m_pkthdr.len);
+
+ return result;
+
+fail:
+ if (result) {
+ m_freem(result);
+ }
+ return NULL;
+}
+
+/*
+ * SADB_GETSASTAT processing
+ * dump all stats for matching entries in SAD.
+ *
+ * m will always be freed.
+ */
+
+static int
+key_getsastat (struct socket *so,
+ struct mbuf *m,
+ const struct sadb_msghdr *mhp)
+{
+ struct sadb_session_id *session_id;
+ u_int32_t bufsize, arg_count, res_count;
+ struct sadb_sastat *sa_stats_arg;
+ struct sastat *sa_stats_sav = NULL;
+ struct mbuf *n;
+ int error = 0;
+
+ /* sanity check */
+ if (so == NULL || m == NULL || mhp == NULL || mhp->msg == NULL)
+ panic("%s: NULL pointer is passed.\n", __FUNCTION__);
+
+ if (mhp->ext[SADB_EXT_SESSION_ID] == NULL) {
+ printf("%s: invalid message is passed. missing session-id.\n", __FUNCTION__);
+ return key_senderror(so, m, EINVAL);
+ }
+ if (mhp->extlen[SADB_EXT_SESSION_ID] < sizeof(struct sadb_session_id)) {
+ printf("%s: invalid message is passed. short session-id.\n", __FUNCTION__);
+ return key_senderror(so, m, EINVAL);
+ }
+ if (mhp->ext[SADB_EXT_SASTAT] == NULL) {
+ printf("%s: invalid message is passed. missing stat args.\n", __FUNCTION__);
+ return key_senderror(so, m, EINVAL);
+ }
+ if (mhp->extlen[SADB_EXT_SASTAT] < sizeof(*sa_stats_arg)) {
+ printf("%s: invalid message is passed. short stat args.\n", __FUNCTION__);
+ return key_senderror(so, m, EINVAL);
+ }
+
+ lck_mtx_assert(sadb_mutex, LCK_MTX_ASSERT_NOTOWNED);
+
+ // exit early if there are no active SAs
+ if (ipsec_sav_count <= 0) {
+ printf("%s: No active SAs.\n", __FUNCTION__);
+ error = ENOENT;
+ goto end;
+ }
+ bufsize = (ipsec_sav_count + 1) * sizeof(*sa_stats_sav);
+
+ KMALLOC_WAIT(sa_stats_sav, __typeof__(sa_stats_sav), bufsize);
+ if (sa_stats_sav == NULL) {
+ printf("%s: No more memory.\n", __FUNCTION__);
+ error = ENOMEM;
+ goto end;
+ }
+ bzero(sa_stats_sav, bufsize);
+
+ sa_stats_arg = (__typeof__(sa_stats_arg))
+ (void *)mhp->ext[SADB_EXT_SASTAT];
+ arg_count = sa_stats_arg->sadb_sastat_list_len;
+ // exit early if there are no requested SAs
+ if (arg_count == 0) {
+ printf("%s: No SAs requested.\n", __FUNCTION__);
+ error = ENOENT;
+ goto end;
+ }
+ res_count = 0;
+
+ if (key_getsastatbyspi((struct sastat *)(sa_stats_arg + 1),
+ arg_count,
+ sa_stats_sav,
+ &res_count)) {
+ printf("%s: Error finding SAs.\n", __FUNCTION__);
+ error = ENOENT;
+ goto end;
+ }
+ if (!res_count) {
+ printf("%s: No SAs found.\n", __FUNCTION__);
+ error = ENOENT;
+ goto end;
+ }
+
+ session_id = (__typeof__(session_id))
+ (void *)mhp->ext[SADB_EXT_SESSION_ID];
+
+ /* send this to the userland. */
+ n = key_setdumpsastats(sa_stats_arg->sadb_sastat_dir,
+ sa_stats_sav,
+ res_count,
+ session_id->sadb_session_id_v,
+ mhp->msg->sadb_msg_seq,
+ mhp->msg->sadb_msg_pid);
+ if (!n) {
+ printf("%s: No bufs to dump stats.\n", __FUNCTION__);
+ error = ENOBUFS;
+ goto end;
+ }
+
+ key_sendup_mbuf(so, n, KEY_SENDUP_ALL);
+end:
+ if (sa_stats_sav) {
+ KFREE(sa_stats_sav);
+ }
+
+ if (error)
+ return key_senderror(so, m, error);
+
+ m_freem(m);
+ return 0;
+}
+
+static void
+key_update_natt_keepalive_timestamp (struct secasvar *sav_sent,
+ struct secasvar *sav_update)
+{
+ struct secasindex saidx_swap_sent_addr;
+
+ // exit early if two SAs are identical, or if sav_update is current
+ if (sav_sent == sav_update ||
+ sav_update->natt_last_activity == natt_now) {
+ return;
+ }
+
+ // assuming that (sav_update->remote_ike_port != 0 && (esp_udp_encap_port & 0xFFFF) != 0)
+
+ bzero(&saidx_swap_sent_addr, sizeof(saidx_swap_sent_addr));
+ memcpy(&saidx_swap_sent_addr.src, &sav_sent->sah->saidx.dst, sizeof(saidx_swap_sent_addr.src));
+ memcpy(&saidx_swap_sent_addr.dst, &sav_sent->sah->saidx.src, sizeof(saidx_swap_sent_addr.dst));
+ saidx_swap_sent_addr.proto = sav_sent->sah->saidx.proto;
+ saidx_swap_sent_addr.mode = sav_sent->sah->saidx.mode;
+ // we ignore reqid for split-tunnel setups
+
+ if (key_cmpsaidx(&sav_sent->sah->saidx, &sav_update->sah->saidx, CMP_MODE | CMP_PORT) ||
+ key_cmpsaidx(&saidx_swap_sent_addr, &sav_update->sah->saidx, CMP_MODE | CMP_PORT)) {
+ sav_update->natt_last_activity = natt_now;
+ }
+}
+
+static int
+key_send_delsp (struct secpolicy *sp)
+{
+ struct mbuf *result = NULL, *m;
+
+ if (sp == NULL)
+ goto fail;
+
+ /* set msg header */
+ m = key_setsadbmsg(SADB_X_SPDDELETE, 0, 0, 0, 0, 0);
+ if (!m) {
+ goto fail;
+ }
+ result = m;
+
+ /* set sadb_address(es) for source */
+ if (sp->spidx.src_range.start.ss_len > 0) {
+ m = key_setsadbaddr(SADB_X_EXT_ADDR_RANGE_SRC_START,
+ (struct sockaddr *)&sp->spidx.src_range.start, sp->spidx.prefs,
+ sp->spidx.ul_proto);
+ if (!m)
+ goto fail;
+ m_cat(result, m);
+
+ m = key_setsadbaddr(SADB_X_EXT_ADDR_RANGE_SRC_END,
+ (struct sockaddr *)&sp->spidx.src_range.end, sp->spidx.prefs,
+ sp->spidx.ul_proto);
+ if (!m)
+ goto fail;
+ m_cat(result, m);
+ } else {
+ m = key_setsadbaddr(SADB_EXT_ADDRESS_SRC,
+ (struct sockaddr *)&sp->spidx.src, sp->spidx.prefs,
+ sp->spidx.ul_proto);
+ if (!m)
+ goto fail;
+ m_cat(result, m);
+ }
+
+ /* set sadb_address(es) for destination */
+ if (sp->spidx.dst_range.start.ss_len > 0) {
+ m = key_setsadbaddr(SADB_X_EXT_ADDR_RANGE_DST_START,
+ (struct sockaddr *)&sp->spidx.dst_range.start, sp->spidx.prefd,
+ sp->spidx.ul_proto);
+ if (!m)
+ goto fail;
+ m_cat(result, m);
+
+ m = key_setsadbaddr(SADB_X_EXT_ADDR_RANGE_DST_END,
+ (struct sockaddr *)&sp->spidx.dst_range.end, sp->spidx.prefd,
+ sp->spidx.ul_proto);
+ if (!m)
+ goto fail;
+ m_cat(result, m);
+ } else {
+ m = key_setsadbaddr(SADB_EXT_ADDRESS_DST,
+ (struct sockaddr *)&sp->spidx.dst, sp->spidx.prefd,
+ sp->spidx.ul_proto);
+ if (!m)
+ goto fail;
+ m_cat(result, m);
+ }
+
+ /* set secpolicy */
+ m = key_sp2msg(sp);
+ if (!m) {
+ goto fail;
+ }
+ m_cat(result, m);
+
+ if ((result->m_flags & M_PKTHDR) == 0) {
+ goto fail;
+ }
+
+ if (result->m_len < sizeof(struct sadb_msg)) {
+ result = m_pullup(result, sizeof(struct sadb_msg));
+ if (result == NULL) {
+ goto fail;
+ }
+ }
+
+ result->m_pkthdr.len = 0;
+ for (m = result; m; m = m->m_next)
+ result->m_pkthdr.len += m->m_len;
+
+ mtod(result, struct sadb_msg *)->sadb_msg_len = PFKEY_UNIT64(result->m_pkthdr.len);
+
+ return key_sendup_mbuf(NULL, result, KEY_SENDUP_REGISTERED);
+
+fail:
+ if (result)
+ m_free(result);
+ return -1;
+}
+
+void
+key_delsp_for_ipsec_if (ifnet_t ipsec_if)
+{
+ struct secashead *sah;
+ struct secasvar *sav, *nextsav;
+ u_int stateidx;
+ u_int state;
+ struct secpolicy *sp, *nextsp;
+ int dir;
+
+ if (ipsec_if == NULL)
+ return;
+
+ lck_mtx_assert(sadb_mutex, LCK_MTX_ASSERT_NOTOWNED);
+
+ lck_mtx_lock(sadb_mutex);
+
+ for (dir = 0; dir < IPSEC_DIR_MAX; dir++) {
+ for (sp = LIST_FIRST(&sptree[dir]);
+ sp != NULL;
+ sp = nextsp) {
+
+ nextsp = LIST_NEXT(sp, chain);
+
+ if (sp->ipsec_if == ipsec_if) {
+ ifnet_release(sp->ipsec_if);
+ sp->ipsec_if = NULL;
+
+ key_send_delsp(sp);
+
+ sp->state = IPSEC_SPSTATE_DEAD;
+ key_freesp(sp, KEY_SADB_LOCKED);
+ }
+ }
+ }
+
+ LIST_FOREACH(sah, &sahtree, chain) {
+ if (sah->ipsec_if == ipsec_if) {
+ /* This SAH is linked to the IPSec interface. It now needs to close. */
+ ifnet_release(sah->ipsec_if);
+ sah->ipsec_if = NULL;
+
+ for (stateidx = 0; stateidx < _ARRAYLEN(saorder_state_alive); stateidx++) {
+ state = saorder_state_any[stateidx];
+ for (sav = LIST_FIRST(&sah->savtree[state]); sav != NULL; sav = nextsav) {
+ nextsav = LIST_NEXT(sav, chain);
+
+ key_sa_chgstate(sav, SADB_SASTATE_DEAD);
+ key_freesav(sav, KEY_SADB_LOCKED);
+ }
+ }
+
+ sah->state = SADB_SASTATE_DEAD;
+ }
+ }
+
+ lck_mtx_unlock(sadb_mutex);
+}
+
+__private_extern__ u_int32_t
+key_fill_offload_frames_for_savs (ifnet_t ifp,
+ struct ifnet_keepalive_offload_frame *frames_array,
+ u_int32_t frames_array_count,
+ size_t frame_data_offset)
+{
+ struct secashead *sah = NULL;
+ struct secasvar *sav = NULL;
+ struct ifnet_keepalive_offload_frame *frame = frames_array;
+ u_int32_t frame_index = 0;
+
+ if (frame == NULL || frames_array_count == 0) {
+ return (frame_index);
+ }
+
+ lck_mtx_lock(sadb_mutex);
+ LIST_FOREACH(sah, &sahtree, chain) {
+ LIST_FOREACH(sav, &sah->savtree[SADB_SASTATE_MATURE], chain) {
+ if (ipsec_fill_offload_frame(ifp, sav, frame, frame_data_offset)) {
+ frame_index++;
+ if (frame_index >= frames_array_count) {
+ lck_mtx_unlock(sadb_mutex);
+ return (frame_index);
+ }
+ frame = &(frames_array[frame_index]);
+ }
+ }
+ }
+ lck_mtx_unlock(sadb_mutex);
+
+ return (frame_index);
+}