+ if (inp->inp_vflag & INP_IPV4) {
+ error = in_getconninfo(so, SAE_CONNID_ANY, flags, ifindex,
+ soerror, src, src_len, dst, dst_len,
+ aux_type, aux_data, aux_len);
+ } else {
+ error = in6_getconninfo(so, SAE_CONNID_ANY, flags, ifindex,
+ soerror, src, src_len, dst, dst_len,
+ aux_type, aux_data, aux_len);
+ }
+
+ if (error != 0) {
+ os_log_error(mptcp_log_handle, "%s - %lx:error from in_getconninfo %d\n",
+ __func__, (unsigned long)VM_KERNEL_ADDRPERM(mpte), error);
+ return error;
+ }
+
+ if (mpts->mpts_flags & MPTSF_MP_CAPABLE) {
+ *flags |= CIF_MP_CAPABLE;
+ }
+ if (mpts->mpts_flags & MPTSF_MP_DEGRADED) {
+ *flags |= CIF_MP_DEGRADED;
+ }
+ if (mpts->mpts_flags & MPTSF_MP_READY) {
+ *flags |= CIF_MP_READY;
+ }
+ if (mpts->mpts_flags & MPTSF_ACTIVE) {
+ *flags |= CIF_MP_ACTIVE;
+ }
+
+ return 0;
+ } else {
+ /* Per-interface stats */
+ const struct mptsub *mpts, *orig_mpts;
+ struct conninfo_tcp tcp_ci;
+ const struct inpcb *inp;
+ struct socket *so;
+ int error = 0;
+ int index;
+
+ bzero(&tcp_ci, sizeof(tcp_ci));
+
+ /* First, get a subflow to fill in the "regular" info. */
+ TAILQ_FOREACH(mpts, &mpte->mpte_subflows, mpts_entry) {
+ const struct ifnet *ifp = sotoinpcb(mpts->mpts_socket)->inp_last_outifp;
+
+ if (ifp && ifp->if_index == *cid) {
+ break;
+ }
+ }
+
+ if (mpts == NULL) {
+ /* No subflow there - well, let's just get the basic itf-info */
+ goto interface_info;
+ }
+
+ so = mpts->mpts_socket;
+ inp = sotoinpcb(so);
+
+ /* Give it USER_ADDR_NULL, because we are doing this on our own */
+ if (inp->inp_vflag & INP_IPV4) {
+ error = in_getconninfo(so, SAE_CONNID_ANY, flags, ifindex,
+ soerror, src, src_len, dst, dst_len,
+ aux_type, USER_ADDR_NULL, aux_len);
+ } else {
+ error = in6_getconninfo(so, SAE_CONNID_ANY, flags, ifindex,
+ soerror, src, src_len, dst, dst_len,
+ aux_type, USER_ADDR_NULL, aux_len);
+ }
+
+ if (error != 0) {
+ os_log_error(mptcp_log_handle, "%s - %lx:error from in_getconninfo %d\n",
+ __func__, (unsigned long)VM_KERNEL_ADDRPERM(mpte), error);
+ return error;
+ }
+
+ /* ToDo: Nobody is reading these flags on subflows. Why bother ? */
+ if (mpts->mpts_flags & MPTSF_MP_CAPABLE) {
+ *flags |= CIF_MP_CAPABLE;
+ }
+ if (mpts->mpts_flags & MPTSF_MP_DEGRADED) {
+ *flags |= CIF_MP_DEGRADED;
+ }
+ if (mpts->mpts_flags & MPTSF_MP_READY) {
+ *flags |= CIF_MP_READY;
+ }
+ if (mpts->mpts_flags & MPTSF_ACTIVE) {
+ *flags |= CIF_MP_ACTIVE;
+ }
+
+ /*
+ * Now, we gather the metrics (aka., tcp_info) and roll them in
+ * across all subflows of this interface to build an aggregated
+ * view.
+ *
+ * We take the TCP_INFO from the first subflow as the "master",
+ * feeding into those fields that we do not roll.
+ */
+ if (aux_data != USER_ADDR_NULL) {
+ tcp_getconninfo(so, &tcp_ci);
+
+ orig_mpts = mpts;
+ TAILQ_FOREACH(mpts, &mpte->mpte_subflows, mpts_entry) {
+ const struct inpcb *mptsinp = sotoinpcb(mpts->mpts_socket);
+ const struct ifnet *ifp;
+
+ ifp = mptsinp->inp_last_outifp;
+
+ if (ifp == NULL || ifp->if_index != *cid || mpts == orig_mpts) {
+ continue;
+ }
+
+ /* Roll the itf-stats into the tcp_info */
+ tcp_ci.tcpci_tcp_info.tcpi_txbytes +=
+ mptsinp->inp_stat->txbytes;
+ tcp_ci.tcpci_tcp_info.tcpi_rxbytes +=
+ mptsinp->inp_stat->rxbytes;
+
+ tcp_ci.tcpci_tcp_info.tcpi_wifi_txbytes +=
+ mptsinp->inp_wstat->txbytes;
+ tcp_ci.tcpci_tcp_info.tcpi_wifi_rxbytes +=
+ mptsinp->inp_wstat->rxbytes;
+
+ tcp_ci.tcpci_tcp_info.tcpi_wired_txbytes +=
+ mptsinp->inp_Wstat->txbytes;
+ tcp_ci.tcpci_tcp_info.tcpi_wired_rxbytes +=
+ mptsinp->inp_Wstat->rxbytes;
+
+ tcp_ci.tcpci_tcp_info.tcpi_cell_txbytes +=
+ mptsinp->inp_cstat->txbytes;
+ tcp_ci.tcpci_tcp_info.tcpi_cell_rxbytes +=
+ mptsinp->inp_cstat->rxbytes;
+ }
+ }
+
+interface_info:
+ *aux_type = CIAUX_TCP;
+ if (*aux_len == 0) {
+ *aux_len = sizeof(tcp_ci);
+ } else if (aux_data != USER_ADDR_NULL) {
+ boolean_t create;
+
+ /*
+ * Finally, old subflows might have been closed - we
+ * want this data as well, so grab it from the interface
+ * stats.
+ */
+ create = orig_mpts != NULL;
+
+ /*
+ * When we found a subflow, we are willing to create a stats-index
+ * because we have some data to return. If there isn't a subflow,
+ * nor anything in the stats, return EINVAL. Because the
+ * ifindex belongs to something that doesn't exist.
+ */
+ index = mptcpstats_get_index_by_ifindex(mpte->mpte_itfstats, *cid, false);
+ if (index == -1) {
+ os_log_error(mptcp_log_handle,
+ "%s - %lx: Asking for too many ifindex: %u subcount %u, mpts? %s\n",
+ __func__, (unsigned long)VM_KERNEL_ADDRPERM(mpte),
+ *cid, mpte->mpte_numflows,
+ orig_mpts ? "yes" : "no");
+
+ if (orig_mpts == NULL) {
+ return EINVAL;
+ }
+ } else {
+ struct mptcp_itf_stats *stats;
+
+ stats = &mpte->mpte_itfstats[index];
+
+ /* Roll the itf-stats into the tcp_info */
+ tcp_ci.tcpci_tcp_info.tcpi_last_outif = *cid;
+ tcp_ci.tcpci_tcp_info.tcpi_txbytes +=
+ stats->mpis_txbytes;
+ tcp_ci.tcpci_tcp_info.tcpi_rxbytes +=
+ stats->mpis_rxbytes;
+
+ tcp_ci.tcpci_tcp_info.tcpi_wifi_txbytes +=
+ stats->mpis_wifi_txbytes;
+ tcp_ci.tcpci_tcp_info.tcpi_wifi_rxbytes +=
+ stats->mpis_wifi_rxbytes;
+
+ tcp_ci.tcpci_tcp_info.tcpi_wired_txbytes +=
+ stats->mpis_wired_txbytes;
+ tcp_ci.tcpci_tcp_info.tcpi_wired_rxbytes +=
+ stats->mpis_wired_rxbytes;
+
+ tcp_ci.tcpci_tcp_info.tcpi_cell_txbytes +=
+ stats->mpis_cell_txbytes;
+ tcp_ci.tcpci_tcp_info.tcpi_cell_rxbytes +=
+ stats->mpis_cell_rxbytes;
+ }
+
+ *aux_len = min(*aux_len, sizeof(tcp_ci));
+ error = copyout(&tcp_ci, aux_data, *aux_len);
+ if (error != 0) {
+ return error;
+ }
+ }
+ }
+
+ return 0;