+flow_divert_set_local_endpoint(struct flow_divert_pcb *fd_cb, struct sockaddr *local_endpoint)
+{
+ struct inpcb *inp = sotoinpcb(fd_cb->so);
+
+ 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)