[ntcore] Add ability to announce mDNS for server (#8373)

We can use this as a new way of resolving addresses.
This commit is contained in:
Thad House
2025-12-16 22:26:56 -08:00
committed by GitHub
parent 7cb58962c5
commit e2d492ac3f
27 changed files with 133 additions and 56 deletions

View File

@@ -87,7 +87,7 @@ void bench() {
auto server = wpi::nt::CreateInstance();
// connect client and server
wpi::nt::StartServer(server, "bench.json", "127.0.0.1", 10000);
wpi::nt::StartServer(server, "bench.json", "127.0.0.1", "", 10000);
wpi::nt::StartClient(client, "client");
wpi::nt::SetServer(client, "127.0.0.1", 10000);
@@ -150,7 +150,7 @@ void bench2() {
auto server = wpi::nt::CreateInstance();
// connect client and server
wpi::nt::StartServer(server, "bench2.json", "127.0.0.1", 10000);
wpi::nt::StartServer(server, "bench2.json", "127.0.0.1", "", 10000);
wpi::nt::StartClient(client1, "client1");
wpi::nt::StartClient(client2, "client2");
wpi::nt::SetServer(client1, "127.0.0.1", 10000);
@@ -223,7 +223,7 @@ static std::uniform_real_distribution<double> dist;
void stress() {
auto server = wpi::nt::CreateInstance();
wpi::nt::StartServer(server, "stress.json", "127.0.0.1", 10000);
wpi::nt::StartServer(server, "stress.json", "127.0.0.1", "", 10000);
wpi::nt::SubscribeMultiple(server, {{std::string_view{}}});
using namespace std::chrono_literals;
@@ -344,7 +344,7 @@ void latency() {
auto server = wpi::nt::CreateInstance();
// connect client and server
wpi::nt::StartServer(server, "latency.json", "127.0.0.1", 10000);
wpi::nt::StartServer(server, "latency.json", "127.0.0.1", "", 10000);
wpi::nt::StartClient(client1, "client1");
wpi::nt::SetServer(client1, "127.0.0.1", 10000);
wpi::nt::StartClient(client2, "client2");

View File

@@ -891,7 +891,7 @@ public final class NetworkTableInstance implements AutoCloseable {
* @param persistFilename the name of the persist file to use
*/
public void startServer(String persistFilename) {
startServer(persistFilename, "");
startServer(persistFilename, "", "");
}
/**
@@ -901,18 +901,32 @@ public final class NetworkTableInstance implements AutoCloseable {
* @param listenAddress the address to listen on, or empty to listen on any address
*/
public void startServer(String persistFilename, String listenAddress) {
startServer(persistFilename, listenAddress, kDefaultPort);
startServer(persistFilename, listenAddress, "");
}
/**
* Starts a server using the specified filename, listening address, and port.
* Starts a server using the specified filename, listening address, and mdns service,
* using the default port.
*
* @param persistFilename the name of the persist file to use
* @param listenAddress the address to listen on, or empty to listen on any address
* @param mdnsService the mDNS service name to advertise, or empty to not advertise
*/
public void startServer(String persistFilename, String listenAddress, String mdnsService) {
startServer(persistFilename, listenAddress, mdnsService, kDefaultPort);
}
/**
* Starts a server using the specified filename, listening address, mdns service and port.
*
* @param persistFilename the name of the persist file to use
* @param listenAddress the address to listen on, or empty to listen on any address
* @param mdnsService the mDNS service name to advertise, or empty to not advertise
* @param port port to communicate over
*/
public void startServer(String persistFilename, String listenAddress, int port) {
NetworkTablesJNI.startServer(m_handle, persistFilename, listenAddress, port);
public void startServer(String persistFilename, String listenAddress, String mdnsService,
int port) {
NetworkTablesJNI.startServer(m_handle, persistFilename, listenAddress, mdnsService, port);
}
/** Stops the server if it is running. */

View File

@@ -864,10 +864,11 @@ public final class NetworkTablesJNI {
* @param inst NT instance handle.
* @param persistFilename the name of the persist file to use
* @param listenAddress the address to listen on, or empty to listen on any address
* @param mdnsService the mDNS service name to advertise, or empty to not advertise
* @param port port to communicate over
*/
public static native void startServer(
int inst, String persistFilename, String listenAddress, int port);
int inst, String persistFilename, String listenAddress, String mdnsService, int port);
/**
* Stops the server if it is running.

View File

@@ -1171,7 +1171,7 @@ public final class NetworkTableInstance implements AutoCloseable {
* @param persistFilename the name of the persist file to use
*/
public void startServer(String persistFilename) {
startServer(persistFilename, "");
startServer(persistFilename, "", "");
}
/**
@@ -1181,18 +1181,32 @@ public final class NetworkTableInstance implements AutoCloseable {
* @param listenAddress the address to listen on, or empty to listen on any address
*/
public void startServer(String persistFilename, String listenAddress) {
startServer(persistFilename, listenAddress, kDefaultPort);
startServer(persistFilename, listenAddress, "");
}
/**
* Starts a server using the specified filename, listening address, and port.
* Starts a server using the specified filename, listening address, and mdns service,
* using the default port.
*
* @param persistFilename the name of the persist file to use
* @param listenAddress the address to listen on, or empty to listen on any address
* @param mdnsService the mDNS service name to advertise, or empty to not advertise
*/
public void startServer(String persistFilename, String listenAddress, String mdnsService) {
startServer(persistFilename, listenAddress, mdnsService, kDefaultPort);
}
/**
* Starts a server using the specified filename, listening address, mdns service and port.
*
* @param persistFilename the name of the persist file to use
* @param listenAddress the address to listen on, or empty to listen on any address
* @param mdnsService the mDNS service name to advertise, or empty to not advertise
* @param port port to communicate over
*/
public void startServer(String persistFilename, String listenAddress, int port) {
NetworkTablesJNI.startServer(m_handle, persistFilename, listenAddress, port);
public void startServer(String persistFilename, String listenAddress, String mdnsService,
int port) {
NetworkTablesJNI.startServer(m_handle, persistFilename, listenAddress, mdnsService, port);
}
/** Stops the server if it is running. */

View File

@@ -1404,10 +1404,11 @@ public final class NetworkTablesJNI {
* @param inst NT instance handle.
* @param persistFilename the name of the persist file to use
* @param listenAddress the address to listen on, or empty to listen on any address
* @param mdnsService the mDNS service name to advertise, or empty to not advertise
* @param port port to communicate over
*/
public static native void startServer(
int inst, String persistFilename, String listenAddress, int port);
int inst, String persistFilename, String listenAddress, String mdnsService, int port);
/**
* Stops the server if it is running.

View File

@@ -103,16 +103,20 @@ void InstanceImpl::StopLocal() {
void InstanceImpl::StartServer(std::string_view persistFilename,
std::string_view listenAddress,
std::string_view mdnsService,
unsigned int port) {
std::scoped_lock lock{m_mutex};
if (networkMode != NT_NET_MODE_NONE) {
return;
}
m_networkServer = std::make_shared<NetworkServer>(
persistFilename, listenAddress, port, localStorage, connectionList,
logger, [this] {
persistFilename, listenAddress, mdnsService, port, localStorage,
connectionList, logger, [this](bool announcingmDNS) {
std::scoped_lock lock{m_mutex};
networkMode &= ~NT_NET_MODE_STARTING;
if (announcingmDNS) {
networkMode |= NT_NET_MODE_MDNS_ANNOUNCING;
}
});
networkMode = NT_NET_MODE_SERVER | NT_NET_MODE_STARTING;
listenerStorage.NotifyTimeSync({}, NT_EVENT_TIMESYNC, 0, 0, true);

View File

@@ -44,7 +44,8 @@ class InstanceImpl {
void StartLocal();
void StopLocal();
void StartServer(std::string_view persistFilename,
std::string_view listenAddress, unsigned int port);
std::string_view listenAddress, std::string_view mdnsService,
unsigned int port);
void StopServer();
void StartClient(std::string_view identity);
void StopClient();

View File

@@ -253,17 +253,19 @@ void NetworkServer::ServerConnection4::ProcessWsUpgrade() {
}
NetworkServer::NetworkServer(std::string_view persistentFilename,
std::string_view listenAddress, unsigned int port,
std::string_view listenAddress,
std::string_view mdnsService, unsigned int port,
net::ILocalStorage& localStorage,
IConnectionList& connList,
wpi::util::Logger& logger,
std::function<void()> initDone)
std::function<void(bool)> initDone)
: m_localStorage{localStorage},
m_connList{connList},
m_logger{logger},
m_initDone{std::move(initDone)},
m_persistentFilename{persistentFilename},
m_listenAddress{wpi::util::trim(listenAddress)},
m_mdnsService{wpi::util::trim(mdnsService)},
m_port{port},
m_serverImpl{logger},
m_localQueue{logger},
@@ -462,9 +464,23 @@ void NetworkServer::Init() {
tcp4->Listen();
}
bool announcingmDNS = false;
if (!m_mdnsService.empty()) {
m_mdnsAnnouncer.emplace(m_mdnsService, "_networktables._tcp", m_port);
if (!m_mdnsAnnouncer->HasImplementation()) {
WARN("mDNS service announcer not available; cannot announce '{}'",
m_mdnsService);
m_mdnsAnnouncer.reset();
} else {
m_mdnsAnnouncer->Start();
announcingmDNS = true;
INFO("mDNS announcing as service '{}' on port {}", m_mdnsService, m_port);
}
}
if (m_initDone) {
DEBUG4("NetworkServer initDone()");
m_initDone();
m_initDone(announcingmDNS);
m_initDone = nullptr;
}
}

View File

@@ -7,6 +7,7 @@
#include <atomic>
#include <functional>
#include <memory>
#include <optional>
#include <string>
#include <string_view>
#include <vector>
@@ -15,6 +16,7 @@
#include "net/Message.hpp"
#include "server/ServerImpl.hpp"
#include "wpi/net/EventLoopRunner.hpp"
#include "wpi/net/MulticastServiceAnnouncer.h"
#include "wpi/net/uv/Async.hpp"
#include "wpi/net/uv/Idle.hpp"
#include "wpi/net/uv/Timer.hpp"
@@ -35,9 +37,10 @@ class IConnectionList;
class NetworkServer {
public:
NetworkServer(std::string_view persistentFilename,
std::string_view listenAddress, unsigned int port,
net::ILocalStorage& localStorage, IConnectionList& connList,
wpi::util::Logger& logger, std::function<void()> initDone);
std::string_view listenAddress, std::string_view mdnsService,
unsigned int port, net::ILocalStorage& localStorage,
IConnectionList& connList, wpi::util::Logger& logger,
std::function<void(bool)> initDone);
~NetworkServer();
void FlushLocal();
@@ -57,13 +60,15 @@ class NetworkServer {
net::ILocalStorage& m_localStorage;
IConnectionList& m_connList;
wpi::util::Logger& m_logger;
std::function<void()> m_initDone;
std::function<void(bool)> m_initDone;
std::string m_persistentData;
std::string m_persistentFilename;
std::string m_listenAddress;
std::string m_mdnsService;
unsigned int m_port;
// used only from loop
std::optional<wpi::net::MulticastServiceAnnouncer> m_mdnsAnnouncer;
std::shared_ptr<wpi::net::uv::Timer> m_readLocalTimer;
std::shared_ptr<wpi::net::uv::Timer> m_savePersistentTimer;
std::shared_ptr<wpi::net::uv::Async<>> m_flushLocal;

View File

@@ -1178,12 +1178,12 @@ Java_org_wpilib_networktables_NetworkTablesJNI_stopLocal
/*
* Class: org_wpilib_networktables_NetworkTablesJNI
* Method: startServer
* Signature: (ILjava/lang/String;Ljava/lang/String;I)V
* Signature: (ILjava/lang/String;Ljava/lang/String;Ljava/lang/String;I)V
*/
JNIEXPORT void JNICALL
Java_org_wpilib_networktables_NetworkTablesJNI_startServer
(JNIEnv* env, jclass, jint inst, jstring persistFilename,
jstring listenAddress, jint port)
jstring listenAddress, jstring mdnsService, jint port)
{
if (!persistFilename) {
nullPointerEx.Throw(env, "persistFilename cannot be null");
@@ -1193,8 +1193,13 @@ Java_org_wpilib_networktables_NetworkTablesJNI_startServer
nullPointerEx.Throw(env, "listenAddress cannot be null");
return;
}
if (!mdnsService) {
nullPointerEx.Throw(env, "mdnsService cannot be null");
return;
}
wpi::nt::StartServer(inst, JStringRef{env, persistFilename}.str(),
JStringRef{env, listenAddress}.c_str(), port);
JStringRef{env, listenAddress}.c_str(),
JStringRef{env, mdnsService}.c_str(), port);
}
/*

View File

@@ -532,9 +532,10 @@ void NT_StopLocal(NT_Inst inst) {
void NT_StartServer(NT_Inst inst, const struct WPI_String* persist_filename,
const struct WPI_String* listen_address,
unsigned int port) {
const struct WPI_String* mdns_service, unsigned int port) {
wpi::nt::StartServer(inst, wpi::util::to_string_view(persist_filename),
wpi::util::to_string_view(listen_address), port);
wpi::util::to_string_view(listen_address),
wpi::util::to_string_view(mdns_service), port);
}
void NT_StopServer(NT_Inst inst) {

View File

@@ -633,9 +633,10 @@ void StopLocal(NT_Inst inst) {
}
void StartServer(NT_Inst inst, std::string_view persist_filename,
std::string_view listen_address, unsigned int port) {
std::string_view listen_address, std::string_view mdns_service,
unsigned int port) {
if (auto ii = InstanceImpl::GetTyped(inst, Handle::kInstance)) {
ii->StartServer(persist_filename, listen_address, port);
ii->StartServer(persist_filename, listen_address, mdns_service, port);
}
}

View File

@@ -607,12 +607,17 @@ class NetworkTableInstance final {
* @param listen_address the address to listen on, or an empty string to
* listen on any address. (UTF-8 string, null
* terminated)
* @param mdns_service the mDNS service name to announce, or an empty
* string to not announce via mDNS (UTF-8 string,
* null terminated)
* @param port port to communicate over
*/
void StartServer(std::string_view persist_filename = "networktables.json",
const char* listen_address = "",
std::string_view listen_address = "",
std::string_view mdns_service = "",
unsigned int port = kDefaultPort) {
::wpi::nt::StartServer(m_handle, persist_filename, listen_address, port);
::wpi::nt::StartServer(m_handle, persist_filename, listen_address,
mdns_service, port);
}
/**

View File

@@ -90,6 +90,7 @@ enum NT_NetworkMode {
NT_NET_MODE_CLIENT = 0x04, /* running in client mode */
NT_NET_MODE_STARTING = 0x08, /* flag for starting (either client or server) */
NT_NET_MODE_LOCAL = 0x10, /* running in local-only mode */
NT_NET_MODE_MDNS_ANNOUNCING = 0x20 /* mDNS is enabled and announcing */
};
/** Event notification flags. */
@@ -1116,10 +1117,14 @@ void NT_StopLocal(NT_Inst inst);
* @param listen_address the address to listen on, or an empty string to
* listen on any address. (UTF-8 string, null
* terminated)
* @param mdns_service the mDNS service name to advertise, or an empty
* string to not advertise via mDNS. (UTF-8 string,
* null terminated)
* @param port port to communicate over
*/
void NT_StartServer(NT_Inst inst, const struct WPI_String* persist_filename,
const struct WPI_String* listen_address, unsigned int port);
const struct WPI_String* listen_address,
const struct WPI_String* mdns_service, unsigned int port);
/**
* Stops the server if it is running.

View File

@@ -1057,10 +1057,14 @@ void StopLocal(NT_Inst inst);
* @param listen_address the address to listen on, or an empty string to
* listen on any address. (UTF-8 string, null
* terminated)
* @param mdns_service the mDNS service name to advertise, or an empty
* string to not advertise via mDNS. (UTF-8 string,
* null terminated)
* @param port port to communicate over
*/
void StartServer(NT_Inst inst, std::string_view persist_filename,
std::string_view listen_address, unsigned int port);
std::string_view listen_address, std::string_view mdns_service,
unsigned int port);
/**
* Stops the server if it is running.

View File

@@ -117,11 +117,11 @@ classes:
StopLocal:
StartServer:
cpp_code: |
[](NetworkTableInstance * self, std::string_view persist_filename, const char* listen_address,
unsigned int port) {
[](NetworkTableInstance * self, std::string_view persist_filename, std::string_view listen_address,
std::string_view mdns_service, unsigned int port) {
pyntcore::onInstanceStart(self);
py::gil_scoped_release release;
self->StartServer(persist_filename, listen_address, port);
self->StartServer(persist_filename, listen_address, mdns_service, port);
}
StopServer:
StopClient:
@@ -156,7 +156,7 @@ classes:
operator==:
StartClient:
inline_code: |
.def("configPythonLogging", [](NetworkTableInstance * self,
.def("configPythonLogging", [](NetworkTableInstance * self,
NetworkTableInstance::LogLevel minLevel, NetworkTableInstance::LogLevel maxLevel, py::str logName) {
py::module::import("ntcore._logutil").attr("_config_logging")(self, minLevel, maxLevel, logName);
}, py::kw_only(),

View File

@@ -39,7 +39,7 @@ class ConnectionListenerTest {
/** Connect to the server. */
private void connect(int port) {
m_serverInst.startServer("connectionlistenertest.json", "127.0.0.1", port);
m_serverInst.startServer("connectionlistenertest.json", "127.0.0.1", "", port);
m_clientInst.startClient("client");
m_clientInst.setServer("127.0.0.1", port);
@@ -113,7 +113,7 @@ class ConnectionListenerTest {
@ParameterizedTest
@ValueSource(strings = {"127.0.0.1", "127.0.0.1 ", " 127.0.0.1 "})
void testThreaded(String address) {
m_serverInst.startServer("connectionlistenertest.json", address, threadedPort);
m_serverInst.startServer("connectionlistenertest.json", address, "", threadedPort);
List<NetworkTableEvent> events = new ArrayList<>();
final int handle =
m_serverInst.addConnectionListener(

View File

@@ -37,7 +37,7 @@ class TimeSyncTest {
try (var poller = new NetworkTableListenerPoller(m_inst)) {
poller.addTimeSyncListener(false);
m_inst.startServer("timesynctest.json", "127.0.0.1", 10030);
m_inst.startServer("timesynctest.json", "127.0.0.1", "", 10030);
var offset = m_inst.getServerTimeOffset();
assertTrue(offset.isPresent());
assertEquals(0L, offset.getAsLong());

View File

@@ -33,7 +33,7 @@ class TopicListenerTest {
}
private void connect() {
m_serverInst.startServer("topiclistenertest.json", "127.0.0.1", 10010);
m_serverInst.startServer("topiclistenertest.json", "127.0.0.1", "", 10010);
m_clientInst.startClient("client");
m_clientInst.setServer("127.0.0.1", 10010);

View File

@@ -32,7 +32,7 @@ class ConnectionListenerTest : public ::testing::Test {
};
void ConnectionListenerTest::Connect(const char* address, unsigned int port4) {
wpi::nt::StartServer(server_inst, "connectionlistenertest.ini", address,
wpi::nt::StartServer(server_inst, "connectionlistenertest.ini", address, "",
port4);
wpi::nt::StartClient(client_inst, "client");
wpi::nt::SetServer(client_inst, address, port4);

View File

@@ -26,7 +26,7 @@ TEST_F(TimeSyncTest, TestServer) {
wpi::nt::NetworkTableListenerPoller poller{m_inst};
poller.AddTimeSyncListener(false);
m_inst.StartServer("timesynctest.json", "127.0.0.1", 10030);
m_inst.StartServer("timesynctest.json", "127.0.0.1", "", 10030);
auto offset = m_inst.GetServerTimeOffset();
ASSERT_TRUE(offset);
ASSERT_EQ(0, *offset);

View File

@@ -52,7 +52,7 @@ class TopicListenerTest : public ::testing::Test {
};
void TopicListenerTest::Connect(unsigned int port) {
wpi::nt::StartServer(m_serverInst, "topiclistenertest.json", "127.0.0.1",
wpi::nt::StartServer(m_serverInst, "topiclistenertest.json", "127.0.0.1", "",
port);
wpi::nt::StartClient(m_clientInst, "client");
wpi::nt::SetServer(m_clientInst, "127.0.0.1", port);