[srp-server] retry other ports when failing to prepareSocket (#9981)

Unicast SRP dataset uses a ephemeral UDP port which could be taken by
another process. Currently SRP server creates the socket at the port
after the server is added into netdata. However, at that moment the
port may not be available on the platform so it may fail to create the
socket and start the server.

This commit adds the logic to restart the enabling process with
another port candidate if SRP server fails to create the socket.
This commit is contained in:
Handa Wang
2024-04-03 10:51:50 -07:00
committed by GitHub
parent 0e36799110
commit 843db1e82d
5 changed files with 45 additions and 15 deletions
+30 -11
View File
@@ -91,7 +91,7 @@ Server::Server(Instance &aInstance)
, mOutstandingUpdatesTimer(aInstance)
, mCompletedUpdateTask(aInstance)
, mServiceUpdateId(Random::NonCrypto::GetUint32())
, mPort(kUdpPortMin)
, mPort(kUninitializedPort)
, mState(kStateDisabled)
, mAddressMode(kDefaultAddressMode)
, mAnycastSequenceNumber(0)
@@ -595,7 +595,7 @@ exit:
}
}
void Server::SelectPort(void)
void Server::InitPort(void)
{
mPort = kUdpPortMin;
@@ -605,24 +605,35 @@ void Server::SelectPort(void)
if (Get<Settings>().Read(info) == kErrorNone)
{
mPort = info.GetPort() + 1;
if (mPort < kUdpPortMin || mPort > kUdpPortMax)
{
mPort = kUdpPortMin;
}
mPort = info.GetPort();
}
}
#endif
}
void Server::SelectPort(void)
{
if (mPort == kUninitializedPort)
{
InitPort();
}
++mPort;
if (mPort < kUdpPortMin || mPort > kUdpPortMax)
{
mPort = kUdpPortMin;
}
LogInfo("Selected port %u", mPort);
}
void Server::Start(void)
{
Error error = kErrorNone;
VerifyOrExit(mState == kStateStopped);
mState = kStateRunning;
PrepareSocket();
SuccessOrExit(error = PrepareSocket());
LogInfo("Start listening on port %u", mPort);
#if OPENTHREAD_CONFIG_SRP_SERVER_ADVERTISING_PROXY_ENABLE
@@ -630,10 +641,15 @@ void Server::Start(void)
#endif
exit:
return;
// Re-enable server to select a new port.
if (error != kErrorNone)
{
Disable();
Enable();
}
}
void Server::PrepareSocket(void)
Error Server::PrepareSocket(void)
{
Error error = kErrorNone;
@@ -659,8 +675,11 @@ exit:
if (error != kErrorNone)
{
LogCrit("Failed to prepare socket: %s", ErrorToString(error));
IgnoreError(mSocket.Close());
Stop();
}
return error;
}
Ip6::Udp::Socket &Server::GetSocket(void)
@@ -689,7 +708,7 @@ void Server::HandleDnssdServerStateChange(void)
if (mState == kStateRunning)
{
PrepareSocket();
IgnoreError(PrepareSocket());
}
}
+3 -1
View File
@@ -912,6 +912,7 @@ private:
static constexpr AddressMode kDefaultAddressMode =
static_cast<AddressMode>(OPENTHREAD_CONFIG_SRP_SERVER_DEFAULT_ADDRESS_MODE);
static constexpr uint16_t kUninitializedPort = 0;
static constexpr uint16_t kAnycastAddressModePort = 53;
// Metadata for a received SRP Update message.
@@ -971,8 +972,9 @@ private:
void Disable(void);
void Start(void);
void Stop(void);
void InitPort(void);
void SelectPort(void);
void PrepareSocket(void);
Error PrepareSocket(void);
Ip6::Udp::Socket &GetSocket(void);
LinkedList<Host> &GetHosts(void) { return mHosts; }
@@ -27,7 +27,6 @@
# POSSIBILITY OF SUCH DAMAGE.
#
import ipaddress
import logging
import unittest
import config
@@ -81,6 +80,12 @@ class SingleHostAndService(thread_cert.TestCase):
host.start(start_radvd=False)
self.simulator.go(5)
# Reserve UDP ports to verify that SRP server can skip the unavailable
# ports correctly
server.reserve_udp_port(53535)
server.reserve_udp_port(53536)
server.reserve_udp_port(53537)
self.assertEqual(server.srp_server_get_state(), 'disabled')
server.srp_server_set_enabled(True)
server.srp_server_set_lease_range(LEASE, LEASE, KEY_LEASE, KEY_LEASE)
@@ -88,6 +93,7 @@ class SingleHostAndService(thread_cert.TestCase):
self.simulator.go(config.BORDER_ROUTER_STARTUP_DELAY)
self.assertEqual('leader', server.get_state())
self.assertEqual(server.srp_server_get_state(), 'running')
self.assertNotIn(server.get_srp_server_port(), [53535, 53536, 53537])
client.start()
self.simulator.go(config.ROUTER_STARTUP_DELAY)
+3
View File
@@ -197,6 +197,9 @@ class OtbrDocker:
self.pexpect.wait()
self.pexpect.proc.kill()
def reserve_udp_port(self, port):
self.bash(f'socat -u UDP6-LISTEN:{port},bindtodevice=wpan0 - &')
def destroy(self):
logging.info("Destroying %s", self)
self._shutdown_docker()
@@ -94,14 +94,14 @@ class SrpServerRebootPort(thread_cert.TestCase):
#
# 2. Reboot the server without any service registered. The server should
# listen to the same port after the reboot.
# switch to a new port after re-enabling.
#
old_port = server.get_srp_server_port()
server.srp_server_set_enabled(False)
self.simulator.go(5)
server.srp_server_set_enabled(True)
self.simulator.go(5)
self.assertEqual(old_port, server.get_srp_server_port())
self.assertNotEqual(old_port, server.get_srp_server_port())
#
# 3. Register a service