diff --git a/src/remote/blockingUDP.h b/src/remote/blockingUDP.h index 7feca9f..be9745a 100644 --- a/src/remote/blockingUDP.h +++ b/src/remote/blockingUDP.h @@ -194,6 +194,19 @@ namespace epics { _sendTo = sendTo; } + virtual void setLocalMulticastAddress(const osiSockAddr& sendTo) { + _localMulticastAddressEnabled = true; + _localMulticastAddress = sendTo; + } + + virtual bool hasLocalMulticastAddress() const { + return _localMulticastAddressEnabled; + } + + virtual const osiSockAddr& getLocalMulticastAddress() const { + return _localMulticastAddress; + } + virtual void flushSerializeBuffer() { // noop } @@ -366,6 +379,12 @@ namespace epics { osiSockAddr _sendTo; bool _sendToEnabled; + /** + * Local multicast address. + */ + osiSockAddr _localMulticastAddress; + bool _localMulticastAddressEnabled; + /** * Receive buffer. */ diff --git a/src/remote/blockingUDPTransport.cpp b/src/remote/blockingUDPTransport.cpp index c981f97..a2f9f9c 100644 --- a/src/remote/blockingUDPTransport.cpp +++ b/src/remote/blockingUDPTransport.cpp @@ -389,6 +389,9 @@ inline int sendto(int s, const char *buf, size_t len, int flags, const struct so (target == inetAddressType_broadcast_multicast && _isSendAddressUnicast[i])) continue; + LOG(logLevelDebug, "Sending to %d bytes to %s.", + buffer->getRemaining(), inetAddressToString((*_sendAddresses)[i]).c_str()); + int retval = sendto(_channel, buffer->getArray(), buffer->getLimit(), 0, &((*_sendAddresses)[i].sa), sizeof(sockaddr)); @@ -460,7 +463,7 @@ inline int sendto(int s, const char *buf, size_t len, int flags, const struct so char errStr[64]; epicsSocketConvertErrnoToString(errStr, sizeof(errStr)); throw std::runtime_error( - string("Failed to set multicast network inteface '") + + string("Failed to set multicast network interface '") + inetAddressToString(nifAddr, false) + "': " + errStr); } @@ -473,7 +476,7 @@ inline int sendto(int s, const char *buf, size_t len, int flags, const struct so char errStr[64]; epicsSocketConvertErrnoToString(errStr, sizeof(errStr)); throw std::runtime_error( - string("Failed to enable multicast loopback on network inteface '") + + string("Failed to enable multicast loopback on network interface '") + inetAddressToString(nifAddr, false) + "': " + errStr); } diff --git a/src/remoteClient/clientContextImpl.cpp b/src/remoteClient/clientContextImpl.cpp index adea100..83d36bb 100644 --- a/src/remoteClient/clientContextImpl.cpp +++ b/src/remoteClient/clientContextImpl.cpp @@ -4468,10 +4468,24 @@ namespace epics { */ bool initializeUDPTransport() { + // where to bind (listen) address + osiSockAddr listenLocalAddress; + listenLocalAddress.ia.sin_family = AF_INET; + listenLocalAddress.ia.sin_port = htons(m_broadcastPort); + listenLocalAddress.ia.sin_addr.s_addr = htonl(INADDR_ANY); + // quary broadcast addresses of all IFs SOCKET socket = epicsSocketCreate(AF_INET, SOCK_DGRAM, 0); if (socket == INVALID_SOCKET) return false; auto_ptr broadcastAddresses(getBroadcastAddresses(socket, m_broadcastPort)); + + int ifIndex = discoverInterfaceIndex(socket, &listenLocalAddress); + if (ifIndex == -1) + { + LOG(logLevelWarn, "Unable to find interface index for %s.", inetAddressToString(listenLocalAddress, false).c_str()); + // TODO fallback + } + epicsSocketDestroy (socket); // set broadcast address list @@ -4497,12 +4511,6 @@ namespace epics { LOG(logLevelDebug, "Broadcast address #%d: %s.", i, inetAddressToString((*broadcastAddresses)[i]).c_str()); - // where to bind (listen) address - osiSockAddr listenLocalAddress; - listenLocalAddress.ia.sin_family = AF_INET; - listenLocalAddress.ia.sin_port = htons(m_broadcastPort); - listenLocalAddress.ia.sin_addr.s_addr = htonl(INADDR_ANY); - ClientContextImpl::shared_pointer thisPointer = shared_from_this(); TransportClient::shared_pointer nullTransportClient; @@ -4540,8 +4548,13 @@ namespace epics { { try { + int lastAddr = 128 + ifIndex; + std::ostringstream o; + // TODO configurable + o << "224.0.0." << lastAddr; + //osiSockAddr group; - aToIPAddr("224.0.0.128", m_broadcastPort, &m_localBroadcastAddress.ia); + aToIPAddr(o.str().c_str(), m_broadcastPort, &m_localBroadcastAddress.ia); m_broadcastTransport->join(m_localBroadcastAddress, loAddr); // NOTE: this disables usage of multicast addresses in EPICS_PVA_ADDR_LIST diff --git a/src/server/responseHandlers.cpp b/src/server/responseHandlers.cpp index 7120101..05acf7a 100644 --- a/src/server/responseHandlers.cpp +++ b/src/server/responseHandlers.cpp @@ -263,10 +263,10 @@ void ServerSearchHandler::handleResponse(osiSockAddr* responseFrom, // // locally broadcast if unicast (qosCode & 0x80 == 0x80) // - if (0) + if ((qosCode & 0x80) == 0x80) { - BlockingUDPTransport::shared_pointer bt = _context->getLocalMulticastTransport(); - if (bt) + BlockingUDPTransport::shared_pointer bt = _context->getBroadcastTransport(); + if (bt && bt->hasLocalMulticastAddress()) { // clear unicast flag payloadBuffer->put(startPosition+4, (int8)(qosCode & ~0x80)); @@ -277,7 +277,7 @@ void ServerSearchHandler::handleResponse(osiSockAddr* responseFrom, payloadBuffer->setPosition(payloadBuffer->getLimit()); // send will call flip() - bt->send(payloadBuffer); + bt->send(payloadBuffer, bt->getLocalMulticastAddress()); return; } } diff --git a/src/server/serverContext.cpp b/src/server/serverContext.cpp index 33d99ce..b3c4e77 100644 --- a/src/server/serverContext.cpp +++ b/src/server/serverContext.cpp @@ -291,6 +291,14 @@ void ServerContextImpl::initializeBroadcastTransport() THROW_BASE_EXCEPTION("Failed to initialize broadcast UDP transport"); } auto_ptr broadcastAddresses(getBroadcastAddresses(socket,_broadcastPort)); + + int ifIndex = discoverInterfaceIndex(socket, &listenLocalAddress); + if (ifIndex == -1) + { + LOG(logLevelWarn, "Unable to find interface index for %s.", inetAddressToString(listenLocalAddress, false).c_str()); + // TODO fallback + } + epicsSocketDestroy(socket); TransportClient::shared_pointer nullTransportClient; @@ -360,50 +368,48 @@ void ServerContextImpl::initializeBroadcastTransport() } - // setup local broadcasting + // + // Setup local broadcasting + // + // Each network interface gets its own multicast group on a local interface. + // Multicast address is determined by prefix 224.0.0.124 + NIF index + // + // TODO configurable local NIF, address osiSockAddr loAddr; getLoopbackNIF(loAddr, "", 0); + + osiSockAddr group; + int lastAddr = 128 + ifIndex; + std::ostringstream o; + // TODO configurable prefix and base + o << "224.0.0." << lastAddr; + aToIPAddr(o.str().c_str(), _broadcastPort, &group.ia); + + _broadcastTransport->setMutlicastNIF(loAddr, true); + _broadcastTransport->setLocalMulticastAddress(group); + if (true) { try { - osiSockAddr group; - - // TODO there should be different mcast groups - // one for all interfaces, and then one per interface - - // if received on specific NIF, then it's resent to speicfic mcast address - // if received on any NIF, then it should be resent to specific mcast address (calculate from receive from and mask) - // if interested for all the NIFs, then it should join to all specific mcast addresses - - // --- server - // UDP bind on broadcast port + mcast as above - - // -- client - // UDP bind to broadcast port + mcast as above - - - aToIPAddr("224.0.0.128", _broadcastPort, &group.ia); - _broadcastTransport->join(group, _ifaceAddr); - - osiSockAddr anyAddress; - anyAddress.ia.sin_family = AF_INET; - anyAddress.ia.sin_port = htons(0); - anyAddress.ia.sin_addr.s_addr = htonl(INADDR_ANY); - - // NOTE: localMulticastTransport is not started (no read is called on a socket) + // NOTE: multicast receiver socket must be "bound" to INADDR_ANY or multicast address _localMulticastTransport = static_pointer_cast(broadcastConnector->connect( nullTransportClient, _responseHandler, - anyAddress, PVA_PROTOCOL_REVISION, + group, PVA_PROTOCOL_REVISION, PVA_DEFAULT_PRIORITY)); + _localMulticastTransport->join(group, loAddr); + /* used for sending _localMulticastTransport->setMutlicastNIF(loAddr, true); InetAddrVector sendAddressList; sendAddressList.push_back(group); _localMulticastTransport->setSendAddresses(&sendAddressList); + */ - LOG(logLevelDebug, "Local multicast enabled on %s using network interface %s.", - inetAddressToString(group).c_str(), inetAddressToString(loAddr, false).c_str()); + LOG(logLevelDebug, "Local multicast for %s enabled on %s/%s.", + inetAddressToString(listenLocalAddress, false).c_str(), + inetAddressToString(loAddr, false).c_str(), + inetAddressToString(group).c_str()); } catch (std::exception& ex) { diff --git a/src/utils/inetAddressUtil.cpp b/src/utils/inetAddressUtil.cpp index 24301f6..27cbde5 100644 --- a/src/utils/inetAddressUtil.cpp +++ b/src/utils/inetAddressUtil.cpp @@ -212,5 +212,144 @@ int getLoopbackNIF(osiSockAddr &loAddr, string const & localNIF, unsigned short return 1; } + + +// copy of base-3.14.12.4/src/libCom/osi/os/default/osdNetIntf.c +// TODO support windows (see osi/os/WIN32/osdNetIntf.c) + +#include +//#include +#include + +/* + * Determine the size of an ifreq structure + * Made difficult by the fact that addresses larger than the structure + * size may be returned from the kernel. + */ +static size_t ifreqSize ( struct ifreq *pifreq ) +{ + size_t size; + + size = ifreq_size ( pifreq ); + if ( size < sizeof ( *pifreq ) ) { + size = sizeof ( *pifreq ); + } + return size; +} + +/* + * Move to the next ifreq structure + */ +static struct ifreq * ifreqNext ( struct ifreq *pifreq ) +{ + struct ifreq *ifr; + + ifr = ( struct ifreq * )( ifreqSize (pifreq) + ( char * ) pifreq ); + return ifr; +} + +int discoverInterfaceIndex + (SOCKET socket, const osiSockAddr *pMatchAddr) +{ + static const unsigned nelem = 100; + int status; + struct ifconf ifconf; + struct ifreq *pIfreqList; + struct ifreq *pIfreqListEnd; + struct ifreq *pifreq; + struct ifreq *pnextifreq; + + /* + * check if pMatchAddr is valid + */ + if ( pMatchAddr->sa.sa_family == AF_UNSPEC || + pMatchAddr->sa.sa_family != AF_INET || + pMatchAddr->ia.sin_addr.s_addr == htonl(INADDR_ANY) ) { + errlogPrintf ("osiSockDiscoverInterfaceIndex(): invalid pMatchAddr\n"); + return -1; + } + + /* + * use pool so that we avoid using too much stack space + * + * nelem is set to the maximum interfaces + * on one machine here + */ + pIfreqList = (struct ifreq *) calloc ( nelem, sizeof(*pifreq) ); + if (!pIfreqList) { + errlogPrintf ("osiSockDiscoverInterfaceIndex(): no memory to complete request\n"); + return -1; + } + + ifconf.ifc_len = nelem * sizeof(*pifreq); + ifconf.ifc_req = pIfreqList; + status = socket_ioctl (socket, SIOCGIFCONF, &ifconf); + if (status < 0 || ifconf.ifc_len == 0) { + errlogPrintf ("osiSockDiscoverInterfaceIndex(): unable to fetch network interface configuration\n"); + free (pIfreqList); + return -1; + } + + pIfreqListEnd = (struct ifreq *) (ifconf.ifc_len + (char *) pIfreqList); + pIfreqListEnd--; + + for ( pifreq = pIfreqList; pifreq <= pIfreqListEnd; pifreq = pnextifreq ) { + uint32_t current_ifreqsize; + + /* + * find the next ifreq + */ + pnextifreq = ifreqNext (pifreq); + + /* determine ifreq size */ + current_ifreqsize = ifreqSize ( pifreq ); + /* copy current ifreq to aligned bufferspace (to start of pIfreqList buffer) */ + memmove(pIfreqList, pifreq, current_ifreqsize); + + /* + * If its not an internet interface then dont use it + */ + if ( pIfreqList->ifr_addr.sa_family != AF_INET ) { + continue; + } + + /* + * if it isnt a wildcarded interface then look for + * an exact match + */ + struct sockaddr_in *pInetAddr = (struct sockaddr_in *) &pIfreqList->ifr_addr; + if ( pInetAddr->sin_addr.s_addr == pMatchAddr->ia.sin_addr.s_addr ) { + + unsigned int index = if_nametoindex(pIfreqList->ifr_name); + if ( !index ) { + errlogPrintf ("osiSockDiscoverInterfaceIndex(): net intf index fetch for \"%s\" failed\n", pIfreqList->ifr_name); + free (pIfreqList); + return -1; + } + + free (pIfreqList); + return index; + + /* + status = socket_ioctl ( socket, SIOCGIFINDEX, pIfreqList ); + if ( status ) { + errlogPrintf ("osiSockDiscoverInterfaceIndex(): net intf index fetch for \"%s\" failed\n", pIfreqList->ifr_name); + free (pIfreqList); + return -1; + } + + free (pIfreqList); + return pIfreqList->ifr_ifindex; + */ + } + + } + + /* not found */ + free ( pIfreqList ); + return -1; +} + + } } diff --git a/src/utils/inetAddressUtil.h b/src/utils/inetAddressUtil.h index 57982af..b46b736 100644 --- a/src/utils/inetAddressUtil.h +++ b/src/utils/inetAddressUtil.h @@ -42,6 +42,11 @@ namespace pvAccess { */ epicsShareFunc InetAddrVector* getBroadcastAddresses(SOCKET sock, unsigned short defaultPort); + /** + * Returns NIF index for given interface address, or -1 on failure. + */ + epicsShareFunc int discoverInterfaceIndex(SOCKET socket, const osiSockAddr *pMatchAddr); + /** * Encode IPv4 address as IPv6 address. * @param buffer byte-buffer where to put encoded data.