X-Git-Url: https://git.saurik.com/apple/xnu.git/blobdiff_plain/e2d2fc5c71f7d145cba7267989251af45e3bb5ba..3e170ce000f1506b7b5d2c5c7faec85ceabb573d:/bsd/net/ntstat.c diff --git a/bsd/net/ntstat.c b/bsd/net/ntstat.c index 833b8ca34..8a295f887 100644 --- a/bsd/net/ntstat.c +++ b/bsd/net/ntstat.c @@ -1,5 +1,5 @@ /* - * Copyright (c) 2010-2011 Apple Inc. All rights reserved. + * Copyright (c) 2010-2015 Apple Inc. All rights reserved. * * @APPLE_OSREFERENCE_LICENSE_HEADER_START@ * @@ -34,6 +34,9 @@ #include #include #include +#include +#include +#include #include #include @@ -44,6 +47,8 @@ #include #include +#include +#include #include #include @@ -53,6 +58,7 @@ #include #include #include +#include #include #include #include @@ -62,26 +68,115 @@ __private_extern__ int nstat_collect = 1; SYSCTL_INT(_net, OID_AUTO, statistics, CTLFLAG_RW | CTLFLAG_LOCKED, &nstat_collect, 0, "Collect detailed statistics"); +static int nstat_privcheck = 0; +SYSCTL_INT(_net, OID_AUTO, statistics_privcheck, CTLFLAG_RW | CTLFLAG_LOCKED, + &nstat_privcheck, 0, "Entitlement check"); + +SYSCTL_NODE(_net, OID_AUTO, stats, + CTLFLAG_RW|CTLFLAG_LOCKED, 0, "network statistics"); + +static int nstat_debug = 0; +SYSCTL_INT(_net_stats, OID_AUTO, debug, CTLFLAG_RW | CTLFLAG_LOCKED, + &nstat_debug, 0, ""); + +static int nstat_sendspace = 2048; +SYSCTL_INT(_net_stats, OID_AUTO, sendspace, CTLFLAG_RW | CTLFLAG_LOCKED, + &nstat_sendspace, 0, ""); + +static int nstat_recvspace = 8192; +SYSCTL_INT(_net_stats, OID_AUTO, recvspace, CTLFLAG_RW | CTLFLAG_LOCKED, + &nstat_recvspace, 0, ""); + +static struct nstat_stats nstat_stats; +SYSCTL_STRUCT(_net_stats, OID_AUTO, stats, CTLFLAG_RD | CTLFLAG_LOCKED, + &nstat_stats, nstat_stats, ""); + + +enum +{ + NSTAT_FLAG_CLEANUP = (1 << 0), + NSTAT_FLAG_REQCOUNTS = (1 << 1), + NSTAT_FLAG_SUPPORTS_UPDATES = (1 << 2), + NSTAT_FLAG_SYSINFO_SUBSCRIBED = (1 << 3), +}; + +#define QUERY_CONTINUATION_SRC_COUNT 100 + typedef struct nstat_control_state { - struct nstat_control_state *next; - u_int32_t watching; + struct nstat_control_state *ncs_next; + u_int32_t ncs_watching; decl_lck_mtx_data(, mtx); - kern_ctl_ref kctl; - u_int32_t unit; - nstat_src_ref_t next_srcref; - struct nstat_src *srcs; - int cleanup; - int suser; + kern_ctl_ref ncs_kctl; + u_int32_t ncs_unit; + nstat_src_ref_t ncs_next_srcref; + struct nstat_src *ncs_srcs; + mbuf_t ncs_accumulated; + u_int32_t ncs_flags; + u_int64_t ncs_provider_filters[NSTAT_PROVIDER_COUNT]; + /* state maintained for partial query requests */ + u_int64_t ncs_context; + u_int64_t ncs_seq; } nstat_control_state; +typedef struct nstat_provider +{ + struct nstat_provider *next; + nstat_provider_id_t nstat_provider_id; + size_t nstat_descriptor_length; + errno_t (*nstat_lookup)(const void *data, u_int32_t length, nstat_provider_cookie_t *out_cookie); + int (*nstat_gone)(nstat_provider_cookie_t cookie); + errno_t (*nstat_counts)(nstat_provider_cookie_t cookie, struct nstat_counts *out_counts, int *out_gone); + errno_t (*nstat_watcher_add)(nstat_control_state *state); + void (*nstat_watcher_remove)(nstat_control_state *state); + errno_t (*nstat_copy_descriptor)(nstat_provider_cookie_t cookie, void *data, u_int32_t len); + void (*nstat_release)(nstat_provider_cookie_t cookie, boolean_t locked); + bool (*nstat_reporting_allowed)(nstat_provider_cookie_t cookie, uint64_t filter); +} nstat_provider; + + +typedef struct nstat_src +{ + struct nstat_src *next; + nstat_src_ref_t srcref; + nstat_provider *provider; + nstat_provider_cookie_t cookie; + uint32_t filter; + uint64_t seq; +} nstat_src; + +static errno_t nstat_control_send_counts(nstat_control_state *, + nstat_src *, unsigned long long, u_int16_t, int *); +static int nstat_control_send_description(nstat_control_state *state, nstat_src *src, u_int64_t context, u_int16_t hdr_flags); +static int nstat_control_send_update(nstat_control_state *state, nstat_src *src, u_int64_t context, u_int16_t hdr_flags, int *gone); +static errno_t nstat_control_send_removed(nstat_control_state *, nstat_src *); +static errno_t nstat_control_send_goodbye(nstat_control_state *state, nstat_src *src); +static void nstat_control_cleanup_source(nstat_control_state *state, nstat_src *src, boolean_t); +static bool nstat_control_reporting_allowed(nstat_control_state *state, nstat_src *src); +static boolean_t nstat_control_begin_query(nstat_control_state *state, const nstat_msg_hdr *hdrp); +static u_int16_t nstat_control_end_query(nstat_control_state *state, nstat_src *last_src, boolean_t partial); + +static u_int32_t nstat_udp_watchers = 0; +static u_int32_t nstat_tcp_watchers = 0; + static void nstat_control_register(void); +/* + * The lock order is as follows: + * + * socket_lock (inpcb) + * nstat_mtx + * state->mtx + */ static volatile OSMallocTag nstat_malloc_tag = NULL; static nstat_control_state *nstat_controls = NULL; -static uint64_t nstat_idle_time = 0ULL; +static uint64_t nstat_idle_time = 0; static decl_lck_mtx_data(, nstat_mtx); +/* some extern definitions */ +extern void mbuf_report_peak_usage(void); +extern void tcp_report_stats(void); + static void nstat_copy_sa_out( const struct sockaddr *src, @@ -94,12 +189,12 @@ nstat_copy_sa_out( if (src->sa_family == AF_INET6 && src->sa_len >= sizeof(struct sockaddr_in6)) { - struct sockaddr_in6 *sin6 = (struct sockaddr_in6*)dst; + struct sockaddr_in6 *sin6 = (struct sockaddr_in6*)(void *)dst; if (IN6_IS_SCOPE_EMBED(&sin6->sin6_addr)) { if (sin6->sin6_scope_id == 0) - sin6->sin6_scope_id = ntohs(sin6->sin6_addr.__u6_addr.__u6_addr16[1]); - sin6->sin6_addr.__u6_addr.__u6_addr16[1] = 0; + sin6->sin6_scope_id = ntohs(sin6->sin6_addr.s6_addr16[1]); + sin6->sin6_addr.s6_addr16[1] = 0; } } } @@ -136,26 +231,63 @@ nstat_ip6_to_sockaddr( sin6->sin6_addr = *ip6; if (IN6_IS_SCOPE_EMBED(&sin6->sin6_addr)) { - sin6->sin6_scope_id = ntohs(sin6->sin6_addr.__u6_addr.__u6_addr16[1]); - sin6->sin6_addr.__u6_addr.__u6_addr16[1] = 0; + sin6->sin6_scope_id = ntohs(sin6->sin6_addr.s6_addr16[1]); + sin6->sin6_addr.s6_addr16[1] = 0; } } -#pragma mark -- Network Statistic Providers -- - -typedef struct nstat_provider +static u_int16_t +nstat_inpcb_to_flags( + const struct inpcb *inp) { - struct nstat_provider *next; - nstat_provider_id_t nstat_provider_id; - size_t nstat_descriptor_length; - errno_t (*nstat_lookup)(const void *data, u_int32_t length, nstat_provider_cookie_t *out_cookie); - int (*nstat_gone)(nstat_provider_cookie_t cookie); - errno_t (*nstat_counts)(nstat_provider_cookie_t cookie, struct nstat_counts *out_counts, int *out_gone); - errno_t (*nstat_watcher_add)(nstat_control_state *state); - void (*nstat_watcher_remove)(nstat_control_state *state); - errno_t (*nstat_copy_descriptor)(nstat_provider_cookie_t cookie, void *data, u_int32_t len); - void (*nstat_release)(nstat_provider_cookie_t cookie); -} nstat_provider; + u_int16_t flags = 0; + + if ((inp != NULL ) && (inp->inp_last_outifp != NULL)) + { + struct ifnet *ifp = inp->inp_last_outifp; + + u_int32_t functional_type = if_functional_type(ifp); + + /* Panic if someone adds a functional type without updating ntstat. */ + VERIFY(0 <= functional_type && functional_type <= IFRTYPE_FUNCTIONAL_LAST); + + switch (functional_type) + { + case IFRTYPE_FUNCTIONAL_UNKNOWN: + flags |= NSTAT_IFNET_IS_UNKNOWN_TYPE; + break; + case IFRTYPE_FUNCTIONAL_LOOPBACK: + flags |= NSTAT_IFNET_IS_LOOPBACK; + break; + case IFRTYPE_FUNCTIONAL_WIRED: + flags |= NSTAT_IFNET_IS_WIRED; + break; + case IFRTYPE_FUNCTIONAL_WIFI_INFRA: + flags |= NSTAT_IFNET_IS_WIFI; + break; + case IFRTYPE_FUNCTIONAL_WIFI_AWDL: + flags |= NSTAT_IFNET_IS_WIFI; + flags |= NSTAT_IFNET_IS_AWDL; + break; + case IFRTYPE_FUNCTIONAL_CELLULAR: + flags |= NSTAT_IFNET_IS_CELLULAR; + break; + } + + if (IFNET_IS_EXPENSIVE(ifp)) + { + flags |= NSTAT_IFNET_IS_EXPENSIVE; + } + } + else + { + flags = NSTAT_IFNET_IS_UNKNOWN_TYPE; + } + + return flags; +} + +#pragma mark -- Network Statistic Providers -- static errno_t nstat_control_source_add(u_int64_t context, nstat_control_state *state, nstat_provider *provider, nstat_provider_cookie_t cookie); struct nstat_provider *nstat_providers = NULL; @@ -186,7 +318,6 @@ nstat_lookup_entry( *out_provider = nstat_find_provider_by_id(id); if (*out_provider == NULL) { - printf("%s:%d: provider %u not found\n", __FUNCTION__, __LINE__, id); return ENOENT; } @@ -196,8 +327,9 @@ nstat_lookup_entry( static void nstat_init_route_provider(void); static void nstat_init_tcp_provider(void); static void nstat_init_udp_provider(void); +static void nstat_init_ifnet_provider(void); -static void +__private_extern__ void nstat_init(void) { if (nstat_malloc_tag != NULL) return; @@ -214,6 +346,7 @@ nstat_init(void) nstat_init_route_provider(); nstat_init_tcp_provider(); nstat_init_udp_provider(); + nstat_init_ifnet_provider(); nstat_control_register(); } } @@ -241,7 +374,7 @@ nstat_malloc_aligned( u_int8_t *aligned = buffer + sizeof(*hdr); aligned = (u_int8_t*)P2ROUNDUP(aligned, alignment); - hdr = (struct align_header*)(aligned - sizeof(*hdr)); + hdr = (struct align_header*)(void *)(aligned - sizeof(*hdr)); hdr->offset = aligned - buffer; hdr->length = size; @@ -253,7 +386,7 @@ nstat_free_aligned( void *buffer, OSMallocTag tag) { - struct align_header *hdr = (struct align_header*)((u_int8_t*)buffer - sizeof(*hdr)); + struct align_header *hdr = (struct align_header*)(void *)((u_int8_t*)buffer - sizeof(*hdr)); OSFree(((char*)buffer) - hdr->offset, hdr->length, tag); } @@ -280,7 +413,6 @@ nstat_route_lookup( if (length < sizeof(*param)) { - printf("%s:%d: expected %lu byte param, received %u\n", __FUNCTION__, __LINE__, sizeof(*param), length); return EINVAL; } @@ -288,19 +420,22 @@ nstat_route_lookup( param->dst.v4.sin_family > AF_MAX || (param->mask.v4.sin_family != 0 && param->mask.v4.sin_family != param->dst.v4.sin_family)) { - printf("%s:%d invalid family (dst=%d, mask=%d)\n", __FUNCTION__, __LINE__, - param->dst.v4.sin_family, param->mask.v4.sin_family); return EINVAL; } if (param->dst.v4.sin_len > sizeof(param->dst) || (param->mask.v4.sin_family && param->mask.v4.sin_len > sizeof(param->mask.v4.sin_len))) { - printf("%s:%d invalid length (dst=%d, mask=%d)\n", __FUNCTION__, __LINE__, - param->dst.v4.sin_len, param->mask.v4.sin_len); + return EINVAL; + } + if ((param->dst.v4.sin_family == AF_INET && + param->dst.v4.sin_len < sizeof(struct sockaddr_in)) || + (param->dst.v6.sin6_family == AF_INET6 && + param->dst.v6.sin6_len < sizeof(struct sockaddr_in6))) + { + return EINVAL; } - // TBD: Need to validate length of sockaddr for different families? dst.const_sa = (const struct sockaddr*)¶m->dst; mask.const_sa = param->mask.v4.sin_family ? (const struct sockaddr*)¶m->mask : NULL; @@ -333,9 +468,9 @@ nstat_route_counts( struct rtentry *rt = (struct rtentry*)cookie; struct nstat_counts *rt_stats = rt->rt_stats; - *out_gone = 0; + if (out_gone) *out_gone = 0; - if ((rt->rt_flags & RTF_UP) == 0) *out_gone = 1; + if (out_gone && (rt->rt_flags & RTF_UP) == 0) *out_gone = 1; if (rt_stats) { @@ -351,16 +486,20 @@ nstat_route_counts( out_counts->nstat_min_rtt = rt_stats->nstat_min_rtt; out_counts->nstat_avg_rtt = rt_stats->nstat_avg_rtt; out_counts->nstat_var_rtt = rt_stats->nstat_var_rtt; + out_counts->nstat_cell_rxbytes = out_counts->nstat_cell_txbytes = 0; } else + { bzero(out_counts, sizeof(*out_counts)); + } return 0; } static void nstat_route_release( - nstat_provider_cookie_t cookie) + nstat_provider_cookie_t cookie, + __unused int locked) { rtfree((struct rtentry*)cookie); } @@ -421,7 +560,6 @@ nstat_route_add_watcher( result = rnh->rnh_walktree(rnh, nstat_route_walktree_add, state); if (result != 0) { - printf("%s:%d rnh_walktree failed: %d\n", __FUNCTION__, __LINE__, result); break; } } @@ -441,9 +579,9 @@ nstat_route_new_entry( if ((rt->rt_flags & RTF_UP) != 0) { nstat_control_state *state; - for (state = nstat_controls; state; state = state->next) + for (state = nstat_controls; state; state = state->ncs_next) { - if ((state->watching & (1 << NSTAT_PROVIDER_ROUTE)) != 0) + if ((state->ncs_watching & (1 << NSTAT_PROVIDER_ROUTE)) != 0) { // this client is watching routes // acquire a reference for the route @@ -474,15 +612,14 @@ nstat_route_copy_descriptor( nstat_route_descriptor *desc = (nstat_route_descriptor*)data; if (len < sizeof(*desc)) { - printf("%s:%d invalid length, wanted %lu, got %d\n", __FUNCTION__, __LINE__, sizeof(*desc), len); return EINVAL; } bzero(desc, sizeof(*desc)); struct rtentry *rt = (struct rtentry*)cookie; - desc->id = (uintptr_t)rt; - desc->parent_id = (uintptr_t)rt->rt_parent; - desc->gateway_id = (uintptr_t)rt->rt_gwroute; + desc->id = (uint64_t)VM_KERNEL_ADDRPERM(rt); + desc->parent_id = (uint64_t)VM_KERNEL_ADDRPERM(rt->rt_parent); + desc->gateway_id = (uint64_t)VM_KERNEL_ADDRPERM(rt->rt_gwroute); // key/dest @@ -710,22 +847,132 @@ nstat_route_rtt( } } + #pragma mark -- TCP Provider -- +/* + * Due to the way the kernel deallocates a process (the process structure + * might be gone by the time we get the PCB detach notification), + * we need to cache the process name. Without this, proc_name() would + * return null and the process name would never be sent to userland. + * + * For UDP sockets, we also store the cached the connection tuples along with + * the interface index. This is necessary because when UDP sockets are + * disconnected, the connection tuples are forever lost from the inpcb, thus + * we need to keep track of the last call to connect() in ntstat. + */ +struct nstat_tucookie { + struct inpcb *inp; + char pname[MAXCOMLEN+1]; + bool cached; + union + { + struct sockaddr_in v4; + struct sockaddr_in6 v6; + } local; + union + { + struct sockaddr_in v4; + struct sockaddr_in6 v6; + } remote; + unsigned int if_index; + uint16_t ifnet_properties; +}; + +static struct nstat_tucookie * +nstat_tucookie_alloc_internal( + struct inpcb *inp, + bool ref, + bool locked) +{ + struct nstat_tucookie *cookie; + + cookie = OSMalloc(sizeof(*cookie), nstat_malloc_tag); + if (cookie == NULL) + return NULL; + if (!locked) + lck_mtx_assert(&nstat_mtx, LCK_MTX_ASSERT_NOTOWNED); + if (ref && in_pcb_checkstate(inp, WNT_ACQUIRE, locked) == WNT_STOPUSING) + { + OSFree(cookie, sizeof(*cookie), nstat_malloc_tag); + return NULL; + } + bzero(cookie, sizeof(*cookie)); + cookie->inp = inp; + proc_name(inp->inp_socket->last_pid, cookie->pname, + sizeof(cookie->pname)); + /* + * We only increment the reference count for UDP sockets because we + * only cache UDP socket tuples. + */ + if (SOCK_PROTO(inp->inp_socket) == IPPROTO_UDP) + OSIncrementAtomic(&inp->inp_nstat_refcnt); + + return cookie; +} + +static struct nstat_tucookie * +nstat_tucookie_alloc( + struct inpcb *inp) +{ + return nstat_tucookie_alloc_internal(inp, false, false); +} + +static struct nstat_tucookie * +nstat_tucookie_alloc_ref( + struct inpcb *inp) +{ + return nstat_tucookie_alloc_internal(inp, true, false); +} + +static struct nstat_tucookie * +nstat_tucookie_alloc_ref_locked( + struct inpcb *inp) +{ + return nstat_tucookie_alloc_internal(inp, true, true); +} + +static void +nstat_tucookie_release_internal( + struct nstat_tucookie *cookie, + int inplock) +{ + if (SOCK_PROTO(cookie->inp->inp_socket) == IPPROTO_UDP) + OSDecrementAtomic(&cookie->inp->inp_nstat_refcnt); + in_pcb_checkstate(cookie->inp, WNT_RELEASE, inplock); + OSFree(cookie, sizeof(*cookie), nstat_malloc_tag); +} + +static void +nstat_tucookie_release( + struct nstat_tucookie *cookie) +{ + nstat_tucookie_release_internal(cookie, false); +} + +static void +nstat_tucookie_release_locked( + struct nstat_tucookie *cookie) +{ + nstat_tucookie_release_internal(cookie, true); +} + + static nstat_provider nstat_tcp_provider; static errno_t nstat_tcpudp_lookup( - struct inpcbinfo *inpinfo, - const void *data, - u_int32_t length, + struct inpcbinfo *inpinfo, + const void *data, + u_int32_t length, nstat_provider_cookie_t *out_cookie) { + struct inpcb *inp = NULL; + // parameter validation const nstat_tcp_add_param *param = (const nstat_tcp_add_param*)data; if (length < sizeof(*param)) { - printf("%s:%d expected %lu byte param, received %u\n", __FUNCTION__, __LINE__, sizeof(*param), length); return EINVAL; } @@ -733,12 +980,9 @@ nstat_tcpudp_lookup( if (param->remote.v4.sin_family != 0 && param->remote.v4.sin_family != param->local.v4.sin_family) { - printf("%s:%d src family (%d) and dst family (%d) don't match\n", - __FUNCTION__, __LINE__, param->local.v4.sin_family, param->remote.v4.sin_family); return EINVAL; } - struct inpcb *inp = NULL; switch (param->local.v4.sin_family) { @@ -748,9 +992,6 @@ nstat_tcpudp_lookup( (param->remote.v4.sin_family != 0 && param->remote.v4.sin_len != sizeof(param->remote.v4))) { - printf("%s:%d invalid length for v4 src (%d) or dst (%d), should be %lu\n", - __FUNCTION__, __LINE__, param->local.v4.sin_len, param->remote.v4.sin_len, - sizeof(param->remote.v4)); return EINVAL; } @@ -772,9 +1013,6 @@ nstat_tcpudp_lookup( (param->remote.v6.sin6_family != 0 && param->remote.v6.sin6_len != sizeof(param->remote.v6))) { - printf("%s:%d invalid length for v6 src (%d) or dst (%d), should be %lu\n", - __FUNCTION__, __LINE__, param->local.v6.sin6_len, param->remote.v6.sin6_len, - sizeof(param->remote.v6)); return EINVAL; } @@ -788,14 +1026,17 @@ nstat_tcpudp_lookup( #endif default: - printf("%s:%d unsupported address family %d\n", __FUNCTION__, __LINE__, param->local.v4.sin_family); return EINVAL; } - if (inp == NULL) return ENOENT; + if (inp == NULL) + return ENOENT; // At this point we have a ref to the inpcb - *out_cookie = inp; + *out_cookie = nstat_tucookie_alloc(inp); + if (*out_cookie == NULL) + in_pcb_checkstate(inp, WNT_RELEASE, 0); + return 0; } @@ -812,9 +1053,14 @@ static int nstat_tcp_gone( nstat_provider_cookie_t cookie) { - struct inpcb *inp = (struct inpcb*)cookie; - struct tcpcb *tp = intotcpcb(inp); - return (inp->inp_state == INPCB_STATE_DEAD || tp->t_state == TCPS_TIME_WAIT) ? 1 : 0; + struct nstat_tucookie *tucookie = + (struct nstat_tucookie *)cookie; + struct inpcb *inp; + struct tcpcb *tp; + + return (!(inp = tucookie->inp) || + !(tp = intotcpcb(inp)) || + inp->inp_state == INPCB_STATE_DEAD) ? 1 : 0; } static errno_t @@ -823,47 +1069,58 @@ nstat_tcp_counts( struct nstat_counts *out_counts, int *out_gone) { - struct inpcb *inp = (struct inpcb*)cookie; - struct tcpcb *tp = intotcpcb(inp); - + struct nstat_tucookie *tucookie = + (struct nstat_tucookie *)cookie; + struct inpcb *inp; + bzero(out_counts, sizeof(*out_counts)); - *out_gone = 0; + if (out_gone) *out_gone = 0; // if the pcb is in the dead state, we should stop using it - if (inp->inp_state == INPCB_STATE_DEAD || tp->t_state == TCPS_TIME_WAIT) + if (nstat_tcp_gone(cookie)) { - *out_gone = 1; - } + if (out_gone) *out_gone = 1; + if (!(inp = tucookie->inp) || !intotcpcb(inp)) + return EINVAL; + } + inp = tucookie->inp; + struct tcpcb *tp = intotcpcb(inp); - if (tp->t_state > TCPS_LISTEN) - { - atomic_get_64(out_counts->nstat_rxpackets, &inp->inp_stat->rxpackets); - atomic_get_64(out_counts->nstat_rxbytes, &inp->inp_stat->rxbytes); - atomic_get_64(out_counts->nstat_txpackets, &inp->inp_stat->txpackets); - atomic_get_64(out_counts->nstat_txbytes, &inp->inp_stat->txbytes); - out_counts->nstat_rxduplicatebytes = tp->t_stat.rxduplicatebytes; - out_counts->nstat_rxoutoforderbytes = tp->t_stat.rxoutoforderbytes; - out_counts->nstat_txretransmit = tp->t_stat.txretransmitbytes; - out_counts->nstat_connectattempts = tp->t_state >= TCPS_SYN_SENT ? 1 : 0; - out_counts->nstat_connectsuccesses = tp->t_state >= TCPS_ESTABLISHED ? 1 : 0; - out_counts->nstat_avg_rtt = tp->t_srtt; - out_counts->nstat_min_rtt = tp->t_rttbest; - out_counts->nstat_var_rtt = tp->t_rttvar; - } + atomic_get_64(out_counts->nstat_rxpackets, &inp->inp_stat->rxpackets); + atomic_get_64(out_counts->nstat_rxbytes, &inp->inp_stat->rxbytes); + atomic_get_64(out_counts->nstat_txpackets, &inp->inp_stat->txpackets); + atomic_get_64(out_counts->nstat_txbytes, &inp->inp_stat->txbytes); + out_counts->nstat_rxduplicatebytes = tp->t_stat.rxduplicatebytes; + out_counts->nstat_rxoutoforderbytes = tp->t_stat.rxoutoforderbytes; + out_counts->nstat_txretransmit = tp->t_stat.txretransmitbytes; + out_counts->nstat_connectattempts = tp->t_state >= TCPS_SYN_SENT ? 1 : 0; + out_counts->nstat_connectsuccesses = tp->t_state >= TCPS_ESTABLISHED ? 1 : 0; + out_counts->nstat_avg_rtt = tp->t_srtt; + out_counts->nstat_min_rtt = tp->t_rttbest; + out_counts->nstat_var_rtt = tp->t_rttvar; + if (out_counts->nstat_avg_rtt < out_counts->nstat_min_rtt) + out_counts->nstat_min_rtt = out_counts->nstat_avg_rtt; + atomic_get_64(out_counts->nstat_cell_rxbytes, &inp->inp_cstat->rxbytes); + atomic_get_64(out_counts->nstat_cell_txbytes, &inp->inp_cstat->txbytes); + atomic_get_64(out_counts->nstat_wifi_rxbytes, &inp->inp_wstat->rxbytes); + atomic_get_64(out_counts->nstat_wifi_txbytes, &inp->inp_wstat->txbytes); + atomic_get_64(out_counts->nstat_wired_rxbytes, &inp->inp_Wstat->rxbytes); + atomic_get_64(out_counts->nstat_wired_txbytes, &inp->inp_Wstat->txbytes); return 0; } static void nstat_tcp_release( - nstat_provider_cookie_t cookie) + nstat_provider_cookie_t cookie, + int locked) { - struct inpcb *inp = (struct inpcb*)cookie; - in_pcb_checkstate(inp, WNT_RELEASE, 0); -} + struct nstat_tucookie *tucookie = + (struct nstat_tucookie *)cookie; -static u_int32_t nstat_tcp_watchers = 0; + nstat_tucookie_release_internal(tucookie, locked); +} static errno_t nstat_tcp_add_watcher( @@ -871,23 +1128,25 @@ nstat_tcp_add_watcher( { OSIncrementAtomic(&nstat_tcp_watchers); - lck_rw_lock_shared(tcbinfo.mtx); + lck_rw_lock_shared(tcbinfo.ipi_lock); // Add all current tcp inpcbs. Ignore those in timewait struct inpcb *inp; - for (inp = LIST_FIRST(tcbinfo.listhead); inp; inp = LIST_NEXT(inp, inp_list)) + struct nstat_tucookie *cookie; + LIST_FOREACH(inp, tcbinfo.ipi_listhead, inp_list) { - if (in_pcb_checkstate(inp, WNT_ACQUIRE, 0) == WNT_STOPUSING) + cookie = nstat_tucookie_alloc_ref(inp); + if (cookie == NULL) continue; - - if (nstat_control_source_add(0, state, &nstat_tcp_provider, inp) != 0) + if (nstat_control_source_add(0, state, &nstat_tcp_provider, + cookie) != 0) { - in_pcb_checkstate(inp, WNT_RELEASE, 0); + nstat_tucookie_release(cookie); break; } } - lck_rw_done(tcbinfo.mtx); + lck_rw_done(tcbinfo.ipi_lock); return 0; } @@ -903,27 +1162,163 @@ __private_extern__ void nstat_tcp_new_pcb( struct inpcb *inp) { + struct nstat_tucookie *cookie; + if (nstat_tcp_watchers == 0) return; - + + socket_lock(inp->inp_socket, 0); lck_mtx_lock(&nstat_mtx); nstat_control_state *state; - for (state = nstat_controls; state; state = state->next) + for (state = nstat_controls; state; state = state->ncs_next) { - if ((state->watching & (1 << NSTAT_PROVIDER_TCP)) != 0) + if ((state->ncs_watching & (1 << NSTAT_PROVIDER_TCP)) != 0) { // this client is watching tcp // acquire a reference for it - if (in_pcb_checkstate(inp, WNT_ACQUIRE, 0) == WNT_STOPUSING) + cookie = nstat_tucookie_alloc_ref_locked(inp); + if (cookie == NULL) + continue; + // add the source, if that fails, release the reference + if (nstat_control_source_add(0, state, + &nstat_tcp_provider, cookie) != 0) + { + nstat_tucookie_release_locked(cookie); + break; + } + } + } + lck_mtx_unlock(&nstat_mtx); + socket_unlock(inp->inp_socket, 0); +} + +__private_extern__ void +nstat_pcb_detach(struct inpcb *inp) +{ + nstat_control_state *state; + nstat_src *src, *prevsrc; + nstat_src *dead_list = NULL; + struct nstat_tucookie *tucookie; + errno_t result; + + if (inp == NULL || (nstat_tcp_watchers == 0 && nstat_udp_watchers == 0)) + return; + + lck_mtx_lock(&nstat_mtx); + for (state = nstat_controls; state; state = state->ncs_next) + { + lck_mtx_lock(&state->mtx); + for (prevsrc = NULL, src = state->ncs_srcs; src; + prevsrc = src, src = src->next) + { + tucookie = (struct nstat_tucookie *)src->cookie; + if (tucookie->inp == inp) break; + } + + if (src) + { + result = nstat_control_send_goodbye(state, src); - // add the source, if that fails, release the reference - if (nstat_control_source_add(0, state, &nstat_tcp_provider, inp) != 0) + if (prevsrc) + prevsrc->next = src->next; + else + state->ncs_srcs = src->next; + + src->next = dead_list; + dead_list = src; + } + lck_mtx_unlock(&state->mtx); + } + lck_mtx_unlock(&nstat_mtx); + + while (dead_list) { + src = dead_list; + dead_list = src->next; + + nstat_control_cleanup_source(NULL, src, TRUE); + } +} + +__private_extern__ void +nstat_pcb_cache(struct inpcb *inp) +{ + nstat_control_state *state; + nstat_src *src; + struct nstat_tucookie *tucookie; + + if (inp == NULL || nstat_udp_watchers == 0 || + inp->inp_nstat_refcnt == 0) + return; + VERIFY(SOCK_PROTO(inp->inp_socket) == IPPROTO_UDP); + 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) + { + tucookie = (struct nstat_tucookie *)src->cookie; + if (tucookie->inp == inp) + { + if (inp->inp_vflag & INP_IPV6) + { + nstat_ip6_to_sockaddr(&inp->in6p_laddr, + inp->inp_lport, + &tucookie->local.v6, + sizeof(tucookie->local)); + nstat_ip6_to_sockaddr(&inp->in6p_faddr, + inp->inp_fport, + &tucookie->remote.v6, + sizeof(tucookie->remote)); + } + else if (inp->inp_vflag & INP_IPV4) + { + nstat_ip_to_sockaddr(&inp->inp_laddr, + inp->inp_lport, + &tucookie->local.v4, + sizeof(tucookie->local)); + nstat_ip_to_sockaddr(&inp->inp_faddr, + inp->inp_fport, + &tucookie->remote.v4, + sizeof(tucookie->remote)); + } + if (inp->inp_last_outifp) + tucookie->if_index = + inp->inp_last_outifp->if_index; + + tucookie->ifnet_properties = nstat_inpcb_to_flags(inp); + tucookie->cached = true; + break; + } + } + lck_mtx_unlock(&state->mtx); + } + lck_mtx_unlock(&nstat_mtx); +} + +__private_extern__ void +nstat_pcb_invalidate_cache(struct inpcb *inp) +{ + nstat_control_state *state; + nstat_src *src; + struct nstat_tucookie *tucookie; + + if (inp == NULL || nstat_udp_watchers == 0 || + inp->inp_nstat_refcnt == 0) + return; + VERIFY(SOCK_PROTO(inp->inp_socket) == IPPROTO_UDP); + 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) + { + tucookie = (struct nstat_tucookie *)src->cookie; + if (tucookie->inp == inp) { - in_pcb_checkstate(inp, WNT_RELEASE, 0); + tucookie->cached = false; break; } } + lck_mtx_unlock(&state->mtx); } lck_mtx_unlock(&nstat_mtx); } @@ -931,19 +1326,22 @@ nstat_tcp_new_pcb( static errno_t nstat_tcp_copy_descriptor( nstat_provider_cookie_t cookie, - void *data, - u_int32_t len) + void *data, + u_int32_t len) { if (len < sizeof(nstat_tcp_descriptor)) { - printf("%s:%d invalid length, wanted %lu, got %d\n", __FUNCTION__, __LINE__, sizeof(nstat_tcp_descriptor), len); return EINVAL; } + + if (nstat_tcp_gone(cookie)) + return EINVAL; nstat_tcp_descriptor *desc = (nstat_tcp_descriptor*)data; - struct inpcb *inp = (struct inpcb*)cookie; - struct tcpcb *tp = intotcpcb(inp); - + struct nstat_tucookie *tucookie = + (struct nstat_tucookie *)cookie; + struct inpcb *inp = tucookie->inp; + struct tcpcb *tp = intotcpcb(inp); bzero(desc, sizeof(*desc)); if (inp->inp_vflag & INP_IPV6) @@ -962,13 +1360,18 @@ nstat_tcp_copy_descriptor( } desc->state = intotcpcb(inp)->t_state; - if (inp->inp_route.ro_rt && inp->inp_route.ro_rt->rt_ifp) - desc->ifindex = inp->inp_route.ro_rt->rt_ifp->if_index; + desc->ifindex = (inp->inp_last_outifp == NULL) ? 0 : + inp->inp_last_outifp->if_index; // danger - not locked, values could be bogus desc->txunacked = tp->snd_max - tp->snd_una; desc->txwindow = tp->snd_wnd; desc->txcwindow = tp->snd_cwnd; + + if (CC_ALGO(tp)->name != NULL) { + strlcpy(desc->cc_algo, CC_ALGO(tp)->name, + sizeof(desc->cc_algo)); + } struct socket *so = inp->inp_socket; if (so) @@ -977,19 +1380,61 @@ nstat_tcp_copy_descriptor( // they're in sync? desc->upid = so->last_upid; desc->pid = so->last_pid; - + desc->traffic_class = so->so_traffic_class; + desc->traffic_mgt_flags = so->so_traffic_mgt_flags; proc_name(desc->pid, desc->pname, sizeof(desc->pname)); - desc->pname[sizeof(desc->pname) - 1] = 0; - + if (desc->pname[0] == 0) + { + strlcpy(desc->pname, tucookie->pname, + sizeof(desc->pname)); + } + else + { + desc->pname[sizeof(desc->pname) - 1] = 0; + strlcpy(tucookie->pname, desc->pname, + sizeof(tucookie->pname)); + } + memcpy(desc->uuid, so->last_uuid, sizeof(so->last_uuid)); + memcpy(desc->vuuid, so->so_vuuid, sizeof(so->so_vuuid)); + if (so->so_flags & SOF_DELEGATED) { + desc->eupid = so->e_upid; + desc->epid = so->e_pid; + memcpy(desc->euuid, so->e_uuid, sizeof(so->e_uuid)); + } else { + desc->eupid = desc->upid; + desc->epid = desc->pid; + memcpy(desc->euuid, desc->uuid, sizeof(desc->uuid)); + } desc->sndbufsize = so->so_snd.sb_hiwat; desc->sndbufused = so->so_snd.sb_cc; desc->rcvbufsize = so->so_rcv.sb_hiwat; desc->rcvbufused = so->so_rcv.sb_cc; } - + + tcp_get_connectivity_status(tp, &desc->connstatus); + desc->ifnet_properties = nstat_inpcb_to_flags(inp); return 0; } +static bool +nstat_tcpudp_reporting_allowed(nstat_provider_cookie_t cookie, uint64_t filter) +{ + bool retval = true; + + /* Only apply interface filter if at least one is allowed. */ + if ((filter & NSTAT_FILTER_ACCEPT_ALL) != 0) + { + struct nstat_tucookie *tucookie = (struct nstat_tucookie *)cookie; + struct inpcb *inp = tucookie->inp; + + uint16_t interface_properties = nstat_inpcb_to_flags(inp); + + /* For now, just check on interface type. */ + retval = ((filter & interface_properties) != 0); + } + return retval; +} + static void nstat_init_tcp_provider(void) { @@ -1003,6 +1448,7 @@ nstat_init_tcp_provider(void) nstat_tcp_provider.nstat_watcher_add = nstat_tcp_add_watcher; nstat_tcp_provider.nstat_watcher_remove = nstat_tcp_remove_watcher; nstat_tcp_provider.nstat_copy_descriptor = nstat_tcp_copy_descriptor; + nstat_tcp_provider.nstat_reporting_allowed = nstat_tcpudp_reporting_allowed; nstat_tcp_provider.next = nstat_providers; nstat_providers = &nstat_tcp_provider; } @@ -1024,67 +1470,84 @@ static int nstat_udp_gone( nstat_provider_cookie_t cookie) { - struct inpcb *inp = (struct inpcb*)cookie; - return (inp->inp_state == INPCB_STATE_DEAD) ? 1 : 0; + struct nstat_tucookie *tucookie = + (struct nstat_tucookie *)cookie; + struct inpcb *inp; + + return (!(inp = tucookie->inp) || + inp->inp_state == INPCB_STATE_DEAD) ? 1 : 0; } static errno_t nstat_udp_counts( nstat_provider_cookie_t cookie, - struct nstat_counts *out_counts, - int *out_gone) + struct nstat_counts *out_counts, + int *out_gone) { - struct inpcb *inp = (struct inpcb*)cookie; + struct nstat_tucookie *tucookie = + (struct nstat_tucookie *)cookie; - *out_gone = 0; + if (out_gone) *out_gone = 0; // if the pcb is in the dead state, we should stop using it - if (inp->inp_state == INPCB_STATE_DEAD) + if (nstat_udp_gone(cookie)) { - *out_gone = 1; + if (out_gone) *out_gone = 1; + if (!tucookie->inp) + return EINVAL; } + struct inpcb *inp = tucookie->inp; atomic_get_64(out_counts->nstat_rxpackets, &inp->inp_stat->rxpackets); atomic_get_64(out_counts->nstat_rxbytes, &inp->inp_stat->rxbytes); atomic_get_64(out_counts->nstat_txpackets, &inp->inp_stat->txpackets); atomic_get_64(out_counts->nstat_txbytes, &inp->inp_stat->txbytes); + atomic_get_64(out_counts->nstat_cell_rxbytes, &inp->inp_cstat->rxbytes); + atomic_get_64(out_counts->nstat_cell_txbytes, &inp->inp_cstat->txbytes); + atomic_get_64(out_counts->nstat_wifi_rxbytes, &inp->inp_wstat->rxbytes); + atomic_get_64(out_counts->nstat_wifi_txbytes, &inp->inp_wstat->txbytes); + atomic_get_64(out_counts->nstat_wired_rxbytes, &inp->inp_Wstat->rxbytes); + atomic_get_64(out_counts->nstat_wired_txbytes, &inp->inp_Wstat->txbytes); return 0; } static void nstat_udp_release( - nstat_provider_cookie_t cookie) + nstat_provider_cookie_t cookie, + int locked) { - struct inpcb *inp = (struct inpcb*)cookie; - in_pcb_checkstate(inp, WNT_RELEASE, 0); -} + struct nstat_tucookie *tucookie = + (struct nstat_tucookie *)cookie; -static u_int32_t nstat_udp_watchers = 0; + nstat_tucookie_release_internal(tucookie, locked); +} static errno_t nstat_udp_add_watcher( nstat_control_state *state) { + struct inpcb *inp; + struct nstat_tucookie *cookie; + OSIncrementAtomic(&nstat_udp_watchers); - lck_rw_lock_shared(tcbinfo.mtx); - - // Add all current tcp inpcbs. Ignore those in timewait - struct inpcb *inp; - for (inp = LIST_FIRST(udbinfo.listhead); inp; inp = LIST_NEXT(inp, inp_list)) + lck_rw_lock_shared(udbinfo.ipi_lock); + // Add all current UDP inpcbs. + LIST_FOREACH(inp, udbinfo.ipi_listhead, inp_list) { - if (in_pcb_checkstate(inp, WNT_ACQUIRE, 0) == WNT_STOPUSING) + cookie = nstat_tucookie_alloc_ref(inp); + if (cookie == NULL) continue; - - if (nstat_control_source_add(0, state, &nstat_udp_provider, inp) != 0) + if (nstat_control_source_add(0, state, &nstat_udp_provider, + cookie) != 0) { - in_pcb_checkstate(inp, WNT_RELEASE, 0); + nstat_tucookie_release(cookie); break; } } - lck_rw_done(tcbinfo.mtx); + lck_rw_done(udbinfo.ipi_lock); return 0; } @@ -1100,29 +1563,34 @@ __private_extern__ void nstat_udp_new_pcb( struct inpcb *inp) { + struct nstat_tucookie *cookie; + if (nstat_udp_watchers == 0) return; + socket_lock(inp->inp_socket, 0); lck_mtx_lock(&nstat_mtx); nstat_control_state *state; - for (state = nstat_controls; state; state = state->next) + for (state = nstat_controls; state; state = state->ncs_next) { - if ((state->watching & (1 << NSTAT_PROVIDER_UDP)) != 0) + if ((state->ncs_watching & (1 << NSTAT_PROVIDER_UDP)) != 0) { // this client is watching tcp // acquire a reference for it - if (in_pcb_checkstate(inp, WNT_ACQUIRE, 0) == WNT_STOPUSING) - break; - + cookie = nstat_tucookie_alloc_ref_locked(inp); + if (cookie == NULL) + continue; // add the source, if that fails, release the reference - if (nstat_control_source_add(0, state, &nstat_udp_provider, inp) != 0) + if (nstat_control_source_add(0, state, + &nstat_udp_provider, cookie) != 0) { - in_pcb_checkstate(inp, WNT_RELEASE, 0); + nstat_tucookie_release_locked(cookie); break; } } } lck_mtx_unlock(&nstat_mtx); + socket_unlock(inp->inp_socket, 0); } static errno_t @@ -1133,88 +1601,977 @@ nstat_udp_copy_descriptor( { if (len < sizeof(nstat_udp_descriptor)) { - printf("%s:%d invalid length, wanted %lu, got %d\n", __FUNCTION__, __LINE__, sizeof(nstat_tcp_descriptor), len); return EINVAL; } - nstat_udp_descriptor *desc = (nstat_udp_descriptor*)data; - struct inpcb *inp = (struct inpcb*)cookie; - + if (nstat_udp_gone(cookie)) + return EINVAL; + + struct nstat_tucookie *tucookie = + (struct nstat_tucookie *)cookie; + nstat_udp_descriptor *desc = (nstat_udp_descriptor*)data; + struct inpcb *inp = tucookie->inp; + bzero(desc, sizeof(*desc)); - if (inp->inp_vflag & INP_IPV6) + if (tucookie->cached == false) { + if (inp->inp_vflag & INP_IPV6) + { + nstat_ip6_to_sockaddr(&inp->in6p_laddr, inp->inp_lport, + &desc->local.v6, sizeof(desc->local.v6)); + nstat_ip6_to_sockaddr(&inp->in6p_faddr, inp->inp_fport, + &desc->remote.v6, sizeof(desc->remote.v6)); + } + else if (inp->inp_vflag & INP_IPV4) + { + nstat_ip_to_sockaddr(&inp->inp_laddr, inp->inp_lport, + &desc->local.v4, sizeof(desc->local.v4)); + nstat_ip_to_sockaddr(&inp->inp_faddr, inp->inp_fport, + &desc->remote.v4, sizeof(desc->remote.v4)); + } + desc->ifnet_properties = nstat_inpcb_to_flags(inp); + } + else { - nstat_ip6_to_sockaddr(&inp->in6p_laddr, inp->inp_lport, - &desc->local.v6, sizeof(desc->local)); - nstat_ip6_to_sockaddr(&inp->in6p_faddr, inp->inp_fport, - &desc->remote.v6, sizeof(desc->remote)); + if (inp->inp_vflag & INP_IPV6) + { + memcpy(&desc->local.v6, &tucookie->local.v6, + sizeof(desc->local.v6)); + memcpy(&desc->remote.v6, &tucookie->remote.v6, + sizeof(desc->remote.v6)); + } + else if (inp->inp_vflag & INP_IPV4) + { + memcpy(&desc->local.v4, &tucookie->local.v4, + sizeof(desc->local.v4)); + memcpy(&desc->remote.v4, &tucookie->remote.v4, + sizeof(desc->remote.v4)); + } + desc->ifnet_properties = tucookie->ifnet_properties; + } + + if (inp->inp_last_outifp) + desc->ifindex = inp->inp_last_outifp->if_index; + else + desc->ifindex = tucookie->if_index; + + struct socket *so = inp->inp_socket; + if (so) + { + // TBD - take the socket lock around these to make sure + // they're in sync? + desc->upid = so->last_upid; + desc->pid = so->last_pid; + proc_name(desc->pid, desc->pname, sizeof(desc->pname)); + if (desc->pname[0] == 0) + { + strlcpy(desc->pname, tucookie->pname, + sizeof(desc->pname)); + } + else + { + desc->pname[sizeof(desc->pname) - 1] = 0; + strlcpy(tucookie->pname, desc->pname, + sizeof(tucookie->pname)); + } + memcpy(desc->uuid, so->last_uuid, sizeof(so->last_uuid)); + memcpy(desc->vuuid, so->so_vuuid, sizeof(so->so_vuuid)); + if (so->so_flags & SOF_DELEGATED) { + desc->eupid = so->e_upid; + desc->epid = so->e_pid; + memcpy(desc->euuid, so->e_uuid, sizeof(so->e_uuid)); + } else { + desc->eupid = desc->upid; + desc->epid = desc->pid; + memcpy(desc->euuid, desc->uuid, sizeof(desc->uuid)); + } + desc->rcvbufsize = so->so_rcv.sb_hiwat; + desc->rcvbufused = so->so_rcv.sb_cc; + desc->traffic_class = so->so_traffic_class; + } + + return 0; +} + +static void +nstat_init_udp_provider(void) +{ + bzero(&nstat_udp_provider, sizeof(nstat_udp_provider)); + nstat_udp_provider.nstat_provider_id = NSTAT_PROVIDER_UDP; + nstat_udp_provider.nstat_descriptor_length = sizeof(nstat_udp_descriptor); + nstat_udp_provider.nstat_lookup = nstat_udp_lookup; + nstat_udp_provider.nstat_gone = nstat_udp_gone; + nstat_udp_provider.nstat_counts = nstat_udp_counts; + nstat_udp_provider.nstat_watcher_add = nstat_udp_add_watcher; + nstat_udp_provider.nstat_watcher_remove = nstat_udp_remove_watcher; + nstat_udp_provider.nstat_copy_descriptor = nstat_udp_copy_descriptor; + nstat_udp_provider.nstat_release = nstat_udp_release; + nstat_udp_provider.nstat_reporting_allowed = nstat_tcpudp_reporting_allowed; + nstat_udp_provider.next = nstat_providers; + nstat_providers = &nstat_udp_provider; +} + +#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 = (const 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, 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; + + if (out_gone) *out_gone = 0; + + // if the ifnet is gone, we should stop using it + if (nstat_ifnet_gone(cookie)) + { + if (out_gone) *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 void +nstat_ifnet_copy_link_status( + struct ifnet *ifp, + struct nstat_ifnet_descriptor *desc) +{ + struct if_link_status *ifsr = ifp->if_link_status; + nstat_ifnet_desc_link_status *link_status = &desc->link_status; + + link_status->link_status_type = NSTAT_IFNET_DESC_LINK_STATUS_TYPE_NONE; + if (ifsr == NULL) + return; + + lck_rw_lock_shared(&ifp->if_link_status_lock); + + if (ifp->if_type == IFT_CELLULAR) { + + nstat_ifnet_desc_cellular_status *cell_status = &link_status->u.cellular; + struct if_cellular_status_v1 *if_cell_sr = + &ifsr->ifsr_u.ifsr_cell.if_cell_u.if_status_v1; + + if (ifsr->ifsr_version != IF_CELLULAR_STATUS_REPORT_VERSION_1) + goto done; + + link_status->link_status_type = NSTAT_IFNET_DESC_LINK_STATUS_TYPE_CELLULAR; + + if (if_cell_sr->valid_bitmask & IF_CELL_LINK_QUALITY_METRIC_VALID) { + cell_status->valid_bitmask |= NSTAT_IFNET_DESC_CELL_LINK_QUALITY_METRIC_VALID; + cell_status->link_quality_metric = if_cell_sr->link_quality_metric; + } + if (if_cell_sr->valid_bitmask & IF_CELL_UL_EFFECTIVE_BANDWIDTH_VALID) { + cell_status->valid_bitmask |= NSTAT_IFNET_DESC_CELL_UL_EFFECTIVE_BANDWIDTH_VALID; + cell_status->ul_effective_bandwidth = if_cell_sr->ul_effective_bandwidth; + } + if (if_cell_sr->valid_bitmask & IF_CELL_UL_MAX_BANDWIDTH_VALID) { + cell_status->valid_bitmask |= NSTAT_IFNET_DESC_CELL_UL_MAX_BANDWIDTH_VALID; + cell_status->ul_max_bandwidth = if_cell_sr->ul_max_bandwidth; + } + if (if_cell_sr->valid_bitmask & IF_CELL_UL_MIN_LATENCY_VALID) { + cell_status->valid_bitmask |= NSTAT_IFNET_DESC_CELL_UL_MIN_LATENCY_VALID; + cell_status->ul_min_latency = if_cell_sr->ul_min_latency; + } + if (if_cell_sr->valid_bitmask & IF_CELL_UL_EFFECTIVE_LATENCY_VALID) { + cell_status->valid_bitmask |= NSTAT_IFNET_DESC_CELL_UL_EFFECTIVE_LATENCY_VALID; + cell_status->ul_effective_latency = if_cell_sr->ul_effective_latency; + } + if (if_cell_sr->valid_bitmask & IF_CELL_UL_MAX_LATENCY_VALID) { + cell_status->valid_bitmask |= NSTAT_IFNET_DESC_CELL_UL_MAX_LATENCY_VALID; + cell_status->ul_max_latency = if_cell_sr->ul_max_latency; + } + if (if_cell_sr->valid_bitmask & IF_CELL_UL_RETXT_LEVEL_VALID) { + cell_status->valid_bitmask |= NSTAT_IFNET_DESC_CELL_UL_RETXT_LEVEL_VALID; + if (if_cell_sr->ul_retxt_level == IF_CELL_UL_RETXT_LEVEL_NONE) + cell_status->ul_retxt_level = NSTAT_IFNET_DESC_CELL_UL_RETXT_LEVEL_NONE; + else if (if_cell_sr->ul_retxt_level == IF_CELL_UL_RETXT_LEVEL_LOW) + cell_status->ul_retxt_level = NSTAT_IFNET_DESC_CELL_UL_RETXT_LEVEL_LOW; + else if (if_cell_sr->ul_retxt_level == IF_CELL_UL_RETXT_LEVEL_MEDIUM) + cell_status->ul_retxt_level = NSTAT_IFNET_DESC_CELL_UL_RETXT_LEVEL_MEDIUM; + else if (if_cell_sr->ul_retxt_level == IF_CELL_UL_RETXT_LEVEL_HIGH) + cell_status->ul_retxt_level = NSTAT_IFNET_DESC_CELL_UL_RETXT_LEVEL_HIGH; + else + cell_status->valid_bitmask &= ~NSTAT_IFNET_DESC_CELL_UL_RETXT_LEVEL_VALID; + } + if (if_cell_sr->valid_bitmask & IF_CELL_UL_BYTES_LOST_VALID) { + cell_status->valid_bitmask |= NSTAT_IFNET_DESC_CELL_UL_BYTES_LOST_VALID; + cell_status->ul_bytes_lost = if_cell_sr->ul_bytes_lost; + } + if (if_cell_sr->valid_bitmask & IF_CELL_UL_MIN_QUEUE_SIZE_VALID) { + cell_status->valid_bitmask |= NSTAT_IFNET_DESC_CELL_UL_MIN_QUEUE_SIZE_VALID; + cell_status->ul_min_queue_size = if_cell_sr->ul_min_queue_size; + } + if (if_cell_sr->valid_bitmask & IF_CELL_UL_AVG_QUEUE_SIZE_VALID) { + cell_status->valid_bitmask |= NSTAT_IFNET_DESC_CELL_UL_AVG_QUEUE_SIZE_VALID; + cell_status->ul_avg_queue_size = if_cell_sr->ul_avg_queue_size; + } + if (if_cell_sr->valid_bitmask & IF_CELL_UL_MAX_QUEUE_SIZE_VALID) { + cell_status->valid_bitmask |= NSTAT_IFNET_DESC_CELL_UL_MAX_QUEUE_SIZE_VALID; + cell_status->ul_max_queue_size = if_cell_sr->ul_max_queue_size; + } + if (if_cell_sr->valid_bitmask & IF_CELL_DL_EFFECTIVE_BANDWIDTH_VALID) { + cell_status->valid_bitmask |= NSTAT_IFNET_DESC_CELL_DL_EFFECTIVE_BANDWIDTH_VALID; + cell_status->dl_effective_bandwidth = if_cell_sr->dl_effective_bandwidth; + } + if (if_cell_sr->valid_bitmask & IF_CELL_DL_MAX_BANDWIDTH_VALID) { + cell_status->valid_bitmask |= NSTAT_IFNET_DESC_CELL_DL_MAX_BANDWIDTH_VALID; + cell_status->dl_max_bandwidth = if_cell_sr->dl_max_bandwidth; + } + if (if_cell_sr->valid_bitmask & IF_CELL_CONFIG_INACTIVITY_TIME_VALID) { + cell_status->valid_bitmask |= NSTAT_IFNET_DESC_CELL_CONFIG_INACTIVITY_TIME_VALID; + cell_status->config_inactivity_time = if_cell_sr->config_inactivity_time; + } + if (if_cell_sr->valid_bitmask & IF_CELL_CONFIG_BACKOFF_TIME_VALID) { + cell_status->valid_bitmask |= NSTAT_IFNET_DESC_CELL_CONFIG_BACKOFF_TIME_VALID; + cell_status->config_backoff_time = if_cell_sr->config_backoff_time; + } + + } else if (ifp->if_subfamily == IFNET_SUBFAMILY_WIFI) { + + nstat_ifnet_desc_wifi_status *wifi_status = &link_status->u.wifi; + struct if_wifi_status_v1 *if_wifi_sr = + &ifsr->ifsr_u.ifsr_wifi.if_wifi_u.if_status_v1; + + if (ifsr->ifsr_version != IF_WIFI_STATUS_REPORT_VERSION_1) + goto done; + + link_status->link_status_type = NSTAT_IFNET_DESC_LINK_STATUS_TYPE_WIFI; + + if (if_wifi_sr->valid_bitmask & IF_WIFI_LINK_QUALITY_METRIC_VALID) { + wifi_status->valid_bitmask |= NSTAT_IFNET_DESC_WIFI_LINK_QUALITY_METRIC_VALID; + wifi_status->link_quality_metric = if_wifi_sr->link_quality_metric; + } + if (if_wifi_sr->valid_bitmask & IF_WIFI_UL_EFFECTIVE_BANDWIDTH_VALID) { + wifi_status->valid_bitmask |= NSTAT_IFNET_DESC_WIFI_UL_EFFECTIVE_BANDWIDTH_VALID; + wifi_status->ul_effective_bandwidth = if_wifi_sr->ul_effective_bandwidth; + } + if (if_wifi_sr->valid_bitmask & IF_WIFI_UL_MAX_BANDWIDTH_VALID) { + wifi_status->valid_bitmask |= NSTAT_IFNET_DESC_WIFI_UL_MAX_BANDWIDTH_VALID; + wifi_status->ul_max_bandwidth = if_wifi_sr->ul_max_bandwidth; + } + if (if_wifi_sr->valid_bitmask & IF_WIFI_UL_MIN_LATENCY_VALID) { + wifi_status->valid_bitmask |= NSTAT_IFNET_DESC_WIFI_UL_MIN_LATENCY_VALID; + wifi_status->ul_min_latency = if_wifi_sr->ul_min_latency; + } + if (if_wifi_sr->valid_bitmask & IF_WIFI_UL_EFFECTIVE_LATENCY_VALID) { + wifi_status->valid_bitmask |= NSTAT_IFNET_DESC_WIFI_UL_EFFECTIVE_LATENCY_VALID; + wifi_status->ul_effective_latency = if_wifi_sr->ul_effective_latency; + } + if (if_wifi_sr->valid_bitmask & IF_WIFI_UL_MAX_LATENCY_VALID) { + wifi_status->valid_bitmask |= NSTAT_IFNET_DESC_WIFI_UL_MAX_LATENCY_VALID; + wifi_status->ul_max_latency = if_wifi_sr->ul_max_latency; + } + if (if_wifi_sr->valid_bitmask & IF_WIFI_UL_RETXT_LEVEL_VALID) { + wifi_status->valid_bitmask |= NSTAT_IFNET_DESC_WIFI_UL_RETXT_LEVEL_VALID; + if (if_wifi_sr->ul_retxt_level == IF_WIFI_UL_RETXT_LEVEL_NONE) + wifi_status->ul_retxt_level = NSTAT_IFNET_DESC_WIFI_UL_RETXT_LEVEL_NONE; + else if (if_wifi_sr->ul_retxt_level == IF_WIFI_UL_RETXT_LEVEL_LOW) + wifi_status->ul_retxt_level = NSTAT_IFNET_DESC_WIFI_UL_RETXT_LEVEL_LOW; + else if (if_wifi_sr->ul_retxt_level == IF_WIFI_UL_RETXT_LEVEL_MEDIUM) + wifi_status->ul_retxt_level = NSTAT_IFNET_DESC_WIFI_UL_RETXT_LEVEL_MEDIUM; + else if (if_wifi_sr->ul_retxt_level == IF_WIFI_UL_RETXT_LEVEL_HIGH) + wifi_status->ul_retxt_level = NSTAT_IFNET_DESC_WIFI_UL_RETXT_LEVEL_HIGH; + else + wifi_status->valid_bitmask &= ~NSTAT_IFNET_DESC_WIFI_UL_RETXT_LEVEL_VALID; + } + if (if_wifi_sr->valid_bitmask & IF_WIFI_UL_BYTES_LOST_VALID) { + wifi_status->valid_bitmask |= NSTAT_IFNET_DESC_WIFI_UL_BYTES_LOST_VALID; + wifi_status->ul_bytes_lost = if_wifi_sr->ul_bytes_lost; + } + if (if_wifi_sr->valid_bitmask & IF_WIFI_UL_ERROR_RATE_VALID) { + wifi_status->valid_bitmask |= NSTAT_IFNET_DESC_WIFI_UL_ERROR_RATE_VALID; + wifi_status->ul_error_rate = if_wifi_sr->ul_error_rate; + } + if (if_wifi_sr->valid_bitmask & IF_WIFI_DL_EFFECTIVE_BANDWIDTH_VALID) { + wifi_status->valid_bitmask |= NSTAT_IFNET_DESC_WIFI_DL_EFFECTIVE_BANDWIDTH_VALID; + wifi_status->dl_effective_bandwidth = if_wifi_sr->dl_effective_bandwidth; + } + if (if_wifi_sr->valid_bitmask & IF_WIFI_DL_MAX_BANDWIDTH_VALID) { + wifi_status->valid_bitmask |= NSTAT_IFNET_DESC_WIFI_DL_MAX_BANDWIDTH_VALID; + wifi_status->dl_max_bandwidth = if_wifi_sr->dl_max_bandwidth; + } + if (if_wifi_sr->valid_bitmask & IF_WIFI_DL_MIN_LATENCY_VALID) { + wifi_status->valid_bitmask |= NSTAT_IFNET_DESC_WIFI_DL_MIN_LATENCY_VALID; + wifi_status->dl_min_latency = if_wifi_sr->dl_min_latency; + } + if (if_wifi_sr->valid_bitmask & IF_WIFI_DL_EFFECTIVE_LATENCY_VALID) { + wifi_status->valid_bitmask |= NSTAT_IFNET_DESC_WIFI_DL_EFFECTIVE_LATENCY_VALID; + wifi_status->dl_effective_latency = if_wifi_sr->dl_effective_latency; + } + if (if_wifi_sr->valid_bitmask & IF_WIFI_DL_MAX_LATENCY_VALID) { + wifi_status->valid_bitmask |= NSTAT_IFNET_DESC_WIFI_DL_MAX_LATENCY_VALID; + wifi_status->dl_max_latency = if_wifi_sr->dl_max_latency; + } + if (if_wifi_sr->valid_bitmask & IF_WIFI_DL_ERROR_RATE_VALID) { + wifi_status->valid_bitmask |= NSTAT_IFNET_DESC_WIFI_DL_ERROR_RATE_VALID; + wifi_status->dl_error_rate = if_wifi_sr->dl_error_rate; + } + if (if_wifi_sr->valid_bitmask & IF_WIFI_CONFIG_FREQUENCY_VALID) { + wifi_status->valid_bitmask |= NSTAT_IFNET_DESC_WIFI_CONFIG_FREQUENCY_VALID; + if (if_wifi_sr->config_frequency == IF_WIFI_CONFIG_FREQUENCY_2_4_GHZ) + wifi_status->config_frequency = NSTAT_IFNET_DESC_WIFI_CONFIG_FREQUENCY_2_4_GHZ; + else if (if_wifi_sr->config_frequency == IF_WIFI_CONFIG_FREQUENCY_5_0_GHZ) + wifi_status->config_frequency = NSTAT_IFNET_DESC_WIFI_CONFIG_FREQUENCY_5_0_GHZ; + else + wifi_status->valid_bitmask &= ~NSTAT_IFNET_DESC_WIFI_CONFIG_FREQUENCY_VALID; + } + if (if_wifi_sr->valid_bitmask & IF_WIFI_CONFIG_MULTICAST_RATE_VALID) { + wifi_status->valid_bitmask |= NSTAT_IFNET_DESC_WIFI_CONFIG_MULTICAST_RATE_VALID; + wifi_status->config_multicast_rate = if_wifi_sr->config_multicast_rate; + } + if (if_wifi_sr->valid_bitmask & IF_WIFI_CONFIG_SCAN_COUNT_VALID) { + wifi_status->valid_bitmask |= NSTAT_IFNET_DESC_WIFI_CONFIG_SCAN_COUNT_VALID; + wifi_status->scan_count = if_wifi_sr->scan_count; + } + if (if_wifi_sr->valid_bitmask & IF_WIFI_CONFIG_SCAN_DURATION_VALID) { + wifi_status->valid_bitmask |= NSTAT_IFNET_DESC_WIFI_CONFIG_SCAN_DURATION_VALID; + wifi_status->scan_duration = if_wifi_sr->scan_duration; + } + } + +done: + lck_rw_done(&ifp->if_link_status_lock); +} + +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)); + nstat_ifnet_copy_link_status(ifp, desc); + 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, 0, NULL); + } + lck_mtx_unlock(&state->mtx); + } + lck_mtx_unlock(&nstat_mtx); +} + +#pragma mark -- Sysinfo -- +static void +nstat_set_keyval_scalar(nstat_sysinfo_keyval *kv, int key, u_int32_t val) +{ + kv->nstat_sysinfo_key = key; + kv->nstat_sysinfo_flags = NSTAT_SYSINFO_FLAG_SCALAR; + kv->u.nstat_sysinfo_scalar = val; +} + +static void +nstat_sysinfo_send_data_internal( + nstat_control_state *control, + 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; + size_t i = 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 = sizeof(struct nstat_sysinfo_mbuf_stats) / + sizeof(u_int32_t); + break; + case NSTAT_SYSINFO_TCP_STATS: + nkeyvals = sizeof(struct nstat_sysinfo_tcp_stats) / + sizeof(u_int32_t); + 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->hdr.length = allocsize; + syscnt->counts.nstat_sysinfo_len = countsize; + + kv = (nstat_sysinfo_keyval *) &syscnt->counts.nstat_sysinfo_keyvals; + switch (data->flags) + { + case NSTAT_SYSINFO_MBUF_STATS: + { + nstat_set_keyval_scalar(&kv[i++], + NSTAT_SYSINFO_KEY_MBUF_256B_TOTAL, + data->u.mb_stats.total_256b); + nstat_set_keyval_scalar(&kv[i++], + NSTAT_SYSINFO_KEY_MBUF_2KB_TOTAL, + data->u.mb_stats.total_2kb); + nstat_set_keyval_scalar(&kv[i++], + NSTAT_SYSINFO_KEY_MBUF_4KB_TOTAL, + data->u.mb_stats.total_4kb); + nstat_set_keyval_scalar(&kv[i++], + NSTAT_SYSINFO_MBUF_16KB_TOTAL, + data->u.mb_stats.total_16kb); + nstat_set_keyval_scalar(&kv[i++], + NSTAT_SYSINFO_KEY_SOCK_MBCNT, + data->u.mb_stats.sbmb_total); + nstat_set_keyval_scalar(&kv[i++], + NSTAT_SYSINFO_KEY_SOCK_ATMBLIMIT, + data->u.mb_stats.sb_atmbuflimit); + nstat_set_keyval_scalar(&kv[i++], + NSTAT_SYSINFO_MBUF_DRAIN_CNT, + data->u.mb_stats.draincnt); + nstat_set_keyval_scalar(&kv[i++], + NSTAT_SYSINFO_MBUF_MEM_RELEASED, + data->u.mb_stats.memreleased); + VERIFY(i == nkeyvals); + break; + } + case NSTAT_SYSINFO_TCP_STATS: + { + nstat_set_keyval_scalar(&kv[i++], + NSTAT_SYSINFO_KEY_IPV4_AVGRTT, + data->u.tcp_stats.ipv4_avgrtt); + nstat_set_keyval_scalar(&kv[i++], + NSTAT_SYSINFO_KEY_IPV6_AVGRTT, + data->u.tcp_stats.ipv6_avgrtt); + nstat_set_keyval_scalar(&kv[i++], + NSTAT_SYSINFO_KEY_SEND_PLR, + data->u.tcp_stats.send_plr); + nstat_set_keyval_scalar(&kv[i++], + NSTAT_SYSINFO_KEY_RECV_PLR, + data->u.tcp_stats.recv_plr); + nstat_set_keyval_scalar(&kv[i++], + NSTAT_SYSINFO_KEY_SEND_TLRTO, + data->u.tcp_stats.send_tlrto_rate); + nstat_set_keyval_scalar(&kv[i++], + NSTAT_SYSINFO_KEY_SEND_REORDERRATE, + data->u.tcp_stats.send_reorder_rate); + nstat_set_keyval_scalar(&kv[i++], + NSTAT_SYSINFO_CONNECTION_ATTEMPTS, + data->u.tcp_stats.connection_attempts); + nstat_set_keyval_scalar(&kv[i++], + NSTAT_SYSINFO_CONNECTION_ACCEPTS, + data->u.tcp_stats.connection_accepts); + nstat_set_keyval_scalar(&kv[i++], + NSTAT_SYSINFO_ECN_CLIENT_ENABLED, + data->u.tcp_stats.ecn_client_enabled); + nstat_set_keyval_scalar(&kv[i++], + NSTAT_SYSINFO_ECN_SERVER_ENABLED, + data->u.tcp_stats.ecn_server_enabled); + nstat_set_keyval_scalar(&kv[i++], + NSTAT_SYSINFO_ECN_CLIENT_SETUP, + data->u.tcp_stats.ecn_client_setup); + nstat_set_keyval_scalar(&kv[i++], + NSTAT_SYSINFO_ECN_SERVER_SETUP, + data->u.tcp_stats.ecn_server_setup); + nstat_set_keyval_scalar(&kv[i++], + NSTAT_SYSINFO_ECN_CLIENT_SUCCESS, + data->u.tcp_stats.ecn_client_success); + nstat_set_keyval_scalar(&kv[i++], + NSTAT_SYSINFO_ECN_SERVER_SUCCESS, + data->u.tcp_stats.ecn_server_success); + nstat_set_keyval_scalar(&kv[i++], + NSTAT_SYSINFO_ECN_NOT_SUPPORTED, + data->u.tcp_stats.ecn_not_supported); + nstat_set_keyval_scalar(&kv[i++], + NSTAT_SYSINFO_ECN_LOST_SYN, + data->u.tcp_stats.ecn_lost_syn); + nstat_set_keyval_scalar(&kv[i++], + NSTAT_SYSINFO_ECN_LOST_SYNACK, + data->u.tcp_stats.ecn_lost_synack); + nstat_set_keyval_scalar(&kv[i++], + NSTAT_SYSINFO_ECN_RECV_CE, + data->u.tcp_stats.ecn_recv_ce); + nstat_set_keyval_scalar(&kv[i++], + NSTAT_SYSINFO_ECN_RECV_ECE, + data->u.tcp_stats.ecn_recv_ece); + nstat_set_keyval_scalar(&kv[i++], + NSTAT_SYSINFO_ECN_SENT_ECE, + data->u.tcp_stats.ecn_sent_ece); + nstat_set_keyval_scalar(&kv[i++], + NSTAT_SYSINFO_ECN_CONN_RECV_CE, + data->u.tcp_stats.ecn_conn_recv_ce); + nstat_set_keyval_scalar(&kv[i++], + NSTAT_SYSINFO_ECN_CONN_RECV_ECE, + data->u.tcp_stats.ecn_conn_recv_ece); + nstat_set_keyval_scalar(&kv[i++], + NSTAT_SYSINFO_ECN_CONN_PLNOCE, + data->u.tcp_stats.ecn_conn_plnoce); + nstat_set_keyval_scalar(&kv[i++], + NSTAT_SYSINFO_ECN_CONN_PL_CE, + data->u.tcp_stats.ecn_conn_pl_ce); + nstat_set_keyval_scalar(&kv[i++], + NSTAT_SYSINFO_ECN_CONN_NOPL_CE, + data->u.tcp_stats.ecn_conn_nopl_ce); + nstat_set_keyval_scalar(&kv[i++], + NSTAT_SYSINFO_TFO_SYN_DATA_RCV, + data->u.tcp_stats.tfo_syn_data_rcv); + nstat_set_keyval_scalar(&kv[i++], + NSTAT_SYSINFO_TFO_COOKIE_REQ_RCV, + data->u.tcp_stats.tfo_cookie_req_rcv); + nstat_set_keyval_scalar(&kv[i++], + NSTAT_SYSINFO_TFO_COOKIE_SENT, + data->u.tcp_stats.tfo_cookie_sent); + nstat_set_keyval_scalar(&kv[i++], + NSTAT_SYSINFO_TFO_COOKIE_INVALID, + data->u.tcp_stats.tfo_cookie_invalid); + nstat_set_keyval_scalar(&kv[i++], + NSTAT_SYSINFO_TFO_COOKIE_REQ, + data->u.tcp_stats.tfo_cookie_req); + nstat_set_keyval_scalar(&kv[i++], + NSTAT_SYSINFO_TFO_COOKIE_RCV, + data->u.tcp_stats.tfo_cookie_rcv); + nstat_set_keyval_scalar(&kv[i++], + NSTAT_SYSINFO_TFO_SYN_DATA_SENT, + data->u.tcp_stats.tfo_syn_data_sent); + nstat_set_keyval_scalar(&kv[i++], + NSTAT_SYSINFO_TFO_SYN_DATA_ACKED, + data->u.tcp_stats.tfo_syn_data_acked); + nstat_set_keyval_scalar(&kv[i++], + NSTAT_SYSINFO_TFO_SYN_LOSS, + data->u.tcp_stats.tfo_syn_loss); + nstat_set_keyval_scalar(&kv[i++], + NSTAT_SYSINFO_TFO_BLACKHOLE, + data->u.tcp_stats.tfo_blackhole); + + VERIFY(i == nkeyvals); + break; + } + } + + if (syscnt != NULL) + { + result = ctl_enqueuedata(control->ncs_kctl, + control->ncs_unit, syscnt, allocsize, CTL_DATA_EOR); + if (result != 0) + { + nstat_stats.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); + if ((control->ncs_flags & NSTAT_FLAG_SYSINFO_SUBSCRIBED) != 0) + { + nstat_sysinfo_send_data_internal(control, 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(); +} + +#pragma mark -- Kernel Control Socket -- + +static kern_ctl_ref nstat_ctlref = NULL; +static lck_grp_t *nstat_lck_grp = NULL; + +static errno_t nstat_control_connect(kern_ctl_ref kctl, struct sockaddr_ctl *sac, void **uinfo); +static errno_t nstat_control_disconnect(kern_ctl_ref kctl, u_int32_t unit, void *uinfo); +static errno_t nstat_control_send(kern_ctl_ref kctl, u_int32_t unit, void *uinfo, mbuf_t m, int flags); + +static errno_t +nstat_enqueue_success( + uint64_t context, + nstat_control_state *state, + u_int16_t flags) +{ + nstat_msg_hdr success; + errno_t result; + + bzero(&success, sizeof(success)); + success.context = context; + success.type = NSTAT_MSG_TYPE_SUCCESS; + success.length = sizeof(success); + success.flags = flags; + result = ctl_enqueuedata(state->ncs_kctl, state->ncs_unit, &success, + sizeof(success), CTL_DATA_EOR | CTL_DATA_CRIT); + if (result != 0) { + if (nstat_debug != 0) + printf("%s: could not enqueue success message %d\n", + __func__, result); + nstat_stats.nstat_successmsgfailures += 1; + } + return result; +} + +static errno_t +nstat_control_send_goodbye( + nstat_control_state *state, + nstat_src *src) +{ + errno_t result = 0; + int failed = 0; + + if (nstat_control_reporting_allowed(state, src)) + { + if ((state->ncs_flags & NSTAT_FLAG_SUPPORTS_UPDATES) != 0) + { + result = nstat_control_send_update(state, src, 0, NSTAT_MSG_HDR_FLAG_CLOSING, NULL); + if (result != 0) + { + failed = 1; + if (nstat_debug != 0) + printf("%s - nstat_control_send_update() %d\n", __func__, result); + } + } + else + { + // send one last counts notification + result = nstat_control_send_counts(state, src, 0, NSTAT_MSG_HDR_FLAG_CLOSING, NULL); + if (result != 0) + { + failed = 1; + if (nstat_debug != 0) + printf("%s - nstat_control_send_counts() %d\n", __func__, result); + } + + // send a last description + result = nstat_control_send_description(state, src, 0, NSTAT_MSG_HDR_FLAG_CLOSING); + if (result != 0) + { + failed = 1; + if (nstat_debug != 0) + printf("%s - nstat_control_send_description() %d\n", __func__, result); + } + } + } + + // send the source removed notification + result = nstat_control_send_removed(state, src); + if (result != 0 && nstat_debug) + { + failed = 1; + if (nstat_debug != 0) + printf("%s - nstat_control_send_removed() %d\n", __func__, result); + } + + if (failed != 0) + nstat_stats.nstat_control_send_goodbye_failures++; + + + return result; +} + +static errno_t +nstat_flush_accumulated_msgs( + nstat_control_state *state) +{ + errno_t result = 0; + if (state->ncs_accumulated && mbuf_len(state->ncs_accumulated)) + { + mbuf_pkthdr_setlen(state->ncs_accumulated, mbuf_len(state->ncs_accumulated)); + result = ctl_enqueuembuf(state->ncs_kctl, state->ncs_unit, state->ncs_accumulated, CTL_DATA_EOR); + if (result != 0 && nstat_debug) + { + nstat_stats.nstat_flush_accumulated_msgs_failures++; + if (nstat_debug != 0) + printf("%s - ctl_enqueuembuf failed: %d\n", __func__, result); + mbuf_freem(state->ncs_accumulated); + } + state->ncs_accumulated = NULL; + } + return result; +} + +static errno_t +nstat_accumulate_msg( + nstat_control_state *state, + nstat_msg_hdr *hdr, + size_t length) +{ + if (state->ncs_accumulated && mbuf_trailingspace(state->ncs_accumulated) < length) + { + // Will send the current mbuf + nstat_flush_accumulated_msgs(state); + } + + errno_t result = 0; + + if (state->ncs_accumulated == NULL) + { + unsigned int one = 1; + if (mbuf_allocpacket(MBUF_DONTWAIT, NSTAT_MAX_MSG_SIZE, &one, &state->ncs_accumulated) != 0) + { + if (nstat_debug != 0) + printf("%s - mbuf_allocpacket failed\n", __func__); + result = ENOMEM; + } + else + { + mbuf_setlen(state->ncs_accumulated, 0); + } } - else if (inp->inp_vflag & INP_IPV4) + + if (result == 0) { - nstat_ip_to_sockaddr(&inp->inp_laddr, inp->inp_lport, - &desc->local.v4, sizeof(desc->local)); - nstat_ip_to_sockaddr(&inp->inp_faddr, inp->inp_fport, - &desc->remote.v4, sizeof(desc->remote)); + hdr->length = length; + result = mbuf_copyback(state->ncs_accumulated, mbuf_len(state->ncs_accumulated), + length, hdr, MBUF_DONTWAIT); } - if (inp->inp_route.ro_rt && inp->inp_route.ro_rt->rt_ifp) - desc->ifindex = inp->inp_route.ro_rt->rt_ifp->if_index; - - struct socket *so = inp->inp_socket; - if (so) + if (result != 0) { - // TBD - take the socket lock around these to make sure - // they're in sync? - desc->upid = so->last_upid; - desc->pid = so->last_pid; - - desc->rcvbufsize = so->so_rcv.sb_hiwat; - desc->rcvbufused = so->so_rcv.sb_cc; - - proc_name(desc->pid, desc->pname, sizeof(desc->pname)); - desc->pname[sizeof(desc->pname) - 1] = 0; + nstat_flush_accumulated_msgs(state); + if (nstat_debug != 0) + printf("%s - resorting to ctl_enqueuedata\n", __func__); + result = ctl_enqueuedata(state->ncs_kctl, state->ncs_unit, hdr, length, CTL_DATA_EOR); } - return 0; -} + if (result != 0) + nstat_stats.nstat_accumulate_msg_failures++; -static void -nstat_init_udp_provider(void) -{ - bzero(&nstat_udp_provider, sizeof(nstat_udp_provider)); - nstat_udp_provider.nstat_provider_id = NSTAT_PROVIDER_UDP; - nstat_udp_provider.nstat_descriptor_length = sizeof(nstat_udp_descriptor); - nstat_udp_provider.nstat_lookup = nstat_udp_lookup; - nstat_udp_provider.nstat_gone = nstat_udp_gone; - nstat_udp_provider.nstat_counts = nstat_udp_counts; - nstat_udp_provider.nstat_watcher_add = nstat_udp_add_watcher; - nstat_udp_provider.nstat_watcher_remove = nstat_udp_remove_watcher; - nstat_udp_provider.nstat_copy_descriptor = nstat_udp_copy_descriptor; - nstat_udp_provider.nstat_release = nstat_udp_release; - nstat_udp_provider.next = nstat_providers; - nstat_providers = &nstat_udp_provider; + return result; } -#pragma mark -- Kernel Control Socket -- - -typedef struct nstat_src -{ - struct nstat_src *next; - nstat_src_ref_t srcref; - nstat_provider *provider; - nstat_provider_cookie_t cookie; -} nstat_src; - -static kern_ctl_ref nstat_ctlref = NULL; -static lck_grp_t *nstat_lck_grp = NULL; - -static errno_t nstat_control_connect(kern_ctl_ref kctl, struct sockaddr_ctl *sac, void **uinfo); -static errno_t nstat_control_disconnect(kern_ctl_ref kctl, u_int32_t unit, void *uinfo); -static errno_t nstat_control_send(kern_ctl_ref kctl, u_int32_t unit, void *uinfo, mbuf_t m, int flags); -static int nstat_control_send_description(nstat_control_state *state, nstat_src *src, u_int64_t context); -static void nstat_control_cleanup_source(nstat_control_state *state, struct nstat_src *src); - - static void* nstat_idle_check( __unused thread_call_param_t p0, @@ -1222,46 +2579,44 @@ nstat_idle_check( { lck_mtx_lock(&nstat_mtx); - nstat_idle_time = 0ULL; + nstat_idle_time = 0; nstat_control_state *control; nstat_src *dead = NULL; nstat_src *dead_list = NULL; - for (control = nstat_controls; control; control = control->next) + for (control = nstat_controls; control; control = control->ncs_next) { lck_mtx_lock(&control->mtx); - nstat_src **srcpp = &control->srcs; + nstat_src **srcpp = &control->ncs_srcs; - while(*srcpp != NULL) + if (!(control->ncs_flags & NSTAT_FLAG_REQCOUNTS)) { - if ((*srcpp)->provider->nstat_gone((*srcpp)->cookie)) - { - // Pull it off the list - dead = *srcpp; - *srcpp = (*srcpp)->next; - - // send a last description - nstat_control_send_description(control, dead, 0ULL); - - // send the source removed notification - nstat_msg_src_removed removed; - removed.hdr.type = NSTAT_MSG_TYPE_SRC_REMOVED; - removed.hdr.context = 0; - removed.srcref = dead->srcref; - (void)ctl_enqueuedata(control->kctl, control->unit, &removed, sizeof(removed), CTL_DATA_EOR); - - // Put this on the list to release later - dead->next = dead_list; - dead_list = dead; - } - else + while(*srcpp != NULL) { - srcpp = &(*srcpp)->next; + if ((*srcpp)->provider->nstat_gone((*srcpp)->cookie)) + { + errno_t result; + + // Pull it off the list + dead = *srcpp; + *srcpp = (*srcpp)->next; + + result = nstat_control_send_goodbye(control, dead); + + // Put this on the list to release later + dead->next = dead_list; + dead_list = dead; + } + else + { + srcpp = &(*srcpp)->next; + } } } + control->ncs_flags &= ~NSTAT_FLAG_REQCOUNTS; lck_mtx_unlock(&control->mtx); } - + if (nstat_controls) { clock_interval_to_deadline(60, NSEC_PER_SEC, &nstat_idle_time); @@ -1270,13 +2625,16 @@ nstat_idle_check( lck_mtx_unlock(&nstat_mtx); + /* Generate any system level reports, if needed */ + nstat_sysinfo_generate_report(); + // Release the sources now that we aren't holding lots of locks while (dead_list) { dead = dead_list; dead_list = dead->next; - nstat_control_cleanup_source(NULL, dead); + nstat_control_cleanup_source(NULL, dead, FALSE); } return NULL; @@ -1297,34 +2655,56 @@ nstat_control_register(void) struct kern_ctl_reg nstat_control; bzero(&nstat_control, sizeof(nstat_control)); strlcpy(nstat_control.ctl_name, NET_STAT_CONTROL_NAME, sizeof(nstat_control.ctl_name)); + nstat_control.ctl_flags = CTL_FLAG_REG_EXTENDED | CTL_FLAG_REG_CRIT; + nstat_control.ctl_sendsize = nstat_sendspace; + nstat_control.ctl_recvsize = nstat_recvspace; nstat_control.ctl_connect = nstat_control_connect; nstat_control.ctl_disconnect = nstat_control_disconnect; nstat_control.ctl_send = nstat_control_send; - errno_t result = ctl_register(&nstat_control, &nstat_ctlref); - if (result != 0) - printf("%s:%d ctl_register failed: %d", __FUNCTION__, __LINE__, result); + ctl_register(&nstat_control, &nstat_ctlref); } static void nstat_control_cleanup_source( nstat_control_state *state, - struct nstat_src *src) + struct nstat_src *src, + boolean_t locked) { + errno_t result; + if (state) { - nstat_msg_src_removed removed; - removed.hdr.type = NSTAT_MSG_TYPE_SRC_REMOVED; - removed.hdr.context = 0; - removed.srcref = src->srcref; - (void)ctl_enqueuedata(state->kctl, state->unit, &removed, sizeof(removed), CTL_DATA_EOR); + result = nstat_control_send_removed(state, src); + if (result != 0) + { + nstat_stats.nstat_control_cleanup_source_failures++; + if (nstat_debug != 0) + printf("%s - nstat_control_send_removed() %d\n", + __func__, result); + } } - // Cleanup the source if we found it. - src->provider->nstat_release(src->cookie); + src->provider->nstat_release(src->cookie, locked); OSFree(src, sizeof(*src), nstat_malloc_tag); } + +static bool +nstat_control_reporting_allowed( + nstat_control_state *state, + nstat_src *src) +{ + if (src->provider->nstat_reporting_allowed == NULL) + return TRUE; + + return ( + src->provider->nstat_reporting_allowed( src->cookie, + state->ncs_provider_filters[src->provider->nstat_provider_id]) + ); +} + + static errno_t nstat_control_connect( kern_ctl_ref kctl, @@ -1336,20 +2716,16 @@ nstat_control_connect( bzero(state, sizeof(*state)); lck_mtx_init(&state->mtx, nstat_lck_grp, NULL); - state->kctl = kctl; - state->unit = sac->sc_unit; + state->ncs_kctl = kctl; + state->ncs_unit = sac->sc_unit; + state->ncs_flags = NSTAT_FLAG_REQCOUNTS; *uinfo = state; - // check if we're super user - proc_t pself = proc_self(); - state->suser = proc_suser(pself) == 0; - proc_rele(pself); - lck_mtx_lock(&nstat_mtx); - state->next = nstat_controls; + state->ncs_next = nstat_controls; nstat_controls = state; - if (nstat_idle_time == 0ULL) + if (nstat_idle_time == 0) { clock_interval_to_deadline(60, NSEC_PER_SEC, &nstat_idle_time); thread_call_func_delayed((thread_call_func_t)nstat_idle_check, NULL, nstat_idle_time); @@ -1364,7 +2740,7 @@ static errno_t nstat_control_disconnect( __unused kern_ctl_ref kctl, __unused u_int32_t unit, - __unused void *uinfo) + void *uinfo) { u_int32_t watching; nstat_control_state *state = (nstat_control_state*)uinfo; @@ -1372,11 +2748,11 @@ nstat_control_disconnect( // pull it out of the global list of states lck_mtx_lock(&nstat_mtx); nstat_control_state **statepp; - for (statepp = &nstat_controls; *statepp; statepp = &(*statepp)->next) + for (statepp = &nstat_controls; *statepp; statepp = &(*statepp)->ncs_next) { if (*statepp == state) { - *statepp = state->next; + *statepp = state->ncs_next; break; } } @@ -1385,8 +2761,8 @@ nstat_control_disconnect( lck_mtx_lock(&state->mtx); // Stop watching for sources nstat_provider *provider; - watching = state->watching; - state->watching = 0; + watching = state->ncs_watching; + state->ncs_watching = 0; for (provider = nstat_providers; provider && watching; provider = provider->next) { if ((watching & (1 << provider->nstat_provider_id)) != 0) @@ -1397,11 +2773,17 @@ nstat_control_disconnect( } // set cleanup flags - state->cleanup = TRUE; + state->ncs_flags |= NSTAT_FLAG_CLEANUP; + + if (state->ncs_accumulated) + { + mbuf_freem(state->ncs_accumulated); + state->ncs_accumulated = NULL; + } // Copy out the list of sources - nstat_src *srcs = state->srcs; - state->srcs = NULL; + nstat_src *srcs = state->ncs_srcs; + state->ncs_srcs = NULL; lck_mtx_unlock(&state->mtx); while (srcs) @@ -1413,9 +2795,9 @@ nstat_control_disconnect( srcs = src->next; // clean it up - nstat_control_cleanup_source(NULL, src); + nstat_control_cleanup_source(NULL, src, FALSE); } - + lck_mtx_destroy(&state->mtx, nstat_lck_grp); OSFree(state, sizeof(*state), nstat_malloc_tag); return 0; @@ -1430,78 +2812,344 @@ nstat_control_next_src_ref( for (i = 0; i < 1000 && toReturn == NSTAT_SRC_REF_INVALID; i++) { - if (state->next_srcref == NSTAT_SRC_REF_INVALID || - state->next_srcref == NSTAT_SRC_REF_ALL) + if (state->ncs_next_srcref == NSTAT_SRC_REF_INVALID || + state->ncs_next_srcref == NSTAT_SRC_REF_ALL) { - state->next_srcref = 1; + state->ncs_next_srcref = 1; } nstat_src *src; - for (src = state->srcs; src; src = src->next) + for (src = state->ncs_srcs; src; src = src->next) { - if (src->srcref == state->next_srcref) + if (src->srcref == state->ncs_next_srcref) break; } - if (src == NULL) toReturn = state->next_srcref; - state->next_srcref++; + if (src == NULL) toReturn = state->ncs_next_srcref; + state->ncs_next_srcref++; } return toReturn; } +static errno_t +nstat_control_send_counts( + nstat_control_state *state, + nstat_src *src, + unsigned long long context, + u_int16_t hdr_flags, + int *gone) +{ + nstat_msg_src_counts counts; + errno_t result = 0; + + /* Some providers may not have any counts to send */ + if (src->provider->nstat_counts == NULL) + return (0); + + bzero(&counts, sizeof(counts)); + counts.hdr.type = NSTAT_MSG_TYPE_SRC_COUNTS; + counts.hdr.length = sizeof(counts); + counts.hdr.flags = hdr_flags; + counts.hdr.context = context; + counts.srcref = src->srcref; + + if (src->provider->nstat_counts(src->cookie, &counts.counts, gone) == 0) + { + if ((src->filter & NSTAT_FILTER_NOZEROBYTES) && + counts.counts.nstat_rxbytes == 0 && + counts.counts.nstat_txbytes == 0) + { + result = EAGAIN; + } + else + { + result = ctl_enqueuedata(state->ncs_kctl, + state->ncs_unit, &counts, sizeof(counts), + CTL_DATA_EOR); + if (result != 0) + nstat_stats.nstat_sendcountfailures += 1; + } + } + return result; +} + +static errno_t +nstat_control_append_counts( + nstat_control_state *state, + nstat_src *src, + int *gone) +{ + /* Some providers may not have any counts to send */ + if (!src->provider->nstat_counts) return 0; + + nstat_msg_src_counts counts; + bzero(&counts, sizeof(counts)); + counts.hdr.type = NSTAT_MSG_TYPE_SRC_COUNTS; + counts.hdr.length = sizeof(counts); + counts.srcref = src->srcref; + + errno_t result = 0; + result = src->provider->nstat_counts(src->cookie, &counts.counts, gone); + if (result != 0) + { + return result; + } + + if ((src->filter & NSTAT_FILTER_NOZEROBYTES) == NSTAT_FILTER_NOZEROBYTES && + counts.counts.nstat_rxbytes == 0 && counts.counts.nstat_txbytes == 0) + { + return EAGAIN; + } + + return nstat_accumulate_msg(state, &counts.hdr, counts.hdr.length); +} + static int nstat_control_send_description( nstat_control_state *state, nstat_src *src, - u_int64_t context) + u_int64_t context, + u_int16_t hdr_flags) { // Provider doesn't support getting the descriptor? Done. if (src->provider->nstat_descriptor_length == 0 || src->provider->nstat_copy_descriptor == NULL) { - lck_mtx_unlock(&state->mtx); - printf("%s:%d - provider doesn't support descriptions\n", __FUNCTION__, __LINE__); return EOPNOTSUPP; } - + // Allocate storage for the descriptor message mbuf_t msg; unsigned int one = 1; u_int32_t size = offsetof(nstat_msg_src_description, data) + src->provider->nstat_descriptor_length; - if (mbuf_allocpacket(MBUF_WAITOK, size, &one, &msg) != 0) + if (mbuf_allocpacket(MBUF_DONTWAIT, size, &one, &msg) != 0) { - lck_mtx_unlock(&state->mtx); - printf("%s:%d - failed to allocate response\n", __FUNCTION__, __LINE__); return ENOMEM; } - + nstat_msg_src_description *desc = (nstat_msg_src_description*)mbuf_data(msg); + bzero(desc, size); mbuf_setlen(msg, size); mbuf_pkthdr_setlen(msg, mbuf_len(msg)); - + // Query the provider for the provider specific bits errno_t result = src->provider->nstat_copy_descriptor(src->cookie, desc->data, src->provider->nstat_descriptor_length); - + if (result != 0) { mbuf_freem(msg); - printf("%s:%d - provider failed to copy descriptor %d\n", __FUNCTION__, __LINE__, result); return result; } - + desc->hdr.context = context; desc->hdr.type = NSTAT_MSG_TYPE_SRC_DESC; + desc->hdr.length = size; + desc->hdr.flags = hdr_flags; + desc->srcref = src->srcref; + desc->provider = src->provider->nstat_provider_id; + + result = ctl_enqueuembuf(state->ncs_kctl, state->ncs_unit, msg, CTL_DATA_EOR); + if (result != 0) + { + nstat_stats.nstat_descriptionfailures += 1; + mbuf_freem(msg); + } + + return result; +} + +static errno_t +nstat_control_append_description( + nstat_control_state *state, + nstat_src *src) +{ + size_t size = offsetof(nstat_msg_src_description, data) + src->provider->nstat_descriptor_length; + if (size > 512 || src->provider->nstat_descriptor_length == 0 || + src->provider->nstat_copy_descriptor == NULL) + { + return EOPNOTSUPP; + } + + // Fill out a buffer on the stack, we will copy to the mbuf later + u_int64_t buffer[size/sizeof(u_int64_t) + 1]; // u_int64_t to ensure alignment + bzero(buffer, size); + + nstat_msg_src_description *desc = (nstat_msg_src_description*)buffer; + desc->hdr.type = NSTAT_MSG_TYPE_SRC_DESC; + desc->hdr.length = size; + desc->srcref = src->srcref; + desc->provider = src->provider->nstat_provider_id; + + errno_t result = 0; + // Fill in the description + // Query the provider for the provider specific bits + result = src->provider->nstat_copy_descriptor(src->cookie, desc->data, + src->provider->nstat_descriptor_length); + if (result != 0) + { + return result; + } + + return nstat_accumulate_msg(state, &desc->hdr, size); +} + +static int +nstat_control_send_update( + nstat_control_state *state, + nstat_src *src, + u_int64_t context, + u_int16_t hdr_flags, + int *gone) +{ + // Provider doesn't support getting the descriptor or counts? Done. + if ((src->provider->nstat_descriptor_length == 0 || + src->provider->nstat_copy_descriptor == NULL) && + src->provider->nstat_counts == NULL) + { + return EOPNOTSUPP; + } + + // Allocate storage for the descriptor message + mbuf_t msg; + unsigned int one = 1; + u_int32_t size = offsetof(nstat_msg_src_update, data) + + src->provider->nstat_descriptor_length; + if (mbuf_allocpacket(MBUF_DONTWAIT, size, &one, &msg) != 0) + { + return ENOMEM; + } + + nstat_msg_src_update *desc = (nstat_msg_src_update*)mbuf_data(msg); + bzero(desc, size); + desc->hdr.context = context; + desc->hdr.type = NSTAT_MSG_TYPE_SRC_UPDATE; + desc->hdr.length = size; + desc->hdr.flags = hdr_flags; desc->srcref = src->srcref; desc->provider = src->provider->nstat_provider_id; - result = ctl_enqueuembuf(state->kctl, state->unit, msg, CTL_DATA_EOR); + mbuf_setlen(msg, size); + mbuf_pkthdr_setlen(msg, mbuf_len(msg)); + + errno_t result = 0; + if (src->provider->nstat_descriptor_length != 0 && src->provider->nstat_copy_descriptor) + { + // Query the provider for the provider specific bits + result = src->provider->nstat_copy_descriptor(src->cookie, desc->data, + src->provider->nstat_descriptor_length); + if (result != 0) + { + mbuf_freem(msg); + return result; + } + } + + if (src->provider->nstat_counts) + { + result = src->provider->nstat_counts(src->cookie, &desc->counts, gone); + if (result == 0) + { + if ((src->filter & NSTAT_FILTER_NOZEROBYTES) == NSTAT_FILTER_NOZEROBYTES && + desc->counts.nstat_rxbytes == 0 && desc->counts.nstat_txbytes == 0) + { + result = EAGAIN; + } + else + { + result = ctl_enqueuembuf(state->ncs_kctl, state->ncs_unit, msg, CTL_DATA_EOR); + } + } + } + if (result != 0) { - printf("%s:%d ctl_enqueuembuf returned error %d\n", __FUNCTION__, __LINE__, result); + nstat_stats.nstat_srcupatefailures += 1; mbuf_freem(msg); } + + return result; +} + +static errno_t +nstat_control_append_update( + nstat_control_state *state, + nstat_src *src, + int *gone) +{ + size_t size = offsetof(nstat_msg_src_update, data) + src->provider->nstat_descriptor_length; + if (size > 512 || ((src->provider->nstat_descriptor_length == 0 || + src->provider->nstat_copy_descriptor == NULL) && + src->provider->nstat_counts == NULL)) + { + return EOPNOTSUPP; + } + + // Fill out a buffer on the stack, we will copy to the mbuf later + u_int64_t buffer[size/sizeof(u_int64_t) + 1]; // u_int64_t to ensure alignment + bzero(buffer, size); + + nstat_msg_src_update *desc = (nstat_msg_src_update*)buffer; + desc->hdr.type = NSTAT_MSG_TYPE_SRC_UPDATE; + desc->hdr.length = size; + desc->srcref = src->srcref; + desc->provider = src->provider->nstat_provider_id; + + errno_t result = 0; + // Fill in the description + if (src->provider->nstat_descriptor_length != 0 && src->provider->nstat_copy_descriptor) + { + // Query the provider for the provider specific bits + result = src->provider->nstat_copy_descriptor(src->cookie, desc->data, + src->provider->nstat_descriptor_length); + if (result != 0) + { + nstat_stats.nstat_copy_descriptor_failures++; + if (nstat_debug != 0) + printf("%s: src->provider->nstat_copy_descriptor: %d\n", __func__, result); + return result; + } + } + + if (src->provider->nstat_counts) + { + result = src->provider->nstat_counts(src->cookie, &desc->counts, gone); + if (result != 0) + { + nstat_stats.nstat_provider_counts_failures++; + if (nstat_debug != 0) + printf("%s: src->provider->nstat_counts: %d\n", __func__, result); + return result; + } + + if ((src->filter & NSTAT_FILTER_NOZEROBYTES) == NSTAT_FILTER_NOZEROBYTES && + desc->counts.nstat_rxbytes == 0 && desc->counts.nstat_txbytes == 0) + { + return EAGAIN; + } + } + return nstat_accumulate_msg(state, &desc->hdr, size); +} + +static errno_t +nstat_control_send_removed( + nstat_control_state *state, + nstat_src *src) +{ + nstat_msg_src_removed removed; + errno_t result; + + bzero(&removed, sizeof(removed)); + removed.hdr.type = NSTAT_MSG_TYPE_SRC_REMOVED; + removed.hdr.length = sizeof(removed); + removed.hdr.context = 0; + removed.srcref = src->srcref; + result = ctl_enqueuedata(state->ncs_kctl, state->ncs_unit, &removed, + sizeof(removed), CTL_DATA_EOR | CTL_DATA_CRIT); + if (result != 0) + nstat_stats.nstat_msgremovedfailures += 1; + return result; } @@ -1511,12 +3159,10 @@ nstat_control_handle_add_request( mbuf_t m) { errno_t result; - + // Verify the header fits in the first mbuf if (mbuf_len(m) < offsetof(nstat_msg_add_src_req, param)) { - printf("mbuf_len(m)=%lu, offsetof(nstat_msg_add_src_req*, param)=%lu\n", - mbuf_len(m), offsetof(nstat_msg_add_src_req, param)); return EINVAL; } @@ -1524,7 +3170,6 @@ nstat_control_handle_add_request( int32_t paramlength = mbuf_pkthdr_len(m) - offsetof(nstat_msg_add_src_req, param); if (paramlength < 0 || paramlength > 2 * 1024) { - printf("invalid paramlength=%d\n", paramlength); return EINVAL; } @@ -1554,151 +3199,146 @@ nstat_control_handle_add_request( result = nstat_control_source_add(req->hdr.context, state, provider, cookie); if (result != 0) - provider->nstat_release(cookie); + provider->nstat_release(cookie, 0); return result; } -static int -nstat_perm_check( - __unused nstat_control_state *state) -{ - int allow = 0; -#if !REQUIRE_ROOT_FOR_STATS - allow = 1; -#else - // If the socket was created by a priv process, allow - if (state->suser) return 1; - - // If the current process is priv, allow - proc_t self = proc_self(); - allow = proc_suser(self) == 0; - proc_rele(self); - - // TBD: check for entitlement, root check is too coarse -#endif /* REQUIRE_ROOT_FOR_STATS */ - - return allow; -} - static errno_t nstat_control_handle_add_all( nstat_control_state *state, mbuf_t m) -{ - errno_t result = 0; - - if (!nstat_perm_check(state)) - { - return EPERM; - } +{ + errno_t result = 0; // Verify the header fits in the first mbuf if (mbuf_len(m) < sizeof(nstat_msg_add_all_srcs)) { - printf("mbuf_len(m)=%lu, sizeof(nstat_msg_add_all_srcs)=%lu\n", - mbuf_len(m), sizeof(nstat_msg_add_all_srcs)); return EINVAL; } + nstat_msg_add_all_srcs *req = mbuf_data(m); + if (req->provider > NSTAT_PROVIDER_LAST) return ENOENT; + nstat_provider *provider = nstat_find_provider_by_id(req->provider); - + u_int64_t filter = req->filter; + if (!provider) return ENOENT; if (provider->nstat_watcher_add == NULL) return ENOTSUP; + if (nstat_privcheck != 0) { + result = priv_check_cred(kauth_cred_get(), + PRIV_NET_PRIVILEGED_NETWORK_STATISTICS, 0); + if (result != 0) + return result; + } + // Make sure we don't add the provider twice lck_mtx_lock(&state->mtx); - if ((state->watching & (1 << provider->nstat_provider_id)) != 0) + if ((state->ncs_watching & (1 << provider->nstat_provider_id)) != 0) result = EALREADY; - state->watching |= (1 << provider->nstat_provider_id); + state->ncs_watching |= (1 << provider->nstat_provider_id); lck_mtx_unlock(&state->mtx); if (result != 0) return result; - + + state->ncs_provider_filters[req->provider] = filter; + result = provider->nstat_watcher_add(state); if (result != 0) { + state->ncs_provider_filters[req->provider] = 0; lck_mtx_lock(&state->mtx); - state->watching &= ~(1 << provider->nstat_provider_id); + state->ncs_watching &= ~(1 << provider->nstat_provider_id); lck_mtx_unlock(&state->mtx); } - if (result == 0) - { - // Notify the client - nstat_msg_hdr success; - success.context = req->hdr.context; - success.type = NSTAT_MSG_TYPE_SUCCESS; - success.pad = 0; - if (ctl_enqueuedata(state->kctl, state->unit, &success, sizeof(success), CTL_DATA_EOR) != 0) - printf("%s:%d - failed to enqueue success message\n", __FUNCTION__, __LINE__); - } + nstat_enqueue_success(req->hdr.context, state, 0); return result; } static errno_t nstat_control_source_add( - u_int64_t context, + u_int64_t context, nstat_control_state *state, nstat_provider *provider, - nstat_provider_cookie_t cookie) + nstat_provider_cookie_t cookie) { - // Fill out source added message - mbuf_t msg = NULL; - unsigned int one = 1; + // Fill out source added message if appropriate + mbuf_t msg = NULL; + nstat_src_ref_t *srcrefp = NULL; + + u_int64_t provider_filters = + state->ncs_provider_filters[provider->nstat_provider_id]; + boolean_t tell_user = + ((provider_filters & NSTAT_FILTER_SUPPRESS_SRC_ADDED) == 0); + u_int32_t src_filter = + (provider_filters & NSTAT_FILTER_PROVIDER_NOZEROBYTES) + ? NSTAT_FILTER_NOZEROBYTES : 0; + + if (tell_user) + { + unsigned int one = 1; - if (mbuf_allocpacket(MBUF_WAITOK, sizeof(nstat_msg_src_added), &one, &msg) != 0) - return ENOMEM; + if (mbuf_allocpacket(MBUF_DONTWAIT, sizeof(nstat_msg_src_added), + &one, &msg) != 0) + return ENOMEM; - mbuf_setlen(msg, sizeof(nstat_msg_src_added)); - mbuf_pkthdr_setlen(msg, mbuf_len(msg)); - nstat_msg_src_added *add = mbuf_data(msg); - bzero(add, sizeof(*add)); - add->hdr.type = NSTAT_MSG_TYPE_SRC_ADDED; - add->hdr.context = context; - add->provider = provider->nstat_provider_id; + mbuf_setlen(msg, sizeof(nstat_msg_src_added)); + mbuf_pkthdr_setlen(msg, mbuf_len(msg)); + nstat_msg_src_added *add = mbuf_data(msg); + bzero(add, sizeof(*add)); + add->hdr.type = NSTAT_MSG_TYPE_SRC_ADDED; + add->hdr.length = mbuf_len(msg); + add->hdr.context = context; + add->provider = provider->nstat_provider_id; + srcrefp = &add->srcref; + } // Allocate storage for the source nstat_src *src = OSMalloc(sizeof(*src), nstat_malloc_tag); if (src == NULL) { - mbuf_freem(msg); + if (msg) mbuf_freem(msg); return ENOMEM; } // Fill in the source, including picking an unused source ref lck_mtx_lock(&state->mtx); - - add->srcref = src->srcref = nstat_control_next_src_ref(state); - if (state->cleanup || src->srcref == NSTAT_SRC_REF_INVALID) + + src->srcref = nstat_control_next_src_ref(state); + if (srcrefp) + *srcrefp = src->srcref; + + if (state->ncs_flags & NSTAT_FLAG_CLEANUP || src->srcref == NSTAT_SRC_REF_INVALID) { lck_mtx_unlock(&state->mtx); OSFree(src, sizeof(*src), nstat_malloc_tag); - mbuf_freem(msg); + if (msg) mbuf_freem(msg); return EINVAL; } src->provider = provider; src->cookie = cookie; - - // send the source added message - errno_t result = ctl_enqueuembuf(state->kctl, state->unit, msg, CTL_DATA_EOR); - if (result != 0) + src->filter = src_filter; + + if (msg) { - lck_mtx_unlock(&state->mtx); - printf("%s:%d ctl_enqueuembuf failed: %d\n", __FUNCTION__, __LINE__, result); - OSFree(src, sizeof(*src), nstat_malloc_tag); - mbuf_freem(msg); - return result; + // send the source added message if appropriate + errno_t result = ctl_enqueuembuf(state->ncs_kctl, state->ncs_unit, msg, + CTL_DATA_EOR); + if (result != 0) + { + nstat_stats.nstat_srcaddedfailures += 1; + lck_mtx_unlock(&state->mtx); + OSFree(src, sizeof(*src), nstat_malloc_tag); + mbuf_freem(msg); + return result; + } } - - // Put the source in the list - src->next = state->srcs; - state->srcs = src; - - // send the description message - // not useful as the source is often not complete -// nstat_control_send_description(state, src, 0ULL); + // Put the source in the list + src->next = state->ncs_srcs; + state->ncs_srcs = src; lck_mtx_unlock(&state->mtx); @@ -1714,7 +3354,6 @@ nstat_control_handle_remove_request( if (mbuf_copydata(m, offsetof(nstat_msg_rem_src_req, srcref), sizeof(srcref), &srcref) != 0) { - printf("%s:%d - invalid length %u, expected %lu\n", __FUNCTION__, __LINE__, (u_int32_t)mbuf_pkthdr_len(m), sizeof(nstat_msg_rem_src_req)); return EINVAL; } @@ -1723,7 +3362,7 @@ nstat_control_handle_remove_request( // Remove this source as we look for it nstat_src **nextp; nstat_src *src = NULL; - for (nextp = &state->srcs; *nextp; nextp = &(*nextp)->next) + for (nextp = &state->ncs_srcs; *nextp; nextp = &(*nextp)->next) { if ((*nextp)->srcref == srcref) { @@ -1735,7 +3374,7 @@ nstat_control_handle_remove_request( lck_mtx_unlock(&state->mtx); - if (src) nstat_control_cleanup_source(state, src); + if (src) nstat_control_cleanup_source(state, src, FALSE); return src ? 0 : ENOENT; } @@ -1756,59 +3395,128 @@ nstat_control_handle_query_request( nstat_src *dead_srcs = NULL; errno_t result = ENOENT; nstat_msg_query_src_req req; + if (mbuf_copydata(m, 0, sizeof(req), &req) != 0) { - printf("%s:%d - invalid length %u, expected %lu\n", __FUNCTION__, __LINE__, (u_int32_t)mbuf_pkthdr_len(m), sizeof(req)); return EINVAL; } + + const boolean_t all_srcs = (req.srcref == NSTAT_SRC_REF_ALL); lck_mtx_lock(&state->mtx); - nstat_src **srcpp = &state->srcs; - while (*srcpp != NULL) + + if (all_srcs) { - int gone; - gone = 0; + state->ncs_flags |= NSTAT_FLAG_REQCOUNTS; + } + nstat_src **srcpp = &state->ncs_srcs; + u_int64_t src_count = 0; + boolean_t partial = FALSE; + + /* + * Error handling policy and sequence number generation is folded into + * nstat_control_begin_query. + */ + partial = nstat_control_begin_query(state, &req.hdr); + + while (*srcpp != NULL + && (!partial || src_count < QUERY_CONTINUATION_SRC_COUNT)) + { + nstat_src *src = NULL; + int gone; - if (req.srcref == NSTAT_SRC_REF_ALL || - (*srcpp)->srcref == req.srcref) - { - nstat_msg_src_counts counts; - counts.hdr.type = NSTAT_MSG_TYPE_SRC_COUNTS; - counts.hdr.context = req.hdr.context; - counts.srcref = (*srcpp)->srcref; - bzero(&counts.counts, sizeof(counts.counts)); - result = (*srcpp)->provider->nstat_counts((*srcpp)->cookie, &counts.counts, &gone); - - if (result == 0) + src = *srcpp; + gone = 0; + // XXX ignore IFACE types? + if (all_srcs || src->srcref == req.srcref) + { + if (nstat_control_reporting_allowed(state, src) + && (!partial || !all_srcs || src->seq != state->ncs_seq)) { - result = ctl_enqueuedata(state->kctl, state->unit, &counts, sizeof(counts), CTL_DATA_EOR); + if (all_srcs && + (req.hdr.flags & NSTAT_MSG_HDR_FLAG_SUPPORTS_AGGREGATE) != 0) + { + result = nstat_control_append_counts(state, src, &gone); + } + else + { + result = nstat_control_send_counts(state, src, req.hdr.context, 0, &gone); + } + + if (ENOMEM == result || ENOBUFS == result) + { + /* + * If the counts message failed to + * enqueue then we should clear our flag so + * that a client doesn't miss anything on + * idle cleanup. We skip the "gone" + * processing in the hope that we may + * catch it another time. + */ + state->ncs_flags &= ~NSTAT_FLAG_REQCOUNTS; + break; + } + if (partial) + { + /* + * We skip over hard errors and + * filtered sources. + */ + src->seq = state->ncs_seq; + src_count++; + } } - else + } + + if (gone) + { + // send one last descriptor message so client may see last state + // If we can't send the notification now, it + // will be sent in the idle cleanup. + result = nstat_control_send_description(state, *srcpp, 0, 0); + if (result != 0) { - printf("%s:%d provider->nstat_counts failed: %d\n", __FUNCTION__, __LINE__, result); + nstat_stats.nstat_control_send_description_failures++; + if (nstat_debug != 0) + printf("%s - nstat_control_send_description() %d\n", __func__, result); + state->ncs_flags &= ~NSTAT_FLAG_REQCOUNTS; + break; } - if (gone) - { - // send one last descriptor message so client may see last state - nstat_control_send_description(state, *srcpp, 0ULL); - - // pull src out of the list - nstat_src *src = *srcpp; - *srcpp = src->next; - - src->next = dead_srcs; - dead_srcs = src; - } + // pull src out of the list + *srcpp = src->next; - if (req.srcref != NSTAT_SRC_REF_ALL) - break; + src->next = dead_srcs; + dead_srcs = src; } - - if (!gone) + else + { srcpp = &(*srcpp)->next; + } + + if (!all_srcs && req.srcref == src->srcref) + { + break; + } } + nstat_flush_accumulated_msgs(state); + + u_int16_t flags = 0; + if (req.srcref == NSTAT_SRC_REF_ALL) + flags = nstat_control_end_query(state, *srcpp, partial); + lck_mtx_unlock(&state->mtx); + + /* + * If an error occurred enqueueing data, then allow the error to + * propagate to nstat_control_send. This way, the error is sent to + * user-level. + */ + if (all_srcs && ENOMEM != result && ENOBUFS != result) + { + nstat_enqueue_success(req.hdr.context, state, flags); + result = 0; + } while (dead_srcs) { @@ -1818,18 +3526,7 @@ nstat_control_handle_query_request( dead_srcs = src->next; // release src and send notification - nstat_control_cleanup_source(state, src); - } - - if (req.srcref == NSTAT_SRC_REF_ALL) - { - nstat_msg_hdr success; - success.context = req.hdr.context; - success.type = NSTAT_MSG_TYPE_SUCCESS; - success.pad = 0; - if (ctl_enqueuedata(state->kctl, state->unit, &success, sizeof(success), CTL_DATA_EOR) != 0) - printf("%s:%d - failed to enqueue success message\n", __FUNCTION__, __LINE__); - result = 0; + nstat_control_cleanup_source(state, src, FALSE); } return result; @@ -1841,40 +3538,347 @@ nstat_control_handle_get_src_description( mbuf_t m) { nstat_msg_get_src_description req; + errno_t result = ENOENT; + nstat_src *src; + if (mbuf_copydata(m, 0, sizeof(req), &req) != 0) { - printf("%s:%d - invalid length %u, expected %lu\n", __FUNCTION__, __LINE__, (u_int32_t)mbuf_pkthdr_len(m), sizeof(req)); return EINVAL; } + + lck_mtx_lock(&state->mtx); + u_int64_t src_count = 0; + boolean_t partial = FALSE; + const boolean_t all_srcs = (req.srcref == NSTAT_SRC_REF_ALL); + + /* + * Error handling policy and sequence number generation is folded into + * nstat_control_begin_query. + */ + partial = nstat_control_begin_query(state, &req.hdr); + + for (src = state->ncs_srcs; + src && (!partial || src_count < QUERY_CONTINUATION_SRC_COUNT); + src = src->next) + { + if (all_srcs || src->srcref == req.srcref) + { + if (nstat_control_reporting_allowed(state, src) + && (!all_srcs || !partial || src->seq != state->ncs_seq)) + { + if ((req.hdr.flags & NSTAT_MSG_HDR_FLAG_SUPPORTS_AGGREGATE) != 0 && all_srcs) + { + result = nstat_control_append_description(state, src); + } + else + { + result = nstat_control_send_description(state, src, req.hdr.context, 0); + } + + if (ENOMEM == result || ENOBUFS == result) + { + /* + * If the description message failed to + * enqueue then we give up for now. + */ + break; + } + if (partial) + { + /* + * Note, we skip over hard errors and + * filtered sources. + */ + src->seq = state->ncs_seq; + src_count++; + } + } + + if (!all_srcs) + { + break; + } + } + } + nstat_flush_accumulated_msgs(state); + + u_int16_t flags = 0; + if (req.srcref == NSTAT_SRC_REF_ALL) + flags = nstat_control_end_query(state, src, partial); + + lck_mtx_unlock(&state->mtx); + /* + * If an error occurred enqueueing data, then allow the error to + * propagate to nstat_control_send. This way, the error is sent to + * user-level. + */ + if (all_srcs && ENOMEM != result && ENOBUFS != result) + { + nstat_enqueue_success(req.hdr.context, state, flags); + result = 0; + } - // Find the source + return result; +} + +static errno_t +nstat_control_handle_set_filter( + nstat_control_state *state, + mbuf_t m) +{ + nstat_msg_set_filter req; + nstat_src *src; + + if (mbuf_copydata(m, 0, sizeof(req), &req) != 0) + return EINVAL; + if (req.srcref == NSTAT_SRC_REF_ALL || + req.srcref == NSTAT_SRC_REF_INVALID) + return EINVAL; + lck_mtx_lock(&state->mtx); - nstat_src *src; - for (src = state->srcs; src; src = src->next) + for (src = state->ncs_srcs; src; src = src->next) + if (req.srcref == src->srcref) + { + src->filter = req.filter; + break; + } + lck_mtx_unlock(&state->mtx); + if (src == NULL) + return ENOENT; + + return 0; +} + +static void +nstat_send_error( + nstat_control_state *state, + u_int64_t context, + u_int32_t error) +{ + errno_t result; + struct nstat_msg_error err; + + bzero(&err, sizeof(err)); + err.hdr.type = NSTAT_MSG_TYPE_ERROR; + err.hdr.length = sizeof(err); + err.hdr.context = context; + err.error = error; + + result = ctl_enqueuedata(state->ncs_kctl, state->ncs_unit, &err, + sizeof(err), CTL_DATA_EOR | CTL_DATA_CRIT); + if (result != 0) + nstat_stats.nstat_msgerrorfailures++; +} + +static boolean_t +nstat_control_begin_query( + nstat_control_state *state, + const nstat_msg_hdr *hdrp) +{ + boolean_t partial = FALSE; + + if (hdrp->flags & NSTAT_MSG_HDR_FLAG_CONTINUATION) + { + /* A partial query all has been requested. */ + partial = TRUE; + + if (state->ncs_context != hdrp->context) + { + if (state->ncs_context != 0) + nstat_send_error(state, state->ncs_context, EAGAIN); + + /* Initialize state for a partial query all. */ + state->ncs_context = hdrp->context; + state->ncs_seq++; + } + } + else if (state->ncs_context != 0) + { + /* + * A continuation of a paced-query was in progress. Send that + * context an error and reset the state. If the same context + * has changed its mind, just send the full query results. + */ + if (state->ncs_context != hdrp->context) + nstat_send_error(state, state->ncs_context, EAGAIN); + } + + return partial; +} + +static u_int16_t +nstat_control_end_query( + nstat_control_state *state, + nstat_src *last_src, + boolean_t partial) +{ + u_int16_t flags = 0; + + if (last_src == NULL || !partial) + { + /* + * We iterated through the entire srcs list or exited early + * from the loop when a partial update was not requested (an + * error occurred), so clear context to indicate internally + * that the query is finished. + */ + state->ncs_context = 0; + } + else + { + /* + * Indicate to userlevel to make another partial request as + * there are still sources left to be reported. + */ + flags |= NSTAT_MSG_HDR_FLAG_CONTINUATION; + } + + return flags; +} + +static errno_t +nstat_control_handle_get_update( + nstat_control_state *state, + mbuf_t m) +{ + nstat_msg_query_src_req req; + + if (mbuf_copydata(m, 0, sizeof(req), &req) != 0) { - if (src->srcref == req.srcref) + return EINVAL; + } + + lck_mtx_lock(&state->mtx); + + state->ncs_flags |= NSTAT_FLAG_SUPPORTS_UPDATES; + + errno_t result = ENOENT; + nstat_src *src; + nstat_src *dead_srcs = NULL; + nstat_src **srcpp = &state->ncs_srcs; + u_int64_t src_count = 0; + boolean_t partial = FALSE; + + /* + * Error handling policy and sequence number generation is folded into + * nstat_control_begin_query. + */ + partial = nstat_control_begin_query(state, &req.hdr); + + while (*srcpp != NULL + && (FALSE == partial + || src_count < QUERY_CONTINUATION_SRC_COUNT)) + { + int gone; + + gone = 0; + src = *srcpp; + if (nstat_control_reporting_allowed(state, src)) + { + /* skip this source if it has the current state + * sequence number as it's already been reported in + * this query-all partial sequence. */ + if (req.srcref == NSTAT_SRC_REF_ALL + && (FALSE == partial || src->seq != state->ncs_seq)) + { + result = nstat_control_append_update(state, src, &gone); + if (ENOMEM == result || ENOBUFS == result) + { + /* + * If the update message failed to + * enqueue then give up. + */ + break; + } + if (partial) + { + /* + * We skip over hard errors and + * filtered sources. + */ + src->seq = state->ncs_seq; + src_count++; + } + } + else if (src->srcref == req.srcref) + { + result = nstat_control_send_update(state, src, req.hdr.context, 0, &gone); + } + } + + if (gone) + { + // pull src out of the list + *srcpp = src->next; + + src->next = dead_srcs; + dead_srcs = src; + } + else + { + srcpp = &(*srcpp)->next; + } + + if (req.srcref != NSTAT_SRC_REF_ALL && req.srcref == src->srcref) + { break; + } + } + + nstat_flush_accumulated_msgs(state); + + + u_int16_t flags = 0; + if (req.srcref == NSTAT_SRC_REF_ALL) + flags = nstat_control_end_query(state, *srcpp, partial); + + lck_mtx_unlock(&state->mtx); + /* + * If an error occurred enqueueing data, then allow the error to + * propagate to nstat_control_send. This way, the error is sent to + * user-level. + */ + if (req.srcref == NSTAT_SRC_REF_ALL && ENOMEM != result && ENOBUFS != result) + { + nstat_enqueue_success(req.hdr.context, state, flags); + result = 0; + } + + while (dead_srcs) + { + src = dead_srcs; + dead_srcs = src->next; + + // release src and send notification + nstat_control_cleanup_source(state, src, FALSE); } - // No source? Done. - if (!src) + return result; +} + +static errno_t +nstat_control_handle_subscribe_sysinfo( + nstat_control_state *state) +{ + errno_t result = priv_check_cred(kauth_cred_get(), PRIV_NET_PRIVILEGED_NETWORK_STATISTICS, 0); + + if (result != 0) { - lck_mtx_unlock(&state->mtx); - printf("%s:%d - no matching source\n", __FUNCTION__, __LINE__); - return ENOENT; + return result; } - errno_t result = nstat_control_send_description(state, src, req.hdr.context); + lck_mtx_lock(&state->mtx); + state->ncs_flags |= NSTAT_FLAG_SYSINFO_SUBSCRIBED; lck_mtx_unlock(&state->mtx); - return result; + return 0; } static errno_t nstat_control_send( kern_ctl_ref kctl, u_int32_t unit, - __unused void *uinfo, + void *uinfo, mbuf_t m, __unused int flags) { @@ -1883,11 +3887,9 @@ nstat_control_send( struct nstat_msg_hdr storage; errno_t result = 0; - if (mbuf_pkthdr_len(m) < sizeof(hdr)) + if (mbuf_pkthdr_len(m) < sizeof(*hdr)) { // Is this the right thing to do? - printf("%s:%d - message too short, was %ld expected %lu\n", __FUNCTION__, __LINE__, - mbuf_pkthdr_len(m), sizeof(*hdr)); mbuf_freem(m); return EINVAL; } @@ -1902,6 +3904,19 @@ nstat_control_send( hdr = &storage; } + // Legacy clients may not set the length + // Those clients are likely not setting the flags either + // Fix everything up so old clients continue to work + if (hdr->length != mbuf_pkthdr_len(m)) + { + hdr->flags = 0; + hdr->length = mbuf_pkthdr_len(m); + if (hdr == &storage) + { + mbuf_copyback(m, 0, sizeof(*hdr), hdr, MBUF_DONTWAIT); + } + } + switch (hdr->type) { case NSTAT_MSG_TYPE_ADD_SRC: @@ -1924,8 +3939,19 @@ nstat_control_send( result = nstat_control_handle_get_src_description(state, m); break; + case NSTAT_MSG_TYPE_SET_FILTER: + result = nstat_control_handle_set_filter(state, m); + break; + + case NSTAT_MSG_TYPE_GET_UPDATE: + result = nstat_control_handle_get_update(state, m); + break; + + case NSTAT_MSG_TYPE_SUBSCRIBE_SYSINFO: + result = nstat_control_handle_subscribe_sysinfo(state); + break; + default: - printf("%s:%d - unknown message type %d\n", __FUNCTION__, __LINE__, hdr->type); result = EINVAL; break; } @@ -1934,14 +3960,36 @@ nstat_control_send( { struct nstat_msg_error err; + bzero(&err, sizeof(err)); err.hdr.type = NSTAT_MSG_TYPE_ERROR; + err.hdr.length = sizeof(err) + mbuf_pkthdr_len(m); err.hdr.context = hdr->context; err.error = result; - result = ctl_enqueuedata(kctl, unit, &err, sizeof(err), CTL_DATA_EOR); + if (mbuf_prepend(&m, sizeof(err), MBUF_DONTWAIT) == 0 && + mbuf_copyback(m, 0, sizeof(err), &err, MBUF_DONTWAIT) == 0) + { + result = ctl_enqueuembuf(kctl, unit, m, CTL_DATA_EOR | CTL_DATA_CRIT); + if (result != 0) + { + mbuf_freem(m); + } + m = NULL; + } + + if (result != 0) + { + // Unable to prepend the error to the request - just send the error + err.hdr.length = sizeof(err); + result = ctl_enqueuedata(kctl, unit, &err, sizeof(err), + CTL_DATA_EOR | CTL_DATA_CRIT); + if (result != 0) + nstat_stats.nstat_msgerrorfailures += 1; + } + nstat_stats.nstat_handle_msg_failures += 1; } - mbuf_freem(m); + if (m) mbuf_freem(m); return result; }