mirror of
https://github.com/saitohirga/WSJT-X.git
synced 2024-11-23 04:38:37 -05:00
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:
parent
9e71d07075
commit
662ed0fa7a
@ -163,6 +163,9 @@
|
||||
#include <QFontDialog>
|
||||
#include <QSerialPortInfo>
|
||||
#include <QScopedPointer>
|
||||
#include <QNetworkInterface>
|
||||
#include <QHostInfo>
|
||||
#include <QHostAddress>
|
||||
#include <QDebug>
|
||||
|
||||
#include "pimpl_impl.hpp"
|
||||
@ -439,6 +442,10 @@ private:
|
||||
void load_audio_devices (QAudio::Mode, QComboBox *, QAudioDeviceInfo *);
|
||||
void update_audio_channels (QComboBox const *, int, QComboBox *, bool);
|
||||
|
||||
void load_network_interfaces (QComboBox *, QString const& current);
|
||||
Q_SLOT void host_info_results (QHostInfo);
|
||||
void check_multicast (QHostAddress const&);
|
||||
|
||||
void find_tab (QWidget *);
|
||||
|
||||
void initialize_models ();
|
||||
@ -492,6 +499,8 @@ private:
|
||||
Q_SLOT void on_add_macro_line_edit_editingFinished ();
|
||||
Q_SLOT void delete_macro ();
|
||||
void delete_selected_macros (QModelIndexList);
|
||||
Q_SLOT void on_udp_server_line_edit_textChanged (QString const&);
|
||||
Q_SLOT void on_udp_server_line_edit_editingFinished ();
|
||||
Q_SLOT void on_save_path_select_push_button_clicked (bool);
|
||||
Q_SLOT void on_azel_path_select_push_button_clicked (bool);
|
||||
Q_SLOT void on_calibration_intercept_spin_box_valueChanged (double);
|
||||
@ -641,7 +650,11 @@ private:
|
||||
bool use_dynamic_grid_;
|
||||
QString opCall_;
|
||||
QString udp_server_name_;
|
||||
bool udp_server_name_edited_;
|
||||
int dns_lookup_id_;
|
||||
port_type udp_server_port_;
|
||||
QString udp_interface_name_;
|
||||
int udp_TTL_;
|
||||
QString n1mm_server_name_;
|
||||
port_type n1mm_server_port_;
|
||||
bool broadcast_to_n1mm_;
|
||||
@ -741,6 +754,8 @@ QString Configuration::opCall() const {return m_->opCall_;}
|
||||
void Configuration::opCall (QString const& call) {m_->opCall_ = call;}
|
||||
QString Configuration::udp_server_name () const {return m_->udp_server_name_;}
|
||||
auto Configuration::udp_server_port () const -> port_type {return m_->udp_server_port_;}
|
||||
QString Configuration::udp_interface_name () const {return m_->udp_interface_name_;}
|
||||
int Configuration::udp_TTL () const {return m_->udp_TTL_;}
|
||||
bool Configuration::accept_udp_requests () const {return m_->accept_udp_requests_;}
|
||||
QString Configuration::n1mm_server_name () const {return m_->n1mm_server_name_;}
|
||||
auto Configuration::n1mm_server_port () const -> port_type {return m_->n1mm_server_port_;}
|
||||
@ -995,6 +1010,8 @@ Configuration::impl::impl (Configuration * self, QNetworkAccessManager * network
|
||||
, transceiver_command_number_ {0}
|
||||
, degrade_ {0.} // initialize to zero each run, not
|
||||
// saved in settings
|
||||
, udp_server_name_edited_ {false}
|
||||
, dns_lookup_id_ {-1}
|
||||
{
|
||||
ui_->setupUi (this);
|
||||
|
||||
@ -1044,6 +1061,7 @@ Configuration::impl::impl (Configuration * self, QNetworkAccessManager * network
|
||||
// this must be done after the default paths above are set
|
||||
read_settings ();
|
||||
|
||||
// set up dynamic loading of audio devices
|
||||
connect (ui_->sound_input_combo_box, &LazyFillComboBox::about_to_show_popup, [this] () {
|
||||
QGuiApplication::setOverrideCursor (QCursor {Qt::WaitCursor});
|
||||
load_audio_devices (QAudio::AudioInput, ui_->sound_input_combo_box, &next_audio_input_device_);
|
||||
@ -1059,6 +1077,13 @@ Configuration::impl::impl (Configuration * self, QNetworkAccessManager * network
|
||||
QGuiApplication::restoreOverrideCursor ();
|
||||
});
|
||||
|
||||
// set up dynamic loading of network interfaces
|
||||
connect (ui_->udp_interface_combo_box, &LazyFillComboBox::about_to_show_popup, [this] () {
|
||||
QGuiApplication::setOverrideCursor (QCursor {Qt::WaitCursor});
|
||||
load_network_interfaces (ui_->udp_interface_combo_box, udp_interface_name_);
|
||||
QGuiApplication::restoreOverrideCursor ();
|
||||
});
|
||||
|
||||
// set up LoTW users CSV file fetching
|
||||
connect (&lotw_users_, &LotWUsers::load_finished, [this] () {
|
||||
ui_->LotW_CSV_fetch_push_button->setEnabled (true);
|
||||
@ -1335,7 +1360,14 @@ void Configuration::impl::initialize_models ()
|
||||
ui_->CAT_poll_interval_spin_box->setValue (rig_params_.poll_interval);
|
||||
ui_->opCallEntry->setText (opCall_);
|
||||
ui_->udp_server_line_edit->setText (udp_server_name_);
|
||||
on_udp_server_line_edit_editingFinished ();
|
||||
ui_->udp_server_port_spin_box->setValue (udp_server_port_);
|
||||
load_network_interfaces (ui_->udp_interface_combo_box, udp_interface_name_);
|
||||
if (!udp_interface_name_.size ())
|
||||
{
|
||||
udp_interface_name_ = ui_->udp_interface_combo_box->currentData ().toString ();
|
||||
}
|
||||
ui_->udp_TTL_spin_box->setValue (udp_TTL_);
|
||||
ui_->accept_udp_requests_check_box->setChecked (accept_udp_requests_);
|
||||
ui_->n1mm_server_name_line_edit->setText (n1mm_server_name_);
|
||||
ui_->n1mm_server_port_spin_box->setValue (n1mm_server_port_);
|
||||
@ -1513,6 +1545,8 @@ void Configuration::impl::read_settings ()
|
||||
rig_params_.split_mode = settings_->value ("SplitMode", QVariant::fromValue (TransceiverFactory::split_mode_none)).value<TransceiverFactory::SplitMode> ();
|
||||
opCall_ = settings_->value ("OpCall", "").toString ();
|
||||
udp_server_name_ = settings_->value ("UDPServer", "127.0.0.1").toString ();
|
||||
udp_interface_name_ = settings_->value ("UDPInterface").toString ();
|
||||
udp_TTL_ = settings_->value ("UDPTTL").toInt ();
|
||||
udp_server_port_ = settings_->value ("UDPServerPort", 2237).toUInt ();
|
||||
n1mm_server_name_ = settings_->value ("N1MMServer", "127.0.0.1").toString ();
|
||||
n1mm_server_port_ = settings_->value ("N1MMServerPort", 2333).toUInt ();
|
||||
@ -1641,6 +1675,8 @@ void Configuration::impl::write_settings ()
|
||||
settings_->setValue ("OpCall", opCall_);
|
||||
settings_->setValue ("UDPServer", udp_server_name_);
|
||||
settings_->setValue ("UDPServerPort", udp_server_port_);
|
||||
settings_->setValue ("UDPInterface", udp_interface_name_);
|
||||
settings_->setValue ("UDPTTL", udp_TTL_);
|
||||
settings_->setValue ("N1MMServer", n1mm_server_name_);
|
||||
settings_->setValue ("N1MMServerPort", n1mm_server_port_);
|
||||
settings_->setValue ("BroadcastToN1MM", broadcast_to_n1mm_);
|
||||
@ -1843,6 +1879,12 @@ bool Configuration::impl::validate ()
|
||||
return false;
|
||||
}
|
||||
|
||||
if (dns_lookup_id_ > -1)
|
||||
{
|
||||
MessageBox::information_message (this, tr ("Pending DNS lookup, please try again later"));
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
@ -2061,20 +2103,30 @@ void Configuration::impl::accept ()
|
||||
pwrBandTxMemory_ = ui_->checkBoxPwrBandTxMemory->isChecked ();
|
||||
pwrBandTuneMemory_ = ui_->checkBoxPwrBandTuneMemory->isChecked ();
|
||||
opCall_=ui_->opCallEntry->text();
|
||||
auto new_server = ui_->udp_server_line_edit->text ();
|
||||
if (new_server != udp_server_name_)
|
||||
|
||||
auto new_server = ui_->udp_server_line_edit->text ().trimmed ();
|
||||
auto new_interface = ui_->udp_interface_combo_box->currentData ().toString ();
|
||||
if (new_server != udp_server_name_ || new_interface != udp_interface_name_)
|
||||
{
|
||||
udp_server_name_ = new_server;
|
||||
Q_EMIT self_->udp_server_changed (new_server);
|
||||
udp_interface_name_ = new_interface;
|
||||
Q_EMIT self_->udp_server_changed (udp_server_name_, udp_interface_name_);
|
||||
}
|
||||
|
||||
auto new_port = ui_->udp_server_port_spin_box->value ();
|
||||
if (new_port != udp_server_port_)
|
||||
{
|
||||
udp_server_port_ = new_port;
|
||||
Q_EMIT self_->udp_server_port_changed (new_port);
|
||||
Q_EMIT self_->udp_server_port_changed (udp_server_port_);
|
||||
}
|
||||
|
||||
|
||||
auto new_TTL = ui_->udp_TTL_spin_box->value ();
|
||||
if (new_TTL != udp_TTL_)
|
||||
{
|
||||
udp_TTL_ = new_TTL;
|
||||
Q_EMIT self_->udp_TTL_changed (udp_TTL_);
|
||||
}
|
||||
|
||||
if (ui_->accept_udp_requests_check_box->isChecked () != accept_udp_requests_)
|
||||
{
|
||||
accept_udp_requests_ = ui_->accept_udp_requests_check_box->isChecked ();
|
||||
@ -2130,6 +2182,12 @@ void Configuration::impl::accept ()
|
||||
|
||||
void Configuration::impl::reject ()
|
||||
{
|
||||
if (dns_lookup_id_ > -1)
|
||||
{
|
||||
QHostInfo::abortHostLookup (dns_lookup_id_);
|
||||
dns_lookup_id_ = -1;
|
||||
}
|
||||
|
||||
initialize_models (); // reverts to settings as at exec ()
|
||||
|
||||
// check if the Transceiver instance changed, in which case we need
|
||||
@ -2344,6 +2402,72 @@ void Configuration::impl::on_add_macro_push_button_clicked (bool /* checked */)
|
||||
}
|
||||
}
|
||||
|
||||
void Configuration::impl::on_udp_server_line_edit_textChanged (QString const&)
|
||||
{
|
||||
udp_server_name_edited_ = true;
|
||||
}
|
||||
|
||||
void Configuration::impl::on_udp_server_line_edit_editingFinished ()
|
||||
{
|
||||
if (udp_server_name_edited_)
|
||||
{
|
||||
auto const& server = ui_->udp_server_line_edit->text ().trimmed ();
|
||||
QHostAddress ha {server};
|
||||
if (server.size () && ha.isNull ())
|
||||
{
|
||||
// queue a host address lookup
|
||||
qDebug () << "server host DNS lookup:" << server;
|
||||
#if QT_VERSION >= QT_VERSION_CHECK(5, 9, 0)
|
||||
dns_lookup_id_ = QHostInfo::lookupHost (server, this, &Configuration::impl::host_info_results);
|
||||
#else
|
||||
dns_lookup_id_ = QHostInfo::lookupHost (server, this, SLOT (host_info_results (QHostInfo)));
|
||||
#endif
|
||||
}
|
||||
else
|
||||
{
|
||||
check_multicast (ha);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void Configuration::impl::host_info_results (QHostInfo host_info)
|
||||
{
|
||||
if (host_info.lookupId () != dns_lookup_id_) return;
|
||||
dns_lookup_id_ = -1;
|
||||
if (QHostInfo::NoError != host_info.error ())
|
||||
{
|
||||
MessageBox::critical_message (this, tr ("UDP server DNS lookup failed"), host_info.errorString ());
|
||||
}
|
||||
else
|
||||
{
|
||||
auto const& server_addresses = host_info.addresses ();
|
||||
qDebug () << "message server addresses:" << server_addresses;
|
||||
if (server_addresses.size ())
|
||||
{
|
||||
check_multicast (server_addresses[0]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void Configuration::impl::check_multicast (QHostAddress const& ha)
|
||||
{
|
||||
auto is_multicast = is_multicast_address (ha);
|
||||
ui_->udp_interface_label->setVisible (is_multicast);
|
||||
ui_->udp_interface_combo_box->setVisible (is_multicast);
|
||||
ui_->udp_TTL_label->setVisible (is_multicast);
|
||||
ui_->udp_TTL_spin_box->setVisible (is_multicast);
|
||||
if (isVisible ())
|
||||
{
|
||||
if (is_MAC_ambiguous_multicast_address (ha))
|
||||
{
|
||||
MessageBox::warning_message (this, tr ("MAC-ambiguous multicast groups addresses not supported"));
|
||||
find_tab (ui_->udp_server_line_edit);
|
||||
ui_->udp_server_line_edit->clear ();
|
||||
}
|
||||
}
|
||||
udp_server_name_edited_ = false;
|
||||
}
|
||||
|
||||
void Configuration::impl::delete_frequencies ()
|
||||
{
|
||||
auto selection_model = ui_->frequencies_table_view->selectionModel ();
|
||||
@ -2868,6 +2992,31 @@ void Configuration::impl::load_audio_devices (QAudio::Mode mode, QComboBox * com
|
||||
combo_box->setCurrentIndex (current_index);
|
||||
}
|
||||
|
||||
// load the available network interfaces into the selection combo box
|
||||
void Configuration::impl::load_network_interfaces (QComboBox * combo_box, QString const& current)
|
||||
{
|
||||
combo_box->clear ();
|
||||
int current_index = -1;
|
||||
for (auto const& interface : QNetworkInterface::allInterfaces ())
|
||||
{
|
||||
if (interface.flags () & QNetworkInterface::IsUp)
|
||||
{
|
||||
auto const& name = interface.name ();
|
||||
combo_box->addItem (interface.humanReadableName (), name);
|
||||
// select the first loopback interface as a default to
|
||||
// discourage spamming the network (possibly the Internet),
|
||||
// particularly important with administratively scoped
|
||||
// multicast UDP
|
||||
if (name == current
|
||||
|| (!current.size () && (interface.flags () & QNetworkInterface::IsLoopBack)))
|
||||
{
|
||||
current_index = combo_box->count () - 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
combo_box->setCurrentIndex (current_index);
|
||||
}
|
||||
|
||||
// enable only the channels that are supported by the selected audio device
|
||||
void Configuration::impl::update_audio_channels (QComboBox const * source_combo_box, int index, QComboBox * combo_box, bool allow_both)
|
||||
{
|
||||
|
@ -21,7 +21,6 @@ class Bands;
|
||||
class FrequencyList_v2;
|
||||
class StationList;
|
||||
class QStringListModel;
|
||||
class QHostAddress;
|
||||
class LotWUsers;
|
||||
class DecodeHighlightingModel;
|
||||
class LogBook;
|
||||
@ -152,6 +151,8 @@ public:
|
||||
void opCall (QString const&);
|
||||
QString udp_server_name () const;
|
||||
port_type udp_server_port () const;
|
||||
QString udp_interface_name () const;
|
||||
int udp_TTL () const;
|
||||
QString n1mm_server_name () const;
|
||||
port_type n1mm_server_port () const;
|
||||
bool valid_n1mm_info () const;
|
||||
@ -273,8 +274,9 @@ public:
|
||||
//
|
||||
// This signal is emitted when the UDP server changes
|
||||
//
|
||||
Q_SIGNAL void udp_server_changed (QString const& udp_server) const;
|
||||
Q_SIGNAL void udp_server_changed (QString& udp_server_name, QString const& network_interface) const;
|
||||
Q_SIGNAL void udp_server_port_changed (port_type server_port) const;
|
||||
Q_SIGNAL void udp_TTL_changed (int TTL) const;
|
||||
Q_SIGNAL void accept_udp_requests_changed (bool checked) const;
|
||||
|
||||
// signal updates to decode highlighting
|
||||
|
@ -7,7 +7,7 @@
|
||||
<x>0</x>
|
||||
<y>0</y>
|
||||
<width>554</width>
|
||||
<height>556</height>
|
||||
<height>560</height>
|
||||
</rect>
|
||||
</property>
|
||||
<property name="windowTitle">
|
||||
@ -1864,12 +1864,6 @@ and DX Grid fields when a 73 or free text message is sent.</string>
|
||||
</item>
|
||||
<item row="0" column="1">
|
||||
<widget class="QLineEdit" name="udp_server_line_edit">
|
||||
<property name="sizePolicy">
|
||||
<sizepolicy hsizetype="Preferred" vsizetype="Fixed">
|
||||
<horstretch>0</horstretch>
|
||||
<verstretch>0</verstretch>
|
||||
</sizepolicy>
|
||||
</property>
|
||||
<property name="toolTip">
|
||||
<string><html><head/><body><p>Optional hostname of network service to receive decodes.</p><p>Formats:</p><ul style="margin-top: 0px; margin-bottom: 0px; margin-left: 0px; margin-right: 0px; -qt-list-indent: 1;"><li style=" margin-top:12px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;">hostname</li><li style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;">IPv4 address</li><li style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;">IPv6 address</li><li style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;">IPv4 multicast group address</li><li style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;">IPv6 multicast group address</li></ul><p>Clearing this field will disable the broadcasting of UDP status updates.</p></body></html></string>
|
||||
</property>
|
||||
@ -1898,6 +1892,39 @@ and DX Grid fields when a 73 or free text message is sent.</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="2" column="0">
|
||||
<widget class="QLabel" name="udp_interface_label">
|
||||
<property name="text">
|
||||
<string>Outgoing interface:</string>
|
||||
</property>
|
||||
<property name="buddy">
|
||||
<cstring>udp_interface_combo_box</cstring>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="2" column="1">
|
||||
<widget class="LazyFillComboBox" name="udp_interface_combo_box"/>
|
||||
</item>
|
||||
<item row="3" column="1">
|
||||
<widget class="QSpinBox" name="udp_TTL_spin_box">
|
||||
<property name="maximum">
|
||||
<number>255</number>
|
||||
</property>
|
||||
<property name="value">
|
||||
<number>1</number>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="3" column="0">
|
||||
<widget class="QLabel" name="udp_TTL_label">
|
||||
<property name="text">
|
||||
<string>Multicast TTL:</string>
|
||||
</property>
|
||||
<property name="buddy">
|
||||
<cstring>udp_TTL_spin_box</cstring>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</item>
|
||||
<item row="0" column="1">
|
||||
@ -3084,6 +3111,8 @@ Right click for insert and delete options.</string>
|
||||
<tabstop>psk_reporter_tcpip_check_box</tabstop>
|
||||
<tabstop>udp_server_line_edit</tabstop>
|
||||
<tabstop>udp_server_port_spin_box</tabstop>
|
||||
<tabstop>udp_interface_combo_box</tabstop>
|
||||
<tabstop>udp_TTL_spin_box</tabstop>
|
||||
<tabstop>accept_udp_requests_check_box</tabstop>
|
||||
<tabstop>udpWindowToFront</tabstop>
|
||||
<tabstop>udpWindowRestore</tabstop>
|
||||
@ -3101,8 +3130,8 @@ Right click for insert and delete options.</string>
|
||||
<tabstop>include_WAE_check_box</tabstop>
|
||||
<tabstop>rescan_log_push_button</tabstop>
|
||||
<tabstop>LotW_CSV_URL_line_edit</tabstop>
|
||||
<tabstop>LotW_days_since_upload_spin_box</tabstop>
|
||||
<tabstop>LotW_CSV_fetch_push_button</tabstop>
|
||||
<tabstop>LotW_days_since_upload_spin_box</tabstop>
|
||||
<tabstop>sbNtrials</tabstop>
|
||||
<tabstop>sbAggressive</tabstop>
|
||||
<tabstop>cbTwoPass</tabstop>
|
||||
@ -3118,11 +3147,11 @@ Right click for insert and delete options.</string>
|
||||
<tabstop>rbHound</tabstop>
|
||||
<tabstop>rbNA_VHF_Contest</tabstop>
|
||||
<tabstop>rbField_Day</tabstop>
|
||||
<tabstop>Field_Day_Exchange</tabstop>
|
||||
<tabstop>rbEU_VHF_Contest</tabstop>
|
||||
<tabstop>rbRTTY_Roundup</tabstop>
|
||||
<tabstop>RTTY_Exchange</tabstop>
|
||||
<tabstop>rbWW_DIGI</tabstop>
|
||||
<tabstop>Field_Day_Exchange</tabstop>
|
||||
<tabstop>RTTY_Exchange</tabstop>
|
||||
</tabstops>
|
||||
<resources/>
|
||||
<connections>
|
||||
@ -3192,13 +3221,13 @@ Right click for insert and delete options.</string>
|
||||
</connection>
|
||||
</connections>
|
||||
<buttongroups>
|
||||
<buttongroup name="PTT_method_button_group"/>
|
||||
<buttongroup name="CAT_data_bits_button_group"/>
|
||||
<buttongroup name="special_op_activity_button_group"/>
|
||||
<buttongroup name="CAT_stop_bits_button_group"/>
|
||||
<buttongroup name="TX_audio_source_button_group"/>
|
||||
<buttongroup name="split_mode_button_group"/>
|
||||
<buttongroup name="TX_mode_button_group"/>
|
||||
<buttongroup name="CAT_stop_bits_button_group"/>
|
||||
<buttongroup name="special_op_activity_button_group"/>
|
||||
<buttongroup name="CAT_handshake_button_group"/>
|
||||
<buttongroup name="CAT_data_bits_button_group"/>
|
||||
<buttongroup name="TX_mode_button_group"/>
|
||||
<buttongroup name="PTT_method_button_group"/>
|
||||
<buttongroup name="TX_audio_source_button_group"/>
|
||||
</buttongroups>
|
||||
</ui>
|
||||
|
@ -6,16 +6,16 @@
|
||||
#include <limits>
|
||||
|
||||
#include <QUdpSocket>
|
||||
#include <QNetworkInterface>
|
||||
#include <QHostInfo>
|
||||
#include <QTimer>
|
||||
#include <QQueue>
|
||||
#include <QByteArray>
|
||||
#include <QHostAddress>
|
||||
#include <QColor>
|
||||
#include <QDebug>
|
||||
|
||||
#include "NetworkMessage.hpp"
|
||||
|
||||
#include "qt_helpers.hpp"
|
||||
#include "pimpl_impl.hpp"
|
||||
|
||||
#include "moc_MessageClient.cpp"
|
||||
@ -34,14 +34,15 @@ class MessageClient::impl
|
||||
|
||||
public:
|
||||
impl (QString const& id, QString const& version, QString const& revision,
|
||||
port_type server_port, MessageClient * self)
|
||||
port_type server_port, int TTL, MessageClient * self)
|
||||
: self_ {self}
|
||||
, dns_lookup_id_ {0}
|
||||
, enabled_ {false}
|
||||
, id_ {id}
|
||||
, version_ {version}
|
||||
, revision_ {revision}
|
||||
, dns_lookup_id_ {-1}
|
||||
, server_port_ {server_port}
|
||||
, TTL_ {TTL}
|
||||
, schema_ {2} // use 2 prior to negotiation not 1 which is broken
|
||||
, heartbeat_timer_ {new QTimer {this}}
|
||||
{
|
||||
@ -49,9 +50,6 @@ public:
|
||||
connect (this, &QIODevice::readyRead, this, &impl::pending_datagrams);
|
||||
|
||||
heartbeat_timer_->start (NetworkMessage::pulse * 1000);
|
||||
|
||||
// bind to an ephemeral port
|
||||
bind ();
|
||||
}
|
||||
|
||||
~impl ()
|
||||
@ -61,7 +59,10 @@ public:
|
||||
|
||||
enum StreamStatus {Fail, Short, OK};
|
||||
|
||||
void parse_message (QByteArray const& msg);
|
||||
void set_server (QString const& server_name, QString const& network_interface_name);
|
||||
Q_SLOT void host_info_results (QHostInfo);
|
||||
void start ();
|
||||
void parse_message (QByteArray const&);
|
||||
void pending_datagrams ();
|
||||
void heartbeat ();
|
||||
void closedown ();
|
||||
@ -69,27 +70,26 @@ public:
|
||||
void send_message (QByteArray const&);
|
||||
void send_message (QDataStream const& out, QByteArray const& message)
|
||||
{
|
||||
if (OK == check_status (out))
|
||||
{
|
||||
send_message (message);
|
||||
}
|
||||
else
|
||||
{
|
||||
Q_EMIT self_->error ("Error creating UDP message");
|
||||
}
|
||||
if (OK == check_status (out))
|
||||
{
|
||||
send_message (message);
|
||||
}
|
||||
else
|
||||
{
|
||||
Q_EMIT self_->error ("Error creating UDP message");
|
||||
}
|
||||
}
|
||||
|
||||
Q_SLOT void host_info_results (QHostInfo);
|
||||
|
||||
MessageClient * self_;
|
||||
int dns_lookup_id_;
|
||||
bool enabled_;
|
||||
QString id_;
|
||||
QString version_;
|
||||
QString revision_;
|
||||
QString server_string_;
|
||||
port_type server_port_;
|
||||
int dns_lookup_id_;
|
||||
QHostAddress server_;
|
||||
port_type server_port_;
|
||||
int TTL_;
|
||||
QNetworkInterface network_interface_;
|
||||
quint32 schema_;
|
||||
QTimer * heartbeat_timer_;
|
||||
std::vector<QHostAddress> blocked_addresses_;
|
||||
@ -101,37 +101,117 @@ public:
|
||||
|
||||
#include "MessageClient.moc"
|
||||
|
||||
void MessageClient::impl::set_server (QString const& server_name, QString const& network_interface_name)
|
||||
{
|
||||
server_.setAddress (server_name);
|
||||
network_interface_ = QNetworkInterface::interfaceFromName (network_interface_name);
|
||||
if (server_.isNull () && server_name.size ()) // DNS lookup required
|
||||
{
|
||||
// queue a host address lookup
|
||||
#if QT_VERSION >= QT_VERSION_CHECK(5, 9, 0)
|
||||
dns_lookup_id_ = QHostInfo::lookupHost (server_name, this, &MessageClient::impl::host_info_results);
|
||||
#else
|
||||
dns_lookup_id_ = QHostInfo::lookupHost (server_name, this, SLOT (host_info_results (QHostInfo)));
|
||||
#endif
|
||||
}
|
||||
else
|
||||
{
|
||||
start ();
|
||||
}
|
||||
}
|
||||
|
||||
void MessageClient::impl::host_info_results (QHostInfo host_info)
|
||||
{
|
||||
if (host_info.lookupId () != dns_lookup_id_) return;
|
||||
dns_lookup_id_ = -1;
|
||||
if (QHostInfo::NoError != host_info.error ())
|
||||
{
|
||||
Q_EMIT self_->error ("UDP server lookup failed:\n" + host_info.errorString ());
|
||||
pending_messages_.clear (); // discard
|
||||
Q_EMIT self_->error ("UDP server DNS lookup failed: " + host_info.errorString ());
|
||||
}
|
||||
else if (host_info.addresses ().size ())
|
||||
else
|
||||
{
|
||||
auto server = host_info.addresses ()[0];
|
||||
if (blocked_addresses_.end () == std::find (blocked_addresses_.begin (), blocked_addresses_.end (), server))
|
||||
auto const& server_addresses = host_info.addresses ();
|
||||
if (server_addresses.size ())
|
||||
{
|
||||
server_ = server;
|
||||
TRACE_UDP ("resulting server:" << server);
|
||||
server_ = server_addresses[0];
|
||||
}
|
||||
}
|
||||
start ();
|
||||
}
|
||||
|
||||
// send initial heartbeat which allows schema negotiation
|
||||
heartbeat ();
|
||||
void MessageClient::impl::start ()
|
||||
{
|
||||
if (server_.isNull ())
|
||||
{
|
||||
Q_EMIT self_->close ();
|
||||
pending_messages_.clear (); // discard
|
||||
return;
|
||||
}
|
||||
|
||||
// clear any backlog
|
||||
while (pending_messages_.size ())
|
||||
if (blocked_addresses_.end () != std::find (blocked_addresses_.begin (), blocked_addresses_.end (), server_))
|
||||
{
|
||||
Q_EMIT self_->error ("UDP server blocked, please try another");
|
||||
pending_messages_.clear (); // discard
|
||||
return;
|
||||
}
|
||||
|
||||
TRACE_UDP ("Trying server:" << server_.toString () << "on interface:" << network_interface_.humanReadableName ());
|
||||
QHostAddress interface_ip {QHostAddress::Any};
|
||||
if (network_interface_.isValid ())
|
||||
{
|
||||
if (is_multicast_address (server_) && !(network_interface_.flags () & QNetworkInterface::CanMulticast))
|
||||
{
|
||||
Q_EMIT self_->error ("Network interface is not multicast capable, please try another");
|
||||
return;
|
||||
}
|
||||
for (auto const& ae : network_interface_.addressEntries ())
|
||||
{
|
||||
auto const& ip = ae.ip ();
|
||||
if (server_.protocol () == ip.protocol ())
|
||||
{
|
||||
send_message (pending_messages_.dequeue ());
|
||||
interface_ip = ip;
|
||||
break;
|
||||
}
|
||||
}
|
||||
else
|
||||
if (QHostAddress {QHostAddress::Any} == interface_ip)
|
||||
{
|
||||
Q_EMIT self_->error ("UDP server blocked, please try another");
|
||||
Q_EMIT self_->error ("Network interface has no suitable address for server IP protocol, please try another");
|
||||
pending_messages_.clear (); // discard
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
if (server_.isBroadcast ())
|
||||
{
|
||||
// only allow broadcast on the loopback interface to avoid
|
||||
// flooding the local subnet which may be large with some ISPs
|
||||
//interface_ip.setAddress ("127.0.0.1");
|
||||
}
|
||||
|
||||
if (localAddress () != interface_ip)
|
||||
{
|
||||
if (UnconnectedState != state () || state ())
|
||||
{
|
||||
close ();
|
||||
}
|
||||
// bind to an ephemeral port on the selected interface and set
|
||||
// up for sending datagrams
|
||||
bind (interface_ip);
|
||||
setMulticastInterface (network_interface_);
|
||||
|
||||
// set multicast TTL to limit scope when sending to multicast
|
||||
// group addresses
|
||||
setSocketOption (MulticastTtlOption, TTL_);
|
||||
}
|
||||
|
||||
// send initial heartbeat which allows schema negotiation
|
||||
heartbeat ();
|
||||
|
||||
// clear any backlog
|
||||
while (pending_messages_.size ())
|
||||
{
|
||||
send_message (pending_messages_.dequeue ());
|
||||
}
|
||||
}
|
||||
|
||||
void MessageClient::impl::pending_datagrams ()
|
||||
@ -428,9 +508,11 @@ auto MessageClient::impl::check_status (QDataStream const& stream) const -> Stre
|
||||
}
|
||||
|
||||
MessageClient::MessageClient (QString const& id, QString const& version, QString const& revision,
|
||||
QString const& server, port_type server_port, QObject * self)
|
||||
QString const& server_name, port_type server_port,
|
||||
QString const& network_interface_name,
|
||||
int TTL, QObject * self)
|
||||
: QObject {self}
|
||||
, m_ {id, version, revision, server_port, this}
|
||||
, m_ {id, version, revision, server_port, TTL, this}
|
||||
{
|
||||
connect (&*m_
|
||||
#if QT_VERSION < QT_VERSION_CHECK(5, 15, 0)
|
||||
@ -449,8 +531,8 @@ MessageClient::MessageClient (QString const& id, QString const& version, QString
|
||||
#endif
|
||||
Q_EMIT error (m_->errorString ());
|
||||
}
|
||||
});
|
||||
set_server (server);
|
||||
});
|
||||
m_->set_server (server_name, network_interface_name);
|
||||
}
|
||||
|
||||
QHostAddress MessageClient::server_address () const
|
||||
@ -463,20 +545,9 @@ auto MessageClient::server_port () const -> port_type
|
||||
return m_->server_port_;
|
||||
}
|
||||
|
||||
void MessageClient::set_server (QString const& server)
|
||||
void MessageClient::set_server (QString const& server_name, QString const& network_interface_name)
|
||||
{
|
||||
m_->server_.clear ();
|
||||
m_->server_string_ = server;
|
||||
if (server.size ())
|
||||
{
|
||||
// queue a host address lookup
|
||||
TRACE_UDP ("server host DNS lookup:" << server);
|
||||
#if QT_VERSION >= QT_VERSION_CHECK(5, 9, 0)
|
||||
m_->dns_lookup_id_ = QHostInfo::lookupHost (server, &*m_, &MessageClient::impl::host_info_results);
|
||||
#else
|
||||
m_->dns_lookup_id_ = QHostInfo::lookupHost (server, &*m_, SLOT (host_info_results (QHostInfo)));
|
||||
#endif
|
||||
}
|
||||
m_->set_server (server_name, network_interface_name);
|
||||
}
|
||||
|
||||
void MessageClient::set_server_port (port_type server_port)
|
||||
@ -484,6 +555,12 @@ void MessageClient::set_server_port (port_type server_port)
|
||||
m_->server_port_ = server_port;
|
||||
}
|
||||
|
||||
void MessageClient::set_TTL (int TTL)
|
||||
{
|
||||
m_->TTL_ = TTL;
|
||||
m_->setSocketOption (QAbstractSocket::MulticastTtlOption, m_->TTL_);
|
||||
}
|
||||
|
||||
void MessageClient::enable (bool flag)
|
||||
{
|
||||
m_->enabled_ = flag;
|
||||
@ -499,7 +576,7 @@ void MessageClient::status_update (Frequency f, QString const& mode, QString con
|
||||
, quint32 frequency_tolerance, quint32 tr_period
|
||||
, QString const& configuration_name)
|
||||
{
|
||||
if (m_->server_port_ && !m_->server_string_.isEmpty ())
|
||||
if (m_->server_port_ && !m_->server_.isNull ())
|
||||
{
|
||||
QByteArray message;
|
||||
NetworkMessage::Builder out {&message, NetworkMessage::Status, m_->id_, m_->schema_};
|
||||
@ -516,7 +593,7 @@ void MessageClient::decode (bool is_new, QTime time, qint32 snr, float delta_tim
|
||||
, QString const& mode, QString const& message_text, bool low_confidence
|
||||
, bool off_air)
|
||||
{
|
||||
if (m_->server_port_ && !m_->server_string_.isEmpty ())
|
||||
if (m_->server_port_ && !m_->server_.isNull ())
|
||||
{
|
||||
QByteArray message;
|
||||
NetworkMessage::Builder out {&message, NetworkMessage::Decode, m_->id_, m_->schema_};
|
||||
@ -531,7 +608,7 @@ void MessageClient::WSPR_decode (bool is_new, QTime time, qint32 snr, float delt
|
||||
, qint32 drift, QString const& callsign, QString const& grid, qint32 power
|
||||
, bool off_air)
|
||||
{
|
||||
if (m_->server_port_ && !m_->server_string_.isEmpty ())
|
||||
if (m_->server_port_ && !m_->server_.isNull ())
|
||||
{
|
||||
QByteArray message;
|
||||
NetworkMessage::Builder out {&message, NetworkMessage::WSPRDecode, m_->id_, m_->schema_};
|
||||
@ -544,7 +621,7 @@ void MessageClient::WSPR_decode (bool is_new, QTime time, qint32 snr, float delt
|
||||
|
||||
void MessageClient::decodes_cleared ()
|
||||
{
|
||||
if (m_->server_port_ && !m_->server_string_.isEmpty ())
|
||||
if (m_->server_port_ && !m_->server_.isNull ())
|
||||
{
|
||||
QByteArray message;
|
||||
NetworkMessage::Builder out {&message, NetworkMessage::Clear, m_->id_, m_->schema_};
|
||||
@ -561,7 +638,7 @@ void MessageClient::qso_logged (QDateTime time_off, QString const& dx_call, QStr
|
||||
, QString const& my_grid, QString const& exchange_sent
|
||||
, QString const& exchange_rcvd, QString const& propmode)
|
||||
{
|
||||
if (m_->server_port_ && !m_->server_string_.isEmpty ())
|
||||
if (m_->server_port_ && !m_->server_.isNull ())
|
||||
{
|
||||
QByteArray message;
|
||||
NetworkMessage::Builder out {&message, NetworkMessage::QSOLogged, m_->id_, m_->schema_};
|
||||
@ -576,7 +653,7 @@ void MessageClient::qso_logged (QDateTime time_off, QString const& dx_call, QStr
|
||||
|
||||
void MessageClient::logged_ADIF (QByteArray const& ADIF_record)
|
||||
{
|
||||
if (m_->server_port_ && !m_->server_string_.isEmpty ())
|
||||
if (m_->server_port_ && !m_->server_.isNull ())
|
||||
{
|
||||
QByteArray message;
|
||||
NetworkMessage::Builder out {&message, NetworkMessage::LoggedADIF, m_->id_, m_->schema_};
|
||||
|
@ -5,6 +5,7 @@
|
||||
#include <QTime>
|
||||
#include <QDateTime>
|
||||
#include <QString>
|
||||
#include <QHostAddress>
|
||||
|
||||
#include "Radio.hpp"
|
||||
#include "pimpl_h.hpp"
|
||||
@ -34,19 +35,25 @@ public:
|
||||
//
|
||||
// messages will be silently dropped until a server host lookup is complete
|
||||
MessageClient (QString const& id, QString const& version, QString const& revision,
|
||||
QString const& server, port_type server_port, QObject * parent = nullptr);
|
||||
QString const& server_name, port_type server_port,
|
||||
QString const& network_interface_name,
|
||||
int TTL, QObject * parent = nullptr);
|
||||
|
||||
// query server details
|
||||
QHostAddress server_address () const;
|
||||
port_type server_port () const;
|
||||
|
||||
// initiate a new server host lookup or is the server name is empty
|
||||
// the sending of messages is disabled
|
||||
Q_SLOT void set_server (QString const& server = QString {});
|
||||
// initiate a new server host lookup or if the server name is empty
|
||||
// the sending of messages is disabled, if an interface is specified
|
||||
// then that interface is used for outgoing datagrams
|
||||
Q_SLOT void set_server (QString const& server_name, QString const& network_interface_name);
|
||||
|
||||
// change the server port messages are sent to
|
||||
Q_SLOT void set_server_port (port_type server_port = 0u);
|
||||
|
||||
// change the server port messages are sent to
|
||||
Q_SLOT void set_TTL (int TTL);
|
||||
|
||||
// enable incoming messages
|
||||
Q_SLOT void enable (bool);
|
||||
|
||||
|
@ -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 ();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -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);
|
||||
|
@ -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 ();
|
||||
}
|
||||
|
@ -117,6 +117,32 @@ namespace std
|
||||
}
|
||||
#endif
|
||||
|
||||
|
||||
inline
|
||||
bool is_multicast_address (QHostAddress const& host_addr)
|
||||
{
|
||||
#if QT_VERSION >= 0x050600
|
||||
return host_addr.isMulticast ();
|
||||
#else
|
||||
bool ok;
|
||||
return (((host_addr.toIPv4Address (&ok) & 0xf0000000u) == 0xe0000000u) && ok)
|
||||
|| host_addr.toIPv6Address ()[0] == 0xff;
|
||||
#endif
|
||||
}
|
||||
|
||||
inline
|
||||
bool is_MAC_ambiguous_multicast_address (QHostAddress const& host_addr)
|
||||
{
|
||||
// sub-ranges 224.128.0.0/24, 225.0.0.0/24, 225.128.0.0/24,
|
||||
// 226.0.0.0/24, 226.128.0.0/24, ..., 239.0.0.0/24, 239.128.0.0/24
|
||||
// are not supported as they are inefficient due to ambiguous
|
||||
// mappings to Ethernet MAC addresses. 224.0.0.0/24 alone is allowed
|
||||
// from these ranges
|
||||
bool ok;
|
||||
auto ipv4 = host_addr.toIPv4Address (&ok);
|
||||
return ok && !((ipv4 & 0xffffff00u) == 0xe0000000) && (ipv4 & 0xf07fff00) == 0xe0000000;
|
||||
}
|
||||
|
||||
// Register some useful Qt types with QMetaType
|
||||
Q_DECLARE_METATYPE (QHostAddress);
|
||||
|
||||
|
@ -131,6 +131,80 @@ private:
|
||||
QDateTime dt {QDate {2020, 8, 6}, QTime {14, 15, 22, 501}};
|
||||
QCOMPARE (qt_truncate_date_time_to (dt, 7500), QDateTime (QDate (2020, 8, 6), QTime (14, 15, 22, 500)));
|
||||
}
|
||||
|
||||
Q_SLOT void is_multicast_address_data ()
|
||||
{
|
||||
QTest::addColumn<QString> ("addr");
|
||||
QTest::addColumn<bool> ("result");
|
||||
|
||||
QTest::newRow ("loopback") << "127.0.0.1" << false;
|
||||
QTest::newRow ("looback IPv6") << "::1" << false;
|
||||
QTest::newRow ("lowest-") << "223.255.255.255" << false;
|
||||
QTest::newRow ("lowest") << "224.0.0.0" << true;
|
||||
QTest::newRow ("lowest- IPv6") << "feff:ffff:ffff:ffff:ffff:ffff:ffff:ffff" << false;
|
||||
QTest::newRow ("lowest IPv6") << "ff00::" << true;
|
||||
QTest::newRow ("highest") << "239.255.255.255" << true;
|
||||
QTest::newRow ("highest+") << "240.0.0.0" << false;
|
||||
QTest::newRow ("highest IPv6") << "ffff:ffff:ffff:ffff:ffff:ffff:ffff:ffff" << true;
|
||||
}
|
||||
|
||||
Q_SLOT void is_multicast_address ()
|
||||
{
|
||||
QFETCH (QString, addr);
|
||||
QFETCH (bool, result);
|
||||
|
||||
QCOMPARE (::is_multicast_address (QHostAddress {addr}), result);
|
||||
}
|
||||
|
||||
Q_SLOT void is_MAC_ambiguous_multicast_address_data ()
|
||||
{
|
||||
QTest::addColumn<QString> ("addr");
|
||||
QTest::addColumn<bool> ("result");
|
||||
|
||||
QTest::newRow ("loopback") << "127.0.0.1" << false;
|
||||
QTest::newRow ("looback IPv6") << "::1" << false;
|
||||
|
||||
QTest::newRow ("lowest- R1") << "223.255.255.255" << false;
|
||||
QTest::newRow ("lowest R1") << "224.0.0.0" << false;
|
||||
QTest::newRow ("highest R1") << "224.0.0.255" << false;
|
||||
QTest::newRow ("highest+ R1") << "224.0.1.0" << false;
|
||||
QTest::newRow ("lowest- R1A") << "224.127.255.255" << false;
|
||||
QTest::newRow ("lowest R1A") << "224.128.0.0" << true;
|
||||
QTest::newRow ("highest R1A") << "224.128.0.255" << true;
|
||||
QTest::newRow ("highest+ R1A") << "224.128.1.0" << false;
|
||||
|
||||
QTest::newRow ("lowest- R2") << "224.255.255.255" << false;
|
||||
QTest::newRow ("lowest R2") << "225.0.0.0" << true;
|
||||
QTest::newRow ("highest R2") << "225.0.0.255" << true;
|
||||
QTest::newRow ("highest+ R2") << "225.0.1.0" << false;
|
||||
QTest::newRow ("lowest- R2A") << "225.127.255.255" << false;
|
||||
QTest::newRow ("lowest R2A") << "225.128.0.0" << true;
|
||||
QTest::newRow ("highest R2A") << "225.128.0.255" << true;
|
||||
QTest::newRow ("highest+ R2A") << "225.128.1.0" << false;
|
||||
|
||||
QTest::newRow ("lowest- R3") << "238.255.255.255" << false;
|
||||
QTest::newRow ("lowest R3") << "239.0.0.0" << true;
|
||||
QTest::newRow ("highest R3") << "239.0.0.255" << true;
|
||||
QTest::newRow ("highest+ R3") << "239.0.1.0" << false;
|
||||
QTest::newRow ("lowest- R3A") << "239.127.255.255" << false;
|
||||
QTest::newRow ("lowest R3A") << "239.128.0.0" << true;
|
||||
QTest::newRow ("highest R3A") << "239.128.0.255" << true;
|
||||
QTest::newRow ("highest+ R3A") << "239.128.1.0" << false;
|
||||
|
||||
QTest::newRow ("lowest- IPv6") << "feff:ffff:ffff:ffff:ffff:ffff:ffff:ffff" << false;
|
||||
QTest::newRow ("lowest IPv6") << "ff00::" << false;
|
||||
QTest::newRow ("highest") << "239.255.255.255" << false;
|
||||
QTest::newRow ("highest+") << "240.0.0.0" << false;
|
||||
QTest::newRow ("highest IPv6") << "ffff:ffff:ffff:ffff:ffff:ffff:ffff:ffff" << false;
|
||||
}
|
||||
|
||||
Q_SLOT void is_MAC_ambiguous_multicast_address ()
|
||||
{
|
||||
QFETCH (QString, addr);
|
||||
QFETCH (bool, result);
|
||||
|
||||
QCOMPARE (::is_MAC_ambiguous_multicast_address (QHostAddress {addr}), result);
|
||||
}
|
||||
};
|
||||
|
||||
QTEST_MAIN (TestQtHelpers);
|
||||
|
@ -417,6 +417,7 @@ MainWindow::MainWindow(QDir const& temp_directory, bool multiple,
|
||||
m_messageClient {new MessageClient {QApplication::applicationName (),
|
||||
version (), revision (),
|
||||
m_config.udp_server_name (), m_config.udp_server_port (),
|
||||
m_config.udp_interface_name (), m_config.udp_TTL (),
|
||||
this}},
|
||||
m_psk_Reporter {&m_config, QString {"WSJT-X v" + version () + " " + m_revision}.simplified ()},
|
||||
m_manual {&m_network_manager},
|
||||
@ -785,6 +786,7 @@ MainWindow::MainWindow(QDir const& temp_directory, bool multiple,
|
||||
connect (&m_config, &Configuration::transceiver_failure, this, &MainWindow::handle_transceiver_failure);
|
||||
connect (&m_config, &Configuration::udp_server_changed, m_messageClient, &MessageClient::set_server);
|
||||
connect (&m_config, &Configuration::udp_server_port_changed, m_messageClient, &MessageClient::set_server_port);
|
||||
connect (&m_config, &Configuration::udp_TTL_changed, m_messageClient, &MessageClient::set_TTL);
|
||||
connect (&m_config, &Configuration::accept_udp_requests_changed, m_messageClient, &MessageClient::enable);
|
||||
connect (&m_config, &Configuration::enumerating_audio_devices, [this] () {
|
||||
showStatusMessage (tr ("Enumerating audio devices"));
|
||||
@ -7835,7 +7837,7 @@ void MainWindow::networkError (QString const& e)
|
||||
, MessageBox::Cancel))
|
||||
{
|
||||
// retry server lookup
|
||||
m_messageClient->set_server (m_config.udp_server_name ());
|
||||
m_messageClient->set_server (m_config.udp_server_name (), m_config.udp_interface_name ());
|
||||
}
|
||||
}
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user