Branch netjack_squashed_interface_selection_plus_ipv6 Original PR: https://github.com/jackaudio/jack2/pull/27 by rufferson, first comment Mar 3, 2013 Commits on Mar 12, 2017 Add interface selection for NetJack components @rufferson rufferson committed on Mar 3, 2013 * Multicast interface selection to UnixSocket - added methods Bind and JoinMCastGroup with <char *if_name> argument * Interface handling to NetInterface class tree - new field and its initialization/usage in Slave branch. * Multicast interface selection to NetSlaves (Adapter/Driver) and NetMaster, where master can now listen all interfaces. Commits on Mar 15, 2017 Add IPv6 support to UnixSocket and NetInterfaces @rufferson rufferson committed on Feb 21, 2013 * Cleaned up direct references to sockaddr_in * Changed fRecvAddr & fSendAddr to sockaddr_storage * Added field fFamily indicating current probed AF. * Added protected method ProbeAF accepting IP to probe and addr to fill in. As well as final call to test which should be either connect or bind. * Reworked protocol specific methods to act on fFamily * IsLocal now checks assigned interface addresses * Introduced internal state tracker to avoid double bind * Workaround for GLIBC bug returning wrong order of AFs http://sourceware.org/bugzilla/show_bug.cgi?id=14967 * Corrected interface selection for Slaves - only one interface could be used to multicast the packet * Retab changes This branch has conflicts that must be resolved to resolve conflicts before continuing. Conflicting files common/JackNetDriver.h posix/JackNetUnixSocket.cpp wget https://github.com/jackaudio/jack2/pull/27.diff $ patch -p 1 < 27.diff patching file common/JackNetAdapter.cpp patching file common/JackNetDriver.cpp patching file common/JackNetDriver.h Hunk #1 FAILED at 79. 1 out of 1 hunk FAILED -- saving rejects to file common/JackNetDriver.h.rej patching file common/JackNetInterface.cpp patching file common/JackNetInterface.h patching file common/JackNetManager.cpp patching file common/JackNetManager.h patching file common/JackNetTool.h patching file posix/JackNetUnixSocket.cpp Hunk #4 FAILED at 229. Hunk #5 succeeded at 281 (offset -8 lines). Hunk #6 succeeded at 303 (offset -8 lines). Hunk #7 succeeded at 325 (offset -8 lines). Hunk #8 succeeded at 363 (offset -8 lines). Hunk #9 succeeded at 486 (offset -8 lines). Hunk #10 succeeded at 508 (offset -8 lines). Hunk #11 succeeded at 537 (offset -8 lines). Hunk #12 succeeded at 586 (offset -8 lines). Hunk #13 succeeded at 607 (offset -8 lines). Hunk #14 succeeded at 615 (offset -8 lines). Hunk #15 succeeded at 644 (offset -8 lines). Hunk #16 succeeded at 673 (offset -8 lines). 1 out of 16 hunks FAILED -- saving rejects to file posix/JackNetUnixSocket.cpp.rej find|grep rej ./posix/JackNetUnixSocket.cpp.rej ./common/JackNetDriver.h.rej --- posix/JackNetUnixSocket.cpp +++ posix/JackNetUnixSocket.cpp @@ -229,58 +311,117 @@ { if (strcmp(ip, "127.0.0.1") == 0) { return true; - } + } else if(!strcmp(ip,"::1")) + return true; - char host_name[32]; - gethostname(host_name, sizeof(host_name)); + struct ifaddrs *ifas, *ifa; + socklen_t len; - struct hostent* host = gethostbyname(host_name); - if (host) { - for (int i = 0; host->h_addr_list[i] != 0; ++i) { - struct in_addr addr; - memcpy(&addr, host->h_addr_list[i], sizeof(struct in_addr)); - if (strcmp(inet_ntoa(addr), ip) == 0) { - return true; - } - } - return false; - } else { - return false; + if (getifaddrs(&ifas) == -1) { + jack_error("JackNetUnixSocket::IsLocal error in getifaddrs"); + return false; } + for (ifa = ifas; ifa != NULL; ifa = ifa->ifa_next) { + if (ifa->ifa_addr == NULL) + continue; // Address is mandatory + len = (ifa->ifa_addr->sa_family==AF_INET)?sizeof(struct sockaddr_in):sizeof(struct sockaddr_in6); + if(!getnameinfo(ifa->ifa_addr, len, f_addr_buff, INET6_ADDRSTRLEN, NULL,0, NI_NUMERICSERV | NI_DGRAM | NI_NUMERICHOST)) + if(!strcmp(f_addr_buff,ip)) + break; + } + freeifaddrs(ifas); + return (ifa != NULL); } int JackNetUnixSocket::Bind() { - return bind(fSockfd, reinterpret_cast<socket_address_t*>(&fRecvAddr), sizeof(socket_address_t)); + int yes=1; + if(fState & JNS_BOUND) return 0; + // Multicast is incompatible with V4MAPPED or V4COMPAT addresses, if probe detected MC we need V6ONLY + if(fFamily == AF_INET6 && IN6_IS_ADDR_UNSPECIFIED(&_sock6(fRecvAddr).sin6_addr) && fState & JNS_MCAST) + if(SetOption(IPPROTO_IPV6, IPV6_V6ONLY, &yes, sizeof(yes))) return SOCKET_ERROR; + if(bind(fSockfd, reinterpret_cast<struct sockaddr*>(&fRecvAddr), sizeof(fRecvAddr))) return SOCKET_ERROR; + fState |= JNS_BOUND; + return 0; + } + int JackNetUnixSocket::Bind(const char *if_name) + { + int ret = Bind(); + if(!ret && strcmp(if_name,"any")) { + if(fFamily == AF_INET) { + // 'all' for this case will lead to 'last valid interface', which is not that one might expect + if(strcmp(if_name,"all")) + ret = BindMCastIface(if_name, IP_MULTICAST_IF, &_sock4(fSendAddr).sin_addr); + else + jack_error("Multicast Interface all not found, sending from default"); + } else if(fFamily == AF_INET6) { + struct if_nameindex *if_ni = if_nameindex(); // In V6 world we do everything differently. + if(if_ni) { + int i; + for (i=0; if_ni[i].if_index > 0; i++) { + if(if_ni[i].if_index == 1) + continue; // Skip loopback + if(!strcmp(if_ni[i].if_name,if_name)) { + ret = SetOption(IPPROTO_IPV6, IPV6_MULTICAST_IF, &if_ni[i].if_index, sizeof(if_ni[i].if_index)); + jack_log("JackNetUnixSocket::Bind Multicasting from %s",if_ni[i].if_name); + break; + } + } + if(if_ni[i].if_index == 0) jack_error("Multicast Interface %s not found, sending from default",if_name); + if_freenameindex(if_ni); + } + } + } + return ret; } int JackNetUnixSocket::BindWith(const char* ip) { - int addr_conv = inet_aton(ip, &fRecvAddr.sin_addr); - if (addr_conv < 0) { - return addr_conv; + if(fFamily == AF_UNSPEC) { + if(!fPort) return SOCKET_ERROR; + if(ProbeAF(ip,&fRecvAddr,&bind)<0) return SOCKET_ERROR; + fState |= JNS_BOUND; + return 0; + } else { + if(SetRecvIP(ip)==-1) return SOCKET_ERROR; + return Bind(); } - return Bind(); } int JackNetUnixSocket::BindWith(int port) { - fRecvAddr.sin_port = htons(port); - return Bind(); + if(fFamily == AF_UNSPEC) { + fPort = port; + if(ProbeAF(NULL,&fRecvAddr,&bind)<0) return SOCKET_ERROR; + fState |= JNS_BOUND; + return 0; + } else { + SetPort(port); + return Bind(); + } } int JackNetUnixSocket::Connect() { - return connect(fSockfd, reinterpret_cast<socket_address_t*>(&fSendAddr), sizeof(socket_address_t)); + if(fFamily != AF_UNSPEC) + return connect(fSockfd, (struct sockaddr*)&fSendAddr,sizeof(fSendAddr)); + jack_error("JackNetUnixSocket::Connect Family not initialized"); + return SOCKET_ERROR; } int JackNetUnixSocket::ConnectTo(const char* ip) { - int addr_conv = inet_aton(ip, &fSendAddr.sin_addr); - if (addr_conv < 0) { - return addr_conv; + socklen_t l=sizeof(fRecvAddr); + if(fPort==0) return SOCKET_ERROR; + if(fState & JNS_PROBED) { + Reset(); + fFamily=AF_UNSPEC; } - return Connect(); + if(fSockfd) + Close(); + if(ProbeAF(ip,&fSendAddr,&connect)<0) return SOCKET_ERROR; + fState |= JNS_CONNCD; + return getsockname(fSockfd, (struct sockaddr *)&fRecvAddr, &l); } void JackNetUnixSocket::Close() --- common/JackNetDriver.h +++ common/JackNetDriver.h @@ -79,7 +79,7 @@ public: JackNetDriver(const char* name, const char* alias, JackLockedEngine* engine, JackSynchro* table, - const char* ip, int port, int mtu, int midi_input_ports, int midi_output_ports, + const char* ip, int port, const char* mcif, int mtu, int midi_input_ports, int midi_output_ports, char* net_name, uint transport_sync, int network_latency, int celt_encoding, int opus_encoding, bool auto_save); virtual ~JackNetDriver();netjack_squashed_interface_selection_plus_ipv6
| @@ -35,7 +35,7 @@ namespace Jack | |||||
| because we don't have full parametering right now, parameters will be parsed from the param list, | because we don't have full parametering right now, parameters will be parsed from the param list, | ||||
| and then JackNetSlaveInterface will be filled with proper values. | and then JackNetSlaveInterface will be filled with proper values. | ||||
| */ | */ | ||||
| char multicast_ip[32]; | |||||
| char multicast_ip[32],multicast_if[32]; | |||||
| uint udp_port; | uint udp_port; | ||||
| GetHostName(fParams.fName, JACK_CLIENT_NAME_SIZE); | GetHostName(fParams.fName, JACK_CLIENT_NAME_SIZE); | ||||
| fSocket.GetName(fParams.fSlaveNetName); | fSocket.GetName(fParams.fSlaveNetName); | ||||
| @@ -58,11 +58,10 @@ namespace Jack | |||||
| udp_port = (default_udp_port) ? atoi(default_udp_port) : DEFAULT_PORT; | udp_port = (default_udp_port) ? atoi(default_udp_port) : DEFAULT_PORT; | ||||
| const char* default_multicast_ip = getenv("JACK_NETJACK_MULTICAST"); | const char* default_multicast_ip = getenv("JACK_NETJACK_MULTICAST"); | ||||
| if (default_multicast_ip) { | |||||
| strcpy(multicast_ip, default_multicast_ip); | |||||
| } else { | |||||
| strcpy(multicast_ip, DEFAULT_MULTICAST_IP); | |||||
| } | |||||
| strcpy(multicast_ip, (default_multicast_ip) ? default_multicast_ip : DEFAULT_MULTICAST_IP); | |||||
| const char* default_multicast_if = getenv("JACK_NETJACK_INTERFACE"); | |||||
| strcpy(multicast_if, (default_multicast_if) ? default_multicast_if : DEFAULT_MULTICAST_IF); | |||||
| //options parsing | //options parsing | ||||
| const JSList* node; | const JSList* node; | ||||
| @@ -76,6 +75,10 @@ namespace Jack | |||||
| assert(strlen(param->value.str) < 32); | assert(strlen(param->value.str) < 32); | ||||
| strcpy(multicast_ip, param->value.str); | strcpy(multicast_ip, param->value.str); | ||||
| break; | break; | ||||
| case 'f' : | |||||
| assert(strlen(param->value.str) < 32); | |||||
| strcpy(multicast_if, param->value.str); | |||||
| break; | |||||
| case 'p' : | case 'p' : | ||||
| udp_port = param->value.ui; | udp_port = param->value.ui; | ||||
| break; | break; | ||||
| @@ -128,9 +131,10 @@ namespace Jack | |||||
| } | } | ||||
| strcpy(fMulticastIP, multicast_ip); | strcpy(fMulticastIP, multicast_ip); | ||||
| if(strcmp(multicast_if,DEFAULT_MULTICAST_IF)) | |||||
| strcpy(fMulticastIF, multicast_if); | |||||
| // Set the socket parameters | // Set the socket parameters | ||||
| fSocket.SetPort(udp_port); | |||||
| fSocket.SetAddress(fMulticastIP, udp_port); | fSocket.SetAddress(fMulticastIP, udp_port); | ||||
| // If not set, takes default | // If not set, takes default | ||||
| @@ -424,6 +428,9 @@ extern "C" | |||||
| strcpy(value.str, DEFAULT_MULTICAST_IP); | strcpy(value.str, DEFAULT_MULTICAST_IP); | ||||
| jack_driver_descriptor_add_parameter(desc, &filler, "multicast-ip", 'a', JackDriverParamString, &value, NULL, "Multicast address, or explicit IP of the master", NULL); | jack_driver_descriptor_add_parameter(desc, &filler, "multicast-ip", 'a', JackDriverParamString, &value, NULL, "Multicast address, or explicit IP of the master", NULL); | ||||
| strcpy(value.str, DEFAULT_MULTICAST_IF); | |||||
| jack_driver_descriptor_add_parameter(desc, &filler, "multicast-if", 'f', JackDriverParamString, &value, NULL, "Multicast interface name or any", "Multicast interface to send probes from (any - kernel chosen (default), if_name - from specified interface)"); | |||||
| value.i = DEFAULT_PORT; | value.i = DEFAULT_PORT; | ||||
| jack_driver_descriptor_add_parameter(desc, &filler, "udp-net-port", 'p', JackDriverParamInt, &value, NULL, "UDP port", NULL); | jack_driver_descriptor_add_parameter(desc, &filler, "udp-net-port", 'p', JackDriverParamInt, &value, NULL, "UDP port", NULL); | ||||
| @@ -28,7 +28,7 @@ using namespace std; | |||||
| namespace Jack | namespace Jack | ||||
| { | { | ||||
| JackNetDriver::JackNetDriver(const char* name, const char* alias, JackLockedEngine* engine, JackSynchro* table, | JackNetDriver::JackNetDriver(const char* name, const char* alias, JackLockedEngine* engine, JackSynchro* table, | ||||
| const char* ip, int udp_port, int mtu, int midi_input_ports, int midi_output_ports, | |||||
| const char* ip, int udp_port, const char* mcif, int mtu, int midi_input_ports, int midi_output_ports, | |||||
| char* net_name, uint transport_sync, int network_latency, | char* net_name, uint transport_sync, int network_latency, | ||||
| int celt_encoding, int opus_encoding, bool auto_save) | int celt_encoding, int opus_encoding, bool auto_save) | ||||
| : JackWaiterDriver(name, alias, engine, table), JackNetSlaveInterface(ip, udp_port) | : JackWaiterDriver(name, alias, engine, table), JackNetSlaveInterface(ip, udp_port) | ||||
| @@ -39,6 +39,8 @@ namespace Jack | |||||
| if (strcmp(net_name, "") == 0) { | if (strcmp(net_name, "") == 0) { | ||||
| GetHostName(net_name, JACK_CLIENT_NAME_SIZE); | GetHostName(net_name, JACK_CLIENT_NAME_SIZE); | ||||
| } | } | ||||
| if(strcmp(mcif,DEFAULT_MULTICAST_IF)) | |||||
| strcpy(fMulticastIF,mcif); | |||||
| fParams.fMtu = mtu; | fParams.fMtu = mtu; | ||||
| @@ -676,6 +678,9 @@ namespace Jack | |||||
| strcpy(value.str, DEFAULT_MULTICAST_IP); | strcpy(value.str, DEFAULT_MULTICAST_IP); | ||||
| jack_driver_descriptor_add_parameter(desc, &filler, "multicast-ip", 'a', JackDriverParamString, &value, NULL, "Multicast address, or explicit IP of the master", NULL); | jack_driver_descriptor_add_parameter(desc, &filler, "multicast-ip", 'a', JackDriverParamString, &value, NULL, "Multicast address, or explicit IP of the master", NULL); | ||||
| strcpy(value.str, DEFAULT_MULTICAST_IF); | |||||
| jack_driver_descriptor_add_parameter(desc, &filler, "multicast-if", 'f', JackDriverParamString, &value, NULL, "Multicast interface name or any", "Multicast interface to send probes from (any - kernel chosen (default), if_name - send from specified interface)"); | |||||
| value.i = DEFAULT_PORT; | value.i = DEFAULT_PORT; | ||||
| jack_driver_descriptor_add_parameter(desc, &filler, "udp-net-port", 'p', JackDriverParamInt, &value, NULL, "UDP port", NULL); | jack_driver_descriptor_add_parameter(desc, &filler, "udp-net-port", 'p', JackDriverParamInt, &value, NULL, "UDP port", NULL); | ||||
| @@ -719,8 +724,9 @@ Deactivated for now.. | |||||
| SERVER_EXPORT Jack::JackDriverClientInterface* driver_initialize(Jack::JackLockedEngine* engine, Jack::JackSynchro* table, const JSList* params) | SERVER_EXPORT Jack::JackDriverClientInterface* driver_initialize(Jack::JackLockedEngine* engine, Jack::JackSynchro* table, const JSList* params) | ||||
| { | { | ||||
| char multicast_if[32]; | |||||
| char multicast_ip[32]; | char multicast_ip[32]; | ||||
| char net_name[JACK_CLIENT_NAME_SIZE+1] = {0}; | |||||
| char net_name[JACK_CLIENT_NAME_SIZE + 1] = {0}; | |||||
| int udp_port; | int udp_port; | ||||
| int mtu = DEFAULT_MTU; | int mtu = DEFAULT_MTU; | ||||
| // Desactivated for now... | // Desactivated for now... | ||||
| @@ -747,6 +753,9 @@ Deactivated for now.. | |||||
| const char* default_multicast_ip = getenv("JACK_NETJACK_MULTICAST"); | const char* default_multicast_ip = getenv("JACK_NETJACK_MULTICAST"); | ||||
| strcpy(multicast_ip, (default_multicast_ip) ? default_multicast_ip : DEFAULT_MULTICAST_IP); | strcpy(multicast_ip, (default_multicast_ip) ? default_multicast_ip : DEFAULT_MULTICAST_IP); | ||||
| const char* default_multicast_if = getenv("JACK_NETJACK_INTERFACE"); | |||||
| strcpy(multicast_if, (default_multicast_if) ? default_multicast_if : DEFAULT_MULTICAST_IF); | |||||
| for (node = params; node; node = jack_slist_next(node)) { | for (node = params; node; node = jack_slist_next(node)) { | ||||
| param = (const jack_driver_param_t*) node->data; | param = (const jack_driver_param_t*) node->data; | ||||
| switch (param->character) | switch (param->character) | ||||
| @@ -755,6 +764,10 @@ Deactivated for now.. | |||||
| assert(strlen(param->value.str) < 32); | assert(strlen(param->value.str) < 32); | ||||
| strcpy(multicast_ip, param->value.str); | strcpy(multicast_ip, param->value.str); | ||||
| break; | break; | ||||
| case 'f' : | |||||
| assert(strlen(param->value.str) < 32); | |||||
| strcpy(multicast_if, param->value.str); | |||||
| break; | |||||
| case 'p': | case 'p': | ||||
| udp_port = param->value.ui; | udp_port = param->value.ui; | ||||
| break; | break; | ||||
| @@ -808,7 +821,7 @@ Deactivated for now.. | |||||
| try { | try { | ||||
| Jack::JackDriverClientInterface* driver = new Jack::JackWaitThreadedDriver( | Jack::JackDriverClientInterface* driver = new Jack::JackWaitThreadedDriver( | ||||
| new Jack::JackNetDriver("system", "net_pcm", engine, table, multicast_ip, udp_port, mtu, | |||||
| new Jack::JackNetDriver("system", "net_pcm", engine, table, multicast_ip, udp_port, multicast_if, mtu, | |||||
| midi_input_ports, midi_output_ports, | midi_input_ports, midi_output_ports, | ||||
| net_name, transport_sync, | net_name, transport_sync, | ||||
| network_latency, celt_encoding, opus_encoding, auto_save)); | network_latency, celt_encoding, opus_encoding, auto_save)); | ||||
| @@ -79,7 +79,7 @@ namespace Jack | |||||
| public: | public: | ||||
| JackNetDriver(const char* name, const char* alias, JackLockedEngine* engine, JackSynchro* table, | JackNetDriver(const char* name, const char* alias, JackLockedEngine* engine, JackSynchro* table, | ||||
| const char* ip, int port, int mtu, int midi_input_ports, int midi_output_ports, | |||||
| const char* ip, int port, const char* mcif, int mtu, int midi_input_ports, int midi_output_ports, | |||||
| char* net_name, uint transport_sync, int network_latency, int celt_encoding, | char* net_name, uint transport_sync, int network_latency, int celt_encoding, | ||||
| int opus_encoding, bool auto_save); | int opus_encoding, bool auto_save); | ||||
| virtual ~JackNetDriver(); | virtual ~JackNetDriver(); | ||||
| @@ -57,6 +57,7 @@ namespace Jack | |||||
| fSetTimeOut = false; | fSetTimeOut = false; | ||||
| fTxBuffer = NULL; | fTxBuffer = NULL; | ||||
| fRxBuffer = NULL; | fRxBuffer = NULL; | ||||
| fMulticastIF[0]=0; | |||||
| fNetAudioCaptureBuffer = NULL; | fNetAudioCaptureBuffer = NULL; | ||||
| fNetAudioPlaybackBuffer = NULL; | fNetAudioPlaybackBuffer = NULL; | ||||
| fNetMidiCaptureBuffer = NULL; | fNetMidiCaptureBuffer = NULL; | ||||
| @@ -700,6 +701,12 @@ namespace Jack | |||||
| if (fSocket.IsLocal(fMulticastIP)) { | if (fSocket.IsLocal(fMulticastIP)) { | ||||
| jack_info("Local IP is used..."); | jack_info("Local IP is used..."); | ||||
| } else if (fMulticastIF[0]) { | |||||
| // bind the socket & interface | |||||
| if (fSocket.Bind(fMulticastIF) == SOCKET_ERROR) { | |||||
| jack_error("Can't bind the socket : %s", StrError(NET_ERROR_CODE)); | |||||
| return NET_SOCKET_ERROR; | |||||
| } | |||||
| } else { | } else { | ||||
| // bind the socket | // bind the socket | ||||
| if (fSocket.Bind() == SOCKET_ERROR) { | if (fSocket.Bind() == SOCKET_ERROR) { | ||||
| @@ -719,8 +726,7 @@ namespace Jack | |||||
| } | } | ||||
| // send 'AVAILABLE' until 'SLAVE_SETUP' received | // send 'AVAILABLE' until 'SLAVE_SETUP' received | ||||
| jack_info("Waiting for a master..."); | |||||
| jack_info("Waiting for a master on %s...",(fMulticastIF[0])?fMulticastIF:"default"); | |||||
| do { | do { | ||||
| // send 'available' | // send 'available' | ||||
| session_params_t net_params; | session_params_t net_params; | ||||
| @@ -26,7 +26,10 @@ Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. | |||||
| namespace Jack | namespace Jack | ||||
| { | { | ||||
| // Should be registered at IANA perhaps | |||||
| #define DEFAULT_MULTICAST_IP6 "FF05::139a" | |||||
| #define DEFAULT_MULTICAST_IP "225.3.19.154" | #define DEFAULT_MULTICAST_IP "225.3.19.154" | ||||
| #define DEFAULT_MULTICAST_IF "any" | |||||
| #define DEFAULT_PORT 19000 | #define DEFAULT_PORT 19000 | ||||
| #define DEFAULT_MTU 1500 | #define DEFAULT_MTU 1500 | ||||
| #define MAX_MTU 9000 | #define MAX_MTU 9000 | ||||
| @@ -60,6 +63,7 @@ namespace Jack | |||||
| session_params_t fParams; | session_params_t fParams; | ||||
| JackNetSocket fSocket; | JackNetSocket fSocket; | ||||
| char fMulticastIP[32]; | char fMulticastIP[32]; | ||||
| char fMulticastIF[32]; | |||||
| // headers | // headers | ||||
| packet_header_t fTxHeader; | packet_header_t fTxHeader; | ||||
| @@ -505,7 +505,7 @@ namespace Jack | |||||
| if (out) { | if (out) { | ||||
| memset(out, 0, sizeof(float) * fParams.fPeriodSize); | memset(out, 0, sizeof(float) * fParams.fPeriodSize); | ||||
| } | } | ||||
| fNetAudioPlaybackBuffer->SetBuffer(audio_port_index, out))); | |||||
| fNetAudioPlaybackBuffer->SetBuffer(audio_port_index, out); | |||||
| #endif | #endif | ||||
| } | } | ||||
| @@ -660,6 +660,13 @@ namespace Jack | |||||
| strcpy(fMulticastIP, DEFAULT_MULTICAST_IP); | strcpy(fMulticastIP, DEFAULT_MULTICAST_IP); | ||||
| } | } | ||||
| const char* default_multicast_if = getenv("JACK_NETJACK_INTERFACE"); | |||||
| if (default_multicast_if) { | |||||
| strcpy(fMulticastIF, default_multicast_if); | |||||
| } else { | |||||
| strcpy(fMulticastIF, DEFAULT_MULTICAST_IF); | |||||
| } | |||||
| for (node = params; node; node = jack_slist_next(node)) { | for (node = params; node; node = jack_slist_next(node)) { | ||||
| param = (const jack_driver_param_t*) node->data; | param = (const jack_driver_param_t*) node->data; | ||||
| @@ -672,6 +679,14 @@ namespace Jack | |||||
| } | } | ||||
| break; | break; | ||||
| case 'f' : | |||||
| if (strlen(param->value.str) < 32) { | |||||
| strcpy(fMulticastIF, param->value.str); | |||||
| } else { | |||||
| jack_error("Can't use multicast interface %s, using default %s", param->value.ui, DEFAULT_MULTICAST_IF); | |||||
| } | |||||
| break; | |||||
| case 'p': | case 'p': | ||||
| fSocket.SetPort(param->value.ui); | fSocket.SetPort(param->value.ui); | ||||
| break; | break; | ||||
| @@ -761,7 +776,7 @@ namespace Jack | |||||
| { | { | ||||
| JackNetMasterManager* master_manager = static_cast<JackNetMasterManager*>(arg); | JackNetMasterManager* master_manager = static_cast<JackNetMasterManager*>(arg); | ||||
| jack_info("Starting Jack NetManager"); | jack_info("Starting Jack NetManager"); | ||||
| jack_info("Listening on '%s:%d'", master_manager->fMulticastIP, master_manager->fSocket.GetPort()); | |||||
| jack_info("Listening on '%s:%d%%%s'", master_manager->fMulticastIP, master_manager->fSocket.GetPort(),master_manager->fMulticastIF); | |||||
| master_manager->Run(); | master_manager->Run(); | ||||
| return NULL; | return NULL; | ||||
| } | } | ||||
| @@ -783,8 +798,8 @@ namespace Jack | |||||
| return; | return; | ||||
| } | } | ||||
| //socket | |||||
| if (fSocket.NewSocket() == SOCKET_ERROR) { | |||||
| //socket: we need to have socket probed first if we want to use multicast | |||||
| if (fSocket.NewSocket(fMulticastIP) == SOCKET_ERROR) { | |||||
| jack_error("Can't create NetManager input socket : %s", StrError(NET_ERROR_CODE)); | jack_error("Can't create NetManager input socket : %s", StrError(NET_ERROR_CODE)); | ||||
| return; | return; | ||||
| } | } | ||||
| @@ -797,7 +812,7 @@ namespace Jack | |||||
| } | } | ||||
| //join multicast group | //join multicast group | ||||
| if (fSocket.JoinMCastGroup(fMulticastIP) == SOCKET_ERROR) { | |||||
| if (fSocket.JoinMCastGroup(fMulticastIP,fMulticastIF) == SOCKET_ERROR) { | |||||
| jack_error("Can't join multicast group : %s", StrError(NET_ERROR_CODE)); | jack_error("Can't join multicast group : %s", StrError(NET_ERROR_CODE)); | ||||
| } | } | ||||
| @@ -843,6 +858,7 @@ namespace Jack | |||||
| } | } | ||||
| break; | break; | ||||
| default: | default: | ||||
| jack_log("JackNetMasterManager::Run: read: %d; type: %d; peer: %s",rx_bytes,host_params.fPacketID,host_params.fName); | |||||
| break; | break; | ||||
| } | } | ||||
| } | } | ||||
| @@ -949,6 +965,9 @@ extern "C" | |||||
| strcpy(value.str, DEFAULT_MULTICAST_IP); | strcpy(value.str, DEFAULT_MULTICAST_IP); | ||||
| jack_driver_descriptor_add_parameter(desc, &filler, "multicast-ip", 'a', JackDriverParamString, &value, NULL, "Multicast address", NULL); | jack_driver_descriptor_add_parameter(desc, &filler, "multicast-ip", 'a', JackDriverParamString, &value, NULL, "Multicast address", NULL); | ||||
| strcpy(value.str, DEFAULT_MULTICAST_IF); | |||||
| jack_driver_descriptor_add_parameter(desc, &filler, "multicast-if", 'f', JackDriverParamString, &value, NULL, "Multicast interface", "Multicast interface to bind to. ('all' - all ip ifs; 'any' (default) kernel chosen; ifname i.e. eth0)"); | |||||
| value.i = DEFAULT_PORT; | value.i = DEFAULT_PORT; | ||||
| jack_driver_descriptor_add_parameter(desc, &filler, "udp-net-port", 'p', JackDriverParamInt, &value, NULL, "UDP port", NULL); | jack_driver_descriptor_add_parameter(desc, &filler, "udp-net-port", 'p', JackDriverParamInt, &value, NULL, "UDP port", NULL); | ||||
| @@ -112,6 +112,7 @@ namespace Jack | |||||
| jack_client_t* fClient; | jack_client_t* fClient; | ||||
| const char* fName; | const char* fName; | ||||
| char fMulticastIP[32]; | char fMulticastIP[32]; | ||||
| char fMulticastIF[32]; | |||||
| JackNetSocket fSocket; | JackNetSocket fSocket; | ||||
| jack_native_thread_t fThread; | jack_native_thread_t fThread; | ||||
| master_list_t fMasterList; | master_list_t fMasterList; | ||||
| @@ -56,8 +56,6 @@ namespace Jack | |||||
| typedef struct _session_params session_params_t; | typedef struct _session_params session_params_t; | ||||
| typedef struct _packet_header packet_header_t; | typedef struct _packet_header packet_header_t; | ||||
| typedef struct _net_transport_data net_transport_data_t; | typedef struct _net_transport_data net_transport_data_t; | ||||
| typedef struct sockaddr socket_address_t; | |||||
| typedef struct in_addr address_t; | |||||
| typedef jack_default_audio_sample_t sample_t; | typedef jack_default_audio_sample_t sample_t; | ||||
| enum JackNetEncoder { | enum JackNetEncoder { | ||||
| @@ -20,11 +20,27 @@ Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. | |||||
| #include "JackNetUnixSocket.h" | #include "JackNetUnixSocket.h" | ||||
| #include "JackError.h" | #include "JackError.h" | ||||
| #include <arpa/inet.h> | |||||
| #include <sys/socket.h> | |||||
| #include <ifaddrs.h> | |||||
| #include <net/if.h> | |||||
| #include <unistd.h> | #include <unistd.h> | ||||
| #include <fcntl.h> | #include <fcntl.h> | ||||
| using namespace std; | using namespace std; | ||||
| // See RFC 3493; The Open Group Base Specifications Issue 6 IEEE Std 1003.1, 2004 Edition | |||||
| #define _sock4(x) (*(struct sockaddr_in *)&x) | |||||
| #define _sock6(x) (*(struct sockaddr_in6*)&x) | |||||
| #define _ss_addr_p(x) ((fFamily==AF_INET6) ? \ | |||||
| (void*)&(((struct sockaddr_in6*)&x)->sin6_addr) \ | |||||
| : (void*)&(((struct sockaddr_in *)&x)->sin_addr)) | |||||
| #define JNS_UNSPEC 0x0 | |||||
| #define JNS_PROBED 0x1 | |||||
| #define JNS_BOUND 0x2 | |||||
| #define JNS_CONNCD 0x4 | |||||
| #define JNS_MCAST 0x10 | |||||
| namespace Jack | namespace Jack | ||||
| { | { | ||||
| //utility ********************************************************************************************************* | //utility ********************************************************************************************************* | ||||
| @@ -40,40 +56,23 @@ namespace Jack | |||||
| //construct/destruct*********************************************************************************************** | //construct/destruct*********************************************************************************************** | ||||
| JackNetUnixSocket::JackNetUnixSocket() | JackNetUnixSocket::JackNetUnixSocket() | ||||
| : fFamily(AF_UNSPEC), fSockfd(0), fState(JNS_UNSPEC), fPort(0), fTimeOut(0) | |||||
| { | { | ||||
| fSockfd = 0; | |||||
| fPort = 0; | |||||
| fTimeOut = 0; | |||||
| fSendAddr.sin_family = AF_INET; | |||||
| fSendAddr.sin_addr.s_addr = htonl(INADDR_ANY); | |||||
| memset(&fSendAddr.sin_zero, 0, 8); | |||||
| fRecvAddr.sin_family = AF_INET; | |||||
| fRecvAddr.sin_addr.s_addr = htonl(INADDR_ANY); | |||||
| memset(&fRecvAddr.sin_zero, 0, 8); | |||||
| Reset(); | |||||
| } | } | ||||
| JackNetUnixSocket::JackNetUnixSocket(const char* ip, int port) | JackNetUnixSocket::JackNetUnixSocket(const char* ip, int port) | ||||
| : fFamily(AF_UNSPEC), fSockfd(0), fState(JNS_UNSPEC), fPort(0), fTimeOut(0) | |||||
| { | { | ||||
| fSockfd = 0; | |||||
| Reset(); | |||||
| fPort = port; | fPort = port; | ||||
| fTimeOut = 0; | |||||
| fSendAddr.sin_family = AF_INET; | |||||
| fSendAddr.sin_port = htons(port); | |||||
| inet_aton(ip, &fSendAddr.sin_addr); | |||||
| memset(&fSendAddr.sin_zero, 0, 8); | |||||
| fRecvAddr.sin_family = AF_INET; | |||||
| fRecvAddr.sin_port = htons(port); | |||||
| fRecvAddr.sin_addr.s_addr = htonl(INADDR_ANY); | |||||
| memset(&fRecvAddr.sin_zero, 0, 8); | |||||
| if(NewSocket(ip)==SOCKET_ERROR) | |||||
| jack_error("JackNetUnixSocket::JackNetUnixSocket Cannot initialize (%s:%d): %s",ip,port,strerror(NET_ERROR_CODE)); | |||||
| } | } | ||||
| JackNetUnixSocket::JackNetUnixSocket(const JackNetUnixSocket& socket) | JackNetUnixSocket::JackNetUnixSocket(const JackNetUnixSocket& socket) | ||||
| { | { | ||||
| fSockfd = 0; | |||||
| fTimeOut = 0; | |||||
| fPort = socket.fPort; | |||||
| fSendAddr = socket.fSendAddr; | |||||
| fRecvAddr = socket.fRecvAddr; | |||||
| Clone(socket); | |||||
| } | } | ||||
| JackNetUnixSocket::~JackNetUnixSocket() | JackNetUnixSocket::~JackNetUnixSocket() | ||||
| @@ -83,23 +82,106 @@ namespace Jack | |||||
| JackNetUnixSocket& JackNetUnixSocket::operator=(const JackNetUnixSocket& socket) | JackNetUnixSocket& JackNetUnixSocket::operator=(const JackNetUnixSocket& socket) | ||||
| { | { | ||||
| if (this != &socket) { | |||||
| fSockfd = 0; | |||||
| fPort = socket.fPort; | |||||
| fSendAddr = socket.fSendAddr; | |||||
| fRecvAddr = socket.fRecvAddr; | |||||
| } | |||||
| if (this != &socket) | |||||
| Clone(socket); | |||||
| return *this; | return *this; | ||||
| } | } | ||||
| //socket*********************************************************************************************************** | //socket*********************************************************************************************************** | ||||
| void JackNetUnixSocket::Clone(const JackNetUnixSocket& socket) | |||||
| { | |||||
| fSockfd = 0; | |||||
| fTimeOut = 0; | |||||
| fPort = socket.fPort; | |||||
| fFamily = socket.fFamily; | |||||
| fState = socket.fState; | |||||
| fState &= (JNS_MCAST | JNS_PROBED); // Reset all fields except mcast and probed | |||||
| memcpy(&fSendAddr, & socket.fSendAddr, sizeof(fSendAddr)); | |||||
| memcpy(&fRecvAddr, & socket.fRecvAddr, sizeof(fRecvAddr)); | |||||
| } | |||||
| /* When using Multicast always create socket with new (mip,port) or call NewSocket(mip) for autoinitialized sockets first. | |||||
| * This makes sure proper workarounds for Multicast incompatibility will be activated while setting up address family. | |||||
| */ | |||||
| int JackNetUnixSocket::ProbeAF(const char* ip, struct sockaddr_storage *addr, int (*call)(int,const struct sockaddr*,socklen_t)) | |||||
| { | |||||
| struct addrinfo hint,*res, *ri; | |||||
| char sport[6]; | |||||
| int ret; | |||||
| snprintf(sport,6,"%d",fPort); | |||||
| memset(&hint,0,sizeof(hint)); | |||||
| hint.ai_family = fFamily; | |||||
| hint.ai_socktype = SOCK_DGRAM; | |||||
| hint.ai_flags = AI_V4MAPPED | AI_ADDRCONFIG | AI_NUMERICSERV; | |||||
| if(ip == NULL) hint.ai_flags |= AI_PASSIVE; | |||||
| ret = getaddrinfo(ip,sport,&hint,&res); | |||||
| if(ret) { | |||||
| jack_error("JackNetUnixSocket::ProbeAF getaddrinfo(%s:%s): %s",ip,sport,gai_strerror(ret)); | |||||
| return ret; | |||||
| } | |||||
| #define GLIBC_BUG | |||||
| // An ugly GLIBC bug, which one may argue to be RFC bug. See http://sourceware.org/bugzilla/show_bug.cgi?id=14967 | |||||
| #ifdef GLIBC_BUG | |||||
| // Of course gai configuration can say to have v4 having precedence explicitly but... here we prefer ipv6. period | |||||
| if(!ip && res->ai_family == AF_INET && res->ai_next && res->ai_next->ai_family == AF_INET6) { | |||||
| jack_log("JackNetUnixSocket::ProbeAF swapping addrinfo entries to work around GLIBC getaddrinfo bug: AF_INET<->AF_INET6"); | |||||
| // Swapping first two entries | |||||
| ri = res->ai_next; | |||||
| res->ai_next = ri->ai_next; | |||||
| ri->ai_next = res; | |||||
| res = ri; | |||||
| } else // << Remove up to this very line including once GLIBC is fixed << | |||||
| #endif | |||||
| ri = res; | |||||
| do { | |||||
| jack_log("JackNetUnixSocket::ProbeAF trying AF[%d], TYPE[%d], PROTO[%d]", | |||||
| ri->ai_family,ri->ai_socktype,ri->ai_protocol); | |||||
| ret = socket(ri->ai_family, ri->ai_socktype, ri->ai_protocol); | |||||
| if(ret < 0) | |||||
| continue; | |||||
| if(!(*call)(ret, ri->ai_addr, ri->ai_addrlen)) | |||||
| break; | |||||
| close(ret); | |||||
| jack_log("JackNetUnixSocket::ProbeAF failed for AF[%d], %s",ri->ai_family,(ri->ai_next)?"trying next":"giving up"); | |||||
| } while((ri = ri->ai_next) != NULL); | |||||
| // probe successfully made a *call on socket for ip represented by ri | |||||
| if(ri) { | |||||
| fSockfd = ret; | |||||
| fFamily = ri->ai_family; | |||||
| memcpy(addr,ri->ai_addr,ri->ai_addrlen); | |||||
| jack_log("JackNetUnixSocket::ProbeAF probed[%d] AF[%d] for %s",ret,fFamily,ip); | |||||
| fState |= JNS_PROBED; | |||||
| if(fFamily == AF_INET6 && IN6_IS_ADDR_MULTICAST(&((struct sockaddr_in6*)addr)->sin6_addr)) | |||||
| fState |= JNS_MCAST; | |||||
| } else | |||||
| ret = SOCKET_ERROR; | |||||
| freeaddrinfo(res); | |||||
| return ret; | |||||
| } | |||||
| int JackNetUnixSocket::NewSocket() | int JackNetUnixSocket::NewSocket() | ||||
| { | { | ||||
| return NewSocket(NULL); | |||||
| } | |||||
| int JackNetUnixSocket::NewSocket(const char *ip) | |||||
| { | |||||
| if(fFamily == AF_UNSPEC) { | |||||
| if(ip) { | |||||
| if(ProbeAF(ip,&fSendAddr,connect)<0) | |||||
| return SOCKET_ERROR; | |||||
| } else { | |||||
| if(ProbeAF(ip,&fRecvAddr,bind)<0) | |||||
| return SOCKET_ERROR; | |||||
| // It should be with AF/port/ANY after bind | |||||
| memcpy(&fSendAddr,&fRecvAddr,sizeof(fRecvAddr)); | |||||
| } | |||||
| // Now re-create the fd to get a brand-new unnamed socket | |||||
| } | |||||
| if (fSockfd) { | if (fSockfd) { | ||||
| Close(); | Close(); | ||||
| Reset(); | Reset(); | ||||
| } | } | ||||
| fSockfd = socket(AF_INET, SOCK_DGRAM, 0); | |||||
| fSockfd = socket(fFamily, SOCK_DGRAM, 0); | |||||
| /* Enable address reuse */ | /* Enable address reuse */ | ||||
| int res, on = 1; | int res, on = 1; | ||||
| @@ -139,58 +221,117 @@ namespace Jack | |||||
| { | { | ||||
| if (strcmp(ip, "127.0.0.1") == 0) { | if (strcmp(ip, "127.0.0.1") == 0) { | ||||
| return true; | return true; | ||||
| } | |||||
| } else if(!strcmp(ip,"::1")) | |||||
| return true; | |||||
| char host_name[32]; | |||||
| gethostname(host_name, sizeof(host_name)); | |||||
| struct ifaddrs *ifas, *ifa; | |||||
| socklen_t len; | |||||
| struct hostent* host = gethostbyname(host_name); | |||||
| if (host) { | |||||
| for (int i = 0; host->h_addr_list[i] != 0; ++i) { | |||||
| struct in_addr addr; | |||||
| memcpy(&addr, host->h_addr_list[i], sizeof(struct in_addr)); | |||||
| if (strcmp(inet_ntoa(addr), ip) == 0) { | |||||
| return true; | |||||
| } | |||||
| } | |||||
| return false; | |||||
| } else { | |||||
| return false; | |||||
| if (getifaddrs(&ifas) == -1) { | |||||
| jack_error("JackNetUnixSocket::IsLocal error in getifaddrs"); | |||||
| return false; | |||||
| } | } | ||||
| for (ifa = ifas; ifa != NULL; ifa = ifa->ifa_next) { | |||||
| if (ifa->ifa_addr == NULL) | |||||
| continue; // Address is mandatory | |||||
| len = (ifa->ifa_addr->sa_family==AF_INET)?sizeof(struct sockaddr_in):sizeof(struct sockaddr_in6); | |||||
| if(!getnameinfo(ifa->ifa_addr, len, f_addr_buff, INET6_ADDRSTRLEN, NULL,0, NI_NUMERICSERV | NI_DGRAM | NI_NUMERICSERV | NI_DGRAM | NI_NUMERICHOST)) | |||||
| if(!strcmp(f_addr_buff,ip)) | |||||
| break; | |||||
| } | |||||
| freeifaddrs(ifas); | |||||
| return (ifa != NULL); | |||||
| } | } | ||||
| int JackNetUnixSocket::Bind() | int JackNetUnixSocket::Bind() | ||||
| { | { | ||||
| return ::bind(fSockfd, reinterpret_cast<socket_address_t*>(&fRecvAddr), sizeof(socket_address_t)); | |||||
| int yes=1; | |||||
| if(fState & JNS_BOUND) return 0; | |||||
| // Multicast is incompatible with V4MAPPED or V4COMPAT addresses, if probe detected MC we need V6ONLY | |||||
| if(fFamily == AF_INET6 && IN6_IS_ADDR_UNSPECIFIED(&_sock6(fRecvAddr).sin6_addr) && fState & JNS_MCAST) | |||||
| if(SetOption(IPPROTO_IPV6, IPV6_V6ONLY, &yes, sizeof(yes))) return SOCKET_ERROR; | |||||
| if(::bind(fSockfd, reinterpret_cast<struct sockaddr*>(&fRecvAddr), sizeof(fRecvAddr))) return SOCKET_ERROR; | |||||
| fState |= JNS_BOUND; | |||||
| return 0; | |||||
| } | |||||
| int JackNetUnixSocket::Bind(const char *if_name) | |||||
| { | |||||
| int ret = Bind(); | |||||
| if(!ret && strcmp(if_name,"any")) { | |||||
| if(fFamily == AF_INET) { | |||||
| // 'all' for this case will lead to 'last valid interface', which is not that one might expect | |||||
| if(strcmp(if_name,"all")) | |||||
| ret = BindMCastIface(if_name, IP_MULTICAST_IF, &_sock4(fSendAddr).sin_addr); | |||||
| else | |||||
| jack_error("Multicast Interface all not found, sending from default"); | |||||
| } else if(fFamily == AF_INET6) { | |||||
| struct if_nameindex *if_ni = if_nameindex(); // In V6 world we do everything differently. | |||||
| if(if_ni) { | |||||
| int i; | |||||
| for (i=0; if_ni[i].if_index > 0; i++) { | |||||
| if(if_ni[i].if_index == 1) | |||||
| continue; // Skip loopback | |||||
| if(!strcmp(if_ni[i].if_name,if_name)) { | |||||
| ret = SetOption(IPPROTO_IPV6, IPV6_MULTICAST_IF, &if_ni[i].if_index, sizeof(if_ni[i].if_index)); | |||||
| jack_log("JackNetUnixSocket::Bind Multicasting from %s",if_ni[i].if_name); | |||||
| break; | |||||
| } | |||||
| } | |||||
| if(if_ni[i].if_index == 0) jack_error("Multicast Interface %s not found, sending from default",if_name); | |||||
| if_freenameindex(if_ni); | |||||
| } | |||||
| } | |||||
| } | |||||
| return ret; | |||||
| } | } | ||||
| int JackNetUnixSocket::BindWith(const char* ip) | int JackNetUnixSocket::BindWith(const char* ip) | ||||
| { | { | ||||
| int addr_conv = inet_aton(ip, &fRecvAddr.sin_addr); | |||||
| if (addr_conv < 0) { | |||||
| return addr_conv; | |||||
| if(fFamily == AF_UNSPEC) { | |||||
| if(!fPort) return SOCKET_ERROR; | |||||
| if(ProbeAF(ip,&fRecvAddr,&bind)<0) return SOCKET_ERROR; | |||||
| fState |= JNS_BOUND; | |||||
| return 0; | |||||
| } else { | |||||
| if(SetRecvIP(ip)==-1) return SOCKET_ERROR; | |||||
| return Bind(); | |||||
| } | } | ||||
| return Bind(); | |||||
| } | } | ||||
| int JackNetUnixSocket::BindWith(int port) | int JackNetUnixSocket::BindWith(int port) | ||||
| { | { | ||||
| fRecvAddr.sin_port = htons(port); | |||||
| return Bind(); | |||||
| if(fFamily == AF_UNSPEC) { | |||||
| fPort = port; | |||||
| if(ProbeAF(NULL,&fRecvAddr,&bind)<0) return SOCKET_ERROR; | |||||
| fState |= JNS_BOUND; | |||||
| return 0; | |||||
| } else { | |||||
| SetPort(port); | |||||
| return Bind(); | |||||
| } | |||||
| } | } | ||||
| int JackNetUnixSocket::Connect() | int JackNetUnixSocket::Connect() | ||||
| { | { | ||||
| return connect(fSockfd, reinterpret_cast<socket_address_t*>(&fSendAddr), sizeof(socket_address_t)); | |||||
| if(fFamily != AF_UNSPEC) | |||||
| return connect(fSockfd, (struct sockaddr*)&fSendAddr,sizeof(fSendAddr)); | |||||
| jack_error("JackNetUnixSocket::Connect Family not initialized"); | |||||
| return SOCKET_ERROR; | |||||
| } | } | ||||
| int JackNetUnixSocket::ConnectTo(const char* ip) | int JackNetUnixSocket::ConnectTo(const char* ip) | ||||
| { | { | ||||
| int addr_conv = inet_aton(ip, &fSendAddr.sin_addr); | |||||
| if (addr_conv < 0) { | |||||
| return addr_conv; | |||||
| socklen_t l=sizeof(fRecvAddr); | |||||
| if(fPort==0) return SOCKET_ERROR; | |||||
| if(fState & JNS_PROBED) { | |||||
| Reset(); | |||||
| fFamily=AF_UNSPEC; | |||||
| } | } | ||||
| return Connect(); | |||||
| if(fSockfd) | |||||
| Close(); | |||||
| if(ProbeAF(ip,&fSendAddr,&connect)<0) return SOCKET_ERROR; | |||||
| fState |= JNS_CONNCD; | |||||
| return getsockname(fSockfd, (struct sockaddr *)&fRecvAddr, &l); | |||||
| } | } | ||||
| void JackNetUnixSocket::Close() | void JackNetUnixSocket::Close() | ||||
| @@ -199,18 +340,17 @@ namespace Jack | |||||
| close(fSockfd); | close(fSockfd); | ||||
| } | } | ||||
| fSockfd = 0; | fSockfd = 0; | ||||
| fState = JNS_UNSPEC; | |||||
| } | } | ||||
| void JackNetUnixSocket::Reset() | void JackNetUnixSocket::Reset() | ||||
| { | { | ||||
| fSendAddr.sin_family = AF_INET; | |||||
| fSendAddr.sin_port = htons(fPort); | |||||
| fSendAddr.sin_addr.s_addr = htonl(INADDR_ANY); | |||||
| memset(&fSendAddr.sin_zero, 0, 8); | |||||
| fRecvAddr.sin_family = AF_INET; | |||||
| fRecvAddr.sin_port = htons(fPort); | |||||
| fRecvAddr.sin_addr.s_addr = htonl(INADDR_ANY); | |||||
| memset(&fRecvAddr.sin_zero, 0, 8); | |||||
| memset(&fSendAddr, 0, sizeof(fSendAddr)); | |||||
| fSendAddr.ss_family = fFamily; | |||||
| memset(&fRecvAddr, 0, sizeof(fRecvAddr)); | |||||
| fRecvAddr.ss_family = fFamily; | |||||
| if(fPort) | |||||
| SetPort(fPort); | |||||
| } | } | ||||
| bool JackNetUnixSocket::IsSocket() | bool JackNetUnixSocket::IsSocket() | ||||
| @@ -222,8 +362,18 @@ namespace Jack | |||||
| void JackNetUnixSocket::SetPort(int port) | void JackNetUnixSocket::SetPort(int port) | ||||
| { | { | ||||
| fPort = port; | fPort = port; | ||||
| fSendAddr.sin_port = htons(port); | |||||
| fRecvAddr.sin_port = htons(port); | |||||
| switch(fFamily) { // Playing dumb here, in fact port section of both sockaddrs is compatible | |||||
| case AF_INET: | |||||
| _sock4(fSendAddr).sin_port = htons(port); | |||||
| _sock4(fRecvAddr).sin_port = htons(port); | |||||
| break; | |||||
| case AF_INET6: | |||||
| _sock6(fSendAddr).sin6_port = htons(port); | |||||
| _sock6(fRecvAddr).sin6_port = htons(port); | |||||
| break; | |||||
| default: | |||||
| jack_info("JackNetUnixSocket::SetPort: Family not initialized"); | |||||
| } | |||||
| } | } | ||||
| int JackNetUnixSocket::GetPort() | int JackNetUnixSocket::GetPort() | ||||
| @@ -234,22 +384,34 @@ namespace Jack | |||||
| //address*********************************************************************************************************** | //address*********************************************************************************************************** | ||||
| int JackNetUnixSocket::SetAddress(const char* ip, int port) | int JackNetUnixSocket::SetAddress(const char* ip, int port) | ||||
| { | { | ||||
| int addr_conv = inet_aton(ip, &fSendAddr.sin_addr); | |||||
| if (addr_conv < 0) { | |||||
| return addr_conv; | |||||
| if(fFamily == AF_UNSPEC) { | |||||
| fPort=port; | |||||
| return ProbeAF(ip,&fSendAddr,&connect); | |||||
| } else { | |||||
| SetPort(port); | |||||
| return inet_pton(fFamily, ip, _ss_addr_p(fSendAddr)); | |||||
| } | } | ||||
| fSendAddr.sin_port = htons(port); | |||||
| return 0; | |||||
| } | |||||
| int JackNetUnixSocket::SetSendIP(const char *ip) | |||||
| { | |||||
| if(fFamily == AF_UNSPEC) return -1; | |||||
| return inet_pton(fFamily, ip, _ss_addr_p(fSendAddr)); | |||||
| } | } | ||||
| char* JackNetUnixSocket::GetSendIP() | char* JackNetUnixSocket::GetSendIP() | ||||
| { | { | ||||
| return inet_ntoa(fSendAddr.sin_addr); | |||||
| return (char*)inet_ntop(fFamily, _ss_addr_p(fSendAddr),f_addr_buff,INET6_ADDRSTRLEN); | |||||
| } | |||||
| int JackNetUnixSocket::SetRecvIP(const char *ip) | |||||
| { | |||||
| if(fFamily == AF_UNSPEC) return -1; | |||||
| return inet_pton(fFamily, ip, _ss_addr_p(fRecvAddr)); | |||||
| } | } | ||||
| char* JackNetUnixSocket::GetRecvIP() | char* JackNetUnixSocket::GetRecvIP() | ||||
| { | { | ||||
| return inet_ntoa(fRecvAddr.sin_addr); | |||||
| return (char*)inet_ntop(fFamily, _ss_addr_p(fRecvAddr),f_addr_buff,INET6_ADDRSTRLEN); | |||||
| } | } | ||||
| //utility************************************************************************************************************ | //utility************************************************************************************************************ | ||||
| @@ -260,10 +422,111 @@ namespace Jack | |||||
| int JackNetUnixSocket::JoinMCastGroup(const char* ip) | int JackNetUnixSocket::JoinMCastGroup(const char* ip) | ||||
| { | { | ||||
| struct ip_mreq multicast_req; | |||||
| inet_aton(ip, &multicast_req.imr_multiaddr); | |||||
| multicast_req.imr_interface.s_addr = htonl(INADDR_ANY); | |||||
| return SetOption(IPPROTO_IP, IP_ADD_MEMBERSHIP, &multicast_req, sizeof(multicast_req)); | |||||
| return JoinMCastGroup(ip,"any"); | |||||
| } | |||||
| /** Glory and shame of IPv6 interoperability: Multicast | |||||
| * Not only API is completely incompatible, but even socket is incompatible. I.e. you cannot use V4MAPPED/COMPATIBLE | |||||
| * sockets/sockaddress for Multicast. Any V4 address is just a legacy unicast socket for IPv6. Moreover, IPv6 API | |||||
| * implementation itself is a big mess even though there's no time anymore and way back. Here we're using POSIX API. | |||||
| * When dealing with Multicast - Address Family should be set once and forever for used multicast address. This | |||||
| * implies we cannot use V4 addresses on V6 sockets. So multicast should have V6ONLY soscket option set to allow v6 | |||||
| * adapter, master or manager coexist on the same host. See ProbeAF/Bind for these tricks. | |||||
| */ | |||||
| int JackNetUnixSocket::JoinMCastGroup(const char* ip, const char *if_name) | |||||
| { | |||||
| int level, option, length; | |||||
| void *mreq; | |||||
| char addr[sizeof(in6_addr)]; | |||||
| inet_pton(fFamily, ip, addr); | |||||
| if(!strcmp(if_name,"any")) { // UNSPEC binding we can do in-place using void pointers | |||||
| if(fFamily == AF_INET) { | |||||
| struct ip_mreq multicast_req; | |||||
| multicast_req.imr_multiaddr.s_addr = *(uint32_t*)(addr); | |||||
| multicast_req.imr_interface.s_addr = htonl(INADDR_ANY); | |||||
| level = IPPROTO_IP; | |||||
| option = IP_ADD_MEMBERSHIP; | |||||
| mreq=&multicast_req; | |||||
| length = sizeof(ip_mreq); | |||||
| } else if(fFamily == AF_INET6) { | |||||
| struct ipv6_mreq mreq6; | |||||
| memcpy(&mreq6.ipv6mr_multiaddr,addr,sizeof(in6_addr)); | |||||
| mreq6.ipv6mr_interface = 0; | |||||
| level = IPPROTO_IPV6; | |||||
| option = IPV6_ADD_MEMBERSHIP; | |||||
| mreq = &mreq6; | |||||
| length = sizeof(ipv6_mreq); | |||||
| } else { | |||||
| jack_error("Unsupported family[%d]",fFamily); | |||||
| return -1; | |||||
| } | |||||
| return SetOption(level, option, mreq, length); | |||||
| } else { // For anything more complex need to call family-specific routine | |||||
| if(fFamily == AF_INET) | |||||
| return BindMCastIface(if_name, IP_ADD_MEMBERSHIP, (struct in_addr *)addr); | |||||
| else if(fFamily == AF_INET6) | |||||
| return BindMCast6Iface(if_name, (struct in6_addr *)addr); | |||||
| else { | |||||
| jack_error("Unsupported family[%d]",fFamily); | |||||
| return -1; | |||||
| } | |||||
| } | |||||
| } | |||||
| int JackNetUnixSocket::BindMCastIface(const char *if_name, const int option, struct in_addr *addr) | |||||
| { | |||||
| struct ifaddrs *ifas, *ifa; | |||||
| struct ip_mreq mreq; | |||||
| int specific = strcmp("all",if_name); | |||||
| int ret=-1; | |||||
| char *if_last=(char *)"any"; | |||||
| if (getifaddrs(&ifas) == -1) { | |||||
| jack_error("JackNetUnixSocket::BindMCastIface error in getifaddrs"); | |||||
| return -1; | |||||
| } | |||||
| mreq.imr_multiaddr.s_addr = addr->s_addr; | |||||
| for (ifa = ifas; ifa != NULL; ifa = ifa->ifa_next) { | |||||
| if (ifa->ifa_addr == NULL) | |||||
| continue; // Address is mandatory | |||||
| if(!ifa->ifa_name || !strcmp(if_last,ifa->ifa_name)) | |||||
| continue; // Name as well, also skip already enabled interface | |||||
| if(!(ifa->ifa_flags & IFF_MULTICAST) || !(ifa->ifa_flags & IFF_RUNNING)) | |||||
| continue; // And non multicast or down interface | |||||
| if(ifa->ifa_addr->sa_family != AF_INET) | |||||
| continue; // And non-matching family | |||||
| if(!specific || !strcmp(ifa->ifa_name,if_name)) { | |||||
| mreq.imr_interface.s_addr = ((struct sockaddr_in *)(ifa->ifa_addr))->sin_addr.s_addr; | |||||
| ret = SetOption(IPPROTO_IP, option, &mreq, sizeof(struct ip_mreq)); | |||||
| if(ret) | |||||
| break; | |||||
| if_last = ifa->ifa_name; | |||||
| jack_log("JackNetUnixSocket::BindMCastIface attaching to %s", if_last); | |||||
| } | |||||
| } | |||||
| freeifaddrs(ifas); | |||||
| if(!strcmp(if_last,"any")) | |||||
| jack_error("JackNetUnixSocket::BindMCastIface cannot find valid interface"); | |||||
| return ret; | |||||
| } | |||||
| int JackNetUnixSocket::BindMCast6Iface(const char *if_name, struct in6_addr *mip) | |||||
| { | |||||
| int i, ret=-1, specific=strcmp("all",if_name); | |||||
| struct if_nameindex *if_ni = if_nameindex(); | |||||
| struct ipv6_mreq mreq; | |||||
| if(if_ni) { | |||||
| memcpy(&mreq.ipv6mr_multiaddr, mip, sizeof(struct in6_addr)); | |||||
| for (i=0; if_ni[i].if_index > 0; i++) { | |||||
| if(if_ni[i].if_index == 1) | |||||
| continue; // Skip loopback | |||||
| mreq.ipv6mr_interface = if_ni[i].if_index; | |||||
| if(!specific || !strcmp(if_ni[i].if_name,if_name)) { | |||||
| ret = SetOption(IPPROTO_IPV6, IPV6_JOIN_GROUP, &mreq, sizeof(mreq)); | |||||
| if(ret < 0) | |||||
| break; | |||||
| } | |||||
| } | |||||
| if_freenameindex(if_ni); | |||||
| } | |||||
| return ret; | |||||
| } | } | ||||
| //options************************************************************************************************************ | //options************************************************************************************************************ | ||||
| @@ -282,19 +545,19 @@ namespace Jack | |||||
| #if defined(__sun__) || defined(sun) | #if defined(__sun__) || defined(sun) | ||||
| int JackNetUnixSocket::SetTimeOut(int us) | int JackNetUnixSocket::SetTimeOut(int us) | ||||
| { | { | ||||
| int flags; | |||||
| int flags; | |||||
| fTimeOut = us; | fTimeOut = us; | ||||
| if ((flags = fcntl(fSockfd, F_GETFL, 0)) < 0) { | if ((flags = fcntl(fSockfd, F_GETFL, 0)) < 0) { | ||||
| jack_error("JackNetUnixSocket::SetTimeOut error in fcntl F_GETFL"); | |||||
| return -1; | |||||
| } | |||||
| jack_error("JackNetUnixSocket::SetTimeOut error in fcntl F_GETFL"); | |||||
| return -1; | |||||
| } | |||||
| flags |= O_NONBLOCK; | |||||
| if (fcntl(fSockfd, F_SETFL, flags) < 0) { | |||||
| jack_error("JackNetUnixSocket::SetTimeOut error in fcntl F_SETFL"); | |||||
| return 1; | |||||
| } | |||||
| flags |= O_NONBLOCK; | |||||
| if (fcntl(fSockfd, F_SETFL, flags) < 0) { | |||||
| jack_error("JackNetUnixSocket::SetTimeOut error in fcntl F_SETFL"); | |||||
| return 1; | |||||
| } | |||||
| return 0; | return 0; | ||||
| } | } | ||||
| @@ -304,25 +567,25 @@ namespace Jack | |||||
| if (fTimeOut > 0) { | if (fTimeOut > 0) { | ||||
| struct timeval tv; | struct timeval tv; | ||||
| fd_set fdset; | |||||
| ssize_t res; | |||||
| fd_set fdset; | |||||
| ssize_t res; | |||||
| tv.tv_sec = fTimeOut / 1000000; | tv.tv_sec = fTimeOut / 1000000; | ||||
| tv.tv_usec = fTimeOut % 1000000; | |||||
| tv.tv_usec = fTimeOut % 1000000; | |||||
| FD_ZERO(&fdset); | |||||
| FD_SET(fSockfd, &fdset); | |||||
| FD_ZERO(&fdset); | |||||
| FD_SET(fSockfd, &fdset); | |||||
| do { | do { | ||||
| res = select(fSockfd + 1, &fdset, NULL, NULL, &tv); | res = select(fSockfd + 1, &fdset, NULL, NULL, &tv); | ||||
| } while (res < 0 && errno == EINTR); | } while (res < 0 && errno == EINTR); | ||||
| if (res < 0) { | |||||
| return res; | |||||
| if (res < 0) { | |||||
| return res; | |||||
| } else if (res == 0) { | } else if (res == 0) { | ||||
| errno = ETIMEDOUT; | errno = ETIMEDOUT; | ||||
| return -1; | |||||
| } | |||||
| return -1; | |||||
| } | |||||
| } | } | ||||
| return 0; | return 0; | ||||
| @@ -333,25 +596,25 @@ namespace Jack | |||||
| if (fTimeOut > 0) { | if (fTimeOut > 0) { | ||||
| struct timeval tv; | struct timeval tv; | ||||
| fd_set fdset; | |||||
| ssize_t res; | |||||
| fd_set fdset; | |||||
| ssize_t res; | |||||
| tv.tv_sec = fTimeOut / 1000000; | tv.tv_sec = fTimeOut / 1000000; | ||||
| tv.tv_usec = fTimeOut % 1000000; | tv.tv_usec = fTimeOut % 1000000; | ||||
| FD_ZERO(&fdset); | |||||
| FD_SET(fSockfd, &fdset); | |||||
| FD_ZERO(&fdset); | |||||
| FD_SET(fSockfd, &fdset); | |||||
| do { | do { | ||||
| res = select(fSockfd + 1, NULL, &fdset, NULL, &tv); | res = select(fSockfd + 1, NULL, &fdset, NULL, &tv); | ||||
| } while (res < 0 && errno == EINTR); | } while (res < 0 && errno == EINTR); | ||||
| if (res < 0) { | |||||
| return res; | |||||
| if (res < 0) { | |||||
| return res; | |||||
| } else if (res == 0) { | } else if (res == 0) { | ||||
| errno = ETIMEDOUT; | errno = ETIMEDOUT; | ||||
| return -1; | |||||
| } | |||||
| return -1; | |||||
| } | |||||
| } | } | ||||
| return 0; | return 0; | ||||
| @@ -382,7 +645,16 @@ namespace Jack | |||||
| int JackNetUnixSocket::SetLocalLoop() | int JackNetUnixSocket::SetLocalLoop() | ||||
| { | { | ||||
| char disable = 0; | char disable = 0; | ||||
| return SetOption(IPPROTO_IP, IP_MULTICAST_LOOP, &disable, sizeof(disable)); | |||||
| unsigned int dis6 = 0; | |||||
| switch(fFamily) { | |||||
| case AF_INET: | |||||
| return SetOption(IPPROTO_IP, IP_MULTICAST_LOOP, &disable, sizeof(disable)); | |||||
| case AF_INET6: | |||||
| return SetOption(IPPROTO_IPV6, IPV6_MULTICAST_LOOP, &dis6, sizeof(dis6)); | |||||
| default: | |||||
| jack_error("JackNetUnixSocket::SetLocalLoop: Family not initialized"); | |||||
| return -1; | |||||
| } | |||||
| } | } | ||||
| //network operations************************************************************************************************** | //network operations************************************************************************************************** | ||||
| @@ -394,7 +666,7 @@ namespace Jack | |||||
| } | } | ||||
| #endif | #endif | ||||
| int res; | int res; | ||||
| if ((res = sendto(fSockfd, buffer, nbytes, flags, reinterpret_cast<socket_address_t*>(&fSendAddr), sizeof(socket_address_t))) < 0) { | |||||
| if ((res = sendto(fSockfd, buffer, nbytes, flags, reinterpret_cast<struct sockaddr *>(&fSendAddr), sizeof(fSendAddr))) < 0) { | |||||
| jack_error("SendTo fd = %ld err = %s", fSockfd, strerror(errno)); | jack_error("SendTo fd = %ld err = %s", fSockfd, strerror(errno)); | ||||
| } | } | ||||
| return res; | return res; | ||||
| @@ -402,11 +674,11 @@ namespace Jack | |||||
| int JackNetUnixSocket::SendTo(const void* buffer, size_t nbytes, int flags, const char* ip) | int JackNetUnixSocket::SendTo(const void* buffer, size_t nbytes, int flags, const char* ip) | ||||
| { | { | ||||
| int addr_conv = inet_aton(ip, &fSendAddr.sin_addr); | |||||
| int addr_conv = inet_pton(fFamily, ip, _ss_addr_p(fSendAddr)); | |||||
| if (addr_conv < 1) { | if (addr_conv < 1) { | ||||
| return addr_conv; | return addr_conv; | ||||
| } | } | ||||
| fSendAddr.sin_port = htons(fPort); | |||||
| //fSendAddr.sin_port = htons(fPort); | |||||
| #if defined(__sun__) || defined(sun) | #if defined(__sun__) || defined(sun) | ||||
| if (WaitWrite() < 0) { | if (WaitWrite() < 0) { | ||||
| return -1; | return -1; | ||||
| @@ -431,14 +703,14 @@ namespace Jack | |||||
| int JackNetUnixSocket::RecvFrom(void* buffer, size_t nbytes, int flags) | int JackNetUnixSocket::RecvFrom(void* buffer, size_t nbytes, int flags) | ||||
| { | { | ||||
| socklen_t addr_len = sizeof(socket_address_t); | |||||
| socklen_t addr_len = sizeof(fRecvAddr); | |||||
| #if defined(__sun__) || defined(sun) | #if defined(__sun__) || defined(sun) | ||||
| if (WaitRead() < 0) { | if (WaitRead() < 0) { | ||||
| return -1; | return -1; | ||||
| } | } | ||||
| #endif | #endif | ||||
| int res; | int res; | ||||
| if ((res = recvfrom(fSockfd, buffer, nbytes, flags, reinterpret_cast<socket_address_t*>(&fRecvAddr), &addr_len)) < 0) { | |||||
| if ((res = recvfrom(fSockfd, buffer, nbytes, flags, reinterpret_cast<struct sockaddr *>(&fRecvAddr), &addr_len)) < 0) { | |||||
| jack_error("RecvFrom fd = %ld err = %s", fSockfd, strerror(errno)); | jack_error("RecvFrom fd = %ld err = %s", fSockfd, strerror(errno)); | ||||
| } | } | ||||
| return res; | return res; | ||||
| @@ -460,14 +732,15 @@ namespace Jack | |||||
| int JackNetUnixSocket::CatchHost(void* buffer, size_t nbytes, int flags) | int JackNetUnixSocket::CatchHost(void* buffer, size_t nbytes, int flags) | ||||
| { | { | ||||
| socklen_t addr_len = sizeof(socket_address_t); | |||||
| jack_log("JackNetUnixSocket::CatchHost"); | |||||
| socklen_t addr_len = sizeof(fSendAddr); | |||||
| #if defined(__sun__) || defined(sun) | #if defined(__sun__) || defined(sun) | ||||
| if (WaitRead() < 0) { | if (WaitRead() < 0) { | ||||
| return -1; | return -1; | ||||
| } | } | ||||
| #endif | #endif | ||||
| int res; | int res; | ||||
| if ((res = recvfrom(fSockfd, buffer, nbytes, flags, reinterpret_cast<socket_address_t*>(&fSendAddr), &addr_len)) < 0) { | |||||
| if ((res = recvfrom(fSockfd, buffer, nbytes, flags, reinterpret_cast<struct sockaddr *>(&fSendAddr), &addr_len)) < 0) { | |||||
| jack_log("CatchHost fd = %ld err = %s", fSockfd, strerror(errno)); | jack_log("CatchHost fd = %ld err = %s", fSockfd, strerror(errno)); | ||||
| } | } | ||||
| return res; | return res; | ||||
| @@ -33,20 +33,27 @@ namespace Jack | |||||
| #define SOCKET_ERROR -1 | #define SOCKET_ERROR -1 | ||||
| #define StrError strerror | #define StrError strerror | ||||
| typedef struct sockaddr socket_address_t; | |||||
| typedef struct in_addr address_t; | |||||
| //JackNetUnixSocket******************************************** | //JackNetUnixSocket******************************************** | ||||
| class SERVER_EXPORT JackNetUnixSocket | class SERVER_EXPORT JackNetUnixSocket | ||||
| { | { | ||||
| private: | |||||
| protected: | |||||
| int fFamily; | |||||
| int fSockfd; | int fSockfd; | ||||
| int fState; | |||||
| int fPort; | int fPort; | ||||
| int fTimeOut; | int fTimeOut; | ||||
| struct sockaddr_storage fSendAddr; | |||||
| struct sockaddr_storage fRecvAddr; | |||||
| void Clone(const JackNetUnixSocket& socket); | |||||
| int ProbeAF(const char* ip, struct sockaddr_storage *addr, int (*call)(int, const struct sockaddr*, socklen_t)); | |||||
| int BindMCastIface(const char *if_name, const int option, struct in_addr *addr); | |||||
| int BindMCast6Iface(const char *if_name, struct in6_addr *addr); | |||||
| private: | |||||
| char f_addr_buff[INET6_ADDRSTRLEN]; | |||||
| struct sockaddr_in fSendAddr; | |||||
| struct sockaddr_in fRecvAddr; | |||||
| #if defined(__sun__) || defined(sun) | #if defined(__sun__) || defined(sun) | ||||
| int WaitRead(); | int WaitRead(); | ||||
| int WaitWrite(); | int WaitWrite(); | ||||
| @@ -62,8 +69,10 @@ namespace Jack | |||||
| JackNetUnixSocket& operator=(const JackNetUnixSocket& socket); | JackNetUnixSocket& operator=(const JackNetUnixSocket& socket); | ||||
| //socket management | //socket management | ||||
| int NewSocket(const char *ip); | |||||
| int NewSocket(); | int NewSocket(); | ||||
| int Bind(); | int Bind(); | ||||
| int Bind(const char *if_name); | |||||
| int BindWith(const char* ip); | int BindWith(const char* ip); | ||||
| int BindWith(int port); | int BindWith(int port); | ||||
| int Connect(); | int Connect(); | ||||
| @@ -79,11 +88,14 @@ namespace Jack | |||||
| //address management | //address management | ||||
| int SetAddress(const char* ip, int port); | int SetAddress(const char* ip, int port); | ||||
| char* GetSendIP(); | char* GetSendIP(); | ||||
| int SetSendIP(const char *ip); | |||||
| char* GetRecvIP(); | char* GetRecvIP(); | ||||
| int SetRecvIP(const char *ip); | |||||
| //utility | //utility | ||||
| int GetName(char* name); | int GetName(char* name); | ||||
| int JoinMCastGroup(const char* mcast_ip); | int JoinMCastGroup(const char* mcast_ip); | ||||
| int JoinMCastGroup(const char* mcast_ip, const char* if_name); | |||||
| //options management | //options management | ||||
| int SetOption(int level, int optname, const void* optval, socklen_t optlen); | int SetOption(int level, int optname, const void* optval, socklen_t optlen); | ||||