* The initial ARP entry for interfaces without an address were never removed.

* arp_update_local() was called without holding the sCacheLock, but did not take
  care about locking either. Now the caller has to lock.
* Updating the local ARP entry is now done while holding the sCacheLock the
  whole time, thus it's now atomic to the outside.
* Fixed a potential deadlock: the arp_entry destructor must not be called with
  the sCacheLock being held as long as there is a potential timer running.
* Fixed a potential double delete in case the arp_entry destructor was called
  with a pending ARP_STATE_REMOVE_FAILED or ARP_STATE_STALE. Now, we set the
  new flag ARP_FLAG_REMOVED to check for that condition (it's now set when an
  entry is removed from the hash).
* arp_start_resolve() would leak non-functional ARP entries around when
  something went wrong during the initialization. It will now remove them via
  their timer.


git-svn-id: file:///srv/svn/repos/haiku/haiku/trunk@30167 a95241bf-73f2-0310-859d-f6bbb57e9c96
This commit is contained in:
Axel Dörfler
2009-04-15 10:29:33 +00:00
parent a96e91db3b
commit 0e9ddc9350
@@ -90,9 +90,12 @@ struct arp_entry {
void ClearQueue(); void ClearQueue();
void MarkFailed(); void MarkFailed();
void MarkValid(); void MarkValid();
void ScheduleRemoval();
}; };
// see arp_control.h for flags // see arp_control.h for more flags
#define ARP_FLAG_REMOVED 0x00010000
#define ARP_PUBLIC_FLAG_MASK 0x0000ffff
#define ARP_NO_STATE 0 #define ARP_NO_STATE 0
#define ARP_STATE_REQUEST 1 #define ARP_STATE_REQUEST 1
@@ -206,6 +209,8 @@ arp_entry::Lookup(in_addr_t address)
arp_entry::Add(in_addr_t protocolAddress, sockaddr_dl *hardwareAddress, arp_entry::Add(in_addr_t protocolAddress, sockaddr_dl *hardwareAddress,
uint32 flags) uint32 flags)
{ {
ASSERT_LOCKED_MUTEX(&sCacheLock);
arp_entry *entry = new (std::nothrow) arp_entry; arp_entry *entry = new (std::nothrow) arp_entry;
if (entry == NULL) if (entry == NULL)
return NULL; return NULL;
@@ -232,6 +237,8 @@ arp_entry::Add(in_addr_t protocolAddress, sockaddr_dl *hardwareAddress,
} }
if (hash_insert(sCache, entry) != B_OK) { if (hash_insert(sCache, entry) != B_OK) {
// We can delete the entry here with the sCacheLock held, since it's
// guaranteed there are no timers pending.
delete entry; delete entry;
return NULL; return NULL;
} }
@@ -294,6 +301,18 @@ arp_entry::MarkValid()
} }
void
arp_entry::ScheduleRemoval()
{
// schedule a timer to remove this entry
timer_state = ARP_STATE_REMOVE_FAILED;
sStackModule->set_timer(&timer, 0);
}
// #pragma mark -
static void static void
ipv4_to_ether_multicast(sockaddr_dl *destination, const sockaddr_in *source) ipv4_to_ether_multicast(sockaddr_dl *destination, const sockaddr_in *source)
{ {
@@ -324,8 +343,7 @@ ipv4_to_ether_multicast(sockaddr_dl *destination, const sockaddr_in *source)
// #pragma mark - // #pragma mark -
/*! /*! Updates the entry determined by \a protocolAddress with the specified
Updates the entry determined by \a protocolAddress with the specified
\a hardwareAddress. \a hardwareAddress.
If such an entry does not exist yet, a new entry is added. If you try If such an entry does not exist yet, a new entry is added. If you try
to update a local existing entry but didn't ask for it (by setting to update a local existing entry but didn't ask for it (by setting
@@ -338,6 +356,8 @@ status_t
arp_update_entry(in_addr_t protocolAddress, sockaddr_dl *hardwareAddress, arp_update_entry(in_addr_t protocolAddress, sockaddr_dl *hardwareAddress,
uint32 flags, arp_entry **_entry = NULL) uint32 flags, arp_entry **_entry = NULL)
{ {
ASSERT_LOCKED_MUTEX(&sCacheLock);
arp_entry *entry = arp_entry::Lookup(protocolAddress); arp_entry *entry = arp_entry::Lookup(protocolAddress);
if (entry != NULL) { if (entry != NULL) {
// We disallow updating of entries that had been resolved before, // We disallow updating of entries that had been resolved before,
@@ -385,9 +405,14 @@ arp_update_entry(in_addr_t protocolAddress, sockaddr_dl *hardwareAddress,
} }
/*! Creates a permanent local entry for the interface belonging to this protocol.
You need to hold the cache lock when calling this function.
*/
static status_t static status_t
arp_update_local(arp_protocol *protocol) arp_update_local(arp_protocol *protocol)
{ {
ASSERT_LOCKED_MUTEX(&sCacheLock);
net_interface *interface = protocol->interface; net_interface *interface = protocol->interface;
in_addr_t inetAddress; in_addr_t inetAddress;
@@ -566,9 +591,13 @@ arp_timer(struct net_timer *timer, void *data)
// the entry has aged so much that we're going to remove it // the entry has aged so much that we're going to remove it
TRACE((" remove ARP entry %p!\n", entry)); TRACE((" remove ARP entry %p!\n", entry));
// TODO: we need to make sure we aren't deleting this entry from
// somewhere else right now!
mutex_lock(&sCacheLock); mutex_lock(&sCacheLock);
if ((entry->flags & ARP_FLAG_REMOVED) != 0) {
// The entry has already been removed, and is about to be deleted
mutex_unlock(&sCacheLock);
break;
}
hash_remove(sCache, entry); hash_remove(sCache, entry);
mutex_unlock(&sCacheLock); mutex_unlock(&sCacheLock);
@@ -616,13 +645,15 @@ arp_timer(struct net_timer *timer, void *data)
/*! Address resolver function: prepares and triggers the ARP request necessary /*! Address resolver function: prepares and triggers the ARP request necessary
to retrieve the hardware address for \a address. to retrieve the hardware address for \a address.
You need to have the sCacheLock held when calling this function - but
note that the lock will be interrupted here if everything goes well. You need to have the sCacheLock held when calling this function.
*/ */
static status_t static status_t
arp_start_resolve(net_datalink_protocol *protocol, in_addr_t address, arp_start_resolve(net_datalink_protocol *protocol, in_addr_t address,
arp_entry **_entry) arp_entry **_entry)
{ {
ASSERT_LOCKED_MUTEX(&sCacheLock);
// create an unresolved ARP entry as a placeholder // create an unresolved ARP entry as a placeholder
arp_entry *entry = arp_entry::Add(address, NULL, 0); arp_entry *entry = arp_entry::Add(address, NULL, 0);
if (entry == NULL) if (entry == NULL)
@@ -632,14 +663,14 @@ arp_start_resolve(net_datalink_protocol *protocol, in_addr_t address,
entry->request_buffer = gBufferModule->create(256); entry->request_buffer = gBufferModule->create(256);
if (entry->request_buffer == NULL) { if (entry->request_buffer == NULL) {
// TODO: do something with the entry entry->ScheduleRemoval();
return B_NO_MEMORY; return B_NO_MEMORY;
} }
NetBufferPrepend<arp_header> bufferHeader(entry->request_buffer); NetBufferPrepend<arp_header> bufferHeader(entry->request_buffer);
status_t status = bufferHeader.Status(); status_t status = bufferHeader.Status();
if (status < B_OK) { if (status < B_OK) {
// TODO: do something with the entry entry->ScheduleRemoval();
return status; return status;
} }
@@ -658,9 +689,12 @@ arp_start_resolve(net_datalink_protocol *protocol, in_addr_t address,
if (protocol->interface->address != NULL) { if (protocol->interface->address != NULL) {
header.protocol_sender header.protocol_sender
= ((sockaddr_in *)protocol->interface->address)->sin_addr.s_addr; = ((sockaddr_in *)protocol->interface->address)->sin_addr.s_addr;
} else } else {
header.protocol_sender = 0; header.protocol_sender = 0;
// TODO: test if this actually works - maybe we should use INADDR_BROADCAST instead // TODO: test if this actually works - maybe we should use
// INADDR_BROADCAST instead
}
memset(header.hardware_target, 0, ETHER_ADDRESS_LENGTH); memset(header.hardware_target, 0, ETHER_ADDRESS_LENGTH);
header.protocol_target = address; header.protocol_target = address;
@@ -734,7 +768,7 @@ arp_control(const char *subsystem, uint32 function, void *buffer,
} else } else
memset(control.ethernet_address, 0, ETHER_ADDRESS_LENGTH); memset(control.ethernet_address, 0, ETHER_ADDRESS_LENGTH);
control.flags = entry->flags; control.flags = entry->flags & ARP_PUBLIC_FLAG_MASK;
return user_memcpy(buffer, &control, sizeof(struct arp_control)); return user_memcpy(buffer, &control, sizeof(struct arp_control));
} }
@@ -761,7 +795,7 @@ arp_control(const char *subsystem, uint32 function, void *buffer,
entry->hardware_address.sdl_data, ETHER_ADDRESS_LENGTH); entry->hardware_address.sdl_data, ETHER_ADDRESS_LENGTH);
} else } else
memset(control.ethernet_address, 0, ETHER_ADDRESS_LENGTH); memset(control.ethernet_address, 0, ETHER_ADDRESS_LENGTH);
control.flags = entry->flags; control.flags = entry->flags & ARP_PUBLIC_FLAG_MASK;
return user_memcpy(buffer, &control, sizeof(struct arp_control)); return user_memcpy(buffer, &control, sizeof(struct arp_control));
} }
@@ -774,9 +808,7 @@ arp_control(const char *subsystem, uint32 function, void *buffer,
if ((entry->flags & ARP_FLAG_LOCAL) != 0) if ((entry->flags & ARP_FLAG_LOCAL) != 0)
return B_BAD_VALUE; return B_BAD_VALUE;
// schedule a timer to remove this entry entry->ScheduleRemoval();
entry->timer_state = ARP_STATE_REMOVE_FAILED;
sStackModule->set_timer(&entry->timer, 0);
return B_OK; return B_OK;
} }
@@ -791,9 +823,7 @@ arp_control(const char *subsystem, uint32 function, void *buffer,
if ((entry->flags & ARP_FLAG_LOCAL) != 0) if ((entry->flags & ARP_FLAG_LOCAL) != 0)
continue; continue;
// schedule a timer to remove this entry entry->ScheduleRemoval();
entry->timer_state = ARP_STATE_REMOVE_FAILED;
sStackModule->set_timer(&entry->timer, 0);
} }
hash_close(sCache, &iterator, false); hash_close(sCache, &iterator, false);
return B_OK; return B_OK;
@@ -930,7 +960,10 @@ arp_up(net_datalink_protocol *_protocol)
// cache this device's address for later use // cache this device's address for later use
mutex_lock(&sCacheLock);
status = arp_update_local(protocol); status = arp_update_local(protocol);
mutex_unlock(&sCacheLock);
if (status < B_OK) { if (status < B_OK) {
protocol->next->module->interface_down(protocol->next); protocol->next->module->interface_down(protocol->next);
return status; return status;
@@ -952,6 +985,9 @@ arp_down(net_datalink_protocol *protocol)
((sockaddr_in *)protocol->interface->address)->sin_addr.s_addr); ((sockaddr_in *)protocol->interface->address)->sin_addr.s_addr);
if (entry != NULL) { if (entry != NULL) {
hash_remove(sCache, entry); hash_remove(sCache, entry);
entry->flags |= ARP_FLAG_REMOVED;
locker.Unlock();
delete entry; delete entry;
} }
} }
@@ -969,12 +1005,10 @@ arp_control(net_datalink_protocol *_protocol, int32 op, void *argument,
if (op == SIOCSIFADDR && (protocol->interface->flags & IFF_UP) != 0) { if (op == SIOCSIFADDR && (protocol->interface->flags & IFF_UP) != 0) {
// The interface may get a new address, so we need to update our // The interface may get a new address, so we need to update our
// local entries. // local entries.
bool hasOldAddress = false;
in_addr_t oldAddress = 0; in_addr_t oldAddress = 0;
if (protocol->interface->address != NULL) { if (protocol->interface->address != NULL) {
oldAddress = ((sockaddr_in *) oldAddress
protocol->interface->address)->sin_addr.s_addr; = ((sockaddr_in *)protocol->interface->address)->sin_addr.s_addr;
hasOldAddress = true;
} }
status_t status = protocol->next->module->control(protocol->next, status_t status = protocol->next->module->control(protocol->next,
@@ -982,21 +1016,22 @@ arp_control(net_datalink_protocol *_protocol, int32 op, void *argument,
if (status < B_OK) if (status < B_OK)
return status; return status;
MutexLocker locker(sCacheLock);
arp_update_local(protocol); arp_update_local(protocol);
if (oldAddress == ((sockaddr_in *) if (oldAddress == ((sockaddr_in *)
protocol->interface->address)->sin_addr.s_addr protocol->interface->address)->sin_addr.s_addr)
|| !hasOldAddress)
return B_OK; return B_OK;
// remove previous address from cache // remove previous address from cache
// TODO: we should be able to do this (add/remove) in one atomic operation!
MutexLocker locker(sCacheLock);
arp_entry *entry = arp_entry::Lookup(oldAddress); arp_entry *entry = arp_entry::Lookup(oldAddress);
if (entry != NULL) { if (entry != NULL) {
hash_remove(sCache, entry); hash_remove(sCache, entry);
entry->flags |= ARP_FLAG_REMOVED;
locker.Unlock();
delete entry; delete entry;
} }