From 3e9c718c3d5a47c4b39ac6ad8ba8690627eb6fed Mon Sep 17 00:00:00 2001 From: Alesandro Dragnev Date: Fri, 4 Sep 2026 13:34:45 +0300 Subject: [PATCH 1/3] Implementing network interface switching --- src/Include/IMediaRessourceVectorInterface.h | 5 ++ src/mvrxchange/CMVRxchangeService.cpp | 55 ++++++++++++++++---- src/mvrxchange/CMVRxchangeService.h | 7 +++ 3 files changed, 57 insertions(+), 10 deletions(-) diff --git a/src/Include/IMediaRessourceVectorInterface.h b/src/Include/IMediaRessourceVectorInterface.h index f9022bf8..2ff44ee8 100644 --- a/src/Include/IMediaRessourceVectorInterface.h +++ b/src/Include/IMediaRessourceVectorInterface.h @@ -2062,6 +2062,11 @@ class DYNAMIC_ATTRIBUTE IGdtfMacro : public IVWUnknown */ virtual VCOMError VCOM_CALLTYPE QueryLocalServices(size_t& out_Count) = 0; virtual VCOMError VCOM_CALLTYPE GetLocalServiceAt(size_t index, ConnectToLocalServiceArgs& outLocalService) = 0; + + using NetworkInterface = std::pair; + // Allows the user to query all available network interfaces on the system, and set one of them for the MVR-xchange service to use + virtual VCOMError VCOM_CALLTYPE QueryAllAvailableInterfaces( std::vector& out ) = 0; + virtual VCOMError VCOM_CALLTYPE SetNetworkInterface( const NetworkInterface& interface ) = 0; typedef IMVRxchangeMessage (*IMVRxchangeIncomingMessage)(const IMVRxchangeMessage& args, void* context); typedef void (*IMVRxchangeReturningMessage)(const IMVRxchangeMessage& outgoingMsg, const IMVRxchangeMessage& returningMsg, void* context); diff --git a/src/mvrxchange/CMVRxchangeService.cpp b/src/mvrxchange/CMVRxchangeService.cpp index 1eb906fe..ddf2fb86 100644 --- a/src/mvrxchange/CMVRxchangeService.cpp +++ b/src/mvrxchange/CMVRxchangeService.cpp @@ -68,23 +68,37 @@ VCOMError VectorworksMVR::CMVRxchangeServiceImpl::ConnectToLocalService(const Co txt += (uint8_t)txt2.size(); txt += txt2; - for(std::pair e : mdns_cpp::mDNS().getInterfaces()) + if ( !fNetworkInterface.first.empty() || fNetworkInterface.second != 0 ) { - // Bitmasking IP Address to check if it is 127.x.x.x - // We dont want to start the mDNS Server on loopback addresses - // If two programs on the same device want to connect, they can use one of the other interfaces as well - if((e.second & 4278190080) == 2130706432) { - continue; - } - mdns_cpp::mDNS* s = new mdns_cpp::mDNS(); s->setServiceHostname(std::string(fCurrentService.Service.fBuffer)); s->setServicePort(fServer->GetPort()); - s->setServiceIP(e.second); + s->setServiceIP(fNetworkInterface.second); s->setServiceName(MVRXChange_Service); s->setServiceTxtRecord(txt); s->startService(); - fmdns.emplace_back(s); // Pointer is now managed by the unique ptr and deleted upon fmdns going out of scope + fmdns.emplace_back(s); + } + else + { + for(NetworkInterface e : mdns_cpp::mDNS().getInterfaces()) + { + // Bitmasking IP Address to check if it is 127.x.x.x + // We dont want to start the mDNS Server on loopback addresses + // If two programs on the same device want to connect, they can use one of the other interfaces as well + if((e.second & 4278190080) == 2130706432) { + continue; + } + + mdns_cpp::mDNS* s = new mdns_cpp::mDNS(); + s->setServiceHostname(std::string(fCurrentService.Service.fBuffer)); + s->setServicePort(fServer->GetPort()); + s->setServiceIP(e.second); + s->setServiceName(MVRXChange_Service); + s->setServiceTxtRecord(txt); + s->startService(); + fmdns.emplace_back(s); // Pointer is now managed by the unique ptr and deleted upon fmdns going out of scope + } } bool doInit = false; // avoid deadlock with temp variable @@ -277,6 +291,27 @@ VCOMError VectorworksMVR::CMVRxchangeServiceImpl::Send_message(const SendMessage return kVCOMError_NoError; } +VCOMError VectorworksMVR::CMVRxchangeServiceImpl::QueryAllAvailableInterfaces(std::vector& out) +{ + out.clear(); + for(NetworkInterface e : mdns_cpp::mDNS().getInterfaces()) + { + // remove loopback addresses (127.x.x.x) from the list of available interfaces, as they are not useful for mDNS service discovery in a local network context + if((e.second & 4278190080) == 2130706432) { + continue; + } + out.push_back(e); + } + + return kVCOMError_NoError; +} + +VCOMError VectorworksMVR::CMVRxchangeServiceImpl::SetNetworkInterface( const NetworkInterface& interface ) +{ + fNetworkInterface = interface; + return kVCOMError_NoError; +} + //--------------------------------------------------------------------------- // TCP Server - Local Network mode diff --git a/src/mvrxchange/CMVRxchangeService.h b/src/mvrxchange/CMVRxchangeService.h index 982280ee..ec6dcada 100644 --- a/src/mvrxchange/CMVRxchangeService.h +++ b/src/mvrxchange/CMVRxchangeService.h @@ -21,6 +21,7 @@ namespace VectorworksMVR }; using TCPMessageInfo = MVRxchangeNetwork::TCPMessageInfo; + using NetworkInterface = std::pair; //---------------------------------------------------------------------------------------- class CMVRxchangeServiceImpl : public VCOMImpl @@ -43,6 +44,9 @@ namespace VectorworksMVR virtual VCOMError VCOM_CALLTYPE OnMessage(OnMessageArgs& messageHandler); virtual VCOMError VCOM_CALLTYPE Send_message(const SendMessageArgs& messageHandler); + virtual VCOMError VCOM_CALLTYPE QueryAllAvailableInterfaces( std::vector& out ); + virtual VCOMError VCOM_CALLTYPE SetNetworkInterface( const NetworkInterface& interface ); + private: void mDNS_Client_Task(); // actual mdns task @@ -67,6 +71,8 @@ namespace VectorworksMVR MVRxchangeNetwork::MVRxchangeServer* fServer; ConnectToLocalServiceArgs fCurrentService; + NetworkInterface fNetworkInterface; + void TCP_Start(); void TCP_Stop(); public: @@ -75,6 +81,7 @@ namespace VectorworksMVR std::mutex fMvrGroupMutex; std::vector fMVRGroup; + private: OnMessageArgs fCallBack; From 34868dbcf2a444aecfc3603b3abfea79fc0a420e Mon Sep 17 00:00:00 2001 From: Alesandro Dragnev Date: Tue, 8 Sep 2026 09:20:48 +0300 Subject: [PATCH 2/3] fixing build error --- src/Include/IMediaRessourceVectorInterface.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Include/IMediaRessourceVectorInterface.h b/src/Include/IMediaRessourceVectorInterface.h index 2ff44ee8..dec12e7a 100644 --- a/src/Include/IMediaRessourceVectorInterface.h +++ b/src/Include/IMediaRessourceVectorInterface.h @@ -2066,7 +2066,7 @@ class DYNAMIC_ATTRIBUTE IGdtfMacro : public IVWUnknown using NetworkInterface = std::pair; // Allows the user to query all available network interfaces on the system, and set one of them for the MVR-xchange service to use virtual VCOMError VCOM_CALLTYPE QueryAllAvailableInterfaces( std::vector& out ) = 0; - virtual VCOMError VCOM_CALLTYPE SetNetworkInterface( const NetworkInterface& interface ) = 0; + virtual VCOMError VCOM_CALLTYPE SetNetworkInterface( const NetworkInterface& inInterface ) = 0; typedef IMVRxchangeMessage (*IMVRxchangeIncomingMessage)(const IMVRxchangeMessage& args, void* context); typedef void (*IMVRxchangeReturningMessage)(const IMVRxchangeMessage& outgoingMsg, const IMVRxchangeMessage& returningMsg, void* context); From de4c33bff32a272c6c9e17ba100334f3b7a9e230 Mon Sep 17 00:00:00 2001 From: Alesandro Dragnev Date: Fri, 11 Sep 2026 16:43:49 +0300 Subject: [PATCH 3/3] Adding interface mdns query filtering --- mdns_cpp/include/mdns_cpp/mdns.hpp | 9 ++ mdns_cpp/src/mdns.cpp | 33 +++++-- src/mvrxchange/CMVRxchangeService.cpp | 90 ++++++++++++++++++- src/mvrxchange/CMVRxchangeService.h | 11 ++- thirdparty/mdns_cpp/include/mdns_cpp/mdns.hpp | 0 5 files changed, 134 insertions(+), 9 deletions(-) create mode 100644 thirdparty/mdns_cpp/include/mdns_cpp/mdns.hpp diff --git a/mdns_cpp/include/mdns_cpp/mdns.hpp b/mdns_cpp/include/mdns_cpp/mdns.hpp index 90c3f656..e25a5b62 100644 --- a/mdns_cpp/include/mdns_cpp/mdns.hpp +++ b/mdns_cpp/include/mdns_cpp/mdns.hpp @@ -77,6 +77,11 @@ class mDNS { void setServiceIP(std::uint32_t ip); std::uint32_t getServiceIP(); + // Restricts outgoing queries to the interface with this IPv4 address (network byte order). + // 0 (the default) queries on every non-loopback interface. + void setQueryInterface(std::uint32_t ip); + std::uint32_t getQueryInterface(); + std::string getServiceIPPort(); // IP:Port void setServiceName(const std::string &name); @@ -91,6 +96,9 @@ class mDNS { private: void runMainLoop(); int openClientSockets(int *sockets, int max_sockets, int port); + // [filter_ipv4] == 0 opens sockets on every interface, otherwise only on the matching one. + // Interface enumeration into [fInterfaces] always covers every interface. + int openClientSocketsFiltered(int *sockets, int max_sockets, int port, std::uint32_t filter_ipv4); int openServiceSockets(int *sockets, int max_sockets); std::string hostname_{"dummy-host"}; @@ -104,6 +112,7 @@ class mDNS { bool has_ipv6_{false}; uint32_t service_address_ipv4_{0}; + uint32_t query_interface_ipv4_{0}; std::vector> fInterfaces; uint8_t service_address_ipv6_[16]{0}; diff --git a/mdns_cpp/src/mdns.cpp b/mdns_cpp/src/mdns.cpp index 2126d5fa..ad6bb7ef 100644 --- a/mdns_cpp/src/mdns.cpp +++ b/mdns_cpp/src/mdns.cpp @@ -64,11 +64,14 @@ int mDNS::openServiceSockets(int *sockets, int max_sockets) { return num_sockets; } -int mDNS::openClientSockets(int *sockets, int max_sockets, int port) { +int mDNS::openClientSocketsFiltered(int *sockets, int max_sockets, int port, std::uint32_t filter_ipv4) { // When sending, each socket can only send to one network interface // Thus we need to open one socket for each interface and address family int num_sockets = 0; + // Every call re-enumerates all interfaces, so start from a clean list + fInterfaces.clear(); + #ifdef _WIN32 IP_ADAPTER_ADDRESSES *adapter_address = nullptr; @@ -113,7 +116,8 @@ int mDNS::openClientSockets(int *sockets, int max_sockets, int port) { has_ipv4_ = 1; - if (num_sockets < max_sockets) { + const bool interface_selected = (filter_ipv4 == 0) || (saddr->sin_addr.s_addr == filter_ipv4); + if (interface_selected && num_sockets < max_sockets) { saddr->sin_port = htons((unsigned short)port); int sock = mdns_socket_open_ipv4(saddr); if (sock >= 0) { @@ -142,7 +146,7 @@ int mDNS::openClientSockets(int *sockets, int max_sockets, int port) { log_addr = 1; } has_ipv6_ = 1; - if (num_sockets < max_sockets) { + if ((filter_ipv4 == 0) && num_sockets < max_sockets) { saddr->sin6_port = htons((unsigned short)port); int sock = mdns_socket_open_ipv6(saddr); if (sock >= 0) { @@ -185,8 +189,9 @@ int mDNS::openClientSockets(int *sockets, int max_sockets, int port) { { int log_addr = 0; fInterfaces.push_back(std::make_pair(ifa->ifa_name, saddr->sin_addr.s_addr)); - - if (num_sockets < max_sockets) { + + const bool interface_selected = (filter_ipv4 == 0) || (saddr->sin_addr.s_addr == filter_ipv4); + if (interface_selected && num_sockets < max_sockets) { saddr->sin_port = htons(port); int sock = mdns_socket_open_ipv4(saddr); if (sock >= 0) { @@ -209,7 +214,7 @@ int mDNS::openClientSockets(int *sockets, int max_sockets, int port) { log_addr = 1; } has_ipv6_ = 1; - if (num_sockets < max_sockets) { + if ((filter_ipv4 == 0) && num_sockets < max_sockets) { saddr->sin6_port = htons(port); int sock = mdns_socket_open_ipv6(saddr); if (sock >= 0) { @@ -235,6 +240,19 @@ int mDNS::openClientSockets(int *sockets, int max_sockets, int port) { return num_sockets; } +int mDNS::openClientSockets(int *sockets, int max_sockets, int port) { + int num_sockets = openClientSocketsFiltered(sockets, max_sockets, port, query_interface_ipv4_); + + if ((query_interface_ipv4_ != 0) && (max_sockets > 0) && (num_sockets == 0)) { + // The selected interface is gone (unplugged, new DHCP lease, ...). + // Fall back to all interfaces instead of silently discovering nothing. + MDNS_LOG << "Selected network interface is not available, falling back to all interfaces\n"; + num_sockets = openClientSocketsFiltered(sockets, max_sockets, port, 0); + } + + return num_sockets; +} + static int query_callback(int sock, const struct sockaddr *from, size_t addrlen, mdns_entry_type_t entry, uint16_t query_id, uint16_t rtype, uint16_t rclass, uint32_t ttl, const void *data, @@ -386,6 +404,9 @@ std::uint16_t mDNS::getServicePort() { return port_; } void mDNS::setServiceIP(std::uint32_t ip) { service_address_ipv4_ = ip; has_ipv4_ = true; } std::uint32_t mDNS::getServiceIP() { return service_address_ipv4_; } +void mDNS::setQueryInterface(std::uint32_t ip) { query_interface_ipv4_ = ip; } +std::uint32_t mDNS::getQueryInterface() { return query_interface_ipv4_; } + std::string mDNS::getServiceIPPort() { char buffer[64]; diff --git a/src/mvrxchange/CMVRxchangeService.cpp b/src/mvrxchange/CMVRxchangeService.cpp index ddf2fb86..95c49d97 100644 --- a/src/mvrxchange/CMVRxchangeService.cpp +++ b/src/mvrxchange/CMVRxchangeService.cpp @@ -11,6 +11,7 @@ #include #include #include +#include VectorworksMVR::CMVRxchangeServiceImpl::CMVRxchangeServiceImpl(): fmdns_long_timer(fmdns_IO_Context) { @@ -43,6 +44,61 @@ void FilterServiceName(MVRxchangeString& service) } +// Parses a dotted quad (an optional ":port" suffix is ignored) into its four octets +static bool ParseIPv4(const std::string& address, std::uint8_t (&outOctets)[4]) +{ + const std::string ip = address.substr(0, address.find(':')); + + size_t start = 0; + for (size_t i = 0; i < 4; i++) + { + const size_t end = (i == 3) ? ip.size() : ip.find('.', start); + if (end == std::string::npos || end == start) + { + return false; + } + + const std::string octet = ip.substr(start, end - start); + if (octet.find_first_not_of("0123456789") != std::string::npos) + { + return false; + } + + const unsigned long value = std::stoul(octet); + if (value > 255) + { + return false; + } + + outOctets[i] = (std::uint8_t)value; + start = end + 1; + } + + return true; +} + +// mdns_cpp reports the address of an interface but not its prefix length, so the network is +// approximated with classful rules (/8, /16, /24). Hosts outside the network of the selected +// interface are not reachable through it, so their announcements are dropped. +static bool IsAddressOnInterface(std::uint32_t interfaceIp, const std::string& address) +{ + std::uint8_t interfaceOctets[4]; + std::memcpy(interfaceOctets, &interfaceIp, sizeof(interfaceOctets)); // stored in network byte order, so octet order matches + + std::uint8_t addressOctets[4]; + if (!ParseIPv4(address, addressOctets)) + { + return false; + } + + size_t compare = 3; + if (interfaceOctets[0] < 128) { compare = 1; } + else if (interfaceOctets[0] < 192) { compare = 2; } + + return std::equal(interfaceOctets, interfaceOctets + compare, addressOctets); +} + + VCOMError VectorworksMVR::CMVRxchangeServiceImpl::ConnectToLocalService(const ConnectToLocalServiceArgs& service) { this->LeaveLocalService(); @@ -68,12 +124,13 @@ VCOMError VectorworksMVR::CMVRxchangeServiceImpl::ConnectToLocalService(const Co txt += (uint8_t)txt2.size(); txt += txt2; - if ( !fNetworkInterface.first.empty() || fNetworkInterface.second != 0 ) + const NetworkInterface selectedInterface = GetSelectedNetworkInterface(); + if ( !selectedInterface.first.empty() || selectedInterface.second != 0 ) { mdns_cpp::mDNS* s = new mdns_cpp::mDNS(); s->setServiceHostname(std::string(fCurrentService.Service.fBuffer)); s->setServicePort(fServer->GetPort()); - s->setServiceIP(fNetworkInterface.second); + s->setServiceIP(selectedInterface.second); s->setServiceName(MVRXChange_Service); s->setServiceTxtRecord(txt); s->startService(); @@ -308,10 +365,17 @@ VCOMError VectorworksMVR::CMVRxchangeServiceImpl::QueryAllAvailableInterfaces(st VCOMError VectorworksMVR::CMVRxchangeServiceImpl::SetNetworkInterface( const NetworkInterface& interface ) { + std::lock_guard lock(fNetworkInterfaceMutex); fNetworkInterface = interface; return kVCOMError_NoError; } +NetworkInterface VectorworksMVR::CMVRxchangeServiceImpl::GetSelectedNetworkInterface() +{ + std::lock_guard lock(fNetworkInterfaceMutex); + return fNetworkInterface; +} + //--------------------------------------------------------------------------- // TCP Server - Local Network mode @@ -517,6 +581,9 @@ mdns_cpp::QueryResList CMVRxchangeServiceImpl::mDNS_Filter_Queries(mdns_cpp::Que std::string serviceAsString(MVRXChange_Service); + const NetworkInterface selectedInterface = GetSelectedNetworkInterface(); + const bool hasInterfaceFilter = !selectedInterface.first.empty() || selectedInterface.second != 0; + for (auto &i : input) { // filter multiple @@ -566,6 +633,22 @@ mdns_cpp::QueryResList CMVRxchangeServiceImpl::mDNS_Filter_Queries(mdns_cpp::Que continue; } + if (hasInterfaceFilter) + { + bool reachable = IsAddressOnInterface(selectedInterface.second, i.mdnsAddress); + + for (auto it = i.ipV4_address.begin(); !reachable && it != i.ipV4_address.end(); ++it) + { + reachable = IsAddressOnInterface(selectedInterface.second, *it); + } + + if (!reachable) + { + // not on the network of the selected interface + continue; + } + } + out.push_back(std::move(i)); } @@ -574,7 +657,10 @@ mdns_cpp::QueryResList CMVRxchangeServiceImpl::mDNS_Filter_Queries(mdns_cpp::Que void CMVRxchangeServiceImpl::mDNS_Client_Task() { + const NetworkInterface selectedInterface = GetSelectedNetworkInterface(); + mdns_cpp::mDNS mdns; + mdns.setQueryInterface(selectedInterface.second); auto query_res = mdns.executeQuery2(MVRXChange_Service); std::vector result; diff --git a/src/mvrxchange/CMVRxchangeService.h b/src/mvrxchange/CMVRxchangeService.h index ec6dcada..93950fc4 100644 --- a/src/mvrxchange/CMVRxchangeService.h +++ b/src/mvrxchange/CMVRxchangeService.h @@ -45,6 +45,11 @@ namespace VectorworksMVR virtual VCOMError VCOM_CALLTYPE Send_message(const SendMessageArgs& messageHandler); virtual VCOMError VCOM_CALLTYPE QueryAllAvailableInterfaces( std::vector& out ); + + // Restricts both the advertised service and the service discovery to [interface]. + // An unset (default constructed) interface means all non-loopback interfaces. + // The value is applied on the next ConnectToLocalService / QueryLocalServices; + // a running discovery loop is not re-bound. virtual VCOMError VCOM_CALLTYPE SetNetworkInterface( const NetworkInterface& interface ); private: @@ -71,7 +76,11 @@ namespace VectorworksMVR MVRxchangeNetwork::MVRxchangeServer* fServer; ConnectToLocalServiceArgs fCurrentService; - NetworkInterface fNetworkInterface; + std::mutex fNetworkInterfaceMutex; + NetworkInterface fNetworkInterface; + + // Thread safe snapshot of [fNetworkInterface]; an unset interface is returned as an empty pair + NetworkInterface GetSelectedNetworkInterface(); void TCP_Start(); void TCP_Stop(); diff --git a/thirdparty/mdns_cpp/include/mdns_cpp/mdns.hpp b/thirdparty/mdns_cpp/include/mdns_cpp/mdns.hpp new file mode 100644 index 00000000..e69de29b