1
0
mirror of https://github.com/solemnwarning/ipxwrapper synced 2024-12-30 16:45:37 +01:00

Refactor address conflict detection.

Detect multiple sockets bound to the same address within a session by creating
a named mutex. Multiple handles to a mutex may exist and closing the last one
destroys it.
This commit is contained in:
Daniel Collins 2014-05-13 00:37:55 +01:00
parent 3c13aab8e5
commit 043ce1495e
7 changed files with 93 additions and 598 deletions

View File

@ -36,7 +36,7 @@ VERSION := git
IPXWRAPPER_DEPS := src/ipxwrapper.o src/winsock.o src/ipxwrapper_stubs.o src/log.o src/common.o \
src/interface.o src/router.o src/ipxwrapper.def src/addrcache.o src/config.o src/addr.o \
src/addrtable.o src/firewall.o src/wpcap_stubs.o
src/firewall.o src/wpcap_stubs.o
BIN_FILES := $(shell cat manifest.bin.txt)
SRC_FILES := $(shell cat manifest.src.txt)

View File

@ -1,442 +0,0 @@
/* 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();
}

View File

@ -1,80 +0,0 @@
/* 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.
*/
#ifndef ADDRTABLE_H
#define ADDRTABLE_H
#include <wsipx.h>
#include <stdint.h>
#include <time.h>
#include "addr.h"
#include "ipxwrapper.h"
#define ADDR_TABLE_MAX_ENTRIES 512
#define ADDR_TABLE_ENTRY_TIMEOUT 10
#define ADDR_TABLE_MUTEX "IPXWrapper_addr_table_mutex"
#define ADDR_TABLE_NAME "IPXWrapper_addr_table"
#define ADDR_TABLE_VERSION 2
typedef struct addr_table_header addr_table_header_t;
struct addr_table_header
{
int version;
};
#define ADDR_TABLE_ENTRY_VALID ((int)(1<<0))
typedef struct addr_table_entry addr_table_entry_t;
struct addr_table_entry
{
addr32_t netnum;
addr48_t nodenum;
uint16_t socket;
enum {
ADDR_TABLE_TYPE_IPX,
ADDR_TABLE_TYPE_SPX,
ADDR_TABLE_TYPE_SPXII
} type;
DWORD process;
int sock;
int flags;
time_t time;
};
void addr_table_init(void);
void addr_table_cleanup(void);
void addr_table_lock(void);
void addr_table_unlock(void);
bool addr_table_check(const struct sockaddr_ipx *addr);
uint16_t addr_table_auto_socket(void);
void addr_table_add(const ipx_socket *sock);
void addr_table_remove(const ipx_socket *sock);
void addr_table_update(void);
#endif /* !ADDRTABLE_H */

View File

@ -31,7 +31,6 @@
#include "interface.h"
#include "router.h"
#include "addrcache.h"
#include "addrtable.h"
int _putenv(const char *envstring);
@ -104,16 +103,12 @@ BOOL WINAPI DllMain(HINSTANCE me, DWORD why, LPVOID res)
return FALSE;
}
addr_table_init();
router_init();
}
else if(why == DLL_PROCESS_DETACH)
{
router_cleanup();
addr_table_cleanup();
WSACleanup();
DeleteCriticalSection(&sockets_cs);

View File

@ -66,6 +66,7 @@ struct ipx_socket {
/* The following values are undefined when IPX_BOUND is not set */
struct sockaddr_ipx addr;
HANDLE sock_mut;
/* Address used with connect call, only set when IPX_CONNECTED is */
struct sockaddr_ipx remote_addr;

View File

@ -27,7 +27,6 @@
#include "ipxwrapper.h"
#include "interface.h"
#include "addrcache.h"
#include "addrtable.h"
static bool router_running = false;
static WSAEVENT router_event = WSA_INVALID_EVENT;
@ -552,8 +551,6 @@ static DWORD router_main(void *arg)
{
DWORD exit_status = 0;
time_t last_at_update = 0;
ipx_interface_t *interfaces = NULL;
HANDLE *wait_events = &router_event;
@ -597,12 +594,6 @@ static DWORD router_main(void *arg)
break;
}
if(last_at_update != time(NULL))
{
addr_table_update();
last_at_update = time(NULL);
}
if(ipx_use_pcap)
{
ipx_interface_t *i;

View File

@ -27,7 +27,6 @@
#include "interface.h"
#include "router.h"
#include "addrcache.h"
#include "addrtable.h"
#define XP_CONNECTIONLESS 0x00000001
#define XP_GUARANTEED_DELIVERY 0x00000002
@ -420,7 +419,7 @@ int WSAAPI closesocket(SOCKET sockfd)
if(sock->flags & IPX_BOUND)
{
addr_table_remove(sock);
CloseHandle(sock->sock_mut);
}
HASH_DEL(sockets, sock);
@ -431,7 +430,59 @@ int WSAAPI closesocket(SOCKET sockfd)
return 0;
}
static bool _complete_bind_address(struct sockaddr_ipx *addr)
static HANDLE _open_socket_mutex(uint16_t socket, bool exclusive)
{
char mutex_name[256];
snprintf(mutex_name, sizeof(mutex_name), "ipxwrapper_socket_%hu", socket);
HANDLE mutex = CreateMutex(NULL, FALSE, mutex_name);
if(!mutex)
{
log_printf(LOG_ERROR, "Error when creating mutex %s: %s",
mutex_name, w32_error(GetLastError()));
}
if(GetLastError() == ERROR_ALREADY_EXISTS && exclusive)
{
CloseHandle(mutex);
return NULL;
}
return mutex;
}
bool _complete_bind(ipx_socket *sock)
{
if(ntohs(sock->addr.sa_socket) == 0)
{
uint16_t socknum = 1024;
do {
HANDLE mutex = _open_socket_mutex(socknum, true);
if(mutex)
{
sock->addr.sa_socket = htons(socknum);
sock->sock_mut = mutex;
sock->flags |= IPX_BOUND;
return true;
}
} while(socknum++ != 65535);
}
else{
if((sock->sock_mut = _open_socket_mutex(
ntohs(sock->addr.sa_socket), !(sock->flags & IPX_REUSE))))
{
sock->flags |= IPX_BOUND;
return true;
}
}
return false;
}
static bool _resolve_bind_address(ipx_socket *sock, const struct sockaddr_ipx *addr)
{
/* Network number 00:00:00:00 is specified as the "current" network, this code
* treats it as a wildcard when used for the network OR node numbers.
@ -466,28 +517,14 @@ static bool _complete_bind_address(struct sockaddr_ipx *addr)
log_printf(LOG_ERROR, "bind failed: no such address");
free_ipx_interface_list(&ifaces);
WSASetLastError(WSAEADDRNOTAVAIL);
return false;
}
addr32_out(addr->sa_netnum, iface->ipx_net);
addr48_out(addr->sa_nodenum, iface->ipx_node);
addr32_out(sock->addr.sa_netnum, iface->ipx_net);
addr48_out(sock->addr.sa_nodenum, iface->ipx_node);
sock->addr.sa_socket = addr->sa_socket;
free_ipx_interface_list(&ifaces);
/* Socket zero signifies automatic allocation. */
if(addr->sa_socket == 0 && (addr->sa_socket = addr_table_auto_socket()) == 0)
{
/* Hmmm. We appear to have ran out of sockets?! */
log_printf(LOG_ERROR, "bind failed: out of sockets?!");
WSASetLastError(WSAEADDRNOTAVAIL);
return false;
}
return true;
}
@ -523,38 +560,30 @@ int WSAAPI bind(SOCKET fd, const struct sockaddr *addr, int addrlen)
return -1;
}
addr_table_lock();
/* Resolve any wildcards in the requested address. */
if(!_complete_bind_address(&ipxaddr))
if(!_resolve_bind_address(sock, &ipxaddr))
{
addr_table_unlock();
unlock_sockets();
WSASetLastError(WSAEADDRNOTAVAIL);
return -1;
}
IPX_STRING_ADDR(got_addr_s, addr32_in(ipxaddr.sa_netnum), addr48_in(ipxaddr.sa_nodenum), ipxaddr.sa_socket);
log_printf(LOG_INFO, "bind address: %s", got_addr_s);
/* Check that the address is free. */
if(!(sock->flags & IPX_REUSE) && !addr_table_check(&ipxaddr))
if(!_complete_bind(sock))
{
/* Address has already been bound. */
log_printf(LOG_ERROR, "bind failed: address already in use");
WSASetLastError(WSAEADDRINUSE);
addr_table_unlock();
unlock_sockets();
WSASetLastError(WSAEADDRINUSE);
return -1;
}
IPX_STRING_ADDR(got_addr_s, addr32_in(sock->addr.sa_netnum), addr48_in(sock->addr.sa_nodenum), sock->addr.sa_socket);
log_printf(LOG_INFO, "bind address: %s", got_addr_s);
/* Bind the underlying socket. */
struct sockaddr_in bind_addr;
@ -567,7 +596,9 @@ int WSAAPI bind(SOCKET fd, const struct sockaddr *addr, int addrlen)
{
log_printf(LOG_ERROR, "Binding local socket failed: %s", w32_error(WSAGetLastError()));
addr_table_unlock();
CloseHandle(sock->sock_mut);
sock->flags &= ~IPX_BOUND;
unlock_sockets();
return -1;
@ -590,26 +621,17 @@ int WSAAPI bind(SOCKET fd, const struct sockaddr *addr, int addrlen)
log_printf(LOG_ERROR, "Cannot get local port of socket: %s", w32_error(WSAGetLastError()));
log_printf(LOG_WARNING, "Socket %d is NOW INCONSISTENT!", fd);
addr_table_unlock();
CloseHandle(sock->sock_mut);
sock->flags &= ~IPX_BOUND;
unlock_sockets();
return -1;
}
sock->port = bind_addr.sin_port;
log_printf(LOG_DEBUG, "Bound to local port %hu", ntohs(sock->port));
/* Mark the IPX socket as bound and insert it into the address
* table.
*/
memcpy(&(sock->addr), &ipxaddr, sizeof(ipxaddr));
sock->flags |= IPX_BOUND;
addr_table_add(sock);
addr_table_unlock();
unlock_sockets();
return 0;
@ -1959,26 +1981,16 @@ static int _connect_spx(ipx_socket *sock, struct sockaddr_ipx *ipxaddr)
/* The sa_netnum and sa_nodenum fields are filled out above. */
addr_table_lock();
if((sock->addr.sa_socket = addr_table_auto_socket()) != 0)
if(!_complete_bind(sock))
{
sock->flags |= IPX_BOUND;
addr_table_add(sock);
}
else{
log_printf(LOG_ERROR, "Cannot allocate socket number for SPX socket");
log_printf(LOG_WARNING, "Socket %d is NOW INCONSISTENT!", sock->fd);
addr_table_unlock();
unlock_sockets();
return -1;
}
addr_table_unlock();
{
IPX_STRING_ADDR(
addr_s,
@ -2311,6 +2323,25 @@ SOCKET PASCAL accept(SOCKET s, struct sockaddr *addr, int *addrlen)
nsock->addr = sock->addr;
/* Duplicate the mutex handle held by the listening
* socket used to detect address collisions. There is no
* way to recover from an error here.
*/
if(!(DuplicateHandle(GetCurrentProcess(), sock->sock_mut,
GetCurrentProcess(), &(nsock->sock_mut),
0, FALSE, DUPLICATE_SAME_ACCESS)))
{
log_printf(LOG_ERROR, "Could not duplicate socket mutex: %s", w32_error(GetLastError()));
closesocket(nsock->fd);
free(nsock);
unlock_sockets();
WSASetLastError(WSAENETDOWN);
return -1;
}
/* Copy remote address from the spxinit packet. */
nsock->remote_addr.sa_family = AF_IPX;
@ -2319,7 +2350,6 @@ SOCKET PASCAL accept(SOCKET s, struct sockaddr *addr, int *addrlen)
nsock->remote_addr.sa_socket = spxinit.socket;
HASH_ADD_INT(sockets, fd, nsock);
addr_table_add(nsock);
if(addr)
{