+#pragma mark -- ifnet Provider --
+
+static nstat_provider nstat_ifnet_provider;
+
+/*
+ * We store a pointer to the ifnet and the original threshold
+ * requested by the client.
+ */
+struct nstat_ifnet_cookie
+{
+ struct ifnet *ifp;
+ uint64_t threshold;
+};
+
+static errno_t
+nstat_ifnet_lookup(
+ const void *data,
+ u_int32_t length,
+ nstat_provider_cookie_t *out_cookie)
+{
+ const nstat_ifnet_add_param *param = (nstat_ifnet_add_param *)data;
+ struct ifnet *ifp;
+ boolean_t changed = FALSE;
+ nstat_control_state *state;
+ nstat_src *src;
+ struct nstat_ifnet_cookie *cookie;
+
+ if (length < sizeof(*param) || param->threshold < 1024*1024)
+ return EINVAL;
+ if (nstat_privcheck != 0) {
+ errno_t result = priv_check_cred(kauth_cred_get(),
+ PRIV_NET_PRIVILEGED_NETWORK_STATISTICS, 0);
+ if (result != 0)
+ return result;
+ }
+ cookie = OSMalloc(sizeof(*cookie), nstat_malloc_tag);
+ if (cookie == NULL)
+ return ENOMEM;
+ bzero(cookie, sizeof(*cookie));
+
+ ifnet_head_lock_shared();
+ TAILQ_FOREACH(ifp, &ifnet_head, if_link)
+ {
+ ifnet_lock_exclusive(ifp);
+ if (ifp->if_index == param->ifindex)
+ {
+ cookie->ifp = ifp;
+ cookie->threshold = param->threshold;
+ *out_cookie = cookie;
+ if (!ifp->if_data_threshold ||
+ ifp->if_data_threshold > param->threshold)
+ {
+ changed = TRUE;
+ ifp->if_data_threshold = param->threshold;
+ }
+ ifnet_lock_done(ifp);
+ ifnet_reference(ifp);
+ break;
+ }
+ ifnet_lock_done(ifp);
+ }
+ ifnet_head_done();
+
+ /*
+ * When we change the threshold to something smaller, we notify
+ * all of our clients with a description message.
+ * We won't send a message to the client we are currently serving
+ * because it has no `ifnet source' yet.
+ */
+ if (changed)
+ {
+ lck_mtx_lock(&nstat_mtx);
+ for (state = nstat_controls; state; state = state->ncs_next)
+ {
+ lck_mtx_lock(&state->mtx);
+ for (src = state->ncs_srcs; src; src = src->next)
+ {
+ if (src->provider != &nstat_ifnet_provider)
+ continue;
+ nstat_control_send_description(state, src, 0);
+ }
+ lck_mtx_unlock(&state->mtx);
+ }
+ lck_mtx_unlock(&nstat_mtx);
+ }
+ if (cookie->ifp == NULL)
+ OSFree(cookie, sizeof(*cookie), nstat_malloc_tag);
+
+ return ifp ? 0 : EINVAL;
+}
+
+static int
+nstat_ifnet_gone(
+ nstat_provider_cookie_t cookie)
+{
+ struct ifnet *ifp;
+ struct nstat_ifnet_cookie *ifcookie =
+ (struct nstat_ifnet_cookie *)cookie;
+
+ ifnet_head_lock_shared();
+ TAILQ_FOREACH(ifp, &ifnet_head, if_link)
+ {
+ if (ifp == ifcookie->ifp)
+ break;
+ }
+ ifnet_head_done();
+
+ return ifp ? 0 : 1;
+}
+
+static errno_t
+nstat_ifnet_counts(
+ nstat_provider_cookie_t cookie,
+ struct nstat_counts *out_counts,
+ int *out_gone)
+{
+ struct nstat_ifnet_cookie *ifcookie =
+ (struct nstat_ifnet_cookie *)cookie;
+ struct ifnet *ifp = ifcookie->ifp;
+
+ *out_gone = 0;
+
+ // if the ifnet is gone, we should stop using it
+ if (nstat_ifnet_gone(cookie))
+ {
+ *out_gone = 1;
+ return EINVAL;
+ }
+
+ bzero(out_counts, sizeof(*out_counts));
+ out_counts->nstat_rxpackets = ifp->if_ipackets;
+ out_counts->nstat_rxbytes = ifp->if_ibytes;
+ out_counts->nstat_txpackets = ifp->if_opackets;
+ out_counts->nstat_txbytes = ifp->if_obytes;
+ out_counts->nstat_cell_rxbytes = out_counts->nstat_cell_txbytes = 0;
+
+ return 0;
+}
+
+static void
+nstat_ifnet_release(
+ nstat_provider_cookie_t cookie,
+ __unused int locked)
+{
+ struct nstat_ifnet_cookie *ifcookie;
+ struct ifnet *ifp;
+ nstat_control_state *state;
+ nstat_src *src;
+ uint64_t minthreshold = UINT64_MAX;
+
+ /*
+ * Find all the clients that requested a threshold
+ * for this ifnet and re-calculate if_data_threshold.
+ */
+ lck_mtx_lock(&nstat_mtx);
+ for (state = nstat_controls; state; state = state->ncs_next)
+ {
+ lck_mtx_lock(&state->mtx);
+ for (src = state->ncs_srcs; src; src = src->next)
+ {
+ /* Skip the provider we are about to detach. */
+ if (src->provider != &nstat_ifnet_provider ||
+ src->cookie == cookie)
+ continue;
+ ifcookie = (struct nstat_ifnet_cookie *)src->cookie;
+ if (ifcookie->threshold < minthreshold)
+ minthreshold = ifcookie->threshold;
+ }
+ lck_mtx_unlock(&state->mtx);
+ }
+ lck_mtx_unlock(&nstat_mtx);
+ /*
+ * Reset if_data_threshold or disable it.
+ */
+ ifcookie = (struct nstat_ifnet_cookie *)cookie;
+ ifp = ifcookie->ifp;
+ if (ifnet_is_attached(ifp, 1)) {
+ ifnet_lock_exclusive(ifp);
+ if (minthreshold == UINT64_MAX)
+ ifp->if_data_threshold = 0;
+ else
+ ifp->if_data_threshold = minthreshold;
+ ifnet_lock_done(ifp);
+ ifnet_decr_iorefcnt(ifp);
+ }
+ ifnet_release(ifp);
+ OSFree(ifcookie, sizeof(*ifcookie), nstat_malloc_tag);
+}
+
+static errno_t
+nstat_ifnet_copy_descriptor(
+ nstat_provider_cookie_t cookie,
+ void *data,
+ u_int32_t len)
+{
+ nstat_ifnet_descriptor *desc = (nstat_ifnet_descriptor *)data;
+ struct nstat_ifnet_cookie *ifcookie =
+ (struct nstat_ifnet_cookie *)cookie;
+ struct ifnet *ifp = ifcookie->ifp;
+
+ if (len < sizeof(nstat_ifnet_descriptor))
+ return EINVAL;
+
+ if (nstat_ifnet_gone(cookie))
+ return EINVAL;
+
+ bzero(desc, sizeof(*desc));
+ ifnet_lock_shared(ifp);
+ strlcpy(desc->name, ifp->if_xname, sizeof(desc->name));
+ desc->ifindex = ifp->if_index;
+ desc->threshold = ifp->if_data_threshold;
+ desc->type = ifp->if_type;
+ if (ifp->if_desc.ifd_len < sizeof(desc->description))
+ memcpy(desc->description, ifp->if_desc.ifd_desc,
+ sizeof(desc->description));
+ ifnet_lock_done(ifp);
+
+ return 0;
+}
+
+static void
+nstat_init_ifnet_provider(void)
+{
+ bzero(&nstat_ifnet_provider, sizeof(nstat_ifnet_provider));
+ nstat_ifnet_provider.nstat_provider_id = NSTAT_PROVIDER_IFNET;
+ nstat_ifnet_provider.nstat_descriptor_length = sizeof(nstat_ifnet_descriptor);
+ nstat_ifnet_provider.nstat_lookup = nstat_ifnet_lookup;
+ nstat_ifnet_provider.nstat_gone = nstat_ifnet_gone;
+ nstat_ifnet_provider.nstat_counts = nstat_ifnet_counts;
+ nstat_ifnet_provider.nstat_watcher_add = NULL;
+ nstat_ifnet_provider.nstat_watcher_remove = NULL;
+ nstat_ifnet_provider.nstat_copy_descriptor = nstat_ifnet_copy_descriptor;
+ nstat_ifnet_provider.nstat_release = nstat_ifnet_release;
+ nstat_ifnet_provider.next = nstat_providers;
+ nstat_providers = &nstat_ifnet_provider;
+}
+
+__private_extern__ void
+nstat_ifnet_threshold_reached(unsigned int ifindex)
+{
+ nstat_control_state *state;
+ nstat_src *src;
+ struct ifnet *ifp;
+ struct nstat_ifnet_cookie *ifcookie;
+
+ lck_mtx_lock(&nstat_mtx);
+ for (state = nstat_controls; state; state = state->ncs_next)
+ {
+ lck_mtx_lock(&state->mtx);
+ for (src = state->ncs_srcs; src; src = src->next)
+ {
+ if (src->provider != &nstat_ifnet_provider)
+ continue;
+ ifcookie = (struct nstat_ifnet_cookie *)src->cookie;
+ ifp = ifcookie->ifp;
+ if (ifp->if_index != ifindex)
+ continue;
+ nstat_control_send_counts(state, src, 0, NULL);
+ }
+ lck_mtx_unlock(&state->mtx);
+ }
+ lck_mtx_unlock(&nstat_mtx);
+}
+
+#pragma mark -- Sysinfo Provider --
+
+static nstat_provider nstat_sysinfo_provider;
+
+/* We store the flags requested by the client */
+typedef struct nstat_sysinfo_cookie
+{
+ u_int32_t flags;
+} nstat_sysinfo_cookie;
+
+static errno_t
+nstat_sysinfo_lookup(
+ const void *data,
+ u_int32_t length,
+ nstat_provider_cookie_t *out_cookie)
+{
+ const nstat_sysinfo_add_param *param = (nstat_sysinfo_add_param *)data;
+ nstat_sysinfo_cookie *cookie;
+
+ if (length < sizeof(*param))
+ return (EINVAL);
+
+ if (nstat_privcheck != 0) {
+ errno_t result = priv_check_cred(kauth_cred_get(),
+ PRIV_NET_PRIVILEGED_NETWORK_STATISTICS, 0);
+ if (result != 0)
+ return (result);
+ }
+
+ cookie = OSMalloc(sizeof(*cookie), nstat_malloc_tag);
+ if (cookie == NULL)
+ return (ENOMEM);
+ cookie->flags = param->flags;
+ *out_cookie = cookie;
+ return (0);
+}
+
+static int
+nstat_sysinfo_gone(
+ __unused nstat_provider_cookie_t cookie)
+{
+ /* Sysinfo always exists */
+ return (0);
+}
+
+static errno_t
+nstat_sysinfo_copy_descriptor(
+ nstat_provider_cookie_t cookie,
+ void *data,
+ u_int32_t len)
+{
+ nstat_sysinfo_descriptor *desc = (nstat_sysinfo_descriptor *)data;
+ struct nstat_sysinfo_cookie *syscookie =
+ (struct nstat_sysinfo_cookie *)cookie;
+
+ if (len < sizeof(nstat_sysinfo_descriptor))
+ return (EINVAL);
+ desc->flags = syscookie->flags;
+ return (0);
+}
+
+static void
+nstat_sysinfo_release(
+ nstat_provider_cookie_t cookie,
+ __unused boolean_t locked)
+{
+ struct nstat_sysinfo_cookie *syscookie =
+ (struct nstat_sysinfo_cookie *)cookie;
+ OSFree(syscookie, sizeof(*syscookie), nstat_malloc_tag);
+}
+
+static errno_t
+nstat_enqueue_success(
+ uint64_t context,
+ nstat_control_state *state)
+{
+ nstat_msg_hdr success;
+ errno_t result;
+
+ bzero(&success, sizeof(success));
+ success.context = context;
+ success.type = NSTAT_MSG_TYPE_SUCCESS;
+ result = ctl_enqueuedata(state->ncs_kctl, state->ncs_unit, &success,
+ sizeof(success), CTL_DATA_EOR | CTL_DATA_CRIT);
+ if (result != 0) {
+ printf("%s: could not enqueue success message %d\n",
+ __func__, result);
+ nstat_successmsgfailures += 1;
+ }
+ return result;
+}
+
+static void
+nstat_init_sysinfo_provider(void)
+{
+ bzero(&nstat_sysinfo_provider, sizeof(nstat_sysinfo_provider));
+ nstat_sysinfo_provider.nstat_provider_id = NSTAT_PROVIDER_SYSINFO;
+ nstat_sysinfo_provider.nstat_descriptor_length = sizeof(nstat_sysinfo_descriptor);
+ nstat_sysinfo_provider.nstat_lookup = nstat_sysinfo_lookup;
+ nstat_sysinfo_provider.nstat_gone = nstat_sysinfo_gone;
+ nstat_sysinfo_provider.nstat_counts = NULL;
+ nstat_sysinfo_provider.nstat_watcher_add = NULL;
+ nstat_sysinfo_provider.nstat_watcher_remove = NULL;
+ nstat_sysinfo_provider.nstat_copy_descriptor = nstat_sysinfo_copy_descriptor;
+ nstat_sysinfo_provider.nstat_release = nstat_sysinfo_release;
+ nstat_sysinfo_provider.next = nstat_providers;
+ nstat_providers = &nstat_sysinfo_provider;
+}
+
+static void
+nstat_sysinfo_send_data_internal(
+ nstat_control_state *control,
+ nstat_src *src,
+ nstat_sysinfo_data *data)
+{
+ nstat_msg_sysinfo_counts *syscnt = NULL;
+ size_t allocsize = 0, countsize = 0, nkeyvals = 0;
+ nstat_sysinfo_keyval *kv;
+ errno_t result = 0;
+
+ allocsize = offsetof(nstat_msg_sysinfo_counts, counts);
+ countsize = offsetof(nstat_sysinfo_counts, nstat_sysinfo_keyvals);
+
+ /* get number of key-vals for each kind of stat */
+ switch (data->flags)
+ {
+ case NSTAT_SYSINFO_MBUF_STATS:
+ nkeyvals = 5;
+ break;
+ case NSTAT_SYSINFO_TCP_STATS:
+ nkeyvals = 6;
+ break;
+ default:
+ return;
+ }
+ countsize += sizeof(nstat_sysinfo_keyval) * nkeyvals;
+ allocsize += countsize;
+
+ syscnt = OSMalloc(allocsize, nstat_malloc_tag);
+ if (syscnt == NULL)
+ return;
+ bzero(syscnt, allocsize);
+
+ syscnt->hdr.type = NSTAT_MSG_TYPE_SYSINFO_COUNTS;
+ syscnt->counts.nstat_sysinfo_len = countsize;
+ syscnt->srcref = src->srcref;
+
+ kv = (nstat_sysinfo_keyval *) &syscnt->counts.nstat_sysinfo_keyvals;
+ switch (data->flags)
+ {
+ case NSTAT_SYSINFO_MBUF_STATS:
+ {
+ kv[0].nstat_sysinfo_key = NSTAT_SYSINFO_KEY_MBUF_256B_TOTAL;
+ kv[0].nstat_sysinfo_flags = NSTAT_SYSINFO_FLAG_SCALAR;
+ kv[0].u.nstat_sysinfo_scalar = data->u.mb_stats.total_256b;
+
+ kv[1].nstat_sysinfo_key = NSTAT_SYSINFO_KEY_MBUF_2KB_TOTAL;
+ kv[1].nstat_sysinfo_flags = NSTAT_SYSINFO_FLAG_SCALAR;
+ kv[1].u.nstat_sysinfo_scalar = data->u.mb_stats.total_2kb;
+
+ kv[2].nstat_sysinfo_key = NSTAT_SYSINFO_KEY_MBUF_4KB_TOTAL;
+ kv[2].nstat_sysinfo_flags = NSTAT_SYSINFO_FLAG_SCALAR;
+ kv[2].u.nstat_sysinfo_scalar = data->u.mb_stats.total_4kb;
+
+ kv[3].nstat_sysinfo_key = NSTAT_SYSINFO_KEY_SOCK_MBCNT;
+ kv[3].nstat_sysinfo_flags = NSTAT_SYSINFO_FLAG_SCALAR;
+ kv[3].u.nstat_sysinfo_scalar = data->u.mb_stats.sbmb_total;
+
+
+ kv[4].nstat_sysinfo_key = NSTAT_SYSINFO_KEY_SOCK_ATMBLIMIT;
+ kv[4].nstat_sysinfo_flags = NSTAT_SYSINFO_FLAG_SCALAR;
+ kv[4].u.nstat_sysinfo_scalar = data->u.mb_stats.sb_atmbuflimit;
+ break;
+ }
+ case NSTAT_SYSINFO_TCP_STATS:
+ {
+ kv[0].nstat_sysinfo_key = NSTAT_SYSINFO_KEY_IPV4_AVGRTT;
+ kv[0].nstat_sysinfo_flags = NSTAT_SYSINFO_FLAG_SCALAR;
+ kv[0].u.nstat_sysinfo_scalar = data->u.tcp_stats.ipv4_avgrtt;
+
+ kv[1].nstat_sysinfo_key = NSTAT_SYSINFO_KEY_IPV6_AVGRTT;
+ kv[1].nstat_sysinfo_flags = NSTAT_SYSINFO_FLAG_SCALAR;
+ kv[1].u.nstat_sysinfo_scalar = data->u.tcp_stats.ipv6_avgrtt;
+
+ kv[2].nstat_sysinfo_key = NSTAT_SYSINFO_KEY_SEND_PLR;
+ kv[2].nstat_sysinfo_flags = NSTAT_SYSINFO_FLAG_SCALAR;
+ kv[2].u.nstat_sysinfo_scalar = data->u.tcp_stats.send_plr;
+
+ kv[3].nstat_sysinfo_key = NSTAT_SYSINFO_KEY_RECV_PLR;
+ kv[3].nstat_sysinfo_flags = NSTAT_SYSINFO_FLAG_SCALAR;
+ kv[3].u.nstat_sysinfo_scalar = data->u.tcp_stats.recv_plr;
+
+ kv[4].nstat_sysinfo_key = NSTAT_SYSINFO_KEY_SEND_TLRTO;
+ kv[4].nstat_sysinfo_flags = NSTAT_SYSINFO_FLAG_SCALAR;
+ kv[4].u.nstat_sysinfo_scalar = data->u.tcp_stats.send_tlrto_rate;
+
+ kv[5].nstat_sysinfo_key = NSTAT_SYSINFO_KEY_SEND_REORDERRATE;
+ kv[5].nstat_sysinfo_flags = NSTAT_SYSINFO_FLAG_SCALAR;
+ kv[5].u.nstat_sysinfo_scalar = data->u.tcp_stats.send_reorder_rate;
+ break;
+ }
+ }
+
+ if (syscnt != NULL)
+ {
+ result = ctl_enqueuedata(control->ncs_kctl,
+ control->ncs_unit, syscnt, allocsize, CTL_DATA_EOR);
+ if (result != 0)
+ nstat_sysinfofailures += 1;
+ OSFree(syscnt, allocsize, nstat_malloc_tag);
+ }
+ return;
+}
+
+__private_extern__ void
+nstat_sysinfo_send_data(
+ nstat_sysinfo_data *data)
+{
+ nstat_control_state *control;
+
+ lck_mtx_lock(&nstat_mtx);
+ for (control = nstat_controls; control; control = control->ncs_next)
+ {
+ lck_mtx_lock(&control->mtx);
+ nstat_src *src;
+ for (src = control->ncs_srcs; src; src = src->next)
+ {
+ if (src->provider->nstat_provider_id ==
+ NSTAT_PROVIDER_SYSINFO)
+ {
+ struct nstat_sysinfo_cookie *syscookie;
+ syscookie = (struct nstat_sysinfo_cookie *) src->cookie;
+ if (syscookie->flags & data->flags)
+ {
+ nstat_sysinfo_send_data_internal(control,
+ src, data);
+ }
+ }
+ }
+ lck_mtx_unlock(&control->mtx);
+ }
+ lck_mtx_unlock(&nstat_mtx);
+
+}
+
+static void
+nstat_sysinfo_generate_report(void)
+{
+ mbuf_report_peak_usage();
+ tcp_report_stats();
+}
+