+ if (local_endpoint->sa_family == AF_INET6) {
+ if (IN6_IS_ADDR_UNSPECIFIED(&inp->in6p_laddr) && (fd_cb->flags & FLOW_DIVERT_SHOULD_SET_LOCAL_ADDR)) {
+ fd_cb->flags |= FLOW_DIVERT_DID_SET_LOCAL_ADDR;
+ inp->in6p_laddr = (satosin6(local_endpoint))->sin6_addr;
+ }
+ if (inp->inp_lport == 0) {
+ inp->inp_lport = (satosin6(local_endpoint))->sin6_port;
+ }
+ } else if (local_endpoint->sa_family == AF_INET) {
+ if (inp->inp_laddr.s_addr == INADDR_ANY && (fd_cb->flags & FLOW_DIVERT_SHOULD_SET_LOCAL_ADDR)) {
+ fd_cb->flags |= FLOW_DIVERT_DID_SET_LOCAL_ADDR;
+ inp->inp_laddr = (satosin(local_endpoint))->sin_addr;
+ }
+ if (inp->inp_lport == 0) {
+ inp->inp_lport = (satosin(local_endpoint))->sin_port;
+ }
+ }
+}
+
+static void
+flow_divert_set_remote_endpoint(struct flow_divert_pcb *fd_cb, struct sockaddr *remote_endpoint)
+{
+ struct inpcb *inp = sotoinpcb(fd_cb->so);
+
+ if (remote_endpoint->sa_family == AF_INET6) {
+ if (IN6_IS_ADDR_UNSPECIFIED(&inp->in6p_faddr)) {
+ inp->in6p_faddr = (satosin6(remote_endpoint))->sin6_addr;
+ }
+ if (inp->inp_fport == 0) {
+ inp->inp_fport = (satosin6(remote_endpoint))->sin6_port;
+ }
+ } else if (remote_endpoint->sa_family == AF_INET) {
+ if (inp->inp_laddr.s_addr == INADDR_ANY) {
+ inp->inp_faddr = (satosin(remote_endpoint))->sin_addr;
+ }
+ if (inp->inp_fport == 0) {
+ inp->inp_fport = (satosin(remote_endpoint))->sin_port;
+ }
+ }
+}
+
+static uint32_t
+flow_divert_derive_kernel_control_unit(uint32_t ctl_unit, uint32_t *aggregate_unit)
+{
+ if (aggregate_unit != NULL && *aggregate_unit != 0) {
+ uint32_t counter;
+ for (counter = 0; counter < (GROUP_COUNT_MAX - 1); counter++) {
+ if ((*aggregate_unit) & (1 << counter)) {
+ break;
+ }
+ }
+ if (counter < (GROUP_COUNT_MAX - 1)) {
+ *aggregate_unit &= ~(1 << counter);
+ return counter + 1;
+ } else {
+ return ctl_unit;
+ }
+ } else {
+ return ctl_unit;
+ }
+}
+
+static int
+flow_divert_try_next(struct flow_divert_pcb *fd_cb)
+{
+ uint32_t current_ctl_unit = 0;
+ uint32_t next_ctl_unit = 0;
+ struct flow_divert_group *current_group = NULL;
+ struct flow_divert_group *next_group = NULL;
+ int error = 0;
+
+ next_ctl_unit = flow_divert_derive_kernel_control_unit(fd_cb->policy_control_unit, &(fd_cb->aggregate_unit));
+ current_ctl_unit = fd_cb->control_group_unit;
+
+ if (current_ctl_unit == next_ctl_unit) {
+ FDLOG0(LOG_NOTICE, fd_cb, "Next control unit is the same as the current control unit, disabling flow divert");
+ error = EALREADY;
+ goto done;
+ }
+
+ if (next_ctl_unit == 0 || next_ctl_unit >= GROUP_COUNT_MAX) {
+ FDLOG0(LOG_NOTICE, fd_cb, "No more valid control units, disabling flow divert");
+ error = ENOENT;
+ goto done;
+ }
+
+ if (g_flow_divert_groups == NULL || g_active_group_count == 0) {
+ FDLOG0(LOG_NOTICE, fd_cb, "No active groups, disabling flow divert");
+ error = ENOENT;
+ goto done;
+ }
+
+ next_group = g_flow_divert_groups[next_ctl_unit];
+ if (next_group == NULL) {
+ FDLOG(LOG_NOTICE, fd_cb, "Group for control unit %u does not exist", next_ctl_unit);
+ error = ENOENT;
+ goto done;
+ }
+
+ current_group = fd_cb->group;
+
+ lck_rw_lock_exclusive(&(current_group->lck));
+ lck_rw_lock_exclusive(&(next_group->lck));
+
+ FDLOG(LOG_NOTICE, fd_cb, "Moving from %u to %u", current_ctl_unit, next_ctl_unit);
+
+ RB_REMOVE(fd_pcb_tree, &(current_group->pcb_tree), fd_cb);
+ if (RB_INSERT(fd_pcb_tree, &(next_group->pcb_tree), fd_cb) != NULL) {
+ panic("group with unit %u already contains a connection with hash %u", next_ctl_unit, fd_cb->hash);
+ }
+
+ fd_cb->group = next_group;
+ fd_cb->control_group_unit = next_ctl_unit;
+
+ lck_rw_done(&(next_group->lck));
+ lck_rw_done(&(current_group->lck));
+
+ error = flow_divert_send_connect_packet(fd_cb);
+ if (error) {
+ FDLOG(LOG_NOTICE, fd_cb, "Failed to send the connect packet to %u, disabling flow divert", next_ctl_unit);
+ error = ENOENT;
+ goto done;
+ }
+
+done:
+ return error;
+}
+
+static void
+flow_divert_disable(struct flow_divert_pcb *fd_cb)
+{
+ struct socket *so = NULL;
+ mbuf_t buffer;
+ int error = 0;
+ proc_t last_proc = NULL;
+ struct sockaddr *remote_endpoint = fd_cb->original_remote_endpoint;
+ bool do_connect = !(fd_cb->flags & FLOW_DIVERT_IMPLICIT_CONNECT);
+ struct inpcb *inp = NULL;
+
+ so = fd_cb->so;
+ if (so == NULL) {
+ goto done;
+ }
+
+ FDLOG0(LOG_NOTICE, fd_cb, "Skipped all flow divert services, disabling flow divert");
+
+ /* Restore the IP state */
+ inp = sotoinpcb(so);
+ inp->inp_vflag = fd_cb->original_vflag;
+ inp->inp_faddr.s_addr = INADDR_ANY;
+ inp->inp_fport = 0;
+ memset(&(inp->in6p_faddr), 0, sizeof(inp->in6p_faddr));
+ inp->in6p_fport = 0;
+ /* If flow divert set the local address, clear it out */
+ if (fd_cb->flags & FLOW_DIVERT_DID_SET_LOCAL_ADDR) {
+ inp->inp_laddr.s_addr = INADDR_ANY;
+ memset(&(inp->in6p_laddr), 0, sizeof(inp->in6p_laddr));
+ }
+ inp->inp_last_outifp = fd_cb->original_last_outifp;
+ inp->in6p_last_outifp = fd_cb->original_last_outifp6;
+
+ /* Dis-associate the socket */
+ so->so_flags &= ~SOF_FLOW_DIVERT;
+ so->so_flags1 |= SOF1_FLOW_DIVERT_SKIP;
+ so->so_fd_pcb = NULL;
+ fd_cb->so = NULL;
+
+ /* Remove from the group */
+ flow_divert_pcb_remove(fd_cb);
+
+ FDRELEASE(fd_cb); /* Release the socket's reference */
+
+ /* Revert back to the original protocol */
+ so->so_proto = pffindproto(SOCK_DOM(so), SOCK_PROTO(so), SOCK_TYPE(so));
+
+ last_proc = proc_find(so->last_pid);
+
+ if (do_connect) {
+ /* Connect using the original protocol */
+ error = (*so->so_proto->pr_usrreqs->pru_connect)(so, remote_endpoint, (last_proc != NULL ? last_proc : current_proc()));
+ if (error) {
+ FDLOG(LOG_ERR, fd_cb, "Failed to connect using the socket's original protocol: %d", error);
+ goto done;
+ }
+ }
+
+ buffer = so->so_snd.sb_mb;
+ if (buffer == NULL) {
+ /* No buffered data, done */
+ goto done;
+ }
+
+ /* Send any buffered data using the original protocol */
+ if (SOCK_TYPE(so) == SOCK_STREAM) {
+ mbuf_t data_to_send = NULL;
+ size_t data_len = so->so_snd.sb_cc;
+
+ error = mbuf_copym(buffer, 0, data_len, MBUF_DONTWAIT, &data_to_send);
+ if (error) {
+ FDLOG0(LOG_ERR, fd_cb, "Failed to copy the mbuf chain in the socket's send buffer");
+ goto done;
+ }
+
+ sbflush(&so->so_snd);
+
+ if (data_to_send->m_flags & M_PKTHDR) {
+ mbuf_pkthdr_setlen(data_to_send, data_len);
+ }
+
+ error = (*so->so_proto->pr_usrreqs->pru_send)(so,
+ 0,
+ data_to_send,
+ NULL,
+ NULL,
+ (last_proc != NULL ? last_proc : current_proc()));
+
+ if (error && error != EWOULDBLOCK) {
+ FDLOG(LOG_ERR, fd_cb, "Failed to send queued data using the socket's original protocol: %d", error);
+ } else {
+ error = 0;
+ }
+ } else if (SOCK_TYPE(so) == SOCK_DGRAM) {
+ struct sockbuf *sb = &so->so_snd;
+ MBUFQ_HEAD(send_queue_head) send_queue;
+ MBUFQ_INIT(&send_queue);
+
+ /* Flush the send buffer, moving all records to a temporary queue */
+ while (sb->sb_mb != NULL) {
+ mbuf_t record = sb->sb_mb;
+ mbuf_t m = record;
+ sb->sb_mb = sb->sb_mb->m_nextpkt;
+ while (m != NULL) {
+ sbfree(sb, m);
+ m = m->m_next;
+ }
+ record->m_nextpkt = NULL;
+ MBUFQ_ENQUEUE(&send_queue, record);
+ }
+ SB_EMPTY_FIXUP(sb);
+
+ while (!MBUFQ_EMPTY(&send_queue)) {
+ mbuf_t next_record = MBUFQ_FIRST(&send_queue);
+ mbuf_t addr = NULL;
+ mbuf_t control = NULL;
+ mbuf_t last_control = NULL;
+ mbuf_t data = NULL;
+ mbuf_t m = next_record;
+ struct sockaddr *to_endpoint = NULL;
+
+ MBUFQ_DEQUEUE(&send_queue, next_record);
+
+ while (m != NULL) {
+ if (m->m_type == MT_SONAME) {
+ addr = m;
+ } else if (m->m_type == MT_CONTROL) {
+ if (control == NULL) {
+ control = m;
+ }
+ last_control = m;
+ } else if (m->m_type == MT_DATA) {
+ data = m;
+ break;
+ }
+ m = m->m_next;
+ }
+
+ if (addr != NULL) {
+ to_endpoint = flow_divert_get_buffered_target_address(addr);
+ if (to_endpoint == NULL) {
+ FDLOG0(LOG_NOTICE, fd_cb, "Failed to get the remote address from the buffer");
+ }
+ }
+
+ if (data == NULL) {
+ FDLOG0(LOG_ERR, fd_cb, "Buffered record does not contain any data");
+ mbuf_freem(next_record);
+ continue;
+ }
+
+ if (!(data->m_flags & M_PKTHDR)) {
+ FDLOG0(LOG_ERR, fd_cb, "Buffered data does not have a packet header");
+ mbuf_freem(next_record);
+ continue;
+ }
+
+ if (addr != NULL) {
+ addr->m_next = NULL;
+ }
+
+ if (last_control != NULL) {
+ last_control->m_next = NULL;
+ }
+
+ error = (*so->so_proto->pr_usrreqs->pru_send)(so,
+ 0,
+ data,
+ to_endpoint,
+ control,
+ (last_proc != NULL ? last_proc : current_proc()));
+
+ if (addr != NULL) {
+ mbuf_freem(addr);
+ }
+
+ if (error) {
+ FDLOG(LOG_ERR, fd_cb, "Failed to send queued data using the socket's original protocol: %d", error);
+ }
+ }
+ }
+done:
+ if (last_proc != NULL) {
+ proc_rele(last_proc);
+ }
+
+ if (error) {
+ so->so_error = (uint16_t)error;
+ flow_divert_disconnect_socket(so);
+ }
+}
+
+static void
+flow_divert_scope(struct flow_divert_pcb *fd_cb, int out_if_index, bool derive_new_address)
+{
+ struct socket *so = NULL;
+ struct inpcb *inp = NULL;
+ struct ifnet *current_ifp = NULL;
+ struct ifnet *new_ifp = NULL;
+ int error = 0;
+
+ so = fd_cb->so;
+ if (so == NULL) {
+ return;
+ }
+
+ inp = sotoinpcb(so);
+
+ if (out_if_index <= 0) {
+ return;
+ }
+
+ if (inp->inp_vflag & INP_IPV6) {
+ current_ifp = inp->in6p_last_outifp;
+ } else {
+ current_ifp = inp->inp_last_outifp;
+ }
+
+ if (current_ifp != NULL) {
+ if (current_ifp->if_index == out_if_index) {
+ /* No change */
+ return;
+ }
+
+ /* Scope the socket to the given interface */
+ error = inp_bindif(inp, out_if_index, &new_ifp);
+ if (error != 0) {
+ FDLOG(LOG_ERR, fd_cb, "failed to scope to %d because inp_bindif returned %d", out_if_index, error);
+ return;
+ }
+
+ if (derive_new_address && fd_cb->original_remote_endpoint != NULL) {
+ /* Get the appropriate address for the given interface */
+ if (inp->inp_vflag & INP_IPV6) {
+ inp->in6p_laddr = sa6_any.sin6_addr;
+ error = in6_pcbladdr(inp, fd_cb->original_remote_endpoint, &(fd_cb->local_endpoint.sin6.sin6_addr), NULL);
+ } else {
+ inp->inp_laddr.s_addr = INADDR_ANY;
+ error = in_pcbladdr(inp, fd_cb->original_remote_endpoint, &(fd_cb->local_endpoint.sin.sin_addr), IFSCOPE_NONE, NULL, 0);
+ }
+
+ if (error != 0) {
+ FDLOG(LOG_WARNING, fd_cb, "failed to derive a new local address from %d because in_pcbladdr returned %d", out_if_index, error);
+ }
+ }
+ } else {
+ ifnet_head_lock_shared();
+ if (out_if_index <= if_index) {
+ new_ifp = ifindex2ifnet[out_if_index];
+ }
+ ifnet_head_done();
+ }
+
+ /* Update the "last interface" of the socket */
+ if (new_ifp != NULL) {
+ if (inp->inp_vflag & INP_IPV6) {
+ inp->in6p_last_outifp = new_ifp;
+ } else {
+ inp->inp_last_outifp = new_ifp;
+ }
+
+ }
+}
+
+static void
+flow_divert_handle_connect_result(struct flow_divert_pcb *fd_cb, mbuf_t packet, int offset)
+{
+ uint32_t connect_error = 0;
+ uint32_t ctl_unit = 0;
+ int error = 0;
+ struct flow_divert_group *grp = NULL;
+ union sockaddr_in_4_6 local_endpoint = {};
+ union sockaddr_in_4_6 remote_endpoint = {};
+ int out_if_index = 0;
+ uint32_t send_window;
+ uint32_t app_data_length = 0;
+
+ memset(&local_endpoint, 0, sizeof(local_endpoint));
+ memset(&remote_endpoint, 0, sizeof(remote_endpoint));
+
+ error = flow_divert_packet_get_tlv(packet, offset, FLOW_DIVERT_TLV_ERROR_CODE, sizeof(connect_error), &connect_error, NULL);
+ if (error) {
+ FDLOG(LOG_ERR, fd_cb, "failed to get the connect result: %d", error);