int tcp_connect(const char *hostname, int port, const char *bindaddr, char *errbuf, size_t errbufsize, int timeout) { int fd, r, res, err; struct addrinfo *ai, hints; char portstr[6]; socklen_t errlen = sizeof(err); snprintf(portstr, 6, "%u", port); memset(&hints, 0, sizeof(struct addrinfo)); hints.ai_family = AF_UNSPEC; res = getaddrinfo(hostname, portstr, &hints, &ai); if (res != 0) { snprintf(errbuf, errbufsize, "%s", gai_strerror(res)); return -1; } fd = tvh_socket(ai->ai_family, SOCK_STREAM, 0); if(fd == -1) { snprintf(errbuf, errbufsize, "Unable to create socket: %s", strerror(errno)); freeaddrinfo(ai); return -1; } /** * Switch to nonblocking */ fcntl(fd, F_SETFL, fcntl(fd, F_GETFL) | O_NONBLOCK); if (ai->ai_family == AF_INET || ai->ai_family == AF_INET6) { if (bindaddr && bindaddr[0] != '\0') { struct sockaddr_storage ip; memset(&ip, 0, sizeof(ip)); ip.ss_family = ai->ai_family; if (inet_pton(AF_INET, bindaddr, IP_IN_ADDR(ip)) <= 0 || bind(fd, (struct sockaddr *)&ip, IP_IN_ADDRLEN(ip)) < 0) { snprintf(errbuf, errbufsize, "Cannot bind to IPv%s addr '%s'", ai->ai_family == AF_INET6 ? "6" : "4", bindaddr); freeaddrinfo(ai); return -1; } } } else { snprintf(errbuf, errbufsize, "Invalid protocol family"); freeaddrinfo(ai); return -1; } r = connect(fd, ai->ai_addr, ai->ai_addrlen); freeaddrinfo(ai); if(r == -1) { /* timeout < 0 - do not wait at all */ if(errno == EINPROGRESS && timeout < 0) { err = 0; } else if(errno == EINPROGRESS) { tvhpoll_event_t ev; tvhpoll_t *efd; efd = tvhpoll_create(1); memset(&ev, 0, sizeof(ev)); ev.events = TVHPOLL_OUT; ev.fd = fd; ev.data.ptr = &fd; tvhpoll_add(efd, &ev, 1); /* minimal timeout is one second */ if (timeout < 1) timeout = 0; while (1) { if (!tvheadend_running) { errbuf[0] = '\0'; tvhpoll_destroy(efd); close(fd); return -1; } r = tvhpoll_wait(efd, &ev, 1, timeout * 1000); if (r > 0) break; if (r == 0) { /* Timeout */ snprintf(errbuf, errbufsize, "Connection attempt timed out"); tvhpoll_destroy(efd); close(fd); return -1; } if (!ERRNO_AGAIN(errno)) { snprintf(errbuf, errbufsize, "poll() error: %s", strerror(errno)); tvhpoll_destroy(efd); close(fd); return -1; } } tvhpoll_destroy(efd); getsockopt(fd, SOL_SOCKET, SO_ERROR, (void *)&err, &errlen); } else { err = errno; } } else { err = 0; } if(err != 0) { snprintf(errbuf, errbufsize, "%s", strerror(err)); close(fd); return -1; } fcntl(fd, F_SETFL, fcntl(fd, F_GETFL) & ~O_NONBLOCK); /* Set the keep-alive active */ err = 1; setsockopt(fd, SOL_SOCKET, SO_KEEPALIVE, (void *)&err, errlen); return fd; }
/* * Discovery thread */ static void * upnp_thread( void *aux ) { char *bindaddr = aux; tvhpoll_t *poll = tvhpoll_create(2); tvhpoll_event_t ev[2]; upnp_data_t *data; udp_connection_t *multicast = NULL, *unicast = NULL; udp_connection_t *conn; unsigned char buf[16384]; upnp_service_t *us; struct sockaddr_storage ip; socklen_t iplen; size_t size; int r; multicast = udp_bind("upnp", "upnp_thread_multicast", "239.255.255.250", 1900, NULL, 32*1024); if (multicast == NULL || multicast == UDP_FATAL_ERROR) goto error; unicast = udp_bind("upnp", "upnp_thread_unicast", bindaddr, 0, NULL, 32*1024); if (unicast == NULL || unicast == UDP_FATAL_ERROR) goto error; memset(&ev, 0, sizeof(ev)); ev[0].fd = multicast->fd; ev[0].events = TVHPOLL_IN; ev[0].data.ptr = multicast; ev[1].fd = unicast->fd; ev[1].events = TVHPOLL_IN; ev[1].data.ptr = unicast; tvhpoll_add(poll, ev, 2); while (upnp_running && multicast->fd >= 0) { r = tvhpoll_wait(poll, ev, 2, 1000); while (r-- > 0) { if ((ev[r].events & TVHPOLL_IN) != 0) { conn = ev[r].data.ptr; iplen = sizeof(ip); size = recvfrom(conn->fd, buf, sizeof(buf), 0, (struct sockaddr *)&ip, &iplen); #if ENABLE_TRACE if (size > 0) { char tbuf[256]; inet_ntop(ip.ss_family, IP_IN_ADDR(ip), tbuf, sizeof(tbuf)); tvhtrace("upnp", "%s - received data from %s:%hu [size=%zi]", conn == multicast ? "multicast" : "unicast", tbuf, (unsigned short) IP_PORT(ip), size); tvhlog_hexdump("upnp", buf, size); } #endif /* TODO: a filter */ TAILQ_FOREACH(us, &upnp_services, us_link) us->us_received(buf, size, conn, &ip); } } while (1) { pthread_mutex_lock(&upnp_lock); data = TAILQ_FIRST(&upnp_data_write); if (data) TAILQ_REMOVE(&upnp_data_write, data, data_link); pthread_mutex_unlock(&upnp_lock); if (data == NULL) break; udp_write_queue(unicast, &data->queue, &data->storage); htsbuf_queue_flush(&data->queue); free(data); } } error: upnp_running = 0; tvhpoll_destroy(poll); udp_close(unicast); udp_close(multicast); return NULL; }