mirror of
https://github.com/solemnwarning/ipxwrapper
synced 2024-12-30 16:45:37 +01:00
- Use process+socket as key rather than local port number. - Store socket protocol in table. - Removed ADDR_TABLE_ENTRY_REUSE flag.
443 lines
9.1 KiB
C
443 lines
9.1 KiB
C
/* IPXWrapper - Address table
|
|
* Copyright (C) 2008-2014 Daniel Collins <solemnwarning@solemnwarning.net>
|
|
*
|
|
* This program is free software; you can redistribute it and/or modify it
|
|
* under the terms of the GNU General Public License version 2 as published by
|
|
* the Free Software Foundation.
|
|
*
|
|
* This program is distributed in the hope that it will be useful, but WITHOUT
|
|
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
|
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
|
|
* more details.
|
|
*
|
|
* You should have received a copy of the GNU General Public License along with
|
|
* this program; if not, write to the Free Software Foundation, Inc., 51
|
|
* Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
|
|
*/
|
|
|
|
/* The address table is used to co-ordinate the IPX addresses in use accross all
|
|
* IPXWrapper instances.
|
|
*/
|
|
|
|
#include <windows.h>
|
|
#include <winsock2.h>
|
|
#include <uthash.h>
|
|
|
|
#include "addrtable.h"
|
|
#include "ipxwrapper.h"
|
|
|
|
#define ADDR_TABLE_SIZE (sizeof(addr_table_header_t) + (ADDR_TABLE_MAX_ENTRIES * sizeof(addr_table_entry_t)))
|
|
|
|
static HANDLE addr_table_mutex = NULL;
|
|
|
|
static HANDLE addr_table_h = NULL;
|
|
|
|
static addr_table_header_t *addr_table_header = NULL;
|
|
static addr_table_entry_t *addr_table_base = NULL;
|
|
|
|
static void _init_fail(void)
|
|
{
|
|
if(addr_table_mutex)
|
|
{
|
|
ReleaseMutex(addr_table_mutex);
|
|
}
|
|
|
|
log_printf(LOG_WARNING, "Multiple processes may have address conflicts!");
|
|
addr_table_cleanup();
|
|
}
|
|
|
|
/* Find the last entry in the address table.
|
|
* Returns addr_table_base if there are none.
|
|
*/
|
|
static addr_table_entry_t *_last_entry()
|
|
{
|
|
addr_table_entry_t *last = addr_table_base, *next = addr_table_base + 1;
|
|
addr_table_entry_t *end = addr_table_base + ADDR_TABLE_MAX_ENTRIES;
|
|
|
|
while(next < end && next->flags & ADDR_TABLE_ENTRY_VALID)
|
|
{
|
|
last = next++;
|
|
}
|
|
|
|
return last;
|
|
}
|
|
|
|
void addr_table_init(void)
|
|
{
|
|
/* Mutex used to protect the address table. */
|
|
if(!(addr_table_mutex = CreateMutex(NULL, FALSE, ADDR_TABLE_MUTEX)))
|
|
{
|
|
log_printf(LOG_ERROR, "Failed to create/open mutex: %s", w32_error(GetLastError()));
|
|
_init_fail();
|
|
|
|
return;
|
|
}
|
|
|
|
addr_table_lock();
|
|
|
|
/* Allocate the address table "file" (shared memory). */
|
|
|
|
if(!(addr_table_h = CreateFileMapping(INVALID_HANDLE_VALUE, NULL, PAGE_READWRITE, 0, ADDR_TABLE_SIZE, ADDR_TABLE_NAME)))
|
|
{
|
|
log_printf(LOG_ERROR, "Failed to create/open address table: %s", w32_error(GetLastError()));
|
|
_init_fail();
|
|
|
|
return;
|
|
}
|
|
|
|
/* True if the table didn't exist before we created it. */
|
|
bool new_table = (GetLastError() != ERROR_ALREADY_EXISTS);
|
|
|
|
/* Map the address table. */
|
|
if(!(addr_table_header = MapViewOfFile(addr_table_h, FILE_MAP_WRITE, 0, 0, ADDR_TABLE_SIZE)))
|
|
{
|
|
log_printf(LOG_ERROR, "Failed to map address table: %s", w32_error(GetLastError()));
|
|
_init_fail();
|
|
|
|
return;
|
|
}
|
|
|
|
addr_table_base = (addr_table_entry_t*)(addr_table_header + 1);
|
|
|
|
if(new_table)
|
|
{
|
|
/* Initialise the address table. */
|
|
|
|
memset(addr_table_header, 0, ADDR_TABLE_SIZE);
|
|
|
|
addr_table_header->version = ADDR_TABLE_VERSION;
|
|
}
|
|
else if(addr_table_header->version != ADDR_TABLE_VERSION)
|
|
{
|
|
log_printf(LOG_ERROR, "Address table from incompatible IPXWrapper version present");
|
|
_init_fail();
|
|
|
|
return;
|
|
}
|
|
|
|
addr_table_unlock();
|
|
}
|
|
|
|
void addr_table_cleanup(void)
|
|
{
|
|
/* Release any remaining addresses bound by this process. */
|
|
|
|
if(addr_table_base)
|
|
{
|
|
ipx_socket *s, *tmp;
|
|
|
|
HASH_ITER(hh, sockets, s, tmp)
|
|
{
|
|
if(s->flags & IPX_BOUND)
|
|
{
|
|
addr_table_remove(s);
|
|
}
|
|
}
|
|
}
|
|
|
|
/* Close handles to the address table. */
|
|
|
|
if(addr_table_header)
|
|
{
|
|
UnmapViewOfFile(addr_table_header);
|
|
}
|
|
|
|
addr_table_header = NULL;
|
|
addr_table_base = NULL;
|
|
|
|
if(addr_table_h)
|
|
{
|
|
CloseHandle(addr_table_h);
|
|
addr_table_h = NULL;
|
|
}
|
|
|
|
if(addr_table_mutex)
|
|
{
|
|
CloseHandle(addr_table_mutex);
|
|
addr_table_mutex = NULL;
|
|
}
|
|
}
|
|
|
|
void addr_table_lock(void)
|
|
{
|
|
if(addr_table_mutex)
|
|
{
|
|
WaitForSingleObject(addr_table_mutex, INFINITE);
|
|
}
|
|
}
|
|
|
|
void addr_table_unlock(void)
|
|
{
|
|
if(addr_table_mutex)
|
|
{
|
|
ReleaseMutex(addr_table_mutex);
|
|
}
|
|
}
|
|
|
|
/* Search the address table for any conflicting binds. Falls back to searching
|
|
* the sockets table if the address table is unavailable.
|
|
*
|
|
* Returns false if a conflict was confirmed, true otherwise.
|
|
*/
|
|
bool addr_table_check(const struct sockaddr_ipx *addr)
|
|
{
|
|
if(addr_table_base)
|
|
{
|
|
addr_table_lock();
|
|
|
|
addr_table_entry_t *entry = addr_table_base;
|
|
addr_table_entry_t *end = addr_table_base + ADDR_TABLE_MAX_ENTRIES;
|
|
|
|
while(entry < end && (entry->flags & ADDR_TABLE_ENTRY_VALID))
|
|
{
|
|
if(addr->sa_socket == entry->socket)
|
|
{
|
|
addr_table_unlock();
|
|
return false;
|
|
}
|
|
|
|
entry++;
|
|
}
|
|
|
|
addr_table_unlock();
|
|
}
|
|
else{
|
|
/* Address table is unavailable, check the sockets table
|
|
* instead. This will not maintain address uniqueness between
|
|
* multiple processes!
|
|
*/
|
|
|
|
lock_sockets();
|
|
|
|
ipx_socket *s, *tmp;
|
|
|
|
HASH_ITER(hh, sockets, s, tmp)
|
|
{
|
|
if(memcmp(&(s->addr), addr, sizeof(struct sockaddr_ipx)) == 0)
|
|
{
|
|
unlock_sockets();
|
|
return true;
|
|
}
|
|
}
|
|
|
|
unlock_sockets();
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
/* Return an unused socket number in network byte order for automatic allocation,
|
|
* zero if none are available.
|
|
*/
|
|
uint16_t addr_table_auto_socket(void)
|
|
{
|
|
/* Automatic socket allocations start at 1024, I have no idea if this is
|
|
* normal IPX behaviour, but IP does it and it doesn't seem to interfere
|
|
* with any IPX software I've tested.
|
|
*/
|
|
|
|
uint16_t sock = 1024;
|
|
|
|
if(addr_table_base)
|
|
{
|
|
addr_table_lock();
|
|
|
|
addr_table_entry_t *entry = addr_table_base;
|
|
addr_table_entry_t *end = addr_table_base + ADDR_TABLE_MAX_ENTRIES;
|
|
|
|
while(entry < end)
|
|
{
|
|
if(ntohs(sock) == entry->socket)
|
|
{
|
|
if(sock == 65535)
|
|
{
|
|
addr_table_unlock();
|
|
return 0;
|
|
}
|
|
|
|
sock++;
|
|
|
|
entry = addr_table_base;
|
|
continue;
|
|
}
|
|
|
|
entry++;
|
|
}
|
|
|
|
addr_table_unlock();
|
|
}
|
|
else{
|
|
lock_sockets();
|
|
|
|
ipx_socket *s, *tmp;
|
|
|
|
HASH_ITER(hh, sockets, s, tmp)
|
|
{
|
|
if((s->flags & IPX_BOUND) && ntohs(sock) == s->addr.sa_socket)
|
|
{
|
|
if(sock == 65535)
|
|
{
|
|
unlock_sockets();
|
|
return 0;
|
|
}
|
|
|
|
sock++;
|
|
|
|
s = sockets;
|
|
continue;
|
|
}
|
|
}
|
|
|
|
unlock_sockets();
|
|
}
|
|
|
|
return htons(sock);
|
|
}
|
|
|
|
/* Insert an entry into the address table.
|
|
*
|
|
* Performs no conflict checking. Take the address table lock in the caller and
|
|
* use addr_table_check() before calling this.
|
|
*/
|
|
void addr_table_add(const ipx_socket *sock)
|
|
{
|
|
if(!addr_table_base)
|
|
{
|
|
return;
|
|
}
|
|
|
|
addr_table_lock();
|
|
|
|
/* Iterate over the address table to find the last entry. */
|
|
|
|
addr_table_entry_t *entry = addr_table_base;
|
|
addr_table_entry_t *end = addr_table_base + ADDR_TABLE_MAX_ENTRIES;
|
|
|
|
while(entry < end && (entry->flags & ADDR_TABLE_ENTRY_VALID))
|
|
{
|
|
entry++;
|
|
}
|
|
|
|
/* Append the new address to the address table. */
|
|
|
|
if(entry < end)
|
|
{
|
|
entry->netnum = addr32_in(sock->addr.sa_netnum);
|
|
entry->nodenum = addr48_in(sock->addr.sa_nodenum);
|
|
entry->socket = sock->addr.sa_socket;
|
|
|
|
if(sock->flags & IPX_IS_SPXII)
|
|
{
|
|
entry->type = ADDR_TABLE_TYPE_SPXII;
|
|
}
|
|
else if(sock->flags & IPX_IS_SPX)
|
|
{
|
|
entry->type = ADDR_TABLE_TYPE_SPX;
|
|
}
|
|
else{
|
|
entry->type = ADDR_TABLE_TYPE_IPX;
|
|
}
|
|
|
|
entry->process = GetCurrentProcessId();
|
|
entry->sock = sock->fd;
|
|
|
|
entry->flags = ADDR_TABLE_ENTRY_VALID;
|
|
entry->time = time(NULL);
|
|
}
|
|
else{
|
|
log_printf(LOG_ERROR, "Out of address table slots, not appending!");
|
|
}
|
|
|
|
addr_table_unlock();
|
|
}
|
|
|
|
/* Remove an entry from the address table. */
|
|
void addr_table_remove(const ipx_socket *sock)
|
|
{
|
|
if(!addr_table_base)
|
|
{
|
|
return;
|
|
}
|
|
|
|
addr_table_lock();
|
|
|
|
/* Iterate over the address table until we find the correct entry... */
|
|
|
|
addr_table_entry_t *entry = addr_table_base;
|
|
addr_table_entry_t *end = addr_table_base + ADDR_TABLE_MAX_ENTRIES;
|
|
|
|
while(entry < end && (entry->flags & ADDR_TABLE_ENTRY_VALID))
|
|
{
|
|
if(entry->process == GetCurrentProcessId() && entry->sock == sock->fd)
|
|
{
|
|
addr_table_entry_t *last = _last_entry();
|
|
|
|
*entry = *last;
|
|
last->flags &= ~ADDR_TABLE_ENTRY_VALID;
|
|
|
|
break;
|
|
}
|
|
|
|
++entry;
|
|
}
|
|
|
|
addr_table_unlock();
|
|
}
|
|
|
|
/* Update the time field for any of our entries in the address table and remove
|
|
* any that have expired (most likely a crashed process).
|
|
*/
|
|
void addr_table_update(void)
|
|
{
|
|
if(!addr_table_base)
|
|
{
|
|
return;
|
|
}
|
|
|
|
lock_sockets();
|
|
|
|
addr_table_lock();
|
|
|
|
/* Remove any expired entries. */
|
|
|
|
addr_table_entry_t *entry = addr_table_base;
|
|
addr_table_entry_t *last = _last_entry();
|
|
addr_table_entry_t *end = addr_table_base + ADDR_TABLE_MAX_ENTRIES;
|
|
|
|
for(; entry < end && (entry->flags & ADDR_TABLE_ENTRY_VALID); entry++)
|
|
{
|
|
if(entry->time + ADDR_TABLE_ENTRY_TIMEOUT <= time(NULL))
|
|
{
|
|
*entry = *last;
|
|
|
|
last->flags &= ~ADDR_TABLE_ENTRY_VALID;
|
|
last--;
|
|
}
|
|
}
|
|
|
|
/* This is really, really efficient. */
|
|
|
|
ipx_socket *sock, *tmp;
|
|
|
|
HASH_ITER(hh, sockets, sock, tmp)
|
|
{
|
|
if(sock->flags & IPX_BOUND)
|
|
{
|
|
/* Search the address table... */
|
|
|
|
for(entry = addr_table_base; entry < end && (entry->flags & ADDR_TABLE_ENTRY_VALID); entry++)
|
|
{
|
|
if(entry->process == GetCurrentProcessId() && entry->sock == sock->fd)
|
|
{
|
|
entry->time = time(NULL);
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
addr_table_unlock();
|
|
|
|
unlock_sockets();
|
|
}
|