net_server: Run DHCP negotiation on a separate thread to fix hangs.

Previously we ran DHCP negotiation on the looper thread. This meant
that we just blocked it for long periods of time, which stopped
configuration settings changes from working properly, and causing
the Network preferences panel to hang when net_server failed to
respond to its messages.

Now, we spawn a separate thread to do the real work on, and unlock
the looper around the recvfrom() with long timeouts. There's now a
kMsgAutoConfigureFailed that is sent when auto-configuration fails,
so that the AutoconfigLooper can fallback as before. Additionally,
the main NetServer class quits loopers for devices on any configuration
change, rather than deferring it unecessarily (and possibly wrongly, in
some cases.)

Fixes #18037. Probably fixes #17300 and may help with others.
This commit is contained in:
Augustin Cavalier
2025-11-27 14:30:54 -05:00
parent 2f598715f1
commit 39c7c29e69
8 changed files with 84 additions and 31 deletions
+1
View File
@@ -12,6 +12,7 @@
#define kNetServerSignature "application/x-vnd.haiku-net_server" #define kNetServerSignature "application/x-vnd.haiku-net_server"
#define kMsgConfigureInterface 'COif' #define kMsgConfigureInterface 'COif'
#define kMsgAutoConfigureFailed 'COaf'
#define kMsgConfigureResolver 'COrs' #define kMsgConfigureResolver 'COrs'
#define kMsgCountPersistentNetworks 'CPnw' #define kMsgCountPersistentNetworks 'CPnw'
#define kMsgGetPersistentNetwork 'GPnw' #define kMsgGetPersistentNetwork 'GPnw'
+1 -1
View File
@@ -25,7 +25,7 @@ AutoconfigClient::~AutoconfigClient()
status_t status_t
AutoconfigClient::Initialize() AutoconfigClient::Start()
{ {
return B_NOT_SUPPORTED; return B_NOT_SUPPORTED;
} }
+1 -1
View File
@@ -20,7 +20,7 @@ public:
BMessenger target, const char* device); BMessenger target, const char* device);
virtual ~AutoconfigClient(); virtual ~AutoconfigClient();
virtual status_t Initialize(); virtual status_t Start();
const BMessenger& Target() const { return fTarget; } const BMessenger& Target() const { return fTarget; }
const char* Device() const { return fDevice.String(); } const char* Device() const { return fDevice.String(); }
+15 -3
View File
@@ -43,6 +43,7 @@ AutoconfigLooper::AutoconfigLooper(BMessenger target, const char* device)
AutoconfigLooper::~AutoconfigLooper() AutoconfigLooper::~AutoconfigLooper()
{ {
_RemoveClient();
} }
@@ -52,7 +53,6 @@ AutoconfigLooper::_RemoveClient()
if (fCurrentClient == NULL) if (fCurrentClient == NULL)
return; return;
RemoveHandler(fCurrentClient);
delete fCurrentClient; delete fCurrentClient;
fCurrentClient = NULL; fCurrentClient = NULL;
} }
@@ -74,9 +74,16 @@ AutoconfigLooper::_ConfigureIPv4()
int32 flags = interface.Flags() & ~IFF_AUTO_CONFIGURED; int32 flags = interface.Flags() & ~IFF_AUTO_CONFIGURED;
interface.SetFlags(flags | IFF_CONFIGURING); interface.SetFlags(flags | IFF_CONFIGURING);
if (fCurrentClient->Initialize() == B_OK) if (fCurrentClient->Start() == B_OK)
return; return;
_ConfigureIPv4Failed();
}
void
AutoconfigLooper::_ConfigureIPv4Failed()
{
_RemoveClient(); _RemoveClient();
puts("DHCP failed miserably!"); puts("DHCP failed miserably!");
@@ -85,6 +92,7 @@ AutoconfigLooper::_ConfigureIPv4()
// TODO: have a look at zeroconf // TODO: have a look at zeroconf
// TODO: this could also be done add-on based // TODO: this could also be done add-on based
BNetworkInterface interface(fDevice.String());
if ((interface.Flags() & IFF_CONFIGURING) == 0) { if ((interface.Flags() & IFF_CONFIGURING) == 0) {
// Someone else configured the interface in the mean time // Someone else configured the interface in the mean time
return; return;
@@ -176,7 +184,7 @@ AutoconfigLooper::_NetworkMonitorNotification(BMessage* message)
// Reconfigure the interface when we have a link again // Reconfigure the interface when we have a link again
_ConfigureIPv4(); _ConfigureIPv4();
//_ConfigureIPv6(); // TODO: router advertisement and dhcpv6 //_ConfigureIPv6(); // TODO: router advertisement and dhcpv6
} else if ((fLastMediaStatus & IFM_ACTIVE) != 0 && (media & IFM_ACTIVE) == 0) { } else if ((media & IFM_ACTIVE) == 0) {
_RemoveClient(); _RemoveClient();
} }
@@ -216,6 +224,10 @@ AutoconfigLooper::MessageReceived(BMessage* message)
_ReadyToRun(); _ReadyToRun();
break; break;
case kMsgAutoConfigureFailed:
_ConfigureIPv4Failed();
break;
case B_NETWORK_MONITOR: case B_NETWORK_MONITOR:
_NetworkMonitorNotification(message); _NetworkMonitorNotification(message);
break; break;
+1
View File
@@ -29,6 +29,7 @@ public:
private: private:
void _RemoveClient(); void _RemoveClient();
void _ConfigureIPv4(); void _ConfigureIPv4();
void _ConfigureIPv4Failed();
void _ReadyToRun(); void _ReadyToRun();
void _NetworkMonitorNotification(BMessage* message); void _NetworkMonitorNotification(BMessage* message);
+56 -18
View File
@@ -30,6 +30,7 @@
#include <Debug.h> #include <Debug.h>
#include <Message.h> #include <Message.h>
#include <MessageRunner.h> #include <MessageRunner.h>
#include <Looper.h>
#include "NetServer.h" #include "NetServer.h"
@@ -482,6 +483,7 @@ DHCPClient::DHCPClient(BMessenger target, const char* device)
fConfiguration(kMsgConfigureInterface), fConfiguration(kMsgConfigureInterface),
fResolverConfiguration(kMsgConfigureResolver), fResolverConfiguration(kMsgConfigureResolver),
fRunner(NULL), fRunner(NULL),
fNegotiateThread(-1),
fAssignedAddress(0), fAssignedAddress(0),
fServer(AF_INET, NULL, DHCP_SERVER_PORT, B_UNCONFIGURED_ADDRESS_FAMILIES), fServer(AF_INET, NULL, DHCP_SERVER_PORT, B_UNCONFIGURED_ADDRESS_FAMILIES),
fStartTime(0), fStartTime(0),
@@ -518,18 +520,27 @@ DHCPClient::DHCPClient(BMessenger target, const char* device)
} }
} }
} }
openlog_thread("DHCP", 0, LOG_DAEMON);
} }
DHCPClient::~DHCPClient() DHCPClient::~DHCPClient()
{ {
if (fStatus != B_OK) thread_id thread = fNegotiateThread;
return; if (thread != -1) {
fNegotiateThread = -1;
suspend_thread(thread);
resume_thread(thread);
UnlockLooper();
wait_for_thread(thread, NULL);
LockLooper();
}
delete fRunner; delete fRunner;
if (fStatus != B_OK)
return;
int socket = ::socket(AF_INET, SOCK_DGRAM, 0); int socket = ::socket(AF_INET, SOCK_DGRAM, 0);
if (socket < 0) if (socket < 0)
return; return;
@@ -541,23 +552,42 @@ DHCPClient::~DHCPClient()
_SendMessage(socket, release, fServer); _SendMessage(socket, release, fServer);
close(socket); close(socket);
}
status_t
DHCPClient::Start()
{
if (fNegotiateThread != -1)
return EINPROGRESS;
fNegotiateThread = spawn_thread(_NegotiatorThread, "DHCP negotiator",
B_NORMAL_PRIORITY, this);
return resume_thread(fNegotiateThread);
}
status_t
DHCPClient::_NegotiatorThread(void* data)
{
openlog_thread("DHCP", 0, LOG_DAEMON);
DHCPClient* client = (DHCPClient*)data;
client->LockLooper();
client->fStatus = client->_Negotiate();
syslog(LOG_DEBUG, "%s: DHCP status = %s\n", client->Device(), strerror(client->fStatus));
client->fNegotiateThread = -1;
client->UnlockLooper();
closelog_thread(); closelog_thread();
return B_OK;
} }
status_t status_t
DHCPClient::Initialize() DHCPClient::_Negotiate()
{
fStatus = _Negotiate(fAssignedAddress == 0 ? INIT : INIT_REBOOT);
syslog(LOG_DEBUG, "%s: DHCP status = %s\n", Device(), strerror(fStatus));
return fStatus;
}
status_t
DHCPClient::_Negotiate(dhcp_state state)
{ {
dhcp_state state = _CurrentState();
if (state == BOUND) if (state == BOUND)
return B_OK; return B_OK;
@@ -595,7 +625,7 @@ DHCPClient::_Negotiate(dhcp_state state)
bigtime_t previousLeaseTime = fLeaseTime; bigtime_t previousLeaseTime = fLeaseTime;
status_t status = B_OK; status_t status = B_OK;
while (state != BOUND) { while (state != BOUND && fNegotiateThread != -1) {
status = _StateTransition(socket, state); status = _StateTransition(socket, state);
if (status != B_OK && (state == SELECTING || state == REBOOTING)) if (status != B_OK && (state == SELECTING || state == REBOOTING))
break; break;
@@ -618,8 +648,12 @@ DHCPClient::_Negotiate(dhcp_state state)
_RestartLease(fRenewalTime); _RestartLease(fRenewalTime);
fStatus = status; fStatus = status;
if (status) if (status != B_OK) {
// If we were asked to quit, don't inform the looper that we failed.
if (fNegotiateThread != -1)
Looper()->PostMessage(kMsgAutoConfigureFailed, Looper(), this);
return status; return status;
}
// configure interface // configure interface
BMessage reply; BMessage reply;
@@ -737,8 +771,12 @@ DHCPClient::_StateTransition(int socket, dhcp_state& state)
char buffer[2048]; char buffer[2048];
struct sockaddr_in from; struct sockaddr_in from;
socklen_t fromLength = sizeof(from); socklen_t fromLength = sizeof(from);
UnlockLooper();
ssize_t bytesReceived = recvfrom(socket, buffer, sizeof(buffer), ssize_t bytesReceived = recvfrom(socket, buffer, sizeof(buffer),
0, (struct sockaddr*)&from, &fromLength); 0, (struct sockaddr*)&from, &fromLength);
LockLooper();
if (bytesReceived < 0 && errno == B_TIMED_OUT) { if (bytesReceived < 0 && errno == B_TIMED_OUT) {
// depending on the state, we'll just try again // depending on the state, we'll just try again
if (!_TimeoutShift(socket, state, timeout)) if (!_TimeoutShift(socket, state, timeout))
@@ -1032,7 +1070,7 @@ DHCPClient::_CurrentState() const
bigtime_t now = system_time(); bigtime_t now = system_time();
if (now > fLeaseTime || fStatus != B_OK) if (now > fLeaseTime || fStatus != B_OK)
return INIT; return fAssignedAddress == 0 ? INIT : INIT_REBOOT;
if (now >= fRebindingTime) if (now >= fRebindingTime)
return REBINDING; return REBINDING;
if (now >= fRenewalTime) if (now >= fRenewalTime)
@@ -1046,7 +1084,7 @@ DHCPClient::MessageReceived(BMessage* message)
{ {
switch (message->what) { switch (message->what) {
case kMsgLeaseTime: case kMsgLeaseTime:
_Negotiate(_CurrentState()); Start();
break; break;
default: default:
+4 -2
View File
@@ -39,12 +39,13 @@ public:
const char* device); const char* device);
virtual ~DHCPClient(); virtual ~DHCPClient();
virtual status_t Initialize(); virtual status_t Start();
virtual void MessageReceived(BMessage* message); virtual void MessageReceived(BMessage* message);
private: private:
status_t _Negotiate(dhcp_state state); static status_t _NegotiatorThread(void* data);
status_t _Negotiate();
status_t _GotMessage(dhcp_state& state, status_t _GotMessage(dhcp_state& state,
dhcp_message* message); dhcp_message* message);
status_t _StateTransition(int socket, dhcp_state& state); status_t _StateTransition(int socket, dhcp_state& state);
@@ -67,6 +68,7 @@ private:
BMessage fConfiguration; BMessage fConfiguration;
BMessage fResolverConfiguration; BMessage fResolverConfiguration;
BMessageRunner* fRunner; BMessageRunner* fRunner;
thread_id fNegotiateThread;
uint8 fMAC[6]; uint8 fMAC[6];
BString fHostName; BString fHostName;
uint32 fTransactionID; uint32 fTransactionID;
+5 -6
View File
@@ -426,10 +426,12 @@ NetServer::_ConfigureInterface(BMessage& message)
if (message.FindInt32("flags", &flags) != B_OK) if (message.FindInt32("flags", &flags) != B_OK)
flags = IFF_UP; flags = IFF_UP;
bool autoConfigured; bool autoConfigured = false;
if (message.FindBool("auto_configured", &autoConfigured) == B_OK if (message.FindBool("auto_configured", &autoConfigured) == B_OK
&& autoConfigured) { && autoConfigured) {
flags |= IFF_AUTO_CONFIGURED; flags |= IFF_AUTO_CONFIGURED;
} else {
_QuitLooperForDevice(name);
} }
int32 mtu; int32 mtu;
@@ -465,10 +467,8 @@ NetServer::_ConfigureInterface(BMessage& message)
&addressMessage) == B_OK; index++) { &addressMessage) == B_OK; index++) {
BNetworkInterfaceAddressSettings addressSettings(addressMessage); BNetworkInterfaceAddressSettings addressSettings(addressMessage);
if (addressSettings.IsAutoConfigure()) { if (addressSettings.IsAutoConfigure())
_QuitLooperForDevice(name);
startAutoConfig = true; startAutoConfig = true;
}
// set address/mask/broadcast/peer // set address/mask/broadcast/peer
@@ -570,8 +570,7 @@ NetServer::_ConfigureInterface(BMessage& message)
looper->Run(); looper->Run();
fDeviceMap[name] = looper; fDeviceMap[name] = looper;
} else if (!autoConfigured) }
_QuitLooperForDevice(name);
return B_OK; return B_OK;
} }