diff --git a/include/shairplay/raop.h b/include/shairplay/raop.h index 172ecf8..e349275 100644 --- a/include/shairplay/raop.h +++ b/include/shairplay/raop.h @@ -51,7 +51,7 @@ RAOP_API raop_t *raop_init_from_keyfile(int max_clients, raop_callbacks_t *callb RAOP_API void raop_set_log_level(raop_t *raop, int level); RAOP_API void raop_set_log_callback(raop_t *raop, raop_log_callback_t callback, void *cls); -RAOP_API int raop_start(raop_t *raop, unsigned short *port, const char *hwaddr, int hwaddrlen, const char *password); +RAOP_API int raop_start(raop_t *raop, unsigned short *port, unsigned short dyn_port_min, unsigned short dyn_port_max, const char *hwaddr, int hwaddrlen, const char *password); RAOP_API int raop_is_running(raop_t *raop); RAOP_API void raop_stop(raop_t *raop); diff --git a/src/bindings/python/Shairplay.py b/src/bindings/python/Shairplay.py index 4400bd9..c92fb1c 100644 --- a/src/bindings/python/Shairplay.py +++ b/src/bindings/python/Shairplay.py @@ -110,7 +110,7 @@ def InitShairplay(libshairplay): libshairplay.raop_is_running.restype = c_int libshairplay.raop_is_running.argtypes = [c_void_p] libshairplay.raop_start.restype = c_int - libshairplay.raop_start.argtypes = [c_void_p, POINTER(c_ushort), POINTER(c_char), c_int, c_char_p] + libshairplay.raop_start.argtypes = [c_void_p, POINTER(c_ushort), c_ushort, c_ushort, POINTER(c_char), c_int, c_char_p] libshairplay.raop_stop.restype = None libshairplay.raop_stop.argtypes = [c_void_p] libshairplay.raop_destroy.restype = None @@ -241,11 +241,11 @@ def log_callback_cb(cls, level, message): self.libshairplay.raop_set_log_callback(self.instance, log_callback_ptr, None) self.log_callback = log_callback_ptr - def start(self, port, hwaddrstr, password=None): + def start(self, port, hwaddrstr, password=None, dyn_port_min=0, dyn_port_max=0): port = c_ushort(port) hwaddr = create_string_buffer(hwaddrstr, len(hwaddrstr)) - ret = self.libshairplay.raop_start(self.instance, pointer(port), hwaddr, c_int(len(hwaddr)), password) + ret = self.libshairplay.raop_start(self.instance, pointer(port), dyn_port_min, dyn_port_max, hwaddr, c_int(len(hwaddr)), password) if ret < 0: raise RuntimeError("Starting RAOP instance failed") return port.value diff --git a/src/bindings/qt4/raopservice.cpp b/src/bindings/qt4/raopservice.cpp index 2250e2f..dabe03f 100644 --- a/src/bindings/qt4/raopservice.cpp +++ b/src/bindings/qt4/raopservice.cpp @@ -206,10 +206,10 @@ bool RaopService::isRunning() return (raop_is_running(m_raop) != 0); } -bool RaopService::start(quint16 port, const QByteArray & hwaddr) +bool RaopService::start(quint16 port, const QByteArray & hwaddr, quint16 dyn_min_port, quint16 dyn_max_port) { int ret; - ret = raop_start(m_raop, &port, hwaddr.data(), hwaddr.size(), 0); + ret = raop_start(m_raop, &port, dyn_min_port, dyn_max_port, hwaddr.data(), hwaddr.size(), 0); if (ret < 0) { return false; } diff --git a/src/bindings/qt4/raopservice.h b/src/bindings/qt4/raopservice.h index 23ce112..5678528 100644 --- a/src/bindings/qt4/raopservice.h +++ b/src/bindings/qt4/raopservice.h @@ -40,7 +40,7 @@ class RaopService : public QObject bool init(int max_clients, RaopAudioHandler *callbacks); void setLogLevel(int level); void setLogHandler(RaopLogHandler *logger); - bool start(quint16 port, const QByteArray & hwaddr); + bool start(quint16 port, const QByteArray & hwaddr, quint16 dyn_min_port=0, quint16 dyn_max_port=0); bool isRunning(); void stop(); diff --git a/src/lib/httpd.c b/src/lib/httpd.c index 74e7541..882c50d 100644 --- a/src/lib/httpd.c +++ b/src/lib/httpd.c @@ -368,13 +368,13 @@ httpd_start(httpd_t *httpd, unsigned short *port) return 0; } - httpd->server_fd4 = netutils_init_socket(port, 0, 0); + httpd->server_fd4 = netutils_init_socket(port, 0, 0, 0, 0); if (httpd->server_fd4 == -1) { logger_log(httpd->logger, LOGGER_ERR, "Error initialising socket %d", SOCKET_GET_ERROR()); MUTEX_UNLOCK(httpd->run_mutex); return -1; } - httpd->server_fd6 = netutils_init_socket(port, 1, 0); + httpd->server_fd6 = netutils_init_socket(port, 1, 0, 0, 0); if (httpd->server_fd6 == -1) { logger_log(httpd->logger, LOGGER_WARNING, "Error initialising IPv6 socket %d", SOCKET_GET_ERROR()); logger_log(httpd->logger, LOGGER_WARNING, "Continuing without IPv6 support"); diff --git a/src/lib/netutils.c b/src/lib/netutils.c index f194539..d301be1 100644 --- a/src/lib/netutils.c +++ b/src/lib/netutils.c @@ -50,7 +50,7 @@ netutils_cleanup() } int -netutils_init_socket(unsigned short *port, int use_ipv6, int use_udp) +netutils_init_socket(unsigned short *port, int use_ipv6, int use_udp, unsigned short dyn_port_min, unsigned short dyn_port_max) { int family = use_ipv6 ? AF_INET6 : AF_INET; int type = use_udp ? SOCK_DGRAM : SOCK_STREAM; @@ -84,6 +84,10 @@ netutils_init_socket(unsigned short *port, int use_ipv6, int use_udp) sin6ptr->sin6_addr = in6addr_any; sin6ptr->sin6_port = htons(*port); + if(*port == 0 && dyn_port_min) { + sin6ptr->sin6_port = htons(dyn_port_min); + } + #ifndef WIN32 /* Make sure we only listen to IPv6 addresses */ setsockopt(server_fd, IPPROTO_IPV6, IPV6_V6ONLY, @@ -91,8 +95,16 @@ netutils_init_socket(unsigned short *port, int use_ipv6, int use_udp) #endif socklen = sizeof(*sin6ptr); - ret = bind(server_fd, (struct sockaddr *)sin6ptr, socklen); - if (ret == -1) { + while(1) { + ret = bind(server_fd, (struct sockaddr *)sin6ptr, socklen); + if(ret == 0) + break; + + if(*port == 0 && dyn_port_min && (!dyn_port_max || ntohs(sin6ptr->sin6_port) < dyn_port_max)) { + sin6ptr->sin6_port = htons(ntohs(sin6ptr->sin6_port) + 1); + continue; + } + goto cleanup; } @@ -109,9 +121,21 @@ netutils_init_socket(unsigned short *port, int use_ipv6, int use_udp) sinptr->sin_addr.s_addr = INADDR_ANY; sinptr->sin_port = htons(*port); + if(*port == 0 && dyn_port_min) { + sinptr->sin_port = htons(dyn_port_min); + } + socklen = sizeof(*sinptr); - ret = bind(server_fd, (struct sockaddr *)sinptr, socklen); - if (ret == -1) { + while(1) { + ret = bind(server_fd, (struct sockaddr *)sinptr, socklen); + if(ret == 0) + break; + + if(*port == 0 && dyn_port_min && (!dyn_port_max || ntohs(sinptr->sin_port) < dyn_port_max)) { + sinptr->sin_port = htons(ntohs(sinptr->sin_port) + 1); + continue; + } + goto cleanup; } diff --git a/src/lib/netutils.h b/src/lib/netutils.h index 63854ad..0ee2706 100644 --- a/src/lib/netutils.h +++ b/src/lib/netutils.h @@ -18,7 +18,7 @@ int netutils_init(); void netutils_cleanup(); -int netutils_init_socket(unsigned short *port, int use_ipv6, int use_udp); +int netutils_init_socket(unsigned short *port, int use_ipv6, int use_udp, unsigned short dyn_port_min, unsigned short dyn_port_max); unsigned char *netutils_get_address(void *sockaddr, int *length); int netutils_parse_address(int family, const char *src, void *dst, int dstlen); diff --git a/src/lib/raop.c b/src/lib/raop.c index fa355ee..2b463bb 100644 --- a/src/lib/raop.c +++ b/src/lib/raop.c @@ -54,6 +54,10 @@ struct raop_s { unsigned char hwaddr[MAX_HWADDR_LEN]; int hwaddrlen; + /* Range of dynamic ports to use for control/timing/data */ + unsigned int dyn_port_min; + unsigned int dyn_port_max; + /* Password information */ char password[MAX_PASSWORD_LEN+1]; }; @@ -293,7 +297,7 @@ conn_request(void *ptr, http_request_t *request, http_response_t **response) free(original); } if (conn->raop_rtp) { - raop_rtp_start(conn->raop_rtp, use_udp, remote_cport, remote_tport, &cport, &tport, &dport); + raop_rtp_start(conn->raop_rtp, use_udp, remote_cport, remote_tport, raop->dyn_port_min, raop->dyn_port_max, &cport, &tport, &dport); } else { logger_log(conn->raop->logger, LOGGER_ERR, "RAOP not initialized at SETUP, playing will fail!"); http_response_set_disconnect(res, 1); @@ -560,7 +564,8 @@ raop_set_log_callback(raop_t *raop, raop_log_callback_t callback, void *cls) } int -raop_start(raop_t *raop, unsigned short *port, const char *hwaddr, int hwaddrlen, const char *password) +raop_start(raop_t *raop, unsigned short *port, unsigned short dyn_port_min, + unsigned short dyn_port_max, const char *hwaddr, int hwaddrlen, const char *password) { assert(raop); assert(port); @@ -586,6 +591,9 @@ raop_start(raop_t *raop, unsigned short *port, const char *hwaddr, int hwaddrlen memcpy(raop->hwaddr, hwaddr, hwaddrlen); raop->hwaddrlen = hwaddrlen; + raop->dyn_port_min = dyn_port_min; + raop->dyn_port_max = dyn_port_max; + return httpd_start(raop->httpd, port); } diff --git a/src/lib/raop_rtp.c b/src/lib/raop_rtp.c index e8bc381..4827d06 100644 --- a/src/lib/raop_rtp.c +++ b/src/lib/raop_rtp.c @@ -179,7 +179,8 @@ raop_rtp_destroy(raop_rtp_t *raop_rtp) } static int -raop_rtp_init_sockets(raop_rtp_t *raop_rtp, int use_ipv6, int use_udp) +raop_rtp_init_sockets(raop_rtp_t *raop_rtp, int use_ipv6, int use_udp, + unsigned short dyn_port_min, unsigned short dyn_port_max) { int csock = -1, tsock = -1, dsock = -1; unsigned short cport = 0, tport = 0, dport = 0; @@ -187,13 +188,13 @@ raop_rtp_init_sockets(raop_rtp_t *raop_rtp, int use_ipv6, int use_udp) assert(raop_rtp); if (use_udp) { - csock = netutils_init_socket(&cport, use_ipv6, use_udp); - tsock = netutils_init_socket(&tport, use_ipv6, use_udp); + csock = netutils_init_socket(&cport, use_ipv6, use_udp, dyn_port_min, dyn_port_max); + tsock = netutils_init_socket(&tport, use_ipv6, use_udp, dyn_port_min, dyn_port_max); if (csock == -1 || tsock == -1) { goto sockets_cleanup; } } - dsock = netutils_init_socket(&dport, use_ipv6, use_udp); + dsock = netutils_init_socket(&dport, use_ipv6, use_udp, dyn_port_min, dyn_port_max); if (dsock == -1) { goto sockets_cleanup; } @@ -601,6 +602,7 @@ raop_rtp_thread_tcp(void *arg) void raop_rtp_start(raop_rtp_t *raop_rtp, int use_udp, unsigned short control_rport, unsigned short timing_rport, + unsigned short dyn_port_min, unsigned short dyn_port_max, unsigned short *control_lport, unsigned short *timing_lport, unsigned short *data_lport) { int use_ipv6 = 0; @@ -619,7 +621,7 @@ raop_rtp_start(raop_rtp_t *raop_rtp, int use_udp, unsigned short control_rport, if (raop_rtp->remote_saddr.ss_family == AF_INET6) { use_ipv6 = 1; } - if (raop_rtp_init_sockets(raop_rtp, use_ipv6, use_udp) < 0) { + if (raop_rtp_init_sockets(raop_rtp, use_ipv6, use_udp, dyn_port_min, dyn_port_max) < 0) { logger_log(raop_rtp->logger, LOGGER_INFO, "Initializing sockets failed"); MUTEX_UNLOCK(raop_rtp->run_mutex); return; diff --git a/src/lib/raop_rtp.h b/src/lib/raop_rtp.h index 45f71e8..ee87f0d 100644 --- a/src/lib/raop_rtp.h +++ b/src/lib/raop_rtp.h @@ -29,6 +29,7 @@ raop_rtp_t *raop_rtp_init(logger_t *logger, raop_callbacks_t *callbacks, const c const char *rtpmap, const char *fmtp, const unsigned char *aeskey, const unsigned char *aesiv); void raop_rtp_start(raop_rtp_t *raop_rtp, int use_udp, unsigned short control_rport, unsigned short timing_rport, + unsigned short dyn_port_min, unsigned short dyn_port_max, unsigned short *control_lport, unsigned short *timing_lport, unsigned short *data_lport); void raop_rtp_set_volume(raop_rtp_t *raop_rtp, float volume); void raop_rtp_set_metadata(raop_rtp_t *raop_rtp, const char *data, int datalen); diff --git a/src/shairplay.c b/src/shairplay.c index 1286812..da3aaaa 100644 --- a/src/shairplay.c +++ b/src/shairplay.c @@ -43,6 +43,8 @@ typedef struct { char apname[56]; char password[56]; unsigned short port; + unsigned short dyn_port_min; + unsigned short dyn_port_max; char hwaddr[6]; char ao_driver[56]; @@ -277,6 +279,10 @@ parse_options(shairplay_options_t *opt, int argc, char *argv[]) opt->port = atoi(*++argv); } else if (!strncmp(arg, "--server_port=", 14)) { opt->port = atoi(arg+14); + } else if (!strncmp(arg, "--dyn_port_min=", 15)) { + opt->dyn_port_min = atoi(arg+15); + } else if (!strncmp(arg, "--dyn_port_max=", 15)) { + opt->dyn_port_max = atoi(arg+15); } else if (!strncmp(arg, "--hwaddr=", 9)) { if (parse_hwaddr(arg+9, opt->hwaddr, sizeof(opt->hwaddr))) { fprintf(stderr, "Invalid format given for hwaddr, aborting...\n"); @@ -296,6 +302,8 @@ parse_options(shairplay_options_t *opt, int argc, char *argv[]) fprintf(stderr, " -a, --apname=AirPort Sets Airport name\n"); fprintf(stderr, " -p, --password=secret Sets password\n"); fprintf(stderr, " -o, --server_port=5000 Sets port for RAOP service\n"); + fprintf(stderr, " --dyn_port_min=0 Sets lower bound of dynamically allocated port range\n"); + fprintf(stderr, " --dyn_port_max=0 Sets upper bound of dynamically allocated port range (must be at least min+3)\n"); fprintf(stderr, " --hwaddr=address Sets the MAC address, useful if running multiple instances\n"); fprintf(stderr, " --ao_driver=driver Sets the ao driver (optional)\n"); fprintf(stderr, " --ao_devicename=devicename Sets the ao device name (optional)\n"); @@ -306,6 +314,20 @@ parse_options(shairplay_options_t *opt, int argc, char *argv[]) } } + if(opt->dyn_port_min != 0 && opt->dyn_port_max != 0) { + if(opt->dyn_port_min > opt->dyn_port_max) + fprintf(stderr, "dyn_port_max must be greater than dyn_port_min\n"); + else if(opt->dyn_port_max - opt->dyn_port_min < 3) + fprintf(stderr, "dyn_port_max must be at least 3 ports above dyn_port_min\n"); + else + return 0; + + return 1; + }else if(opt->dyn_port_min == 0 && opt->dyn_port_max != 0) { + fprintf(stderr, "dyn_port_max is useless without dyn_port_min\n"); + return 1; + } + return 0; } @@ -361,7 +383,7 @@ main(int argc, char *argv[]) password = options.password; } raop_set_log_level(raop, RAOP_LOG_DEBUG); - raop_start(raop, &options.port, options.hwaddr, sizeof(options.hwaddr), password); + raop_start(raop, &options.port, options.dyn_port_min, options.dyn_port_max, options.hwaddr, sizeof(options.hwaddr), password); error = 0; dnssd = dnssd_init(&error);