From 11eda63345411fc3638727b9876752b364f56a8c Mon Sep 17 00:00:00 2001 From: Street Pea Date: Wed, 16 Oct 2024 19:47:12 -0700 Subject: [PATCH] Offer immediately --- lib/src/remote/holepunch.c | 561 ++++++++++++++++++++++++++++++++----- 1 file changed, 488 insertions(+), 73 deletions(-) diff --git a/lib/src/remote/holepunch.c b/lib/src/remote/holepunch.c index 1becbcf4..d15b9791 100644 --- a/lib/src/remote/holepunch.c +++ b/lib/src/remote/holepunch.c @@ -356,6 +356,7 @@ static void bytes_to_hex(const uint8_t* bytes, size_t len, char* hex_str, size_t static void random_uuidv4(char* out); static void *websocket_thread_func(void *user); static NotificationType parse_notification_type(ChiakiLog *log, json_object* json); +static ChiakiErrorCode send_starter_offer(Session *session, int req_id, Candidate *local_candidates); static ChiakiErrorCode send_offer(Session *session, int req_id, Candidate *local_console_candidate, Candidate *local_candidates, Candidate *candidates_received, size_t num_candidates); static ChiakiErrorCode send_accept(Session *session, int req_id, Candidate *selected_candidate); static ChiakiErrorCode http_create_session(Session *session); @@ -1383,21 +1384,119 @@ CHIAKI_EXPORT ChiakiErrorCode chiaki_holepunch_session_punch_hole(Session* sessi // NOTE: Needs to be kept around until the end, we're using the candidates in the message later on SessionMessage *console_offer_msg = NULL; - err = wait_for_session_message(session, &console_offer_msg, SESSION_MESSAGE_ACTION_OFFER, SESSION_START_TIMEOUT_SEC * 1000); - if (err == CHIAKI_ERR_TIMEOUT) + SessionMessage *starter_ack_msg = NULL; + + // Send our starter OFFER + const int starter_offer_req_id = session->local_req_id; + session->local_req_id++; + local_candidates = calloc(2, sizeof(Candidate)); + if(!local_candidates) { - CHIAKI_LOGE(session->log, "chiaki_holepunch_session_punch_holes: Timed out waiting for OFFER holepunch session message."); - return err; + err = CHIAKI_ERR_MEMORY; + goto cleanup; } - else if (err == CHIAKI_ERR_CANCELED) + err = send_starter_offer(session, starter_offer_req_id, local_candidates); + if(err != CHIAKI_ERR_SUCCESS) { - CHIAKI_LOGI(session->log, "chiaki_holepunch_session_punch_holes: canceled"); - return err; + goto cleanup; } - else if (err != CHIAKI_ERR_SUCCESS) + + bool offer_received = false; + bool ack_received = false; + + while(!offer_received && !ack_received) { - CHIAKI_LOGE(session->log, "chiaki_holepunch_session_punch_holes: Failed to wait for OFFER holepunch session message."); - return err; + err = wait_for_session_message(session, &starter_ack_msg, SESSION_MESSAGE_ACTION_OFFER | SESSION_MESSAGE_ACTION_RESULT, SESSION_START_TIMEOUT_SEC * 1000); + if (err == CHIAKI_ERR_TIMEOUT) + { + CHIAKI_LOGE(session->log, "chiaki_holepunch_session_punch_holes: Timed out waiting for OFFER holepunch session message."); + goto cleanup; + } + else if (err == CHIAKI_ERR_CANCELED) + { + CHIAKI_LOGI(session->log, "chiaki_holepunch_session_punch_holes: canceled"); + goto cleanup; + } + else if (err != CHIAKI_ERR_SUCCESS) + { + CHIAKI_LOGE(session->log, "chiaki_holepunch_session_punch_holes: Failed to wait for OFFER holepunch session message."); + goto cleanup; + } + chiaki_mutex_lock(&session->stop_mutex); + if(session->main_should_stop) + { + session->main_should_stop = false; + chiaki_mutex_unlock(&session->stop_mutex); + err = CHIAKI_ERR_CANCELED; + goto cleanup; + } + chiaki_mutex_unlock(&session->stop_mutex); + if(starter_ack_msg->action == SESSION_MESSAGE_ACTION_OFFER && !offer_received) + { + CHIAKI_LOGV(session->log, "Received console offer message"); + console_offer_msg = starter_ack_msg; + starter_ack_msg = NULL; + offer_received = true; + // ACK the message + SessionMessage ack_msg = { + .action = SESSION_MESSAGE_ACTION_RESULT, + .req_id = console_offer_msg->req_id, + .error = 0, + .conn_request = NULL, + .notification = NULL, + }; + err = http_send_session_message(session, &ack_msg, true); + if(err != CHIAKI_ERR_SUCCESS) + { + CHIAKI_LOGE(session->log, "chiaki_holepunch_session_punch_holes: Couldn't send holepunch session message"); + goto cleanup; + } + break; + } + if(starter_ack_msg->action == SESSION_MESSAGE_ACTION_RESULT && !ack_received) + { + ack_received = true; + CHIAKI_LOGV(session->log, "Received starter ack message"); + break; + } + } + if(!ack_received) + { + err = wait_for_session_message_ack(session, starter_offer_req_id, SESSION_START_TIMEOUT_SEC * 1000); + if (err == CHIAKI_ERR_TIMEOUT) + { + CHIAKI_LOGE(session->log, "chiaki_holepunch_session_punch_holes: Timed out waiting for ACK of our connection offer."); + goto cleanup; + } + else if (err == CHIAKI_ERR_CANCELED) + { + CHIAKI_LOGI(session->log, "chiaki_holepunch_session_punch_holes: canceled"); + goto cleanup; + } + else if (err != CHIAKI_ERR_SUCCESS) + { + CHIAKI_LOGE(session->log, "chiaki_holepunch_session_punch_holes: Failed to wait for ACK of our connection offer."); + goto cleanup; + } + } + if(!offer_received) + { + err = wait_for_session_message(session, &console_offer_msg, SESSION_MESSAGE_ACTION_OFFER, SESSION_START_TIMEOUT_SEC * 1000); + if (err == CHIAKI_ERR_TIMEOUT) + { + CHIAKI_LOGE(session->log, "chiaki_holepunch_session_punch_holes: Timed out waiting for ACK of our connection offer."); + goto cleanup; + } + else if (err == CHIAKI_ERR_CANCELED) + { + CHIAKI_LOGI(session->log, "chiaki_holepunch_session_punch_holes: canceled"); + goto cleanup; + } + else if (err != CHIAKI_ERR_SUCCESS) + { + CHIAKI_LOGE(session->log, "chiaki_holepunch_session_punch_holes: Failed to wait for ACK of our connection offer."); + goto cleanup; + } } ConnectionRequest *console_req = console_offer_msg->conn_request; memcpy(session->hashed_id_console, console_req->local_hashed_id, sizeof(session->hashed_id_console)); @@ -1410,31 +1509,17 @@ CHIAKI_EXPORT ChiakiErrorCode chiaki_holepunch_session_punch_hole(Session* sessi session->state |= SESSION_STATE_DATA_OFFER_RECEIVED; chiaki_mutex_unlock(&session->state_mutex); - Candidate *console_candidate_local; - for (int i=0; i < console_req->num_candidates; i++) - { - Candidate *candidate = &console_req->candidates[i]; - if (candidate->type == CANDIDATE_TYPE_LOCAL) - { - console_candidate_local = candidate; - break; - } - } + // Candidate *console_candidate_local; + // for (int i=0; i < console_req->num_candidates; i++) + // { + // Candidate *candidate = &console_req->candidates[i]; + // if (candidate->type == CANDIDATE_TYPE_LOCAL) + // { + // console_candidate_local = candidate; + // break; + // } + // } - // ACK the message - SessionMessage ack_msg = { - .action = SESSION_MESSAGE_ACTION_RESULT, - .req_id = console_offer_msg->req_id, - .error = 0, - .conn_request = NULL, - .notification = NULL, - }; - err = http_send_session_message(session, &ack_msg, true); - if(err != CHIAKI_ERR_SUCCESS) - { - CHIAKI_LOGE(session->log, "chiaki_holepunch_session_punch_holes: Couldn't send holepunch session message"); - goto cleanup; - } chiaki_mutex_lock(&session->stop_mutex); if(session->main_should_stop) { @@ -1446,36 +1531,39 @@ CHIAKI_EXPORT ChiakiErrorCode chiaki_holepunch_session_punch_hole(Session* sessi } chiaki_mutex_unlock(&session->stop_mutex); - // Send our own OFFER - const int our_offer_req_id = session->local_req_id; - session->local_req_id++; - local_candidates = calloc(2, sizeof(Candidate)); - if(!local_candidates) - { - err = CHIAKI_ERR_MEMORY; - goto cleanup; - } - send_offer(session, our_offer_req_id, console_candidate_local, local_candidates, console_req->candidates, console_req->num_candidates); - - // Wait for ACK of OFFER, ignore other OFFERs, simply ACK them - err = wait_for_session_message_ack( - session, our_offer_req_id, SESSION_START_TIMEOUT_SEC * 1000); - if (err == CHIAKI_ERR_TIMEOUT) - { - CHIAKI_LOGE(session->log, "chiaki_holepunch_session_punch_holes: Timed out waiting for ACK of our connection offer."); - goto cleanup; - } - else if (err == CHIAKI_ERR_CANCELED) - { - CHIAKI_LOGI(session->log, "chiaki_holepunch_session_punch_holes: canceled"); - goto cleanup; - } - else if (err != CHIAKI_ERR_SUCCESS) - { - CHIAKI_LOGE(session->log, "chiaki_holepunch_session_punch_holes: Failed to wait for ACK of our connection offer."); - goto cleanup; - } - http_check_session(session, true); + // // Send our true OFFER + // const int our_offer_req_id = session->local_req_id; + // session->local_req_id++; + // local_candidates = calloc(2, sizeof(Candidate)); + // if(!local_candidates) + // { + // err = CHIAKI_ERR_MEMORY; + // goto cleanup; + // } + // // err = send_offer(session, our_offer_req_id, console_candidate_local, local_candidates, console_req->candidates, console_req->num_candidates); + // // if(err != CHIAKI_ERR_SUCCESS) + // // { + // // goto cleanup; + // // } + + // // // Wait for ACK of OFFER, ignore other OFFERs, simply ACK them + // // err = wait_for_session_message_ack( + // // session, our_offer_req_id, SESSION_START_TIMEOUT_SEC * 1000); + // // if (err == CHIAKI_ERR_TIMEOUT) + // // { + // // CHIAKI_LOGE(session->log, "chiaki_holepunch_session_punch_holes: Timed out waiting for ACK of our connection offer."); + // // goto cleanup; + // // } + // // else if (err == CHIAKI_ERR_CANCELED) + // // { + // // CHIAKI_LOGI(session->log, "chiaki_holepunch_session_punch_holes: canceled"); + // // goto cleanup; + // // } + // // else if (err != CHIAKI_ERR_SUCCESS) + // // { + // // CHIAKI_LOGE(session->log, "chiaki_holepunch_session_punch_holes: Failed to wait for ACK of our connection offer."); + // // goto cleanup; + // // } // Find candidate that we can use to connect to the console chiaki_socket_t sock = CHIAKI_INVALID_SOCKET; @@ -2180,7 +2268,9 @@ static void* websocket_thread_func(void *user) { }; http_send_session_message(session, &ack_msg, true); } + chiaki_mutex_lock(&session->notif_mutex); session_message_free(msg); + chiaki_mutex_unlock(&session->notif_mutex); } ChiakiErrorCode mutex_err = chiaki_mutex_lock(&session->notif_mutex); assert(mutex_err == CHIAKI_ERR_SUCCESS); @@ -2248,19 +2338,18 @@ static NotificationType parse_notification_type( } } - -/** Sends an OFFER connection request session message to the console via PSN. +/** Sends a starter OFFER connection request session message to the console via PSN. * * @param session The Session instance. * @return CHIAKI_ERR_SUCCESS on success, or an error code on failure. */ -static ChiakiErrorCode send_offer(Session *session, int req_id, Candidate *local_console_candidate, Candidate *local_candidates, Candidate *candidates_received, size_t num_candidates) +static ChiakiErrorCode send_starter_offer(Session *session, int req_id, Candidate *local_candidates) { // Create socket with available local port for connection session->ipv4_sock = socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP); if (CHIAKI_SOCKET_IS_INVALID(session->ipv4_sock)) { - CHIAKI_LOGE(session->log, "send_offer: Creating ipv4 socket failed"); + CHIAKI_LOGE(session->log, "send_starter_offer: Creating ipv4 socket failed"); return CHIAKI_ERR_UNKNOWN; } struct sockaddr_in client_addr; @@ -2287,7 +2376,7 @@ static ChiakiErrorCode send_offer(Session *session, int req_id, Candidate *local #endif if (bind(session->ipv4_sock, (struct sockaddr*)&client_addr, client_addr_len) < 0) { - CHIAKI_LOGE(session->log, "send_offer: Binding ipv4 socket failed with error " CHIAKI_SOCKET_ERROR_FMT, CHIAKI_SOCKET_ERROR_VALUE); + CHIAKI_LOGE(session->log, "send_starter_offer: Binding ipv4 socket failed with error " CHIAKI_SOCKET_ERROR_FMT, CHIAKI_SOCKET_ERROR_VALUE); if(!CHIAKI_SOCKET_IS_INVALID(session->ipv4_sock)) { CHIAKI_SOCKET_CLOSE(session->ipv4_sock); @@ -2298,7 +2387,7 @@ static ChiakiErrorCode send_offer(Session *session, int req_id, Candidate *local } if(getsockname(session->ipv4_sock, (struct sockaddr*)&client_addr, &client_addr_len) < 0) { - CHIAKI_LOGE(session->log, "send_offer: Getting ipv4 socket name failed with error " CHIAKI_SOCKET_ERROR_FMT, CHIAKI_SOCKET_ERROR_VALUE); + CHIAKI_LOGE(session->log, "send_starter_offer: Getting ipv4 socket name failed with error " CHIAKI_SOCKET_ERROR_FMT, CHIAKI_SOCKET_ERROR_VALUE); if(!CHIAKI_SOCKET_IS_INVALID(session->ipv4_sock)) { CHIAKI_SOCKET_CLOSE(session->ipv4_sock); @@ -2312,7 +2401,7 @@ static ChiakiErrorCode send_offer(Session *session, int req_id, Candidate *local session->ipv6_sock = socket(AF_INET6, SOCK_DGRAM, IPPROTO_UDP); if (CHIAKI_SOCKET_IS_INVALID(session->ipv6_sock)) { - CHIAKI_LOGE(session->log, "send_offer: Creating ipv6 socket failed with error " CHIAKI_SOCKET_ERROR_FMT, CHIAKI_SOCKET_ERROR_VALUE); + CHIAKI_LOGE(session->log, "send_starter_offer: Creating ipv6 socket failed with error " CHIAKI_SOCKET_ERROR_FMT, CHIAKI_SOCKET_ERROR_VALUE); err = CHIAKI_ERR_UNKNOWN; goto cleanup_socket; } @@ -2339,7 +2428,7 @@ static ChiakiErrorCode send_offer(Session *session, int req_id, Candidate *local #endif if (bind(session->ipv6_sock, (struct sockaddr*)&client_addr_ipv6, client_addr_ipv6_len) < 0) { - CHIAKI_LOGE(session->log, "send_offer: Binding ipv6 socket failed with error " CHIAKI_SOCKET_ERROR_FMT, CHIAKI_SOCKET_ERROR_VALUE); + CHIAKI_LOGE(session->log, "send_starter_offer: Binding ipv6 socket failed with error " CHIAKI_SOCKET_ERROR_FMT, CHIAKI_SOCKET_ERROR_VALUE); if(!CHIAKI_SOCKET_IS_INVALID(session->ipv6_sock)) { CHIAKI_SOCKET_CLOSE(session->ipv6_sock); @@ -2366,6 +2455,324 @@ static ChiakiErrorCode send_offer(Session *session, int req_id, Candidate *local else session->local_port_data = local_port; + msg.conn_request->sid = session->sid_local; + msg.conn_request->peer_sid = session->sid_console; + msg.conn_request->nat_type = 2; + memset(msg.conn_request->skey, 0, sizeof(msg.conn_request->skey)); + memset(msg.conn_request->default_route_mac_addr, 0, sizeof(msg.conn_request->default_route_mac_addr)); + memcpy(msg.conn_request->local_hashed_id, session->hashed_id_local, sizeof(session->hashed_id_local)); + msg.conn_request->num_candidates = 2; + // allocate extra space for stun candidates + msg.conn_request->candidates = calloc(4, sizeof(Candidate)); + if(!msg.conn_request->candidates) + { + free(msg.conn_request); + if(!CHIAKI_SOCKET_IS_INVALID(session->ipv4_sock)) + { + CHIAKI_SOCKET_CLOSE(session->ipv4_sock); + session->ipv4_sock = CHIAKI_INVALID_SOCKET; + } + if(!CHIAKI_SOCKET_IS_INVALID(session->ipv6_sock)) + { + CHIAKI_SOCKET_CLOSE(session->ipv6_sock); + session->ipv6_sock = CHIAKI_INVALID_SOCKET; + } + return CHIAKI_ERR_MEMORY; + } + Candidate *candidate_local = &msg.conn_request->candidates[2]; + candidate_local->type = CANDIDATE_TYPE_LOCAL; + memcpy(candidate_local->addr_mapped, "0.0.0.0", 8); + candidate_local->port = local_port; + candidate_local->port_mapped = 0; + + bool have_addr = false; + Candidate *candidate_remote = &msg.conn_request->candidates[1]; + candidate_remote->type = CANDIDATE_TYPE_STATIC; + UPNPGatewayInfo upnp_gw; + upnp_gw.data = calloc(1, sizeof(struct IGDdatas)); + if(!upnp_gw.data) + { + err = CHIAKI_ERR_MEMORY; + goto cleanup; + } + upnp_gw.urls = calloc(1, sizeof(struct UPNPUrls)); + if(!upnp_gw.urls) + { + err = CHIAKI_ERR_MEMORY; + free(upnp_gw.data); + goto cleanup; + } + err = upnp_get_gateway_info(session->log, &upnp_gw); + if (err == CHIAKI_ERR_SUCCESS) { + memcpy(candidate_local->addr, upnp_gw.lan_ip, sizeof(upnp_gw.lan_ip)); + have_addr = get_client_addr_remote_upnp(session->log, &upnp_gw, candidate_remote->addr); + if(upnp_add_udp_port_mapping(session->log, &upnp_gw, local_port, local_port)) + { + CHIAKI_LOGI(session->log, "Added local UPNP port mapping to port %u", local_port); + session->gw = upnp_gw; + } + else + CHIAKI_LOGE(session->log, "Adding upnp port mapping failed"); + } + else { + err = get_client_addr_local(session, candidate_local, candidate_local->addr, sizeof(candidate_local->addr)); + if(err != CHIAKI_ERR_SUCCESS) + goto cleanup; + } + memcpy(session->client_local_ip, candidate_local->addr, sizeof(candidate_local->addr)); + if (!have_addr) + { + // Move current candidates behind STUN candidates so when the console reaches out to our STUN candidate it will be using the correct port if behind symmetric NAT + Candidate *candidate_stun = &msg.conn_request->candidates[0]; + candidate_stun->type = CANDIDATE_TYPE_STUN; + memcpy(candidate_stun->addr_mapped, "0.0.0.0", 8); + candidate_stun->port_mapped = 0; + have_addr = get_client_addr_remote_stun(session, candidate_stun->addr, &candidate_stun->port, &session->ipv4_sock, true); + if(have_addr) + { + memcpy(candidate_remote->addr, candidate_stun->addr, sizeof(candidate_stun->addr)); + // Local port is used externally so don't make duplicate STUN candidate since STATIC candidate will have same ip and port number + uint16_t stun_port = candidate_stun->port; + if(session->stun_allocation_increment != 0) + { + Candidate original_candidates[3]; + memcpy(original_candidates, msg.conn_request->candidates, sizeof(Candidate) * 3); + candidate_stun = &original_candidates[0]; + if(!session->stun_random_allocation || true) + { + Candidate *tmp = NULL; + tmp = realloc(msg.conn_request->candidates, sizeof(Candidate) * 11); + if(tmp) + msg.conn_request->candidates = tmp; + else + { + err = CHIAKI_ERR_MEMORY; + goto cleanup; + } + int32_t port_check = candidate_stun->port; + // Setup extra stun candidate in case there was an allocation in between the stun request and our allocation + for(int i=0; i<8; i++) + { + Candidate *candidate_stun2 = &msg.conn_request->candidates[i]; + candidate_stun2->type = CANDIDATE_TYPE_STUN; + memcpy(candidate_stun2->addr_mapped, "0.0.0.0", 8); + candidate_stun2->port_mapped = 0; + memcpy(candidate_stun2->addr, candidate_stun->addr, sizeof(candidate_stun->addr)); + int32_t tmp = port_check; + // skip well known ports 0-1024 unless current allocation is within that range since most routers don't (implies router uses those ports) + port_check += session->stun_allocation_increment; + if(port_check < 1024 && tmp > 1024) + port_check = UINT16_MAX - (1024 - port_check); + else if(port_check < 1) + port_check += UINT16_MAX; + else if(port_check > UINT16_MAX) + port_check = port_check - UINT16_MAX + 1024; + candidate_stun2->port = port_check; + } + memcpy(&msg.conn_request->candidates[8], &original_candidates[1], sizeof(Candidate)); + memcpy(&msg.conn_request->candidates[9], &original_candidates[2], sizeof(Candidate)); + candidate_remote = &msg.conn_request->candidates[8]; + candidate_local = &msg.conn_request->candidates[9]; + msg.conn_request->num_candidates = 10; + } + // else + // { + // Candidate *tmp = NULL; + // tmp = realloc(msg.conn_request->candidates, sizeof(Candidate) * (RANDOM_ALLOCATION_GUESSES_NUMBER + 3)); + // if(tmp) + // msg.conn_request->candidates = tmp; + // else + // { + // err = CHIAKI_ERR_MEMORY; + // goto cleanup; + // } + // int32_t port_check = candidate_stun->port; + // // Setup RANDOM_ALLOCATION_GUESSES_NUMBER STUN candidates because we have a random allocation and usually 64 port blocks are minimum + // for(int i=0; icandidates[i]; + // candidate_stun2->type = CANDIDATE_TYPE_STUN; + // memcpy(candidate_stun2->addr_mapped, "0.0.0.0", 8); + // candidate_stun2->port_mapped = 0; + // candidate_stun2->port = port_check; + // memcpy(candidate_stun2->addr, candidate_stun->addr, sizeof(candidate_stun->addr)); + // port_check += 1; + // // use ports in IANA dynamic range if possible + // if(port_check > UINT16_MAX) + // port_check = 49152; + // } + // memcpy(&msg.conn_request->candidates[RANDOM_ALLOCATION_GUESSES_NUMBER], &original_candidates[1], sizeof(Candidate)); + // memcpy(&msg.conn_request->candidates[RANDOM_ALLOCATION_GUESSES_NUMBER + 1], &original_candidates[2], sizeof(Candidate)); + // candidate_remote = &msg.conn_request->candidates[RANDOM_ALLOCATION_GUESSES_NUMBER]; + // candidate_local = &msg.conn_request->candidates[RANDOM_ALLOCATION_GUESSES_NUMBER + 1]; + // msg.conn_request->num_candidates = RANDOM_ALLOCATION_GUESSES_NUMBER + 2; + // } + } + else + { + if(local_port == stun_port) + { + memcpy(&msg.conn_request->candidates[0], &msg.conn_request->candidates[1], sizeof(Candidate)); + memcpy(&msg.conn_request->candidates[1], &msg.conn_request->candidates[2], sizeof(Candidate)); + candidate_remote = &msg.conn_request->candidates[0]; + candidate_local = &msg.conn_request->candidates[1]; + } + else + { + msg.conn_request->num_candidates = 3; + candidate_remote = &msg.conn_request->candidates[1]; + candidate_local = &msg.conn_request->candidates[2]; + } + } + } + else + CHIAKI_LOGE(session->log, "send_offer: Could not get remote address from STUN"); + if(CHIAKI_SOCKET_IS_INVALID(session->ipv4_sock)) + { + CHIAKI_LOGE(session->log, "STUN caused socket to close due to error"); + err = CHIAKI_ERR_UNKNOWN; + goto cleanup; + } +#ifdef _WIN32 + int timeout = 0; +#else + struct timeval timeout; + timeout.tv_sec = 0; + timeout.tv_usec = 0; +#endif + if (setsockopt(session->ipv4_sock, SOL_SOCKET, SO_RCVTIMEO, (const CHIAKI_SOCKET_BUF_TYPE)&timeout, sizeof(timeout)) < 0) + { + CHIAKI_LOGE(session->log, "send_offer: Failed to unset socket timeout, error was " CHIAKI_SOCKET_ERROR_FMT, CHIAKI_SOCKET_ERROR_VALUE); + err = CHIAKI_ERR_UNKNOWN; + goto cleanup; + } + // Only PS5 supports ipv6 + if(session->console_type == CHIAKI_HOLEPUNCH_CONSOLE_TYPE_PS5 && ENABLE_IPV6) + { + Candidate *candidate_stun_ipv6 = &msg.conn_request->candidates[msg.conn_request->num_candidates]; + candidate_stun_ipv6->type = CANDIDATE_TYPE_STUN; + memcpy(candidate_stun_ipv6->addr_mapped, "0.0.0.0", 8); + candidate_stun_ipv6->port_mapped = 0; + if(get_client_addr_remote_stun(session, candidate_stun_ipv6->addr, &candidate_stun_ipv6->port, &session->ipv6_sock, false)) + { + if (setsockopt(session->ipv6_sock, SOL_SOCKET, SO_RCVTIMEO, (const CHIAKI_SOCKET_BUF_TYPE)&timeout, sizeof(timeout)) < 0) + { + CHIAKI_LOGE(session->log, "send_offer: Failed to unset socket timeout, error was " CHIAKI_SOCKET_ERROR_FMT, CHIAKI_SOCKET_ERROR_VALUE); + if(!CHIAKI_SOCKET_IS_INVALID(session->ipv6_sock)) + { + CHIAKI_SOCKET_CLOSE(session->ipv6_sock); + session->ipv6_sock = CHIAKI_INVALID_SOCKET; + } + } + else + msg.conn_request->num_candidates++; + } + else + { + CHIAKI_LOGW(session->log, "IPV6 NOT supported by device. Couldn't get IPV6 STUN address."); + if(!CHIAKI_SOCKET_IS_INVALID(session->ipv6_sock)) + { + CHIAKI_SOCKET_CLOSE(session->ipv6_sock); + session->ipv6_sock = CHIAKI_INVALID_SOCKET; + } + } + } + else + { + CHIAKI_LOGI(session->log, "IPV6 NOT supported by your PlayStation console. Skipping IPV6 connection"); + if(!CHIAKI_SOCKET_IS_INVALID(session->ipv6_sock)) + { + CHIAKI_SOCKET_CLOSE(session->ipv6_sock); + session->ipv6_sock = CHIAKI_INVALID_SOCKET; + } + } + } + else + { + // If no STUN address the static and local candidates are our first candidates + memcpy(&msg.conn_request->candidates[0], &msg.conn_request->candidates[1], sizeof(Candidate)); + memcpy(&msg.conn_request->candidates[1], &msg.conn_request->candidates[2], sizeof(Candidate)); + candidate_remote = &msg.conn_request->candidates[0]; + candidate_local = &msg.conn_request->candidates[1]; + } + if (!have_addr) { + CHIAKI_LOGE(session->log, "send_offer: Could not get remote address"); + err = CHIAKI_ERR_UNKNOWN; + goto cleanup; + } + err = chiaki_socket_set_nonblock(session->ipv4_sock, true); + if(err != CHIAKI_ERR_SUCCESS) + { + CHIAKI_LOGE(session->log, "Failed to set ipv4 socket to non-blocking: %s", chiaki_error_string(err)); + err = CHIAKI_ERR_UNKNOWN; + goto cleanup; + } + if(!CHIAKI_SOCKET_IS_INVALID(session->ipv6_sock)) + { + err = chiaki_socket_set_nonblock(session->ipv6_sock, true); + if(err != CHIAKI_ERR_SUCCESS) + { + CHIAKI_LOGE(session->log, "Failed to set ipv6 socket to non-blocking: %s", chiaki_error_string(err)); + err = CHIAKI_ERR_UNKNOWN; + goto cleanup; + } + } + memcpy(candidate_remote->addr_mapped, "0.0.0.0", 8); + candidate_remote->port = local_port; + candidate_remote->port_mapped = 0; + print_session_request(session->log, msg.conn_request); + err = http_send_session_message(session, &msg, false); + if (err != CHIAKI_ERR_SUCCESS) + { + CHIAKI_LOGE(session->log, "send_offer: Sending session message failed"); + } + memcpy(&local_candidates[0], candidate_local, sizeof(Candidate)); + // either STUN candidate if it exists, else STATIC candidate + memcpy(&local_candidates[1], &msg.conn_request->candidates[0], sizeof(Candidate)); + +cleanup: + free(msg.conn_request->candidates); + free(msg.conn_request); +cleanup_socket: + if(err != CHIAKI_ERR_SUCCESS) + { + if(!CHIAKI_SOCKET_IS_INVALID(session->ipv4_sock)) + { + CHIAKI_SOCKET_CLOSE(session->ipv4_sock); + session->ipv4_sock = CHIAKI_INVALID_SOCKET; + } + if(!CHIAKI_SOCKET_IS_INVALID(session->ipv6_sock)) + { + CHIAKI_SOCKET_CLOSE(session->ipv6_sock); + session->ipv6_sock = CHIAKI_INVALID_SOCKET; + } + } + return err; +} + +/** Sends an OFFER connection request session message to the console via PSN. + * + * @param session The Session instance. + * @return CHIAKI_ERR_SUCCESS on success, or an error code on failure. + */ +static ChiakiErrorCode send_offer(Session *session, int req_id, Candidate *local_console_candidate, Candidate *local_candidates, Candidate *candidates_received, size_t num_candidates) +{ + ChiakiErrorCode err = CHIAKI_ERR_SUCCESS; + uint16_t local_port = session->local_port_data ? session->local_port_data : session->local_port_ctrl; + + SessionMessage msg = { + .action = SESSION_MESSAGE_ACTION_OFFER, + .req_id = req_id, + .error = 0, + .conn_request = malloc(sizeof(ConnectionRequest)), + .notification = NULL, + }; + if(!msg.conn_request) + { + err = CHIAKI_ERR_MEMORY; + goto cleanup_socket; + } + msg.conn_request->sid = session->sid_local; msg.conn_request->peer_sid = session->sid_console; msg.conn_request->nat_type = 2; @@ -2427,7 +2834,9 @@ static ChiakiErrorCode send_offer(Session *session, int req_id, Candidate *local CHIAKI_LOGE(session->log, "Adding upnp port mapping failed"); } else { - get_client_addr_local(session, candidate_local, candidate_local->addr, sizeof(candidate_local->addr)); + err = get_client_addr_local(session, candidate_local, candidate_local->addr, sizeof(candidate_local->addr)); + if(err != CHIAKI_ERR_SUCCESS) + goto cleanup; } memcpy(session->client_local_ip, candidate_local->addr, sizeof(candidate_local->addr)); if (!have_addr) @@ -4681,20 +5090,26 @@ static ChiakiErrorCode wait_for_session_message( if (err != CHIAKI_ERR_SUCCESS) { CHIAKI_LOGE(session->log, "Failed to parse holepunch session message"); + chiaki_mutex_lock(&session->notif_mutex); session_message_free(msg); + chiaki_mutex_unlock(&session->notif_mutex); return err; } if(msg->action & SESSION_MESSAGE_ACTION_TERMINATE) { CHIAKI_LOGW(session->log, "Holepunch session received Terminate message, terminating %d", msg->action); err = CHIAKI_ERR_CANCELED; + chiaki_mutex_lock(&session->notif_mutex); session_message_free(msg); + chiaki_mutex_unlock(&session->notif_mutex); return err; } if (!(msg->action & types)) { CHIAKI_LOGV(session->log, "Ignoring holepunch session message with action %d", msg->action); + chiaki_mutex_lock(&session->notif_mutex); session_message_free(msg); + chiaki_mutex_unlock(&session->notif_mutex); msg = NULL; clear_notification(session, notif); continue;