Network interface selection for outgoing UDP multicast datagrams

Default  selection  is  the  loop-back interface.  Users  who  require
interoperation  between  WSJT-X   instances  cooperating  applications
running on different hosts should  select a suitable network interface
and  carefully choose  a multicast  group address,  and TTL,  that has
minimal scope covering the necessary  network(s). Using 224.0.0.1 is a
reasonable    strategy    if   all    hosts    are    on   the    same
subnet. Administratively  scoped multicast group addresses  like those
within 239.255.0.0/16  can cover larger  boundaries, but care  must be
taken if the local subnet has access to a multicast enabled router.

The  IPv4  broadcast  address  (255.255.255.255) may  be  used  as  an
alternative  to multicast  UDP, but  note that  WSJT-X will  only send
broadcast UDP datagrams  on the loop-back interface,  so all recipient
applications must be running on the same host system.

The reference UDP Message protocol  applications are being extended to
be configurable  with a list of  interfaces to join a  multicast group
address on. By default they will only join on the loop-back interface,
which is also  recommended for any applications designed  to take part
in the WSJT-X  UDP Message Protocol. This allows full  user control of
the  scope of  multicast  group membership  with  a very  conservative
default  mode  that will  work  with  all interoperating  applications
running on the same host system.
This commit is contained in:
Bill Somerville
2020-11-02 15:33:44 +00:00
parent 9e71d07075
commit 662ed0fa7a
11 changed files with 510 additions and 139 deletions
+37 -48
View File
@@ -32,7 +32,6 @@ public:
: self_ {self}
, version_ {version}
, revision_ {revision}
, port_ {0u}
, clock_ {new QTimer {this}}
{
// register the required types with Qt
@@ -78,8 +77,8 @@ public:
MessageServer * self_;
QString version_;
QString revision_;
port_type port_;
QHostAddress multicast_group_address_;
QStringList network_interfaces_;
static BindMode constexpr bind_mode_ = ShareAddress | ReuseAddressHint;
struct Client
{
@@ -109,56 +108,39 @@ MessageServer::impl::BindMode constexpr MessageServer::impl::bind_mode_;
void MessageServer::impl::leave_multicast_group ()
{
if (!multicast_group_address_.isNull () && BoundState == state ()
#if QT_VERSION >= 0x050600
&& multicast_group_address_.isMulticast ()
#endif
)
if (BoundState == state () && is_multicast_address (multicast_group_address_))
{
for (auto const& interface : QNetworkInterface::allInterfaces ())
for (auto const& if_name : network_interfaces_)
{
if (QNetworkInterface::CanMulticast & interface.flags ())
{
leaveMulticastGroup (multicast_group_address_, interface);
}
leaveMulticastGroup (multicast_group_address_, QNetworkInterface::interfaceFromName (if_name));
}
}
}
void MessageServer::impl::join_multicast_group ()
{
if (BoundState == state ()
&& !multicast_group_address_.isNull ()
#if QT_VERSION >= 0x050600
&& multicast_group_address_.isMulticast ()
#endif
)
if (BoundState == state () && is_multicast_address (multicast_group_address_))
{
auto mcast_iface = multicastInterface ();
if (IPv4Protocol == multicast_group_address_.protocol ()
&& IPv4Protocol != localAddress ().protocol ())
if (network_interfaces_.size ())
{
close ();
bind (QHostAddress::AnyIPv4, port_, bind_mode_);
}
bool joined {false};
for (auto const& interface : QNetworkInterface::allInterfaces ())
{
if (QNetworkInterface::CanMulticast & interface.flags ())
for (auto const& if_name : network_interfaces_)
{
// Windows requires outgoing interface to match
// interface to be joined while joining, at least for
// IPv4 it seems to
setMulticastInterface (interface);
joined |= joinMulticastGroup (multicast_group_address_, interface);
joinMulticastGroup (multicast_group_address_, QNetworkInterface::interfaceFromName (if_name));
}
}
if (!joined)
else
{
multicast_group_address_.clear ();
// find the loop-back interface and join on that
for (auto const& net_if : QNetworkInterface::allInterfaces ())
{
auto flags = QNetworkInterface::IsUp | QNetworkInterface::IsLoopBack | QNetworkInterface::CanMulticast;
if ((net_if.flags () & flags) == flags)
{
joinMulticastGroup (multicast_group_address_, net_if);
break;
}
}
}
setMulticastInterface (mcast_iface);
}
}
@@ -448,27 +430,34 @@ MessageServer::MessageServer (QObject * parent, QString const& version, QString
{
}
void MessageServer::start (port_type port, QHostAddress const& multicast_group_address)
void MessageServer::start (port_type port, QHostAddress const& multicast_group_address
, QStringList const& network_interface_names)
{
if (port != m_->port_
|| multicast_group_address != m_->multicast_group_address_)
if (port != m_->localPort () || multicast_group_address != m_->multicast_group_address_)
{
m_->leave_multicast_group ();
if (impl::BoundState == m_->state ())
if (impl::UnconnectedState != m_->state ())
{
m_->close ();
}
m_->multicast_group_address_ = multicast_group_address;
auto address = m_->multicast_group_address_.isNull ()
|| impl::IPv4Protocol != m_->multicast_group_address_.protocol () ? QHostAddress::Any : QHostAddress::AnyIPv4;
if (port && m_->bind (address, port, m_->bind_mode_))
if (!(multicast_group_address.isNull () || is_multicast_address (multicast_group_address)))
{
m_->port_ = port;
m_->join_multicast_group ();
Q_EMIT error ("Invalid multicast group address");
}
else if (is_MAC_ambiguous_multicast_address (multicast_group_address))
{
Q_EMIT error ("MAC-ambiguous IPv4 multicast group address not supported");
}
else
{
m_->port_ = 0;
m_->multicast_group_address_ = multicast_group_address;
m_->network_interfaces_ = network_interface_names;
QHostAddress local_addr {is_multicast_address (multicast_group_address)
&& impl::IPv4Protocol == multicast_group_address.protocol () ? QHostAddress::AnyIPv4 : QHostAddress::Any};
if (port && m_->bind (local_addr, port, m_->bind_mode_))
{
m_->join_multicast_group ();
}
}
}
}
+5 -2
View File
@@ -2,6 +2,8 @@
#define MESSAGE_SERVER_HPP__
#include <QObject>
#include <QString>
#include <QStringList>
#include <QTime>
#include <QDateTime>
#include <QHostAddress>
@@ -38,8 +40,9 @@ public:
// start or restart the server, if the multicast_group_address
// argument is given it is assumed to be a multicast group address
// which the server will join
Q_SLOT void start (port_type port,
QHostAddress const& multicast_group_address = QHostAddress {});
Q_SLOT void start (port_type port
, QHostAddress const& multicast_group_address = QHostAddress {}
, QStringList const& network_interface_names = QStringList {});
// ask the client to clear one or both of the decode windows
Q_SLOT void clear_decodes (QString const& id, quint8 window = 0);
+16 -3
View File
@@ -20,6 +20,9 @@
#include <QCoreApplication>
#include <QCommandLineParser>
#include <QCommandLineOption>
#include <QString>
#include <QStringList>
#include <QDateTime>
#include <QTime>
#include <QHash>
@@ -144,7 +147,7 @@ class Server
Q_OBJECT
public:
Server (port_type port, QHostAddress const& multicast_group)
Server (port_type port, QHostAddress const& multicast_group, QStringList const& network_interface_names)
: server_ {new MessageServer {this}}
{
// connect up server
@@ -154,7 +157,7 @@ public:
connect (server_, &MessageServer::client_opened, this, &Server::add_client);
connect (server_, &MessageServer::client_closed, this, &Server::remove_client);
server_->start (port, multicast_group);
server_->start (port, multicast_group, network_interface_names);
}
private:
@@ -232,9 +235,19 @@ int main (int argc, char * argv[])
app.translate ("UDPDaemon", "GROUP"));
parser.addOption (multicast_addr_option);
QCommandLineOption network_interface_option (QStringList {"i", "network-interface"},
app.translate ("UDPDaemon",
"Where <INTERFACE> is the network interface name to join on.\n"
"This option can be passed more than once to specify multiple network interfaces\n"
"The default is use just the loop back interface."),
app.translate ("UDPDaemon", "INTERFACE"));
parser.addOption (network_interface_option);
parser.process (app);
Server server {static_cast<port_type> (parser.value (port_option).toUInt ()), QHostAddress {parser.value (multicast_addr_option)}};
Server server {static_cast<port_type> (parser.value (port_option).toUInt ())
, QHostAddress {parser.value (multicast_addr_option).trimmed ()}
, parser.values (network_interface_option)};
return app.exec ();
}