From 44d8073df06a5c2bd19cd78513fe5cc0da141f60 Mon Sep 17 00:00:00 2001
From: Michal Kubecek <mkubecek@suse.cz>
Date: Sat, 1 Nov 2014 20:46:45 +0100
Subject: [PATCH 9/9] allow listening on multiple sockets

Allow RemoteBindAddress configuration value to be a semicolon separated
list of addresses to listen on. The implementation creates multiple
listening sockets and makes poll() / select() watch all of them.
---
 builds/install/misc/firebird.conf.in       |  13 +-
 src/remote/inet.cpp                        | 319 ++++++++++++++++++++++-------
 src/remote/inet_proto.h                    |   1 +
 src/remote/remote.cpp                      |   5 -
 src/remote/remote.h                        |   9 +-
 src/remote/server/os/posix/inet_server.cpp |  19 +-
 src/remote/server/os/win32/srvr_w32.cpp    |  16 +-
 src/remote/server/serve_proto.h            |   2 +-
 src/remote/server/server.cpp               |  70 +++++--
 9 files changed, 328 insertions(+), 126 deletions(-)

diff --git a/builds/install/misc/firebird.conf.in b/builds/install/misc/firebird.conf.in
index 53727b6..4bff213 100644
--- a/builds/install/misc/firebird.conf.in
+++ b/builds/install/misc/firebird.conf.in
@@ -648,13 +648,12 @@
 #IPv6V6Only = 0
 
 #
-# Allows incoming connections to be bound to the IP address of a
-# specific network card. It enables rejection of incoming connections
-# through any other network interface except this one. By default,
-# connections from any available network interface are allowed.
-# If you are using Classic Server, this setting is for Windows only.
-# Under Linux, BSD or Mac OS X, with Classic server use xinetd or launchd
-# configuration file (bind parameter).
+# Allows incoming connections to be bound to specific IP addresses
+# (semicolon separated list). Connections to other addresses are
+# rejected. By default, connections to all local addresses (both IPv6
+# and IPv4) are accepted. If you are using Classic Server, this setting
+# is for Windows only. Under Linux, BSD or Mac OS X, with Classic
+# server use xinetd or launchd configuration file (bind parameter).
 #
 # Type: string
 #
diff --git a/src/remote/inet.cpp b/src/remote/inet.cpp
index b602191..f29fec7 100644
--- a/src/remote/inet.cpp
+++ b/src/remote/inet.cpp
@@ -449,6 +449,7 @@ static rem_port*		inet_try_connect(	PACKET*,
 									const PathName*);
 static bool_t	inet_write(XDR*); //, int);
 static void INET_server_socket(rem_port* port, USHORT flag, const addrinfo* pai);
+static rem_port* INET_server_standalone(RemPortArray* ports, USHORT flag);
 
 #ifdef DEBUG
 static void packet_print(const TEXT*, const UCHAR*, int, ULONG);
@@ -459,9 +460,9 @@ static bool		packet_send(rem_port*, const SCHAR*, SSHORT);
 static rem_port*		receive(rem_port*, PACKET *);
 static rem_port*		select_accept(rem_port*);
 
-static void		select_port(rem_port*, Select*, RemPortPtr&);
-static bool		select_multi(rem_port*, UCHAR* buffer, SSHORT bufsize, SSHORT* length, RemPortPtr&);
-static bool		select_wait(rem_port*, Select*);
+static void		select_port(RemPortArray*, Select*, RemPortPtr&);
+static bool		select_multi(RemPortArray*, UCHAR* buffer, SSHORT bufsize, SSHORT* length, RemPortPtr&);
+static bool		select_wait(RemPortArray*, Select*);
 static int		send_full(rem_port*, PACKET *);
 static int		send_partial(rem_port*, PACKET *);
 
@@ -681,6 +682,99 @@ rem_port* INET_analyze(ClntAuthBlock* cBlock,
 	return port;
 }
 
+RemPortArray* INET_connect_all(const TEXT* name, USHORT flag)
+{
+/**************************************
+ *
+ *	I N E T _ c o n n e c t _ a l l
+ *
+ **************************************
+ *
+ * Functional description
+ *	A wrapper around INET_connect() allowing creation of multiple main
+ *	sockets so that the server can listen on more than one address.
+ *
+ **************************************/
+	RefPtr<Config> config = Config::getDefaultConfig();
+	string addr_list = config->getRemoteBindAddress() ?: "";
+	bool ipv6_v6only = config->getIPv6V6Only();
+
+	// with IPV6_V6ONLY, we need two sockets to listen for both IPv4 and IPv6
+	addr_list.trim();
+	if (addr_list.empty() && ipv6_v6only)
+		addr_list = "0.0.0.0;::";
+
+	unsigned maxcnt = 1;
+	for (unsigned i = 0; i < addr_list.length(); i++)
+		if (addr_list[i] == ';')
+			maxcnt++;
+	RemPortArray* ports = new RemPortArray(maxcnt);
+
+	size_t pos1 = 0, pos2;
+	do {
+		RemPortPtr new_port;
+		string addr;
+
+		pos2 = addr_list.find(';', pos1);
+		if (pos2 != string::npos)
+		{
+			addr = addr_list.substr(pos1, pos2 - pos1);
+			pos1 = pos2 + 1;
+		}
+		else
+			addr = addr_list.substr(pos1);
+
+		addr.trim();
+		addr += name;
+		new_port = INET_connect(addr.c_str(), NULL, flag, NULL, NULL);
+		if (new_port)
+		{
+			ports->add(new_port);
+		}
+	} while (pos2 != string::npos);
+
+	if (ports->isEmpty())
+	{
+		delete ports;
+		return NULL;
+	}
+	if (flag & SRVR_multi_client)
+		return ports;
+
+	RemPortPtr conn_port;
+	conn_port = INET_server_standalone(ports, flag);
+	for (unsigned i = 0; i < ports->getCount(); i++)
+	{
+		if ((*ports)[i] == conn_port)
+			continue;
+
+		SOCLOSE((*ports)[i]->port_handle);
+	}
+	if (conn_port)
+	{
+		int cnt;
+
+		conn_port->addRef(); cnt = conn_port->release();
+		RemPortArray* one_port = new RemPortArray(1);
+		one_port->add(conn_port);
+		conn_port->addRef(); cnt = conn_port->release();
+		for (unsigned i = 0; i < ports->getCount(); i++)
+			(*ports)[i] = NULL;
+		conn_port->addRef(); cnt = conn_port->release();
+		delete ports;
+		conn_port->addRef(); cnt = conn_port->release();
+		ports = one_port;
+		conn_port->addRef(); cnt = conn_port->release();
+	}
+	else
+	{
+		delete ports;
+		ports = NULL;
+	}
+
+	return ports;
+}
+
 rem_port* INET_connect(const TEXT* name,
 					   PACKET* packet,
 					   USHORT flag,
@@ -950,9 +1044,57 @@ static void INET_server_socket(rem_port* port, USHORT flag, const addrinfo* pai)
 		port->port_server_flags |= (SRVR_server | SRVR_multi_client);
 		return;
 	}
+}
+
+static rem_port* INET_server_standalone(RemPortArray* ports, USHORT flag)
+{
+/**************************************
+ *
+ *	I N E T _ s e r v e r _ s t a n d a l o n e
+ *
+ **************************************
+ *
+ * Functional description
+ *	Main loop of a standalone server. Accepts connections on a set of
+ *	listening sockets and forks children for them.
+ *
+ **************************************/
+
+	Select selct;
+	rem_port* port = NULL;
+	unsigned i;
 
 	while (true)
 	{
+		if (!port)
+		{
+			selct.clear();
+			for (i = 0; i < ports->getCount(); i++)
+				selct.set((*ports)[i]->port_handle);
+			selct.select(NULL);
+		}
+
+		port = NULL;
+		for (i = 0; i < ports->getCount(); i++)
+		{
+			Select::HandleState result = selct.ok((*ports)[i]);
+
+			if (result == Select::SEL_READY)
+			{
+				port = (*ports)[i];
+				break;
+			}
+
+			if (result == Select::SEL_BAD)
+			{
+				gds__log("INET/server_standalone: error in select()");
+				return NULL;
+			}
+		}
+		if (!port)
+			continue;
+
+		selct.unset(port->port_handle);
 		SOCKET s = accept(port->port_handle, NULL, NULL);
 		const int inetErrNo = INET_ERRNO;
 		if (s == INVALID_SOCKET)
@@ -970,7 +1112,7 @@ static void INET_server_socket(rem_port* port, USHORT flag, const addrinfo* pai)
 			port->port_handle = s;
 			port->port_server_flags |= SRVR_server;
 			port->port_flags |= PORT_server;
-			return;
+			return port;
 		}
 
 #ifdef WIN_NT
@@ -1250,7 +1392,7 @@ static rem_port* alloc_port(rem_port* const parent, const USHORT flags)
 	port->port_request = aux_request;
 	port->port_buff_size = (USHORT) INET_remote_buffer;
 	port->port_async_receive = inet_async_receive;
-	port->port_flags = flags;
+	port->port_flags = parent ? (flags & ~PORT_main) : flags;
 
 	xdrinet_create(	&port->port_send, port,
 					&port->port_buffer[INET_remote_buffer],
@@ -1835,7 +1977,7 @@ static rem_port* receive( rem_port* main_port, PACKET * packet)
 	return main_port;
 }
 
-static bool select_multi(rem_port* main_port, UCHAR* buffer, SSHORT bufsize, SSHORT* length,
+static bool select_multi(RemPortArray* main_ports, UCHAR* buffer, SSHORT bufsize, SSHORT* length,
 						 RemPortPtr& port)
 {
 /**************************************
@@ -1845,8 +1987,8 @@ static bool select_multi(rem_port* main_port, UCHAR* buffer, SSHORT bufsize, SSH
  **************************************
  *
  * Functional description
- *	Receive an IP packet from a port or clients of a port.
- *  Used only by the multiclient server on main server's port.
+ *	Receive an IP packet from ports  or their clients.
+ *	Used only by the multiclient server on main server's ports.
  *	If a connection request comes in, generate a new port
  *	block for the client.
  *
@@ -1854,20 +1996,25 @@ static bool select_multi(rem_port* main_port, UCHAR* buffer, SSHORT bufsize, SSH
 
 	for (;;)
 	{
-		select_port(main_port, &INET_select, port);
-		if (port == main_port && (port->port_server_flags & SRVR_multi_client))
+		select_port(main_ports, &INET_select, port);
+		if (port && (port->port_flags & PORT_main) && (port->port_server_flags & SRVR_multi_client))
 		{
 			if (INET_shutting_down)
 			{
-				if (main_port->port_state != rem_port::BROKEN)
+				for (int i = 0; i < main_ports->getCount(); i++)
 				{
-					main_port->port_state = rem_port::BROKEN;
+					rem_port* main_port = (*main_ports)[i];
+
+					if (main_port->port_state != rem_port::BROKEN)
+					{
+						main_port->port_state = rem_port::BROKEN;
 
-					shutdown(main_port->port_handle, 2);
-					SOCLOSE(main_port->port_handle);
+						shutdown(main_port->port_handle, 2);
+						SOCLOSE(main_port->port_handle);
+					}
 				}
 			}
-			else if (port = select_accept(main_port))
+			else if (port = select_accept(port))
 			{
 				if (!packet_receive(port, buffer, bufsize, length))
 				{
@@ -1898,7 +2045,7 @@ static bool select_multi(rem_port* main_port, UCHAR* buffer, SSHORT bufsize, SSH
 			}
 			return (*length) ? true : false;
 		}
-		if (!select_wait(main_port, &INET_select))
+		if (!select_wait(main_ports, &INET_select))
 		{
 			port = NULL;
 			return false;
@@ -1942,7 +2089,7 @@ static rem_port* select_accept( rem_port* main_port)
 	return 0;
 }
 
-static void select_port(rem_port* main_port, Select* selct, RemPortPtr& port)
+static void select_port(RemPortArray* main_ports, Select* selct, RemPortPtr& port)
 {
 /**************************************
  *
@@ -1962,35 +2109,40 @@ static void select_port(rem_port* main_port, Select* selct, RemPortPtr& port)
 
 	MutexLockGuard guard(port_mutex, FB_FUNCTION);
 
-	for (port = main_port; port; port = port->port_next)
+	for (int i = 0; i < main_ports->getCount(); i++)
 	{
-		Select::HandleState result = selct->ok(port);
-		selct->unset(port->port_handle);
+		rem_port* main_port = (*main_ports)[i];
 
-		switch (result)
+		for (port = main_port; port; port = port->port_next)
 		{
-		case Select::SEL_BAD:
-			if (port->port_state == rem_port::BROKEN || (port->port_flags & PORT_connecting))
+			Select::HandleState result = selct->ok(port);
+			selct->unset(port->port_handle);
+
+			switch (result)
+			{
+			case Select::SEL_BAD:
+				if (port->port_state == rem_port::BROKEN || (port->port_flags & PORT_connecting))
+					continue;
+				return;
+
+			case Select::SEL_DISCONNECTED:
 				continue;
-			return;
 
-		case Select::SEL_DISCONNECTED:
-			continue;
+			case Select::SEL_READY:
+				port->port_dummy_timeout = port->port_dummy_packet_interval;
+				return;
 
-		case Select::SEL_READY:
-			port->port_dummy_timeout = port->port_dummy_packet_interval;
-			return;
+			default:
+				break;
+			}
 
-		default:
-			break;
+			if (port->port_dummy_timeout < 0)
+				return;
 		}
-
-		if (port->port_dummy_timeout < 0)
-			return;
 	}
 }
 
-static bool select_wait( rem_port* main_port, Select* selct)
+static bool select_wait(RemPortArray* main_ports, Select* selct)
 {
 /**************************************
  *
@@ -2029,59 +2181,64 @@ static bool select_wait( rem_port* main_port, Select* selct)
 
 		{ // port_mutex scope
 			MutexLockGuard guard(port_mutex, FB_FUNCTION);
-			for (rem_port* port = main_port; port; port = port->port_next)
+			for (int i = 0; i < main_ports->getCount(); i++)
 			{
-				if (port->port_state == rem_port::PENDING &&
-					// don't wait on still listening (not connected) async port
-					!(port->port_handle == INVALID_SOCKET && (port->port_flags & PORT_async)))
- 				{
-					// Adjust down the port's keepalive timer.
+				rem_port* main_port = (*main_ports)[i];
 
-					if (port->port_dummy_packet_interval)
+				for (rem_port* port = main_port; port; port = port->port_next)
+				{
+					if (port->port_state == rem_port::PENDING &&
+						// don't wait on still listening (not connected) async port
+						!(port->port_handle == INVALID_SOCKET && (port->port_flags & PORT_async)))
 					{
-						port->port_dummy_timeout -= delta_time;
-					}
+						// Adjust down the port's keepalive timer.
 
-					if (checkPorts)
-					{
-						// select() returned EBADF\WSAENOTSOCK - we have a broken socket
-						// in current fdset. Search and return it to caller to close
-						// broken connection correctly
+						if (port->port_dummy_packet_interval)
+						{
+							port->port_dummy_timeout -= delta_time;
+						}
+
+						if (checkPorts)
+						{
+							// select() returned EBADF\WSAENOTSOCK - we have a broken socket
+							// in current fdset. Search and return it to caller to close
+							// broken connection correctly
 
-						struct linger lngr;
-						socklen_t optlen = sizeof(lngr);
-						const bool badSocket =
+							struct linger lngr;
+							socklen_t optlen = sizeof(lngr);
+							const bool badSocket =
 #ifdef WIN_NT
-							false;
+								false;
 #else
-							(port->port_handle < 0 || port->port_handle >= FD_SETSIZE);
+								(port->port_handle < 0 || port->port_handle >= FD_SETSIZE);
 #endif
 
-						if (badSocket || getsockopt(port->port_handle,
-								SOL_SOCKET, SO_LINGER, (SCHAR*) &lngr, &optlen) != 0)
-						{
-							if (badSocket || INET_ERRNO == NOTASOCKET)
+							if (badSocket || getsockopt(port->port_handle,
+									SOL_SOCKET, SO_LINGER, (SCHAR*) &lngr, &optlen) != 0)
 							{
-								// not a socket, strange !
-								gds__log("INET/select_wait: found \"not a socket\" socket : %"HANDLEFORMAT,
-										 port->port_handle);
-
-								// this will lead to receive() which will break bad connection
-								selct->clear();
-								if (!badSocket)
+								if (badSocket || INET_ERRNO == NOTASOCKET)
 								{
-									selct->set(port->port_handle);
+									// not a socket, strange !
+									gds__log("INET/select_wait: found \"not a socket\" socket : %"HANDLEFORMAT,
+											 port->port_handle);
+
+									// this will lead to receive() which will break bad connection
+									selct->clear();
+									if (!badSocket)
+									{
+										selct->set(port->port_handle);
+									}
+									return true;
 								}
-								return true;
 							}
 						}
-					}
 
-					// if process is shuting down - don't listen on main port
-					if (!INET_shutting_down || port != main_port)
-					{
-						selct->set(port->port_handle);
-						found = true;
+						// if process is shuting down - don't listen on main port
+						if (!INET_shutting_down || port != main_port)
+						{
+							selct->set(port->port_handle);
+							found = true;
+						}
 					}
 				}
 			}
@@ -2090,7 +2247,7 @@ static bool select_wait( rem_port* main_port, Select* selct)
 
 		if (!found)
 		{
-			if (!INET_shutting_down && (main_port->port_server_flags & SRVR_multi_client))
+			if (!INET_shutting_down && ((*main_ports)[0]->port_server_flags & SRVR_multi_client))
 				gds__log("INET/select_wait: client rundown complete, server exiting");
 
 			return false;
@@ -2101,8 +2258,9 @@ static bool select_wait( rem_port* main_port, Select* selct)
 			// Before waiting for incoming packet, check for server shutdown
 			if (tryStopMainThread && tryStopMainThread())
 			{
-				// this is not server port any more
-				main_port->port_server_flags &= ~SRVR_multi_client;
+				// these are not server ports any more
+				for (int i = 0; i < main_ports->getCount(); i++)
+					(*main_ports)[i]->port_server_flags &= ~SRVR_multi_client;
 				return false;
 			}
 
@@ -2128,9 +2286,14 @@ static bool select_wait( rem_port* main_port, Select* selct)
 				if (selct->getCount() == 0)
 				{
 					MutexLockGuard guard(port_mutex, FB_FUNCTION);
-					for (rem_port* port = main_port; port; port = port->port_next)
+					for (int i = 0; i < main_ports->getCount(); i++)
 					{
-						selct->unset(port->port_handle);
+						rem_port* main_port = (*main_ports)[i];
+
+						for (rem_port* port = main_port; port; port = port->port_next)
+						{
+							selct->unset(port->port_handle);
+						}
 					}
 				}
 				return true;
diff --git a/src/remote/inet_proto.h b/src/remote/inet_proto.h
index af00e8f..7981f7b 100644
--- a/src/remote/inet_proto.h
+++ b/src/remote/inet_proto.h
@@ -35,6 +35,7 @@ namespace Firebird
 
 rem_port*	INET_analyze(ClntAuthBlock*, const Firebird::PathName&, const TEXT*,
 						 bool, Firebird::ClumpletReader&, Firebird::RefPtr<Config>*, const Firebird::PathName*);
+RemPortArray*	INET_connect_all(const TEXT*, USHORT);
 rem_port*	INET_connect(const TEXT*, struct packet*, USHORT, Firebird::ClumpletReader*,
 						 Firebird::RefPtr<Config>*);
 rem_port*	INET_reconnect(SOCKET);
diff --git a/src/remote/remote.cpp b/src/remote/remote.cpp
index 6cb0bbd..db17543 100644
--- a/src/remote/remote.cpp
+++ b/src/remote/remote.cpp
@@ -674,11 +674,6 @@ rem_port* rem_port::receive(PACKET* pckt)
 	return (*this->port_receive_packet)(this, pckt);
 }
 
-bool rem_port::select_multi(UCHAR* buffer, SSHORT bufsize, SSHORT* length, RemPortPtr& port)
-{
-	return (*this->port_select_multi)(this, buffer, bufsize, length, port);
-}
-
 void rem_port::abort_aux_connection()
 {
 	if (this->port_abort_aux_connection)
diff --git a/src/remote/remote.h b/src/remote/remote.h
index 60e365a..f47e938 100644
--- a/src/remote/remote.h
+++ b/src/remote/remote.h
@@ -36,6 +36,7 @@
 #include "../common/Auth.h"
 #include "../common/classes/objects_array.h"
 #include "../common/classes/fb_string.h"
+#include "../common/classes/array.h"
 #include "../common/classes/ClumpletWriter.h"
 #include "../common/classes/RefMutex.h"
 #include "../common/StatusHolder.h"
@@ -811,12 +812,14 @@ const USHORT PORT_server		= 0x0080;	// Server (not client) port
 const USHORT PORT_detached		= 0x0100;	// op_detach, op_drop_database or op_service_detach was processed
 const USHORT PORT_rdb_shutdown	= 0x0200;	// Database is shut down
 const USHORT PORT_connecting	= 0x0400;	// Aux connection waits for a channel to be activated by client
+const USHORT PORT_main			= 0x0800;	// A main port
 
 // Port itself
 
 typedef rem_port* (*t_port_connect)(rem_port*, PACKET*);
 
 typedef Firebird::RefPtr<rem_port> RemPortPtr;
+typedef Firebird::Array<RemPortPtr> RemPortArray;
 
 struct rem_port : public Firebird::GlobalStorage, public Firebird::RefCounted
 {
@@ -838,7 +841,7 @@ struct rem_port : public Firebird::GlobalStorage, public Firebird::RefCounted
 	XDR_INT			(*port_send_partial)(rem_port*, PACKET*);
 	t_port_connect	port_connect;		// Establish secondary connection
 	rem_port*		(*port_request)(rem_port*, PACKET*);	// Request to establish secondary connection
-	bool			(*port_select_multi)(rem_port*, UCHAR*, SSHORT, SSHORT*, RemPortPtr&);	// get packet from active port
+	bool			(*port_select_multi)(RemPortArray*, UCHAR*, SSHORT, SSHORT*, RemPortPtr&);	// get packet from active port
 	void			(*port_abort_aux_connection)(rem_port*);	// stop waiting for secondary connection
 
 	enum rem_port_t {
@@ -862,6 +865,7 @@ struct rem_port : public Firebird::GlobalStorage, public Firebird::RefCounted
 	USHORT			port_protocol;		// protocol version number
 	USHORT			port_buff_size;		// port buffer size (approx)
 	USHORT			port_flags;			// Misc flags
+	USHORT			port_index;			// index in the main_ports set
 	SLONG			port_connect_timeout;   // Connection timeout value
 	SLONG			port_dummy_packet_interval; // keep alive dummy packet interval
 	SLONG			port_dummy_timeout;	// time remaining until keepalive packet
@@ -932,7 +936,7 @@ public:
 		port_type(t), port_state(PENDING), port_clients(0), port_next(0),
 		port_parent(0), port_async(0), port_async_receive(0),
 		port_server(0), port_server_flags(0), port_protocol(0), port_buff_size(0),
-		port_flags(0), port_connect_timeout(0), port_dummy_packet_interval(0),
+		port_flags(0), port_index(0), port_connect_timeout(0), port_dummy_packet_interval(0),
 		port_dummy_timeout(0), port_handle(INVALID_SOCKET), port_channel(INVALID_SOCKET), port_context(0),
 		port_events_thread(0), port_events_shutdown(0),
 #ifdef WIN_NT
@@ -1045,7 +1049,6 @@ public:
 	XDR_INT	send_partial(PACKET* pckt);
 	rem_port*	connect(PACKET* pckt);
 	rem_port*	request(PACKET* pckt);
-	bool		select_multi(UCHAR* buffer, SSHORT bufsize, SSHORT* length, RemPortPtr& port);
 	void		abort_aux_connection();
 
 	bool haveRecvData()
diff --git a/src/remote/server/os/posix/inet_server.cpp b/src/remote/server/os/posix/inet_server.cpp
index 9be2afe..af1b0bf 100644
--- a/src/remote/server/os/posix/inet_server.cpp
+++ b/src/remote/server/os/posix/inet_server.cpp
@@ -131,7 +131,7 @@ static void logSecurityDatabaseError(const char* path, ISC_STATUS* status)
 	exit(STARTUP_ERROR);
 }
 
-static int closePort(const int reason, const int, void* arg)
+static int closePorts(const int reason, const int, void* arg)
 {
 	if (reason == fb_shutrsn_signal)
 	{
@@ -157,7 +157,7 @@ int CLIB_ROUTINE main( int argc, char** argv)
  **************************************/
 	try
 	{
-		RemPortPtr port;
+		RemPortArray* ports;
 
 		// We should support 3 modes:
 		// 1. Standalone single-process listener (like SS).
@@ -351,7 +351,12 @@ int CLIB_ROUTINE main( int argc, char** argv)
 		{
 			try
 			{
-				port = INET_connect(protocol, 0, INET_SERVER_flag, 0, NULL);
+				ports = INET_connect_all(protocol, INET_SERVER_flag);
+				if (!ports)
+				{
+					Firebird::Syslog::Record(Firebird::Syslog::Error, "Unable to listen on any address/port");
+					exit(STARTUP_ERROR);
+				}
 			}
 			catch (const Firebird::Exception& ex)
 			{
@@ -365,12 +370,14 @@ int CLIB_ROUTINE main( int argc, char** argv)
 
 		if (classic)
 		{
-			port = INET_server(channel);
+			RemPortPtr port(INET_server(channel));
 			if (!port)
 			{
 				fprintf(stderr, "fbserver: Unable to start INET_server\n");
 				exit(STARTUP_ERROR);
 			}
+			ports = new RemPortArray(1);
+			ports->add(port);
 		}
 
 		{ // scope for interface ptr
@@ -405,9 +412,9 @@ int CLIB_ROUTINE main( int argc, char** argv)
 			}
 		} // end scope
 
-		fb_shutdown_callback(NULL, closePort, fb_shut_exit, port);
+		fb_shutdown_callback(NULL, closePorts, fb_shut_exit, ports);
 
-		SRVR_multi_thread(port, INET_SERVER_flag);
+		SRVR_multi_thread(ports, INET_SERVER_flag);
 
 #ifdef DEBUG_GDS_ALLOC
 		// In Debug mode - this will report all server-side memory leaks due to remote access
diff --git a/src/remote/server/os/win32/srvr_w32.cpp b/src/remote/server/os/win32/srvr_w32.cpp
index 9fb3826..9037f77 100644
--- a/src/remote/server/os/win32/srvr_w32.cpp
+++ b/src/remote/server/os/win32/srvr_w32.cpp
@@ -287,7 +287,10 @@ int WINAPI WinMain(HINSTANCE hThisInst, HINSTANCE /*hPrevInst*/, LPSTR lpszArgs,
 
 				if (port)
 				{
-					SRVR_multi_thread(port, server_flag);
+					RemPortArray* ports = new RemPortArray(1);
+
+					ports->add(port);
+					SRVR_multi_thread(ports, server_flag);
 					port = NULL;
 				}
 			}
@@ -392,28 +395,29 @@ static THREAD_ENTRY_DECLARE inet_connect_wait_thread(THREAD_ENTRY_PARAM)
 
 	while (!server_shutdown)
 	{
-		rem_port* port = NULL;
+		RemPortArray* ports;
 		try
 		{
-			port = INET_connect(protocol_inet, NULL, server_flag, 0, NULL);
+			ports = INET_connect_all(protocol_inet, server_flag);
 		}
 		catch (const Exception& ex)
 		{
 			iscLogException("INET_connect", ex);
 		}
 
-		if (!port)
+		if (!ports)
 			break;
 
 		if (server_flag & SRVR_multi_client)
 		{
-			SRVR_multi_thread(port, server_flag);
+			SRVR_multi_thread(ports, server_flag);
 			break;
 		}
 
 		try
 		{
-			Thread::start(process_connection_thread, port, THREAD_medium);
+			for (unsigned i = 0; i < ports->getCount(); i++)
+				Thread::start(process_connection_thread, (*ports)[i], THREAD_medium);
 		}
 		catch (const Exception& )
 		{
diff --git a/src/remote/server/serve_proto.h b/src/remote/server/serve_proto.h
index 00605fb..1d7f529 100644
--- a/src/remote/server/serve_proto.h
+++ b/src/remote/server/serve_proto.h
@@ -29,7 +29,7 @@
 #endif
 
 void SRVR_main(rem_port*, USHORT);
-void SRVR_multi_thread(rem_port*, USHORT);
+void SRVR_multi_thread(RemPortArray*, USHORT);
 void SRVR_enum_attachments(ULONG& att_cnt, ULONG& dbs_cnt, ULONG& svc_cnt);
 
 #endif	// REMOTE_SERVE_PROTO_H
diff --git a/src/remote/server/server.cpp b/src/remote/server/server.cpp
index e3ff8b4..685a861 100644
--- a/src/remote/server/server.cpp
+++ b/src/remote/server/server.cpp
@@ -1327,7 +1327,7 @@ static bool link_request(rem_port* port, server_req_t* request)
 }
 
 
-void SRVR_multi_thread( rem_port* main_port, USHORT flags)
+void SRVR_multi_thread(RemPortArray* main_ports, USHORT flags)
 {
 /**************************************
  *
@@ -1342,16 +1342,40 @@ void SRVR_multi_thread( rem_port* main_port, USHORT flags)
 	server_req_t* request = NULL;
 	RemPortPtr port;		// Was volatile PORT port = NULL;
 
-	// hold reference on main_port to free asyncPacket before deletion of port
-	RemPortPtr mainPortRef(main_port);
-	PACKET asyncPacket;
+	// hold references on main_port elements to free asyncPackets before deletion of port
+	unsigned port_cnt = main_ports->getCount();
+	RemPortArray mainPortsRef(*main_ports);
+	PACKET asyncPackets[port_cnt];
+	bool (*select_multi)(RemPortArray*, UCHAR*, SSHORT, SSHORT*, RemPortPtr&);
+	select_multi = (*main_ports)[0]->port_select_multi;
 
-	zap_packet(&asyncPacket, true);
+	for (unsigned i = 0; i < port_cnt; i++)
+	{
+		rem_port* p = (*main_ports)[i];
+
+		p->port_flags |= PORT_main;
+		for (rem_port* p2 = p; p2; p2 = p2->port_next)
+		{
+			p2->port_index = i;
+		}
+
+		zap_packet(&asyncPackets[i], true);
+	}
 	++cntServers;
 
 	try
 	{
-		set_server(main_port, flags);
+		const size_t MAX_PACKET_SIZE = MAX_SSHORT;
+		SSHORT bufSize = MAX_PACKET_SIZE;
+
+		for (unsigned i = 0; i < port_cnt; i++)
+		{
+			rem_port* main_port = (*main_ports)[i];
+
+			set_server(main_port, flags);
+			if (bufSize > main_port->port_buff_size)
+				bufSize = main_port->port_buff_size;
+		}
 
 		// We need to have this error handler as there is so much underlaying code
 		// that is depending on it being there.  The expected failure
@@ -1366,8 +1390,6 @@ void SRVR_multi_thread( rem_port* main_port, USHORT flags)
 
 		try
 		{
-			const size_t MAX_PACKET_SIZE = MAX_SSHORT;
-			const SSHORT bufSize = MIN(main_port->port_buff_size, MAX_PACKET_SIZE);
 			UCharBuffer packet_buffer;
 			UCHAR* const buffer = packet_buffer.getBuffer(bufSize);
 
@@ -1384,18 +1406,19 @@ void SRVR_multi_thread( rem_port* main_port, USHORT flags)
 			{
 				SSHORT dataSize;
 				// We have a request block - now get some information to stick into it
-				const bool ok = main_port->select_multi(buffer, bufSize, &dataSize, port);
+				const bool ok = select_multi(main_ports, buffer, bufSize, &dataSize, port);
 				if (!port)
 				{
-					if ((main_port->port_server_flags & SRVR_multi_client) && !server_shutdown)
+					if (((*main_ports)[0]->port_server_flags & SRVR_multi_client) && !server_shutdown)
 					{
-						gds__log("SRVR_multi_thread/RECEIVE: error on main_port, shutting down");
+						gds__log("SRVR_multi_thread/RECEIVE: error on main_ports, shutting down");
 					}
 					break;
 				}
 				if (dataSize)
 				{
-					const SSHORT asyncSize = port->asyncReceive(&asyncPacket, buffer, dataSize);
+					USHORT idx = port->port_index;
+					const SSHORT asyncSize = port->asyncReceive(&asyncPackets[idx], buffer, dataSize);
 					if (asyncSize == dataSize)
 					{
 						port = NULL;
@@ -1482,13 +1505,17 @@ void SRVR_multi_thread( rem_port* main_port, USHORT flags)
 
 			// All worker threads are stopped and will never run any more
 			// Disconnect remaining ports gracefully
-			rem_port* run_port = main_port;
-			while (run_port)
+			for (unsigned i = 0; i < port_cnt; i++)
 			{
-				rem_port* current_port = run_port;	// important order of operations
-				run_port = run_port->port_next;		// disconnect() can modify linked list of ports
-				if (!(current_port->port_flags & PORT_disconnect))
-					current_port->disconnect(NULL, NULL);
+				rem_port* run_port = (*main_ports)[i];
+
+				while (run_port)
+				{
+					rem_port* current_port = run_port;	// important order of operations
+					run_port = run_port->port_next;		// disconnect() can modify linked list of ports
+					if (!(current_port->port_flags & PORT_disconnect))
+						current_port->disconnect(NULL, NULL);
+				}
 			}
 		}
 		catch (const Exception& e)
@@ -1553,9 +1580,12 @@ void SRVR_multi_thread( rem_port* main_port, USHORT flags)
 			}
 		}
 
-		if (main_port->port_async_receive)
+		for (unsigned i = 0; i < port_cnt; i++)
 		{
-			REMOTE_free_packet(main_port->port_async_receive, &asyncPacket);
+			rem_port* port = (*main_ports)[i];
+
+			if (port->port_async_receive)
+				REMOTE_free_packet(port, &asyncPackets[i]);
 		}
 	}
 	catch (const Exception&)
-- 
1.8.4.5

