#include "ng_client.h" #include "media_socket.h" struct endpoint_sockets { endpoint_t dst; socket_slist *sockets; }; static void socket_free(socket_t *s) { close_socket(s); g_free(s); } static void endpoint_socket_free(struct endpoint_sockets *es) { t_slist_free_full(es->sockets, socket_free); g_free(es); } TYPED_GHASHTABLE(endpoint_socket_ht, endpoint_t, struct endpoint_sockets, endpoint_hash, endpoint_eq, NULL, endpoint_socket_free) static endpoint_socket_ht ng_client_endpoints; static rwlock_t ng_client_endpoints_lock; void ng_client_init(void) { ng_client_endpoints = endpoint_socket_ht_new(); } void ng_client_cleanup(void) { t_hash_table_destroy(ng_client_endpoints); } static struct endpoint_sockets *ng_client_get_entry(const endpoint_t *dst) { struct endpoint_sockets *es; // quick check for existing entry { RWLOCK_R(&ng_client_endpoints_lock); es = t_hash_table_lookup(ng_client_endpoints, dst); } if (es) return es; // we have to create one es = g_new0(__typeof(*es), 1); es->dst = *dst; RWLOCK_W(&ng_client_endpoints_lock); // ... but someone may have beaten us to it __auto_type es2 = t_hash_table_lookup(ng_client_endpoints, dst); if (es2) { // lost the race g_free(es); es = es2; } else t_hash_table_insert(ng_client_endpoints, &es->dst, es); return es; } static socket_slist *ng_client_get_socket(struct endpoint_sockets *es) { // see if we can grab a socket socket_slist *link = __atomic_load_n(&es->sockets, __ATOMIC_SEQ_CST); bool success = false; while (link && !success) success = atomic_compare_exchange(&es->sockets, &link, link->next); if (link) { link->next = NULL; return link; } // no socket available, we need to create one link = g_new0(socket_slist, 1); __auto_type sock = link->data = g_new0(socket_t, 1); if (!connect_socket(sock, SOCK_DGRAM, &es->dst)) { // oops... ilog(LOG_ERR, "Failed to create or connect socket to remote NG peer (%s): %s", endpoint_print_buf(&es->dst), strerror(errno)); g_free(link->data); g_free(link); return NULL; } socket_getsockname(sock); interfaces_exclude_port(&sock->local); socket_rcvtimeout(sock, rtpe_config.ng_client_timeout); return link; } static void ng_client_put_socket(struct endpoint_sockets *es, socket_slist *link) { link->next = __atomic_load_n(&es->sockets, __ATOMIC_SEQ_CST); bool success; do success = atomic_compare_exchange(&es->sockets, &link->next, link); while (!success); } bencode_item_t *ng_client_request(const endpoint_t *dst, const str *req, bencode_buffer_t *rbuf) { __auto_type es = ng_client_get_entry(dst); __auto_type link = ng_client_get_socket(es); if (!link) return NULL; __auto_type sock = link->data; // construct message char cookie[17]; rand_hex_str(cookie, 8); cookie[16] = ' '; struct iovec iov[2] = { { .iov_base = cookie, .iov_len = sizeof(cookie), }, { .iov_base = req->s, .iov_len = req->len, }, }; ilog(LOG_DEBUG, "Sending NG request to %s: '" STR_FORMAT "'", endpoint_print_buf(dst), STR_FMT(req)); static const size_t buflen = 4096; char *buf = bencode_buffer_alloc(rbuf, buflen); ssize_t len = 0; for (unsigned int try = 0; try < rtpe_config.ng_client_retries; try++) { ilog(LOG_DEBUG, "Attempt #%u sending NG request", try + 1); ssize_t ret = socket_sendiov(sock, iov, G_N_ELEMENTS(iov), NULL, NULL); if (ret <= 0) goto err; // receive the response len = socket_recvfrom(sock, buf, buflen, NULL); if (len > 0) ilog(LOG_DEBUG, "Received response from NG peer (%s): '%.*s'", endpoint_print_buf(dst), (int) len, buf); if (len == buflen) ilog(LOG_WARN, "Possibly truncated response from remote NG peer (%s)", endpoint_print_buf(dst)); if (len > sizeof(cookie) && memcmp(buf, cookie, sizeof(cookie)) == 0) break; if (len < 0) ilog(LOG_WARN, "Error reading from socket from remote NG peer (%s): %s", endpoint_print_buf(dst), strerror(errno)); else if (len == 0) ilog(LOG_WARN, "EOF while reading from socket from remote NG peer (%s)", endpoint_print_buf(dst)); else ilog(LOG_WARN, "Short packet or mismatched cookie while reading from socket " "from remote NG peer (%s)", endpoint_print_buf(dst)); } if (len <= 0) goto err; ilog(LOG_DEBUG, "Received valid NG response: '%.*s'", (int) len, buf); bencode_item_t *ret = bencode_decode_expect(rbuf, buf + sizeof(cookie), len - sizeof(cookie), BENCODE_DICTIONARY); if (!ret) { errno = EIO; goto err; } ng_client_put_socket(es, link); return ret; err: ilog(LOG_ERR, "Error communicating with remote NG peer (%s): %s", endpoint_print_buf(&sock->remote), strerror(errno)); ng_client_put_socket(es, link); return NULL; }