+ 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);
+ return;
+ }
+
+ connect_error = ntohl(connect_error);
+ FDLOG(LOG_INFO, fd_cb, "received connect result %u", connect_error);
+
+ error = flow_divert_packet_get_tlv(packet, offset, FLOW_DIVERT_TLV_SPACE_AVAILABLE, sizeof(send_window), &send_window, NULL);
+ if (error) {
+ FDLOG(LOG_ERR, fd_cb, "failed to get the send window: %d", error);
+ return;
+ }
+
+ error = flow_divert_packet_get_tlv(packet, offset, FLOW_DIVERT_TLV_CTL_UNIT, sizeof(ctl_unit), &ctl_unit, NULL);
+ if (error) {
+ FDLOG0(LOG_INFO, fd_cb, "No control unit provided in the connect result");
+ }
+
+ error = flow_divert_packet_get_tlv(packet, offset, FLOW_DIVERT_TLV_LOCAL_ADDR, sizeof(local_endpoint), &(local_endpoint.sa), NULL);
+ if (error) {
+ FDLOG0(LOG_INFO, fd_cb, "No local address provided");
+ }
+
+ error = flow_divert_packet_get_tlv(packet, offset, FLOW_DIVERT_TLV_REMOTE_ADDR, sizeof(remote_endpoint), &(remote_endpoint.sa), NULL);
+ if (error) {
+ FDLOG0(LOG_INFO, fd_cb, "No remote address provided");
+ }
+
+ error = flow_divert_packet_get_tlv(packet, offset, FLOW_DIVERT_TLV_OUT_IF_INDEX, sizeof(out_if_index), &out_if_index, NULL);
+ if (error) {
+ FDLOG0(LOG_INFO, fd_cb, "No output if index provided");
+ }
+
+ error = flow_divert_packet_get_tlv(packet, offset, FLOW_DIVERT_TLV_APP_DATA, 0, NULL, &app_data_length);
+ if (error) {
+ FDLOG0(LOG_INFO, fd_cb, "No application data provided in connect result");
+ }
+
+ error = 0;
+ ctl_unit = ntohl(ctl_unit);
+
+ lck_rw_lock_shared(&g_flow_divert_group_lck);
+
+ if (connect_error == 0 && ctl_unit > 0) {
+ if (ctl_unit >= GROUP_COUNT_MAX) {
+ FDLOG(LOG_ERR, fd_cb, "Connect result contains an invalid control unit: %u", ctl_unit);
+ error = EINVAL;
+ } else if (g_flow_divert_groups == NULL || g_active_group_count == 0) {
+ FDLOG0(LOG_ERR, fd_cb, "No active groups, dropping connection");
+ error = EINVAL;
+ } else {
+ grp = g_flow_divert_groups[ctl_unit];
+ if (grp == NULL) {
+ error = ECONNRESET;
+ }
+ }
+ }
+
+ FDLOCK(fd_cb);
+ if (fd_cb->so != NULL) {
+ struct inpcb *inp = NULL;
+ struct flow_divert_group *old_group;
+ struct socket *so = fd_cb->so;
+ bool local_address_is_valid = false;
+
+ socket_lock(so, 0);
+
+ if (!(so->so_flags & SOF_FLOW_DIVERT)) {
+ FDLOG0(LOG_NOTICE, fd_cb, "socket is not attached any more, ignoring connect result");
+ goto done;
+ }
+
+ if (SOCK_TYPE(so) == SOCK_STREAM && !(so->so_state & SS_ISCONNECTING)) {
+ FDLOG0(LOG_ERR, fd_cb, "TCP socket is not in the connecting state, ignoring connect result");
+ goto done;
+ }
+
+ inp = sotoinpcb(so);
+
+ if (connect_error || error) {
+ goto set_socket_state;
+ }
+
+ if (flow_divert_is_sockaddr_valid(&(local_endpoint.sa))) {
+ if (local_endpoint.sa.sa_family == AF_INET) {
+ local_endpoint.sa.sa_len = sizeof(struct sockaddr_in);
+ if ((inp->inp_vflag & INP_IPV4) && local_endpoint.sin.sin_addr.s_addr != INADDR_ANY) {
+ local_address_is_valid = true;
+ fd_cb->local_endpoint = local_endpoint;
+ inp->inp_laddr.s_addr = INADDR_ANY;
+ } else {
+ fd_cb->local_endpoint.sin.sin_port = local_endpoint.sin.sin_port;
+ }
+ } else if (local_endpoint.sa.sa_family == AF_INET6) {
+ local_endpoint.sa.sa_len = sizeof(struct sockaddr_in6);
+ if ((inp->inp_vflag & INP_IPV6) && !IN6_IS_ADDR_UNSPECIFIED(&local_endpoint.sin6.sin6_addr)) {
+ local_address_is_valid = true;
+ fd_cb->local_endpoint = local_endpoint;
+ inp->in6p_laddr = sa6_any.sin6_addr;
+ } else {
+ fd_cb->local_endpoint.sin6.sin6_port = local_endpoint.sin6.sin6_port;
+ }
+ }
+ }
+
+ flow_divert_scope(fd_cb, out_if_index, !local_address_is_valid);
+ flow_divert_set_local_endpoint(fd_cb, &(fd_cb->local_endpoint.sa));
+
+ if (flow_divert_is_sockaddr_valid(&(remote_endpoint.sa)) && SOCK_TYPE(so) == SOCK_STREAM) {
+ if (remote_endpoint.sa.sa_family == AF_INET) {
+ remote_endpoint.sa.sa_len = sizeof(struct sockaddr_in);
+ } else if (remote_endpoint.sa.sa_family == AF_INET6) {
+ remote_endpoint.sa.sa_len = sizeof(struct sockaddr_in6);
+ }
+ flow_divert_set_remote_endpoint(fd_cb, &(remote_endpoint.sa));
+ }
+
+ if (app_data_length > 0) {
+ uint8_t *app_data = NULL;
+ MALLOC(app_data, uint8_t *, app_data_length, M_TEMP, M_WAITOK);
+ if (app_data != NULL) {
+ error = flow_divert_packet_get_tlv(packet, offset, FLOW_DIVERT_TLV_APP_DATA, app_data_length, app_data, NULL);
+ if (error == 0) {
+ FDLOG(LOG_INFO, fd_cb, "Got %u bytes of app data from the connect result", app_data_length);
+ if (fd_cb->app_data != NULL) {
+ FREE(fd_cb->app_data, M_TEMP);
+ }