diff --git a/src/agent.c b/src/agent.c index 182686de..b4d3a505 100644 --- a/src/agent.c +++ b/src/agent.c @@ -679,7 +679,8 @@ int agent_relay_send(juice_agent_t *agent, agent_stun_entry_t *entry, const addr msg.msg_class = STUN_CLASS_INDICATION; msg.msg_method = STUN_METHOD_SEND; juice_random(msg.transaction_id, STUN_TRANSACTION_ID_SIZE); - msg.peer = *dst; + msg.peers_size = 1; + msg.peers[0] = *dst; msg.data = data; msg.data_size = size; @@ -1989,7 +1990,8 @@ int agent_send_turn_create_permission_request(juice_agent_t *agent, agent_stun_e return -1; msg.credentials = entry->turn->credentials; - msg.peer = *record; + msg.peers_size = 1; + msg.peers[0] = *record; char buffer[BUFFER_SIZE]; int size = stun_write(buffer, BUFFER_SIZE, &msg, entry->turn->password); @@ -2093,7 +2095,8 @@ int agent_send_turn_channel_bind_request(juice_agent_t *agent, agent_stun_entry_ msg.credentials = entry->turn->credentials; msg.channel_number = channel; - msg.peer = *record; + msg.peers_size = 1; + msg.peers[0] = *record; if (out_channel) *out_channel = channel; @@ -2127,11 +2130,12 @@ int agent_process_turn_data(juice_agent_t *agent, const stun_message_t *msg, JLOG_WARN("Missing data in TURN Data indication"); return -1; } - if (!msg->peer.len) { + if (!msg->peers_size) { JLOG_WARN("Missing peer address in TURN Data indication"); return -1; } - return agent_input(agent, (char *)msg->data, msg->data_size, &msg->peer, &entry->relayed); + const addr_record_t *peer = msg->peers; + return agent_input(agent, (char *)msg->data, msg->data_size, peer, &entry->relayed); } int agent_process_channel_data(juice_agent_t *agent, agent_stun_entry_t *entry, char *buf, diff --git a/src/server.c b/src/server.c index a72e3e4c..8ad2ab1f 100644 --- a/src/server.c +++ b/src/server.c @@ -503,7 +503,8 @@ int server_forward(juice_server_t *server, server_turn_alloc_t *alloc) { memset(&msg, 0, sizeof(msg)); msg.msg_class = STUN_CLASS_INDICATION; msg.msg_method = STUN_METHOD_DATA; - msg.peer = record; + msg.peers_size = 1; + msg.peers[0] = record; msg.data = buffer; msg.data_size = len; juice_random(msg.transaction_id, STUN_TRANSACTION_ID_SIZE); @@ -978,9 +979,15 @@ int server_process_turn_create_permission(juice_server_t *server, const stun_mes JLOG_DEBUG("Processing STUN CreatePermission request"); - if (!msg->peer.len) { + // RFC 5766 9.2. Receiving a CreatePermission Request: + // The CreatePermission request MUST contain at least one XOR-PEER-ADDRESS attribute and MAY + // contain multiple such attributes. If no such attribute exists, or if any of these attributes + // are invalid, then a 400 (Bad Request) error is returned. + if (!msg->peers_size) { JLOG_WARN("Missing peer address in TURN CreatePermission request"); - return -1; + return server_answer_stun_error(server, msg->transaction_id, src, msg->msg_method, + 400, // Bad request + credentials); } server_turn_alloc_t *alloc = find_allocation(server->allocs, server->allocs_count, src, false); @@ -995,10 +1002,13 @@ int server_process_turn_create_permission(juice_server_t *server, const stun_mes credentials); } - if (!turn_set_permission(&alloc->map, msg->transaction_id, &msg->peer, PERMISSION_LIFETIME)) { - server_answer_stun_error(server, msg->transaction_id, src, msg->msg_method, 500, - credentials); - return -1; + for (size_t i = 0; i < msg->peers_size; ++i) { + const addr_record_t *peer = msg->peers + i; + if (!turn_set_permission(&alloc->map, msg->transaction_id, peer, PERMISSION_LIFETIME)) { + server_answer_stun_error(server, msg->transaction_id, src, msg->msg_method, 500, + credentials); + return -1; + } } stun_message_t ans; @@ -1020,13 +1030,17 @@ int server_process_turn_channel_bind(juice_server_t *server, const stun_message_ JLOG_DEBUG("Processing STUN ChannelBind request"); - if (!msg->peer.len) { + if (!msg->peers_size) { JLOG_WARN("Missing peer address in TURN ChannelBind request"); - return -1; + return server_answer_stun_error(server, msg->transaction_id, src, msg->msg_method, + 400, // Bad request + credentials); } if (!msg->channel_number) { JLOG_WARN("Missing channel number in TURN ChannelBind request"); - return -1; + return server_answer_stun_error(server, msg->transaction_id, src, msg->msg_method, + 400, // Bad request + credentials); } server_turn_alloc_t *alloc = find_allocation(server->allocs, server->allocs_count, src, false); @@ -1049,7 +1063,8 @@ int server_process_turn_channel_bind(juice_server_t *server, const stun_message_ credentials); } - if (!turn_bind_channel(&alloc->map, &msg->peer, msg->transaction_id, channel, BIND_LIFETIME)) { + const addr_record_t *peer = msg->peers; + if (!turn_bind_channel(&alloc->map, peer, msg->transaction_id, channel, BIND_LIFETIME)) { server_answer_stun_error(server, msg->transaction_id, src, msg->msg_method, 500, credentials); return -1; @@ -1077,7 +1092,7 @@ int server_process_turn_send(juice_server_t *server, const stun_message_t *msg, JLOG_WARN("Missing data in TURN Send indication"); return -1; } - if (!msg->peer.len) { + if (!msg->peers_size) { JLOG_WARN("Missing peer address in TURN Send indication"); return -1; } @@ -1088,14 +1103,15 @@ int server_process_turn_send(juice_server_t *server, const stun_message_t *msg, return -1; } - if (!turn_has_permission(&alloc->map, &msg->peer)) { + const addr_record_t *peer = msg->peers; + if (!turn_has_permission(&alloc->map, peer)) { JLOG_WARN("No permission for peer address"); return -1; } JLOG_VERBOSE("Forwarding datagram to peer, size=%zu", msg->data_size); - int ret = udp_sendto(alloc->sock, msg->data, msg->data_size, &msg->peer); + int ret = udp_sendto(alloc->sock, msg->data, msg->data_size, peer); if (ret < 0 && sockerrno != SEAGAIN && sockerrno != SEWOULDBLOCK) JLOG_WARN("Forwarding failed, errno=%d", sockerrno); diff --git a/src/stun.c b/src/stun.c index 85143e9b..affe7551 100644 --- a/src/stun.c +++ b/src/stun.c @@ -182,19 +182,22 @@ int stun_write(void *buf, size_t size, const stun_message_t *msg, const char *pa goto overflow; pos += len; } - if (msg->peer.len) { - JLOG_VERBOSE("Writing XOR peer address"); - uint8_t value[32]; - uint8_t mask[16]; - *((uint32_t *)mask) = htonl(STUN_MAGIC); - memcpy(mask + 4, msg->transaction_id, 12); - int value_len = stun_write_value_mapped_address( - value, 32, (const struct sockaddr *)&msg->peer.addr, msg->peer.len, mask); - if (value_len > 0) { - len = stun_write_attr(pos, end - pos, STUN_ATTR_XOR_PEER_ADDRESS, value, value_len); - if (len <= 0) - goto overflow; - pos += len; + for (size_t i = 0; i < msg->peers_size; ++i) { + const addr_record_t *peer = msg->peers + i; + if (peer->len) { + JLOG_VERBOSE("Writing XOR peer address"); + uint8_t value[32]; + uint8_t mask[16]; + *((uint32_t *)mask) = htonl(STUN_MAGIC); + memcpy(mask + 4, msg->transaction_id, 12); + int value_len = stun_write_value_mapped_address( + value, 32, (const struct sockaddr *)&peer->addr, peer->len, mask); + if (value_len > 0) { + len = stun_write_attr(pos, end - pos, STUN_ATTR_XOR_PEER_ADDRESS, value, value_len); + if (len <= 0) + goto overflow; + pos += len; + } } } if (msg->relayed.len) { @@ -908,7 +911,7 @@ int stun_read_attr(const void *data, size_t size, stun_message_t *msg, uint8_t * JLOG_DEBUG("STUN ICE controlling attribute length invalid, length=%zu", length); return -1; } - uint32_t* value32 = (uint32_t *)attr->value; + uint32_t *value32 = (uint32_t *)attr->value; msg->ice_controlling = ((uint64_t)ntohl(value32[0]) << 32) | ntohl(value32[1]); break; } @@ -918,7 +921,7 @@ int stun_read_attr(const void *data, size_t size, stun_message_t *msg, uint8_t * JLOG_DEBUG("STUN ICE controlled attribute length invalid, length=%zu", length); return -1; } - uint32_t* value32 = (uint32_t *)attr->value; + uint32_t *value32 = (uint32_t *)attr->value; msg->ice_controlled = ((uint64_t)ntohl(value32[0]) << 32) | ntohl(value32[1]); break; } @@ -945,11 +948,18 @@ int stun_read_attr(const void *data, size_t size, stun_message_t *msg, uint8_t * } case STUN_ATTR_XOR_PEER_ADDRESS: { JLOG_VERBOSE("Reading XOR peer address"); - uint8_t mask[16]; - *((uint32_t *)mask) = htonl(STUN_MAGIC); - memcpy(mask + 4, msg->transaction_id, 12); - if (stun_read_value_mapped_address(attr->value, length, &msg->peer, mask) < 0) - return -1; + if (msg->peers_size < STUN_MAX_PEER_ADDRESSES) { + uint8_t mask[16]; + *((uint32_t *)mask) = htonl(STUN_MAGIC); + memcpy(mask + 4, msg->transaction_id, 12); + addr_record_t *peer = msg->peers + msg->peers_size; + if (stun_read_value_mapped_address(attr->value, length, peer, mask) < 0) + return -1; + if (peer->len) + ++msg->peers_size; + } else { + JLOG_WARN("Too many STUN XOR-PEER-ADDRESS attributes, ignoring"); + } break; } case STUN_ATTR_XOR_RELAYED_ADDRESS: { @@ -1004,7 +1014,7 @@ int stun_read_attr(const void *data, size_t size, stun_message_t *msg, uint8_t * JLOG_DEBUG("STUN reservation token length invalid, length=%zu", length); return -1; } - uint32_t* value32 = (uint32_t *)attr->value; + uint32_t *value32 = (uint32_t *)attr->value; msg->reservation_token = ((uint64_t)ntohl(value32[0]) << 32) | ntohl(value32[1]); break; } diff --git a/src/stun.h b/src/stun.h index b3aeade4..9332acfd 100644 --- a/src/stun.h +++ b/src/stun.h @@ -297,6 +297,9 @@ typedef enum stun_password_algorithm { #define STUN_MAX_PASSWORD_ALGORITHMS_VALUE_SIZE 256 +// RFC 5766: When forming a CreatePermission request, the client MUST include at least one XOR-PEER-ADDRESS attribute, and MAY include more than one such attribute. +#define STUN_MAX_PEER_ADDRESSES 8 + typedef struct stun_credentials { char username[STUN_MAX_USERNAME_LEN]; char realm[STUN_MAX_REALM_LEN]; @@ -326,7 +329,8 @@ typedef struct stun_message { bool has_fingerprint; // TURN - addr_record_t peer; + addr_record_t peers[STUN_MAX_PEER_ADDRESSES]; + size_t peers_size; addr_record_t relayed; addr_record_t alternate_server; const char *data;