mirror of
https://github.com/FirebirdSQL/firebird.git
synced 2025-01-26 07:23:08 +01:00
3403 lines
81 KiB
C++
3403 lines
81 KiB
C++
/*
|
|
* PROGRAM: JRD Remote Interface/Server
|
|
* MODULE: inet.cpp
|
|
* DESCRIPTION: TCP/UCP/IP Communications module.
|
|
*
|
|
* The contents of this file are subject to the Interbase Public
|
|
* License Version 1.0 (the "License"); you may not use this file
|
|
* except in compliance with the License. You may obtain a copy
|
|
* of the License at http://www.Inprise.com/IPL.html
|
|
*
|
|
* Software distributed under the License is distributed on an
|
|
* "AS IS" basis, WITHOUT WARRANTY OF ANY KIND, either express
|
|
* or implied. See the License for the specific language governing
|
|
* rights and limitations under the License.
|
|
*
|
|
* The Original Code was created by Inprise Corporation
|
|
* and its predecessors. Portions created by Inprise Corporation are
|
|
* Copyright (C) Inprise Corporation.
|
|
*
|
|
* All Rights Reserved.
|
|
* Contributor(s): ______________________________________.
|
|
*
|
|
* 2002.02.15 Sean Leyne - Code Cleanup, removed obsolete "EPSON" port
|
|
* 2002.02.15 Sean Leyne - Code Cleanup, removed obsolete "XENIX" port
|
|
* 2002.02.15 Sean Leyne - Code Cleanup, removed obsolete "IMP" port
|
|
* 2002.02.15 Sean Leyne - Code Cleanup, removed obsolete "NCR3000" port
|
|
*
|
|
* 2002-02-23 Sean Leyne - Code Cleanup, removed old M88K and NCR3000 port
|
|
*
|
|
* 2002.10.27 Sean Leyne - Code Cleanup, removed obsolete "UNIXWARE" port
|
|
* 2002.10.27 Sean Leyne - Code Cleanup, removed obsolete "Ultrix/MIPS" port
|
|
*
|
|
* 2002.10.28 Sean Leyne - Completed removal of obsolete "DGUX" port
|
|
*
|
|
* 2002.10.29 Sean Leyne - Removed obsolete "Netware" port
|
|
*
|
|
* 2002.10.30 Sean Leyne - Removed support for obsolete "PC_PLATFORM" define
|
|
* 2002.10.30 Sean Leyne - Code Cleanup, removed obsolete "SUN3_3" port
|
|
* 2005.04.01 Konstantin Kuznetsov - allow setting NoNagle option in Classic
|
|
*
|
|
*/
|
|
|
|
#include "firebird.h"
|
|
#include <stdio.h>
|
|
#ifdef HAVE_SYS_TYPES_H
|
|
#include <sys/types.h>
|
|
#endif
|
|
#include <errno.h>
|
|
#include <string.h>
|
|
#include <stdlib.h>
|
|
#include "../jrd/common.h"
|
|
#include "../jrd/file_params.h"
|
|
#include <stdarg.h>
|
|
|
|
#include "../common/classes/timestamp.h"
|
|
#include "../common/classes/init.h"
|
|
#include "../jrd/ThreadStart.h"
|
|
|
|
#ifdef HAVE_PWD_H
|
|
#include <pwd.h>
|
|
#endif
|
|
|
|
#ifdef HAVE_SYS_PARAM_H
|
|
#include <sys/param.h>
|
|
#endif
|
|
|
|
#ifdef HAVE_GRP_H
|
|
#include <grp.h>
|
|
#endif
|
|
|
|
#ifdef HAVE_SYS_SOCKET_H
|
|
#include <sys/socket.h> // for socket()
|
|
#endif
|
|
|
|
#ifdef HAVE_UNISTD_H
|
|
#include <unistd.h>
|
|
#endif
|
|
|
|
|
|
|
|
#ifdef WIN_NT
|
|
#define FD_SETSIZE 1024
|
|
#endif
|
|
|
|
#ifndef WIN_NT
|
|
#include <netinet/tcp.h>
|
|
#include <netinet/in.h>
|
|
#include <netdb.h>
|
|
#include <arpa/inet.h>
|
|
#include <sys/wait.h>
|
|
|
|
#if defined(HAVE_POLL_H)
|
|
#include <poll.h>
|
|
#elif defined(HAVE_SYS_SELECT_H)
|
|
#include <sys/select.h>
|
|
#endif
|
|
|
|
#endif // !WIN_NT
|
|
|
|
const int INET_RETRY_CALL = 5;
|
|
|
|
#include "../remote/remote.h"
|
|
#include "../jrd/ibase.h"
|
|
#include "../common/thd.h"
|
|
#include "../remote/inet_proto.h"
|
|
#include "../remote/proto_proto.h"
|
|
#include "../remote/remot_proto.h"
|
|
#include "../jrd/gds_proto.h"
|
|
#include "../jrd/isc_proto.h"
|
|
#include "../jrd/os/isc_i_proto.h"
|
|
|
|
#include "../common/config/config.h"
|
|
#include "../common/utils_proto.h"
|
|
#include "../common/classes/ClumpletWriter.h"
|
|
|
|
// Please review. Maybe not needed. See H_ERRNO in common.h.
|
|
#if defined HPUX
|
|
extern int h_errno;
|
|
#endif
|
|
|
|
using namespace Firebird;
|
|
|
|
const USHORT MAX_PTYPE = ptype_lazy_send;
|
|
|
|
|
|
#ifdef WIN_NT
|
|
|
|
#include <fcntl.h>
|
|
#include <process.h>
|
|
#include <signal.h>
|
|
#include "../utilities/install/install_nt.h"
|
|
|
|
#define INET_RETRY_ERRNO WSAEINPROGRESS
|
|
#define INET_ADDR_IN_USE WSAEADDRINUSE
|
|
#define sleep(seconds) Sleep ((seconds) * 1000)
|
|
const int NOTASOCKET = WSAENOTSOCK;
|
|
|
|
#else // WIN_NT
|
|
|
|
#ifndef INET_ADDR_IN_USE
|
|
#define INET_ADDR_IN_USE EADDRINUSE
|
|
#endif
|
|
#ifndef INET_RETRY_ERRNO
|
|
#define INET_RETRY_ERRNO TRY_AGAIN
|
|
#endif
|
|
const int NOTASOCKET = EBADF;
|
|
|
|
#endif // WIN_NT
|
|
|
|
|
|
static void SOCLOSE(SOCKET& socket)
|
|
{
|
|
if (socket != INVALID_SOCKET)
|
|
{
|
|
#ifdef WIN_NT
|
|
closesocket(socket);
|
|
#else
|
|
close(socket);
|
|
#endif
|
|
socket = INVALID_SOCKET;
|
|
}
|
|
};
|
|
|
|
// Can't find were it's used.
|
|
//#ifndef SIGURG
|
|
//#define SIGURG SIGINT
|
|
//#endif
|
|
|
|
#ifndef ENOBUFS
|
|
#define ENOBUFS 0
|
|
#endif
|
|
|
|
#ifndef FB_SEND_FLAGS
|
|
#define FB_SEND_FLAGS 0
|
|
#endif
|
|
|
|
#ifndef FB_SETOPT_FLAGS
|
|
#define FB_SETOPT_FLAGS 0
|
|
#endif
|
|
|
|
//
|
|
//#define DEBUG 1
|
|
//
|
|
|
|
#ifdef DEBUG
|
|
#ifdef HAVE_SYS_TIMEB_H
|
|
# include <sys/timeb.h>
|
|
#endif
|
|
const int TRACE_packets = 1 << 0; // bit 0
|
|
const int TRACE_operations = 1 << 1; // bit 1
|
|
const int TRACE_summary = 1 << 2; // bit 2
|
|
|
|
static int INET_trace = TRACE_summary | TRACE_packets | TRACE_operations;
|
|
static time_t INET_start_time = 0;
|
|
SLONG INET_force_error = -1; // simulate a network error
|
|
static ULONG INET_count_send = 0;
|
|
static ULONG INET_count_recv = 0;
|
|
static ULONG INET_bytes_send = 0;
|
|
static ULONG INET_bytes_recv = 0;
|
|
|
|
static ULONG inet_debug_timer()
|
|
{
|
|
/**************************************
|
|
*
|
|
* i n e t _ d e b u g _ t i m e r
|
|
*
|
|
**************************************
|
|
*
|
|
* Functional description
|
|
* Utility function used in DEBUG mode only to put a timestamp
|
|
* since start of connection on each debug output message.
|
|
*
|
|
* This has been implemented and tested on SOLARIS, and may
|
|
* need tweeking on any other platform where DEBUG is needed.
|
|
*
|
|
**************************************/
|
|
#ifdef HAVE_GETTIMEOFDAY
|
|
struct timeval tv;
|
|
GETTIMEOFDAY(&tv);
|
|
return (tv.tv_sec * 1000 + tv.tv_usec - INET_start_time);
|
|
#else
|
|
struct timeb now;
|
|
ftime(&now);
|
|
return (now.time * 1000 + now.millitm - INET_start_time);
|
|
#endif // HAVE_GETTIMEOFDAY
|
|
}
|
|
#endif // DEBUG
|
|
|
|
const SLONG MAX_DATA_LW = 1448; // Low Water mark
|
|
const SLONG MAX_DATA_HW = 32768; // High Water mark
|
|
const SLONG DEF_MAX_DATA = 8192;
|
|
|
|
//const int MAXHOSTLEN = 64;
|
|
|
|
const int SELECT_TIMEOUT = 60; // Dispatch thread select timeout (sec)
|
|
|
|
class Select
|
|
{
|
|
#ifdef HAVE_POLL
|
|
private:
|
|
static const int SEL_INIT_EVENTS = POLLIN;
|
|
static const int SEL_CHECK_MASK = POLLIN;
|
|
|
|
pollfd* getPollFd(int n)
|
|
{
|
|
pollfd* const end = slct_poll.end();
|
|
for (pollfd* pf = slct_poll.begin(); pf < end; ++pf)
|
|
{
|
|
if (n == pf->fd)
|
|
{
|
|
return pf;
|
|
}
|
|
}
|
|
|
|
return NULL;
|
|
}
|
|
|
|
static int compare(const void* a, const void* b)
|
|
{
|
|
// use C-cast here to be for sure compatible with libc
|
|
return ((pollfd*)a)->fd - ((pollfd*)b)->fd;
|
|
}
|
|
#endif
|
|
|
|
public:
|
|
#ifdef HAVE_POLL
|
|
Select()
|
|
: slct_time(0), slct_count(0), slct_poll(*getDefaultMemoryPool())
|
|
{ }
|
|
#else
|
|
Select()
|
|
: slct_time(0), slct_count(0), slct_width(0)
|
|
{
|
|
memset(&slct_fdset, 0, sizeof slct_fdset);
|
|
}
|
|
#endif
|
|
|
|
enum HandleState {SEL_BAD, SEL_DISCONNECTED, SEL_NO_DATA, SEL_READY};
|
|
|
|
HandleState ok(const rem_port* port)
|
|
{
|
|
SOCKET n = port->port_handle;
|
|
#if defined(WIN_NT)
|
|
return FD_ISSET(n, &slct_fdset) ? SEL_READY : SEL_NO_DATA;
|
|
#elif defined(HAVE_POLL)
|
|
const pollfd* pf = getPollFd(n);
|
|
if (pf)
|
|
{
|
|
return pf->events & SEL_CHECK_MASK ? SEL_READY : SEL_NO_DATA;
|
|
}
|
|
return (n < 0) ? (port->port_flags & PORT_disconnect ? SEL_DISCONNECTED : SEL_BAD) : SEL_NO_DATA;
|
|
#else
|
|
if (n < 0 || n >= FD_SETSIZE)
|
|
{
|
|
return port->port_flags & PORT_disconnect ? SEL_DISCONNECTED : SEL_BAD;
|
|
}
|
|
return (n < slct_width && FD_ISSET(n, &slct_fdset)) ? SEL_READY : SEL_NO_DATA;
|
|
#endif
|
|
}
|
|
|
|
void unset(SOCKET handle)
|
|
{
|
|
#if defined(HAVE_POLL)
|
|
pollfd* pf = getPollFd(handle);
|
|
if (pf)
|
|
{
|
|
pf->events = 0;
|
|
}
|
|
#else
|
|
FD_CLR(handle, &slct_fdset);
|
|
--slct_count;
|
|
#endif
|
|
}
|
|
|
|
void set(SOCKET handle)
|
|
{
|
|
#ifdef HAVE_POLL
|
|
pollfd* pf = getPollFd(handle);
|
|
if (pf)
|
|
{
|
|
pf->events = SEL_INIT_EVENTS;
|
|
return;
|
|
}
|
|
pollfd f;
|
|
f.fd = handle;
|
|
f.events = SEL_INIT_EVENTS;
|
|
slct_poll.push(f);
|
|
#else
|
|
FD_SET(handle, &slct_fdset);
|
|
#ifdef WIN_NT
|
|
++slct_width;
|
|
#else
|
|
slct_width = MAX(slct_width, handle + 1);
|
|
#endif // WIN_NT
|
|
#endif // HAVE_POLL
|
|
}
|
|
|
|
void clear()
|
|
{
|
|
slct_count = 0;
|
|
#if defined(HAVE_POLL)
|
|
slct_poll.clear();
|
|
#else
|
|
slct_width = 0;
|
|
FD_ZERO(&slct_fdset);
|
|
#endif
|
|
}
|
|
|
|
int select(timeval* timeout)
|
|
{
|
|
bool hasRequest = false;
|
|
|
|
#ifdef HAVE_POLL
|
|
pollfd* const end = slct_poll.end();
|
|
for (pollfd* pf = slct_poll.begin(); pf < end; ++pf)
|
|
{
|
|
pf->revents = pf->events;
|
|
if (pf->events & SEL_CHECK_MASK)
|
|
{
|
|
hasRequest = true;
|
|
}
|
|
}
|
|
|
|
if (!hasRequest)
|
|
{
|
|
errno = NOTASOCKET;
|
|
return -1;
|
|
}
|
|
|
|
int milliseconds = timeout ? timeout->tv_sec * 1000 + timeout->tv_usec / 1000 : -1;
|
|
slct_count = ::poll(slct_poll.begin(), slct_poll.getCount(), milliseconds);
|
|
|
|
for (pollfd* pf = slct_poll.begin(); pf < end; ++pf)
|
|
{
|
|
pf->events = pf->revents;
|
|
}
|
|
#else
|
|
#ifdef WIN_NT
|
|
slct_count = ::select(FD_SETSIZE, &slct_fdset, NULL, NULL, timeout);
|
|
#else
|
|
|
|
|
|
slct_count = ::select(slct_width, &slct_fdset, NULL, NULL, timeout);
|
|
#endif // WIN_NT
|
|
#endif // HAVE_POLL
|
|
|
|
|
|
return slct_count;
|
|
}
|
|
|
|
int getCount()
|
|
{
|
|
return slct_count;
|
|
}
|
|
|
|
time_t slct_time;
|
|
|
|
private:
|
|
int slct_count;
|
|
#ifdef HAVE_POLL
|
|
Firebird::HalfStaticArray<pollfd, 8> slct_poll;
|
|
#else
|
|
int slct_width;
|
|
fd_set slct_fdset;
|
|
#endif
|
|
};
|
|
|
|
static bool accept_connection(rem_port*, const P_CNCT*);
|
|
#ifdef HAVE_SETITIMER
|
|
static void alarm_handler(int);
|
|
#endif
|
|
static rem_port* alloc_port(rem_port*, const USHORT = 0);
|
|
static rem_port* aux_connect(rem_port*, PACKET*);
|
|
static rem_port* aux_request(rem_port*, PACKET*);
|
|
|
|
#if !defined(WIN_NT)
|
|
static bool check_host(rem_port*);
|
|
static THREAD_ENTRY_DECLARE waitThread(THREAD_ENTRY_PARAM);
|
|
|
|
static Firebird::GlobalPtr<Firebird::Mutex> waitThreadMutex;
|
|
static unsigned int procCount = 0;
|
|
#endif // WIN_NT
|
|
|
|
static void disconnect(rem_port*);
|
|
static void force_close(rem_port*);
|
|
static int cleanup_ports(const int, const int, void*);
|
|
|
|
#ifdef NO_FORK
|
|
static int fork();
|
|
#endif
|
|
|
|
#ifdef WIN_NT
|
|
static void wsaExitHandler(void*);
|
|
static int fork(SOCKET, USHORT);
|
|
static THREAD_ENTRY_DECLARE forkThread(THREAD_ENTRY_PARAM);
|
|
|
|
static Firebird::GlobalPtr<Firebird::Mutex> forkMutex;
|
|
static HANDLE forkEvent = INVALID_HANDLE_VALUE;
|
|
static bool forkThreadStarted = false;
|
|
|
|
typedef Firebird::Array<SOCKET> SocketsArray;
|
|
static SocketsArray* forkSockets;
|
|
|
|
#endif
|
|
|
|
static in_addr get_bind_address();
|
|
static int get_host_address(const char* name, in_addr* const host_addr_arr, const int arr_size);
|
|
|
|
static void copy_p_cnct_repeat_array( p_cnct::p_cnct_repeat* pDest,
|
|
const p_cnct::p_cnct_repeat* pSource,
|
|
size_t nEntries);
|
|
|
|
static int inet_destroy(XDR*);
|
|
static void inet_gen_error(rem_port*, const Firebird::Arg::StatusVector& v);
|
|
static bool_t inet_getbytes(XDR*, SCHAR *, u_int);
|
|
static bool_t inet_getlong(XDR*, SLONG *);
|
|
static u_int inet_getpostn(XDR*);
|
|
static caddr_t inet_inline(XDR*, u_int);
|
|
static void inet_error(rem_port*, const TEXT*, ISC_STATUS, int);
|
|
static bool_t inet_putlong(XDR*, const SLONG*);
|
|
static bool_t inet_putbytes(XDR*, const SCHAR*, u_int);
|
|
static bool_t inet_read(XDR*);
|
|
static bool_t inet_setpostn(XDR*, u_int);
|
|
static rem_port* inet_try_connect( PACKET*,
|
|
Rdb*,
|
|
const Firebird::PathName&,
|
|
const TEXT*,
|
|
ISC_STATUS*,
|
|
Firebird::ClumpletReader&);
|
|
static bool_t inet_write(XDR*); //, int);
|
|
|
|
#ifdef DEBUG
|
|
static void packet_print(const TEXT*, const UCHAR*, int, ULONG);
|
|
#endif
|
|
|
|
static bool packet_receive(rem_port*, UCHAR*, SSHORT, SSHORT*);
|
|
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 int send_full(rem_port*, PACKET *);
|
|
static int send_partial(rem_port*, PACKET *);
|
|
|
|
static int xdrinet_create(XDR*, rem_port*, UCHAR *, USHORT, enum xdr_op);
|
|
static bool setNoNagleOption(rem_port*);
|
|
static FPTR_INT tryStopMainThread = 0;
|
|
|
|
|
|
|
|
static XDR::xdr_ops inet_ops =
|
|
{
|
|
inet_getlong,
|
|
inet_putlong,
|
|
inet_getbytes,
|
|
inet_putbytes,
|
|
inet_getpostn,
|
|
inet_setpostn,
|
|
inet_inline,
|
|
inet_destroy
|
|
};
|
|
|
|
|
|
#define MAXCLIENTS NOFILE - 10
|
|
|
|
// Select uses bit masks of file descriptors in longs.
|
|
|
|
#ifndef NBBY
|
|
#define NBBY 8
|
|
#endif
|
|
|
|
#ifndef NFDBITS
|
|
#if !defined(WIN_NT)
|
|
#define NFDBITS (sizeof(SLONG) * NBBY)
|
|
|
|
#define FD_SET(n, p) ((p)->fds_bits[(n) / NFDBITS] |= (1 << ((n) % NFDBITS)))
|
|
#define FD_CLR(n, p) ((p)->fds_bits[(n) / NFDBITS] &= ~(1 << ((n) % NFDBITS)))
|
|
#define FD_ISSET(n, p) ((p)->fds_bits[(n) / NFDBITS] & (1 << ((n) % NFDBITS)))
|
|
#define FD_ZERO(p) memset(p, 0, sizeof(*(p)))
|
|
#endif
|
|
#endif
|
|
|
|
#ifdef WIN_NT
|
|
#define INTERRUPT_ERROR(x) (SYSCALL_INTERRUPTED(x) || (x) == WSAEINTR)
|
|
#else
|
|
#define INTERRUPT_ERROR(x) (SYSCALL_INTERRUPTED(x))
|
|
#endif
|
|
|
|
|
|
|
|
SLONG INET_remote_buffer;
|
|
static Firebird::GlobalPtr<Firebird::Mutex> init_mutex;
|
|
static volatile bool INET_initialized = false;
|
|
static volatile bool INET_shutting_down = false;
|
|
static Select INET_select;
|
|
static int INET_max_clients;
|
|
static rem_port* inet_async_receive = NULL;
|
|
|
|
|
|
static Firebird::GlobalPtr<Firebird::Mutex> port_mutex;
|
|
static Firebird::GlobalPtr<PortsCleanup> inet_ports;
|
|
|
|
|
|
rem_port* INET_analyze(const Firebird::PathName& file_name,
|
|
ISC_STATUS* status_vector,
|
|
const TEXT* node_name,
|
|
const TEXT* user_string,
|
|
bool uv_flag,
|
|
Firebird::ClumpletReader &dpb)
|
|
{
|
|
/**************************************
|
|
*
|
|
* I N E T _ a n a l y z e
|
|
*
|
|
**************************************
|
|
*
|
|
* Functional description
|
|
* File_name is on node_name.
|
|
* Establish an external connection to node_name.
|
|
*
|
|
* If a connection is established, return a port block, otherwise
|
|
* return NULL.
|
|
*
|
|
* If the "uv_flag" is non-zero, user verification also takes place.
|
|
*
|
|
**************************************/
|
|
// We need to establish a connection to a remote server. Allocate the necessary
|
|
// blocks and get ready to go.
|
|
|
|
Rdb* rdb = new Rdb;
|
|
PACKET* packet = &rdb->rdb_packet;
|
|
|
|
// Pick up some user identification information
|
|
Firebird::ClumpletWriter user_id(Firebird::ClumpletReader::UnTagged, MAX_DPB_SIZE);
|
|
Firebird::string buffer;
|
|
int eff_gid;
|
|
int eff_uid;
|
|
|
|
ISC_get_user(&buffer, &eff_uid, &eff_gid, user_string);
|
|
user_id.insertString(CNCT_user, buffer);
|
|
|
|
ISC_get_host(buffer);
|
|
buffer.lower();
|
|
user_id.insertString(CNCT_host, buffer);
|
|
|
|
if ((eff_uid == -1) || uv_flag) {
|
|
user_id.insertTag(CNCT_user_verification);
|
|
}
|
|
else
|
|
{
|
|
// Communicate group id info to server, as user maybe running under group
|
|
// id other than default specified in /etc/passwd.
|
|
|
|
eff_gid = htonl(eff_gid);
|
|
user_id.insertBytes(CNCT_group, reinterpret_cast<UCHAR*>(&eff_gid), sizeof(eff_gid));
|
|
}
|
|
|
|
// Establish connection to server
|
|
|
|
// Note: prior to V3.1E a recievers could not in truth handle more
|
|
// than 5 protocol descriptions, so we try them in chunks of 5 or less
|
|
|
|
// If we want user verification, we can't speak anything less than version 7
|
|
|
|
P_CNCT* cnct = &packet->p_cnct;
|
|
|
|
cnct->p_cnct_user_id.cstr_length = (USHORT) user_id.getBufferLength();
|
|
cnct->p_cnct_user_id.cstr_address = user_id.getBuffer();
|
|
|
|
static const p_cnct::p_cnct_repeat protocols_to_try1[] =
|
|
{
|
|
REMOTE_PROTOCOL(PROTOCOL_VERSION8, ptype_rpc, MAX_PTYPE, 1),
|
|
REMOTE_PROTOCOL(PROTOCOL_VERSION10, ptype_rpc, MAX_PTYPE, 2),
|
|
REMOTE_PROTOCOL(PROTOCOL_VERSION11, ptype_rpc, MAX_PTYPE, 3),
|
|
REMOTE_PROTOCOL(PROTOCOL_VERSION12, ptype_rpc, MAX_PTYPE, 4)
|
|
#ifdef SCROLLABLE_CURSORS
|
|
,
|
|
REMOTE_PROTOCOL(PROTOCOL_SCROLLABLE_CURSORS, ptype_rpc, MAX_PTYPE, 99)
|
|
#endif
|
|
};
|
|
|
|
cnct->p_cnct_count = FB_NELEM(protocols_to_try1);
|
|
|
|
copy_p_cnct_repeat_array(cnct->p_cnct_versions, protocols_to_try1, cnct->p_cnct_count);
|
|
|
|
// Try connection using first set of protocols. punt if error
|
|
|
|
rem_port* port = inet_try_connect(packet, rdb, file_name, node_name, status_vector, dpb);
|
|
if (!port) {
|
|
return NULL;
|
|
}
|
|
|
|
if (packet->p_operation == op_reject && !uv_flag)
|
|
{
|
|
disconnect(port);
|
|
|
|
// try again with next set of known protocols
|
|
|
|
cnct->p_cnct_user_id.cstr_length = (USHORT) user_id.getBufferLength();
|
|
cnct->p_cnct_user_id.cstr_address = user_id.getBuffer();
|
|
|
|
static const p_cnct::p_cnct_repeat protocols_to_try2[] =
|
|
{
|
|
REMOTE_PROTOCOL(PROTOCOL_VERSION6, ptype_rpc, ptype_batch_send, 1),
|
|
REMOTE_PROTOCOL(PROTOCOL_VERSION7, ptype_rpc, MAX_PTYPE, 2)
|
|
};
|
|
|
|
cnct->p_cnct_count = FB_NELEM(protocols_to_try2);
|
|
|
|
copy_p_cnct_repeat_array(cnct->p_cnct_versions, protocols_to_try2, cnct->p_cnct_count);
|
|
|
|
port = inet_try_connect(packet, rdb, file_name, node_name, status_vector, dpb);
|
|
if (!port) {
|
|
return NULL;
|
|
}
|
|
}
|
|
|
|
if (packet->p_operation == op_reject && !uv_flag)
|
|
{
|
|
disconnect(port);
|
|
|
|
// try again with next set of known protocols
|
|
|
|
cnct->p_cnct_user_id.cstr_length = (USHORT) user_id.getBufferLength();
|
|
cnct->p_cnct_user_id.cstr_address = user_id.getBuffer();
|
|
|
|
static const p_cnct::p_cnct_repeat protocols_to_try3[] =
|
|
{
|
|
REMOTE_PROTOCOL(PROTOCOL_VERSION3, ptype_rpc, ptype_batch_send, 1),
|
|
REMOTE_PROTOCOL(PROTOCOL_VERSION4, ptype_rpc, ptype_batch_send, 2)
|
|
};
|
|
|
|
cnct->p_cnct_count = FB_NELEM(protocols_to_try3);
|
|
|
|
copy_p_cnct_repeat_array(cnct->p_cnct_versions, protocols_to_try3, cnct->p_cnct_count);
|
|
|
|
port = inet_try_connect(packet, rdb, file_name, node_name, status_vector, dpb);
|
|
if (!port) {
|
|
return NULL;
|
|
}
|
|
}
|
|
|
|
if (packet->p_operation != op_accept)
|
|
{
|
|
*status_vector++ = isc_arg_gds;
|
|
*status_vector++ = isc_connect_reject;
|
|
*status_vector++ = 0;
|
|
disconnect(port);
|
|
delete rdb;
|
|
return NULL;
|
|
}
|
|
|
|
port->port_protocol = packet->p_acpt.p_acpt_version;
|
|
|
|
// once we've decided on a protocol, concatenate the version
|
|
// string to reflect it...
|
|
Firebird::string temp;
|
|
temp.printf("%s/P%d", port->port_version->str_data, port->port_protocol & FB_PROTOCOL_MASK);
|
|
delete port->port_version;
|
|
port->port_version = REMOTE_make_string(temp.c_str());
|
|
|
|
if (packet->p_acpt.p_acpt_architecture == ARCHITECTURE) {
|
|
port->port_flags |= PORT_symmetric;
|
|
}
|
|
|
|
if (packet->p_acpt.p_acpt_type == ptype_rpc) {
|
|
port->port_flags |= PORT_rpc;
|
|
}
|
|
|
|
if (packet->p_acpt.p_acpt_type != ptype_out_of_band) {
|
|
port->port_flags |= PORT_no_oob;
|
|
}
|
|
|
|
if (packet->p_acpt.p_acpt_type == ptype_lazy_send) {
|
|
port->port_flags |= PORT_lazy;
|
|
}
|
|
|
|
return port;
|
|
}
|
|
|
|
rem_port* INET_connect(const TEXT* name,
|
|
PACKET* packet,
|
|
ISC_STATUS* status_vector,
|
|
USHORT flag, Firebird::ClumpletReader* dpb)
|
|
{
|
|
/**************************************
|
|
*
|
|
* I N E T _ c o n n e c t
|
|
*
|
|
**************************************
|
|
*
|
|
* Functional description
|
|
* Establish half of a communication link. If a connect packet is given,
|
|
* the connection is on behalf of a remote interface. Otherwise the connect
|
|
* is for a server process.
|
|
*
|
|
**************************************/
|
|
#ifdef DEBUG
|
|
{
|
|
if (INET_trace & TRACE_operations)
|
|
{
|
|
fprintf(stdout, "INET_connect\n");
|
|
fflush(stdout);
|
|
}
|
|
INET_start_time = inet_debug_timer();
|
|
// CVC: I don't see the point in replacing this with fb_utils::readenv().
|
|
const char* p = getenv("INET_force_error");
|
|
if (p != NULL) {
|
|
INET_force_error = atoi(p);
|
|
}
|
|
}
|
|
#endif
|
|
|
|
rem_port* const port = alloc_port(NULL);
|
|
port->port_status_vector = status_vector;
|
|
REMOTE_get_timeout_params(port, dpb);
|
|
status_vector[0] = isc_arg_gds;
|
|
status_vector[1] = 0;
|
|
status_vector[2] = isc_arg_end;
|
|
|
|
Firebird::string host;
|
|
Firebird::string protocol;
|
|
|
|
if (name)
|
|
{
|
|
host = name;
|
|
const size_t pos = host.find("/");
|
|
if (pos != Firebird::string::npos)
|
|
{
|
|
protocol = host.substr(pos + 1);
|
|
host = host.substr(0, pos);
|
|
}
|
|
}
|
|
|
|
if (host.hasData())
|
|
{
|
|
delete port->port_connection;
|
|
port->port_connection = REMOTE_make_string(host.c_str());
|
|
}
|
|
else {
|
|
host = port->port_host->str_data;
|
|
}
|
|
|
|
if (protocol.isEmpty())
|
|
{
|
|
const unsigned short port2 = Config::getRemoteServicePort();
|
|
if (port2) {
|
|
protocol.printf("%hu", port2);
|
|
}
|
|
else {
|
|
protocol = Config::getRemoteServiceName();
|
|
}
|
|
}
|
|
|
|
// Set up Inter-Net socket address
|
|
|
|
struct sockaddr_in address;
|
|
memset(&address, 0, sizeof(address));
|
|
|
|
// U N I X style sockets
|
|
|
|
address.sin_family = AF_INET;
|
|
|
|
// define maximum numbers of addresses for a host that we can handle
|
|
const int MAX_HOST_ADDRESS_NUMBER = 8;
|
|
|
|
in_addr host_addr;
|
|
in_addr host_addr_arr[MAX_HOST_ADDRESS_NUMBER];
|
|
int hostAddressNumber = 0;
|
|
|
|
if (packet)
|
|
{
|
|
// client connection
|
|
hostAddressNumber = get_host_address(host.c_str(), host_addr_arr, MAX_HOST_ADDRESS_NUMBER);
|
|
if (hostAddressNumber > MAX_HOST_ADDRESS_NUMBER)
|
|
{
|
|
hostAddressNumber = MAX_HOST_ADDRESS_NUMBER;
|
|
}
|
|
|
|
if (! hostAddressNumber)
|
|
{
|
|
gds__log("INET/INET_connect: gethostbyname (%s) failed, error code = %d",
|
|
host.c_str(), H_ERRNO);
|
|
inet_gen_error(port, Arg::Gds(isc_net_lookup_err) << Arg::Gds(isc_host_unknown));
|
|
|
|
disconnect(port);
|
|
return NULL;
|
|
}
|
|
host_addr = host_addr_arr[0];
|
|
}
|
|
else
|
|
{
|
|
// server connection
|
|
host_addr = get_bind_address();
|
|
}
|
|
|
|
const struct servent* service = getservbyname(protocol.c_str(), "tcp");
|
|
#ifdef WIN_NT
|
|
// On Windows NT/9x, getservbyname can only accomodate
|
|
// 1 call at a time. In this case it returns the error
|
|
// WSAEINPROGRESS.
|
|
// If this happens, retry the operation a few times.
|
|
// NOTE: This still does not guarantee success, but helps.
|
|
if (!service)
|
|
{
|
|
for (int retry = 0; H_ERRNO == INET_RETRY_ERRNO && retry < INET_RETRY_CALL; retry++)
|
|
{
|
|
if ( (service = getservbyname(protocol.c_str(), "tcp")) )
|
|
break;
|
|
}
|
|
}
|
|
#endif // WIN_NT
|
|
|
|
// Modification by luz (slightly modified by FSG)
|
|
// instead of failing here, try applying hard-wired
|
|
// translation of "gds_db" into "3050"
|
|
// This way, a connection to a remote FB server
|
|
// works even from clients with missing "gds_db"
|
|
// entry in "services" file, which is important
|
|
// for zero-installation clients.
|
|
|
|
if (!service)
|
|
{
|
|
if (protocol == FB_SERVICE_NAME)
|
|
{
|
|
// apply hardwired translation
|
|
address.sin_port = htons(FB_SERVICE_PORT);
|
|
}
|
|
// modification by FSG 23.MAR.2001
|
|
else
|
|
{
|
|
// modification by FSG 23.MAR.2001
|
|
// The user has supplied something as protocol
|
|
// let's see whether this is a port number
|
|
// instead of a service name
|
|
address.sin_port = htons(atoi(protocol.c_str()));
|
|
}
|
|
|
|
if (address.sin_port == 0)
|
|
{
|
|
// end of modification by FSG
|
|
// this is the original code
|
|
gds__log("INET/INET_connect: getservbyname failed, error code = %d", H_ERRNO);
|
|
inet_gen_error(port, Arg::Gds(isc_net_lookup_err) <<
|
|
Arg::Gds(isc_service_unknown) << Arg::Str(protocol) << Arg::Str("tcp"));
|
|
return NULL;
|
|
} // else / not hardwired gds_db translation
|
|
}
|
|
else
|
|
{
|
|
// if we have got a service-struct, get port number from there
|
|
// (in case of hardwired gds_db to 3050 translation, address.sin_port was
|
|
// already set above
|
|
address.sin_port = service->s_port;
|
|
} // else (service found)
|
|
|
|
// end of modifications by luz
|
|
|
|
// Allocate a port block and initialize a socket for communications
|
|
|
|
port->port_handle = socket(AF_INET, SOCK_STREAM, 0);
|
|
|
|
if (port->port_handle == INVALID_SOCKET)
|
|
{
|
|
inet_error(port, "socket", isc_net_connect_err, INET_ERRNO);
|
|
disconnect(port);
|
|
return NULL;
|
|
}
|
|
|
|
// If we're a host, just make the connection
|
|
|
|
int n;
|
|
|
|
if (packet)
|
|
{
|
|
if (! setNoNagleOption(port))
|
|
{
|
|
inet_error(port, "setsockopt TCP_NODELAY", isc_net_connect_err, INET_ERRNO);
|
|
disconnect(port);
|
|
return NULL;
|
|
}
|
|
|
|
int inetErrNo = 0;
|
|
for (int i = 0; i < hostAddressNumber; i++)
|
|
{
|
|
address.sin_addr = host_addr_arr[i];
|
|
|
|
// If host has two addresses and the first one failed,
|
|
// but the second one succeeded - no need to worry
|
|
|
|
n = connect(port->port_handle, (struct sockaddr*) &address, sizeof(address));
|
|
inetErrNo = INET_ERRNO;
|
|
|
|
if (n != -1 && send_full(port, packet))
|
|
return port;
|
|
}
|
|
inet_error(port, "connect", isc_net_connect_err, inetErrNo);
|
|
disconnect(port);
|
|
return NULL;
|
|
}
|
|
|
|
// We're a server, so wait for a host to show up
|
|
|
|
memcpy(&address.sin_addr, &host_addr, sizeof(address.sin_addr));
|
|
|
|
if (flag & SRVR_multi_client)
|
|
{
|
|
struct linger lingerInfo;
|
|
|
|
lingerInfo.l_onoff = 0;
|
|
lingerInfo.l_linger = 0;
|
|
|
|
#ifndef WIN_NT
|
|
// dimitr: on Windows, lack of SO_REUSEADDR works the same way as it was specified on POSIX,
|
|
// i.e. it allows binding to a port in a TIME_WAIT/FIN_WAIT state. If this option
|
|
// is turned on explicitly, then a port can be re-bound regardless of its state,
|
|
// e.g. while it's listening. This is surely not what we want.
|
|
|
|
int optval = TRUE;
|
|
n = setsockopt(port->port_handle, SOL_SOCKET, SO_REUSEADDR,
|
|
(SCHAR*) &optval, sizeof(optval));
|
|
if (n == -1)
|
|
{
|
|
inet_error(port, "setsockopt REUSE", isc_net_connect_listen_err, INET_ERRNO);
|
|
disconnect(port);
|
|
return NULL;
|
|
}
|
|
#endif
|
|
|
|
// Get any values for SO_LINGER so that they can be reset during
|
|
// disconnect. SO_LINGER should be set by default on the socket
|
|
|
|
socklen_t optlen = sizeof(port->port_linger);
|
|
n = getsockopt(port->port_handle, SOL_SOCKET, SO_LINGER,
|
|
(SCHAR *) & port->port_linger, &optlen);
|
|
|
|
if (n != 0) // getsockopt failed
|
|
port->port_linger.l_onoff = 0;
|
|
|
|
n = setsockopt(port->port_handle, SOL_SOCKET, SO_LINGER,
|
|
(SCHAR *) & lingerInfo, sizeof(lingerInfo));
|
|
if (n == -1)
|
|
{
|
|
inet_error(port, "setsockopt LINGER", isc_net_connect_listen_err, INET_ERRNO);
|
|
disconnect(port);
|
|
return NULL;
|
|
}
|
|
|
|
if (! setNoNagleOption(port))
|
|
{
|
|
inet_error(port, "setsockopt TCP_NODELAY", isc_net_connect_listen_err, INET_ERRNO);
|
|
disconnect(port);
|
|
return NULL;
|
|
}
|
|
}
|
|
|
|
n = bind(port->port_handle, (struct sockaddr*) &address, sizeof(address));
|
|
|
|
if (n == -1)
|
|
{
|
|
// On Linux platform, when the server dies the system holds a port
|
|
// for some time.
|
|
|
|
for (int retry = 0; INET_ERRNO == INET_ADDR_IN_USE && retry < INET_RETRY_CALL; retry++)
|
|
{
|
|
sleep(10);
|
|
n = bind(port->port_handle, (struct sockaddr *) &address, sizeof(address));
|
|
if (n == 0)
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (n == -1)
|
|
{
|
|
inet_error(port, "bind", isc_net_connect_listen_err, INET_ERRNO);
|
|
disconnect(port);
|
|
return NULL;
|
|
}
|
|
|
|
n = listen(port->port_handle, SOMAXCONN);
|
|
|
|
if (n == -1)
|
|
{
|
|
inet_error(port, "listen", isc_net_connect_listen_err, INET_ERRNO);
|
|
return NULL;
|
|
}
|
|
|
|
inet_ports->registerPort(port);
|
|
|
|
if (flag & SRVR_multi_client)
|
|
{
|
|
// Prevent the generation of dummy keepalive packets on the connect port.
|
|
|
|
port->port_dummy_packet_interval = 0;
|
|
port->port_dummy_timeout = 0;
|
|
port->port_server_flags |= (SRVR_server | SRVR_multi_client);
|
|
|
|
return port;
|
|
}
|
|
|
|
while (true)
|
|
{
|
|
socklen_t l = sizeof(address);
|
|
SOCKET s = accept(port->port_handle, (struct sockaddr*) &address, &l);
|
|
const int inetErrNo = INET_ERRNO;
|
|
if (s == INVALID_SOCKET)
|
|
{
|
|
if (!INET_shutting_down)
|
|
{
|
|
inet_error(port, "accept", isc_net_connect_err, inetErrNo);
|
|
disconnect(port);
|
|
}
|
|
return NULL;
|
|
}
|
|
#ifdef WIN_NT
|
|
if (flag & SRVR_debug)
|
|
#else
|
|
if ((flag & SRVR_debug) || !fork())
|
|
#endif
|
|
{
|
|
SOCLOSE(port->port_handle);
|
|
port->port_handle = s;
|
|
port->port_server_flags |= SRVR_server | SRVR_debug;
|
|
port->port_flags |= PORT_server;
|
|
return port;
|
|
}
|
|
|
|
#ifdef WIN_NT
|
|
Firebird::MutexLockGuard forkGuard(forkMutex);
|
|
if (!forkThreadStarted)
|
|
{
|
|
forkThreadStarted = true;
|
|
forkEvent = CreateEvent(NULL, FALSE, FALSE, NULL);
|
|
forkSockets = new SocketsArray(*getDefaultMemoryPool());
|
|
|
|
gds__thread_start(forkThread, (void*) flag, THREAD_medium, 0, 0);
|
|
}
|
|
forkSockets->add(s);
|
|
SetEvent(forkEvent);
|
|
#else
|
|
Firebird::MutexLockGuard guard(waitThreadMutex);
|
|
if (! procCount++) {
|
|
gds__thread_start(waitThread, 0, THREAD_medium, 0, 0);
|
|
}
|
|
|
|
SOCLOSE(s);
|
|
#endif
|
|
}
|
|
|
|
#ifdef WIN_NT
|
|
Firebird::MutexLockGuard forkGuard(forkMutex);
|
|
if (forkThreadStarted)
|
|
{
|
|
SetEvent(forkEvent);
|
|
CloseHandle(forkEvent);
|
|
|
|
delete forkSockets;
|
|
forkSockets = NULL;
|
|
}
|
|
#endif
|
|
}
|
|
|
|
|
|
rem_port* INET_reconnect(SOCKET handle, ISC_STATUS* status_vector)
|
|
{
|
|
/**************************************
|
|
*
|
|
* I N E T _ r e c o n n e c t
|
|
*
|
|
**************************************
|
|
*
|
|
* Functional description
|
|
* A communications link has been established by another
|
|
* process. We have inheritted the handle. Set up
|
|
* a port block.
|
|
*
|
|
**************************************/
|
|
rem_port* const port = alloc_port(NULL);
|
|
port->port_status_vector = status_vector;
|
|
status_vector[0] = isc_arg_gds;
|
|
status_vector[1] = 0;
|
|
status_vector[2] = isc_arg_end;
|
|
|
|
port->port_handle = handle;
|
|
port->port_flags |= PORT_server;
|
|
port->port_server_flags |= SRVR_server;
|
|
|
|
int n = 0, optval = TRUE;
|
|
n = setsockopt(port->port_handle, SOL_SOCKET, SO_KEEPALIVE, (SCHAR*) &optval, sizeof(optval));
|
|
if (n == -1) {
|
|
gds__log("inet server err: setting KEEPALIVE socket option \n");
|
|
}
|
|
|
|
if (! setNoNagleOption(port)) {
|
|
gds__log("inet server err: setting NODELAY socket option \n");
|
|
}
|
|
|
|
return port;
|
|
}
|
|
|
|
rem_port* INET_server(SOCKET sock)
|
|
{
|
|
/**************************************
|
|
*
|
|
* I N E T _ s e r v e r
|
|
*
|
|
**************************************
|
|
*
|
|
* Functional description
|
|
* We have been spawned by a master server with a connection
|
|
* established. Set up port block with the appropriate socket.
|
|
*
|
|
**************************************/
|
|
int n = 0;
|
|
rem_port* const port = alloc_port(NULL);
|
|
port->port_flags |= PORT_server;
|
|
port->port_server_flags |= SRVR_server;
|
|
port->port_handle = sock;
|
|
|
|
int optval = 1;
|
|
n = setsockopt(sock, SOL_SOCKET, SO_KEEPALIVE, (SCHAR*) &optval, sizeof(optval));
|
|
if (n == -1) {
|
|
gds__log("inet server err: setting KEEPALIVE socket option \n");
|
|
}
|
|
|
|
if (! setNoNagleOption(port)) {
|
|
gds__log("inet server err: setting NODELAY socket option \n");
|
|
}
|
|
|
|
return port;
|
|
}
|
|
|
|
void INET_set_clients( int count)
|
|
{
|
|
/**************************************
|
|
*
|
|
* I N E T _ s e t _ c l i e n t s
|
|
*
|
|
**************************************
|
|
*
|
|
* Functional description
|
|
* Set maxinum number of clients served before
|
|
* starting new server
|
|
*
|
|
**************************************/
|
|
INET_max_clients = (count && count < MAXCLIENTS) ? count : MAXCLIENTS;
|
|
}
|
|
|
|
|
|
static bool accept_connection(rem_port* port, const P_CNCT* cnct)
|
|
{
|
|
/**************************************
|
|
*
|
|
* a c c e p t _ c o n n e c t i o n
|
|
*
|
|
**************************************
|
|
*
|
|
* Functional description
|
|
* Accept an incoming request for connection. This is purely a lower
|
|
* level handshaking function, and does not constitute the server
|
|
* response for protocol selection.
|
|
*
|
|
**************************************/
|
|
// Default account to "guest" (in theory all packets contain a name)
|
|
|
|
Firebird::string name("guest"), password;
|
|
|
|
// Pick up account and password, if given
|
|
|
|
Firebird::ClumpletReader id(Firebird::ClumpletReader::UnTagged,
|
|
cnct->p_cnct_user_id.cstr_address,
|
|
cnct->p_cnct_user_id.cstr_length);
|
|
|
|
SLONG eff_gid = -1, eff_uid = -1;
|
|
bool user_verification = false;
|
|
for (id.rewind(); !id.isEof(); id.moveNext())
|
|
{
|
|
switch (id.getClumpTag())
|
|
{
|
|
case CNCT_user:
|
|
id.getString(name);
|
|
break;
|
|
|
|
case CNCT_passwd:
|
|
id.getString(password);
|
|
break;
|
|
|
|
case CNCT_group:
|
|
{
|
|
const size_t length = id.getClumpLength();
|
|
if (length != 0)
|
|
{
|
|
eff_gid = 0;
|
|
memcpy(&eff_gid, id.getBytes(), length);
|
|
eff_gid = ntohl(eff_gid);
|
|
}
|
|
break;
|
|
}
|
|
|
|
// this case indicates that the client has requested that
|
|
// we force the user name/password to be verified against
|
|
// the security database
|
|
|
|
case CNCT_user_verification:
|
|
user_verification = true;
|
|
break;
|
|
}
|
|
}
|
|
|
|
// See if user exists. If not, reject connection
|
|
if (user_verification)
|
|
{
|
|
eff_gid = eff_uid = -1;
|
|
// port->port_flags |= PORT_not_trusted; // never tested
|
|
}
|
|
|
|
#ifndef WIN_NT
|
|
else
|
|
{
|
|
if (!check_host(port))
|
|
{
|
|
return false;
|
|
}
|
|
}
|
|
|
|
{ // scope
|
|
// If the environment variable ISC_INET_SERVER_HOME is set,
|
|
// change the home directory to the specified directory.
|
|
// Note that this will overrule the normal setting of
|
|
// the current directory to the effective user's home directory.
|
|
// This feature was added primarily for testing via remote
|
|
// loopback - but does seem to be of good general use, so
|
|
// is activated for the release version.
|
|
// 1995-February-27 David Schnepper
|
|
|
|
Firebird::PathName home;
|
|
if (fb_utils::readenv("ISC_INET_SERVER_HOME", home))
|
|
{
|
|
if (chdir(home.c_str())) {
|
|
gds__log("inet_server: unable to cd to %s errno %d\n", home.c_str(), INET_ERRNO);
|
|
// We continue after the error
|
|
}
|
|
}
|
|
} // end scope
|
|
#endif // !WIN_NT
|
|
|
|
// store FULL user identity in port_user_name for security purposes
|
|
|
|
Firebird::string temp;
|
|
temp.printf("%s.%ld.%ld", name.c_str(), eff_gid, eff_uid);
|
|
port->port_user_name = REMOTE_make_string(temp.c_str());
|
|
|
|
port->port_protocol_str = REMOTE_make_string("TCPv4");
|
|
|
|
struct sockaddr_in address;
|
|
socklen_t l = sizeof(address);
|
|
|
|
memset(&address, 0, sizeof(address));
|
|
int status = getpeername(port->port_handle, (struct sockaddr *) &address, &l);
|
|
if (status == 0)
|
|
{
|
|
Firebird::string addr_str;
|
|
const UCHAR* ip = (UCHAR*) &address.sin_addr;
|
|
addr_str.printf(
|
|
"%d.%d.%d.%d",
|
|
static_cast<int>(ip[0]),
|
|
static_cast<int>(ip[1]),
|
|
static_cast<int>(ip[2]),
|
|
static_cast<int>(ip[3]) );
|
|
port->port_address_str = REMOTE_make_string(addr_str.c_str());
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
|
|
static rem_port* alloc_port(rem_port* const parent, const USHORT flags)
|
|
{
|
|
/**************************************
|
|
*
|
|
* a l l o c _ p o r t
|
|
*
|
|
**************************************
|
|
*
|
|
* Functional description
|
|
* Allocate a port block, link it in to parent (if there is a parent),
|
|
* and initialize input and output XDR streams.
|
|
*
|
|
**************************************/
|
|
|
|
if (!INET_initialized)
|
|
{
|
|
Firebird::MutexLockGuard guard(init_mutex);
|
|
if (!INET_initialized)
|
|
{
|
|
#ifdef WIN_NT
|
|
static WSADATA wsadata;
|
|
const WORD version = MAKEWORD(2, 0);
|
|
const int wsaError = WSAStartup(version, &wsadata);
|
|
if (wsaError)
|
|
{
|
|
if (parent)
|
|
inet_error(parent, "WSAStartup", isc_net_init_error, wsaError);
|
|
else {
|
|
gds__log("INET/alloc_port: WSAStartup failed, error code = %d", wsaError);
|
|
}
|
|
return NULL;
|
|
}
|
|
gds__register_cleanup(wsaExitHandler, 0);
|
|
#endif
|
|
INET_remote_buffer = Config::getTcpRemoteBufferSize();
|
|
if (INET_remote_buffer < MAX_DATA_LW || INET_remote_buffer > MAX_DATA_HW)
|
|
{
|
|
INET_remote_buffer = DEF_MAX_DATA;
|
|
}
|
|
#ifdef DEBUG
|
|
gds__log(" Info: Remote Buffer Size set to %ld", INET_remote_buffer);
|
|
#endif
|
|
|
|
fb_shutdown_callback(0, cleanup_ports, fb_shut_postproviders, 0);
|
|
|
|
INET_initialized = true;
|
|
|
|
// This should go AFTER 'INET_initialized = true' to avoid recursion
|
|
inet_async_receive = alloc_port(0);
|
|
inet_async_receive->port_flags |= PORT_server;
|
|
}
|
|
}
|
|
|
|
rem_port* const port = new rem_port(rem_port::INET, INET_remote_buffer * 2);
|
|
REMOTE_get_timeout_params(port, 0);
|
|
|
|
TEXT buffer[BUFFER_SMALL];
|
|
gethostname(buffer, sizeof(buffer));
|
|
|
|
port->port_host = REMOTE_make_string(buffer);
|
|
port->port_connection = REMOTE_make_string(buffer);
|
|
SNPRINTF(buffer, FB_NELEM(buffer), "tcp (%s)", port->port_host->str_data);
|
|
port->port_version = REMOTE_make_string(buffer);
|
|
|
|
port->port_accept = accept_connection;
|
|
port->port_disconnect = disconnect;
|
|
port->port_force_close = force_close;
|
|
port->port_receive_packet = receive;
|
|
port->port_select_multi = select_multi;
|
|
port->port_send_packet = send_full;
|
|
port->port_send_partial = send_partial;
|
|
port->port_connect = aux_connect;
|
|
port->port_request = aux_request;
|
|
port->port_buff_size = (USHORT) INET_remote_buffer;
|
|
port->port_async_receive = inet_async_receive;
|
|
port->port_flags = flags;
|
|
|
|
xdrinet_create( &port->port_send, port,
|
|
&port->port_buffer[INET_remote_buffer],
|
|
(USHORT) INET_remote_buffer,
|
|
XDR_ENCODE);
|
|
|
|
xdrinet_create( &port->port_receive, port, port->port_buffer, 0, XDR_DECODE);
|
|
|
|
if (parent && !(parent->port_server_flags & SRVR_thread_per_port))
|
|
{
|
|
Firebird::MutexLockGuard guard(port_mutex);
|
|
port->linkParent(parent);
|
|
}
|
|
|
|
return port;
|
|
}
|
|
|
|
static rem_port* aux_connect(rem_port* port, PACKET* packet)
|
|
{
|
|
/**************************************
|
|
*
|
|
* a u x _ c o n n e c t
|
|
*
|
|
**************************************
|
|
*
|
|
* Functional description
|
|
* Try to establish an alternative connection. Somebody has already
|
|
* done a successfull connect request ("packet" contains the response).
|
|
*
|
|
**************************************/
|
|
struct sockaddr_in address;
|
|
socklen_t l = sizeof(address);
|
|
|
|
// If this is a server, we're got an auxiliary connection. Accept it
|
|
|
|
if (port->port_server_flags)
|
|
{
|
|
struct timeval timeout;
|
|
timeout.tv_sec = port->port_connect_timeout;
|
|
timeout.tv_usec = 0;
|
|
|
|
Select slct;
|
|
slct.set(port->port_channel);
|
|
|
|
int inetErrNo = 0;
|
|
|
|
while (true)
|
|
{
|
|
slct.select(&timeout);
|
|
const int count = slct.getCount();
|
|
inetErrNo = INET_ERRNO;
|
|
|
|
if (count != -1 || !INTERRUPT_ERROR(inetErrNo))
|
|
{
|
|
if (count == 1)
|
|
{
|
|
break;
|
|
}
|
|
else
|
|
{
|
|
const ISC_STATUS error_code =
|
|
(count == 0) ? isc_net_event_connect_timeout : isc_net_event_connect_err;
|
|
inet_error(port, "select", error_code, inetErrNo);
|
|
SOCLOSE(port->port_channel);
|
|
return NULL;
|
|
}
|
|
}
|
|
}
|
|
|
|
const SOCKET n = accept(port->port_channel, (struct sockaddr*) &address, &l);
|
|
inetErrNo = INET_ERRNO;
|
|
|
|
if (n == INVALID_SOCKET)
|
|
{
|
|
inet_error(port, "accept", isc_net_event_connect_err, inetErrNo);
|
|
SOCLOSE(port->port_channel);
|
|
return NULL;
|
|
}
|
|
|
|
SOCLOSE(port->port_channel);
|
|
port->port_handle = n;
|
|
port->port_flags |= PORT_async;
|
|
return port;
|
|
}
|
|
|
|
rem_port* const new_port = alloc_port(port->port_parent);
|
|
port->port_async = new_port;
|
|
new_port->port_dummy_packet_interval = port->port_dummy_packet_interval;
|
|
new_port->port_dummy_timeout = new_port->port_dummy_packet_interval;
|
|
new_port->port_flags = port->port_flags & PORT_no_oob;
|
|
new_port->port_flags |= PORT_async;
|
|
P_RESP* response = &packet->p_resp;
|
|
|
|
// Set up new socket
|
|
|
|
SOCKET n = socket(AF_INET, SOCK_STREAM, 0);
|
|
if (n == INVALID_SOCKET)
|
|
{
|
|
inet_error(port, "socket", isc_net_event_connect_err, INET_ERRNO);
|
|
return NULL;
|
|
}
|
|
|
|
// NJK - Determine address and port to use.
|
|
//
|
|
// The address returned by the server may be incorrect if it is behind a NAT box
|
|
// so we must use the address that was used to connect the main socket, not the
|
|
// address reported by the server.
|
|
//
|
|
// The port number reported by the server is used. For NAT support the port number
|
|
// should be configured to be a fixed port number in the server configuration.
|
|
|
|
memset(&address, 0, sizeof(address));
|
|
int status = getpeername(port->port_handle, (struct sockaddr *) &address, &l);
|
|
if (status != 0)
|
|
{
|
|
inet_error(port, "socket", isc_net_event_connect_err, INET_ERRNO);
|
|
SOCLOSE(n);
|
|
return NULL;
|
|
}
|
|
address.sin_family = AF_INET;
|
|
address.sin_port = ((struct sockaddr_in *)(response->p_resp_data.cstr_address))->sin_port;
|
|
|
|
int optval = 1;
|
|
setsockopt(n, SOL_SOCKET, SO_KEEPALIVE, (SCHAR*) &optval, sizeof(optval));
|
|
|
|
status = connect(n, (struct sockaddr *) &address, sizeof(address));
|
|
const int inetErrNo = INET_ERRNO;
|
|
|
|
if (status < 0)
|
|
{
|
|
inet_error(port, "connect", isc_net_event_connect_err, inetErrNo);
|
|
SOCLOSE(n);
|
|
return NULL;
|
|
}
|
|
|
|
new_port->port_handle = n;
|
|
|
|
return new_port;
|
|
}
|
|
|
|
static rem_port* aux_request( rem_port* port, PACKET* packet)
|
|
{
|
|
/**************************************
|
|
*
|
|
* a u x _ r e q u e s t
|
|
*
|
|
**************************************
|
|
*
|
|
* Functional description
|
|
* A remote interface has requested the server prepare an auxiliary
|
|
* connection; the server calls aux_request to set up the connection.
|
|
*
|
|
**************************************/
|
|
struct sockaddr_in address;
|
|
|
|
// Set up new socket
|
|
|
|
address.sin_family = AF_INET;
|
|
in_addr bind_addr = get_bind_address();
|
|
memcpy(&address.sin_addr, &bind_addr, sizeof(address.sin_addr));
|
|
address.sin_port = htons(Config::getRemoteAuxPort());
|
|
|
|
SOCKET n = socket(AF_INET, SOCK_STREAM, 0);
|
|
if (n == INVALID_SOCKET)
|
|
{
|
|
inet_error(port, "socket", isc_net_event_listen_err, INET_ERRNO);
|
|
return NULL;
|
|
}
|
|
|
|
#ifndef WIN_NT
|
|
// dimitr: on Windows, lack of SO_REUSEADDR works the same way as it was specified on POSIX,
|
|
// i.e. it allows binding to a port in a TIME_WAIT/FIN_WAIT state. If this option
|
|
// is turned on explicitly, then a port can be re-bound regardless of its state,
|
|
// e.g. while it's listening. This is surely not what we want.
|
|
|
|
int optval = TRUE;
|
|
if (setsockopt(n, SOL_SOCKET, SO_REUSEADDR, (SCHAR*) &optval, sizeof(optval)) < 0)
|
|
{
|
|
inet_error(port, "setsockopt REUSE", isc_net_event_listen_err, INET_ERRNO);
|
|
return NULL;
|
|
}
|
|
#endif
|
|
|
|
if (bind(n, (struct sockaddr *) &address, sizeof(address)) < 0)
|
|
{
|
|
inet_error(port, "bind", isc_net_event_listen_err, INET_ERRNO);
|
|
return NULL;
|
|
}
|
|
|
|
socklen_t length = sizeof(address);
|
|
|
|
if (getsockname(n, (struct sockaddr *) &address, &length) < 0)
|
|
{
|
|
inet_error(port, "getsockname", isc_net_event_listen_err, INET_ERRNO);
|
|
return NULL;
|
|
}
|
|
|
|
if (listen(n, 1) < 0)
|
|
{
|
|
inet_error(port, "listen", isc_net_event_listen_err, INET_ERRNO);
|
|
return NULL;
|
|
}
|
|
|
|
rem_port* const new_port = alloc_port(port->port_parent, PORT_async);
|
|
port->port_async = new_port;
|
|
new_port->port_dummy_packet_interval = port->port_dummy_packet_interval;
|
|
new_port->port_dummy_timeout = new_port->port_dummy_packet_interval;
|
|
|
|
new_port->port_server_flags = port->port_server_flags;
|
|
new_port->port_channel = (int) n;
|
|
new_port->port_flags |= port->port_flags & PORT_no_oob;
|
|
|
|
P_RESP* response = &packet->p_resp;
|
|
|
|
struct sockaddr_in port_address;
|
|
if (getsockname(port->port_handle, (struct sockaddr *) &port_address, &length) < 0)
|
|
{
|
|
inet_error(port, "getsockname", isc_net_event_listen_err, INET_ERRNO);
|
|
return NULL;
|
|
}
|
|
memcpy(&address.sin_addr, &port_address.sin_addr, sizeof(address.sin_addr));
|
|
|
|
response->p_resp_data.cstr_length = sizeof(address);
|
|
memcpy(response->p_resp_data.cstr_address, &address, sizeof(address));
|
|
|
|
return new_port;
|
|
}
|
|
|
|
#ifndef WIN_NT
|
|
static bool check_host(rem_port* port)
|
|
{
|
|
/**************************************
|
|
*
|
|
* c h e c k _ h o s t ( n o n - V M S )
|
|
*
|
|
**************************************
|
|
*
|
|
* Functional description
|
|
* Check the host on the other end of the socket to see if it's localhost
|
|
*
|
|
**************************************/
|
|
struct sockaddr_in address;
|
|
|
|
socklen_t length = sizeof(address);
|
|
|
|
if (getpeername(port->port_handle, (struct sockaddr*) &address, &length) == -1)
|
|
return false;
|
|
|
|
// If source address is in the loopback net - trust it
|
|
return (ntohl(address.sin_addr.s_addr) >> IN_CLASSA_NSHIFT) == IN_LOOPBACKNET;
|
|
}
|
|
#endif // WIN_NT
|
|
|
|
#if !(defined WIN_NT)
|
|
static THREAD_ENTRY_DECLARE waitThread(THREAD_ENTRY_PARAM)
|
|
{
|
|
/**************************************
|
|
*
|
|
* w a i t T h r e a d
|
|
*
|
|
**************************************
|
|
*
|
|
* Functional description
|
|
* Waits for processes started by standalone classic server (avoid zombies)
|
|
*
|
|
**************************************/
|
|
while (procCount > 0)
|
|
{
|
|
int rc = wait(0);
|
|
|
|
Firebird::MutexLockGuard guard(waitThreadMutex);
|
|
if (rc > 0) {
|
|
--procCount;
|
|
}
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
#endif // !defined(WIN_NT)
|
|
|
|
static void disconnect(rem_port* const port)
|
|
{
|
|
/**************************************
|
|
*
|
|
* d i s c o n n e c t
|
|
*
|
|
**************************************
|
|
*
|
|
* Functional description
|
|
* Break a remote connection.
|
|
*
|
|
**************************************/
|
|
|
|
// SO_LINGER was turned off on the initial bind when the server was started.
|
|
// This will force a reset to be sent to the client when the socket is closed.
|
|
// We only want this behavior in the case of the server terminating
|
|
// abnormally and not on an orderly shut down. Because of this, turn the
|
|
// SO_LINGER option back on for the socket. The result of setsockopt isn't
|
|
// too important at this stage since we are closing the socket anyway. This
|
|
// is an attempt to return the socket to a state where a graceful shutdown can
|
|
// occur.
|
|
|
|
if (port->port_linger.l_onoff)
|
|
{
|
|
setsockopt(port->port_handle, SOL_SOCKET, SO_LINGER,
|
|
(SCHAR*) &port->port_linger, sizeof(port->port_linger));
|
|
}
|
|
|
|
if (port->port_handle != INVALID_SOCKET)
|
|
{
|
|
shutdown(port->port_handle, 2);
|
|
}
|
|
|
|
Firebird::MutexLockGuard guard(port_mutex);
|
|
port->port_state = rem_port::DISCONNECTED;
|
|
|
|
if (port->port_async)
|
|
{
|
|
disconnect(port->port_async);
|
|
port->port_async = NULL;
|
|
}
|
|
|
|
// If this is a sub-port, unlink it from its parent
|
|
port->unlinkParent();
|
|
|
|
inet_ports->unRegisterPort(port);
|
|
|
|
SOCLOSE(port->port_handle);
|
|
SOCLOSE(port->port_channel);
|
|
|
|
port->release();
|
|
|
|
#ifdef DEBUG
|
|
if (INET_trace & TRACE_summary)
|
|
{
|
|
fprintf(stdout, "INET_count_send = %u packets\n", INET_count_send);
|
|
fprintf(stdout, "INET_bytes_send = %u bytes\n", INET_bytes_send);
|
|
fprintf(stdout, "INET_count_recv = %u packets\n", INET_count_recv);
|
|
fprintf(stdout, "INET_bytes_recv = %u bytes\n", INET_bytes_recv);
|
|
fflush(stdout);
|
|
}
|
|
#endif
|
|
|
|
return;
|
|
}
|
|
|
|
|
|
static void force_close(rem_port* port)
|
|
{
|
|
/**************************************
|
|
*
|
|
* f o r c e _ c l o s e
|
|
*
|
|
**************************************
|
|
*
|
|
* Functional description
|
|
* Forcebly close remote connection.
|
|
*
|
|
**************************************/
|
|
|
|
if (port->port_state != rem_port::PENDING)
|
|
return;
|
|
|
|
port->port_state = rem_port::BROKEN;
|
|
|
|
if (port->port_handle != INVALID_SOCKET)
|
|
{
|
|
shutdown(port->port_handle, 2);
|
|
SOCLOSE(port->port_handle);
|
|
}
|
|
}
|
|
|
|
|
|
static int cleanup_ports(const int, const int, void* /*arg*/)
|
|
{
|
|
/**************************************
|
|
*
|
|
* c l e a n u p _ p o r t s
|
|
*
|
|
**************************************
|
|
*
|
|
* Functional description
|
|
* Shutdown all active connections
|
|
* to allow correct shutdown.
|
|
*
|
|
**************************************/
|
|
INET_shutting_down = true;
|
|
|
|
inet_ports->closePorts();
|
|
return 0;
|
|
}
|
|
|
|
|
|
#ifdef NO_FORK
|
|
static int fork()
|
|
{
|
|
/**************************************
|
|
*
|
|
* f o r k ( N O _ F O R K )
|
|
*
|
|
**************************************
|
|
*
|
|
* Functional description
|
|
* Hmmm.
|
|
*
|
|
**************************************/
|
|
|
|
return 1;
|
|
}
|
|
#endif
|
|
|
|
#ifdef WIN_NT
|
|
static void wsaExitHandler(void*)
|
|
{
|
|
/**************************************
|
|
*
|
|
* w s a E x i t H a n d l e r
|
|
*
|
|
**************************************
|
|
*
|
|
* Functional description
|
|
* Cleanup WSA.
|
|
*
|
|
**************************************/
|
|
SleepEx(0, FALSE); // let select in other thread(s) shutdown gracefully
|
|
WSACleanup();
|
|
}
|
|
|
|
|
|
static int fork(SOCKET old_handle, USHORT flag)
|
|
{
|
|
/**************************************
|
|
*
|
|
* f o r k ( W I N _ N T )
|
|
*
|
|
**************************************
|
|
*
|
|
* Functional description
|
|
* Create a child process.
|
|
*
|
|
**************************************/
|
|
TEXT name[MAXPATHLEN];
|
|
GetModuleFileName(NULL, name, sizeof(name));
|
|
|
|
HANDLE new_handle;
|
|
if (!DuplicateHandle(GetCurrentProcess(), (HANDLE) old_handle,
|
|
GetCurrentProcess(), &new_handle,
|
|
0, TRUE, DUPLICATE_SAME_ACCESS))
|
|
{
|
|
gds__log("INET/inet_error: fork/DuplicateHandle errno = %d", GetLastError());
|
|
return 0;
|
|
}
|
|
|
|
Firebird::string cmdLine;
|
|
cmdLine.printf("%s -i -h %"HANDLEFORMAT"@%"ULONGFORMAT, name, new_handle, GetCurrentProcessId());
|
|
|
|
STARTUPINFO start_crud;
|
|
start_crud.cb = sizeof(STARTUPINFO);
|
|
start_crud.lpReserved = NULL;
|
|
start_crud.lpReserved2 = NULL;
|
|
start_crud.cbReserved2 = 0;
|
|
start_crud.lpDesktop = NULL;
|
|
start_crud.lpTitle = NULL;
|
|
start_crud.dwFlags = STARTF_FORCEOFFFEEDBACK;
|
|
|
|
PROCESS_INFORMATION pi;
|
|
if (CreateProcess(NULL, cmdLine.begin(), NULL, NULL, FALSE,
|
|
(flag & SRVR_high_priority ?
|
|
HIGH_PRIORITY_CLASS | DETACHED_PROCESS :
|
|
NORMAL_PRIORITY_CLASS | DETACHED_PROCESS),
|
|
NULL, NULL, &start_crud, &pi))
|
|
{
|
|
CloseHandle(pi.hThread);
|
|
CloseHandle(pi.hProcess);
|
|
// hvlad: child process will close our handle of just accepted socket
|
|
return 1;
|
|
}
|
|
|
|
gds__log("INET/inet_error: fork/CreateProcess errno = %d", GetLastError());
|
|
CloseHandle(new_handle);
|
|
return 0;
|
|
}
|
|
|
|
|
|
THREAD_ENTRY_DECLARE forkThread(THREAD_ENTRY_PARAM arg)
|
|
{
|
|
const USHORT flag = (USHORT) arg;
|
|
|
|
while (!INET_shutting_down)
|
|
{
|
|
if (WaitForSingleObject(forkEvent, INFINITE) != WAIT_OBJECT_0)
|
|
break;
|
|
|
|
while (!INET_shutting_down)
|
|
{
|
|
SOCKET s = 0;
|
|
{ // scope
|
|
Firebird::MutexLockGuard forkGuard(forkMutex);
|
|
|
|
if (!forkSockets || forkSockets->getCount() == 0)
|
|
break;
|
|
|
|
s = (*forkSockets)[0];
|
|
forkSockets->remove((size_t) 0);
|
|
}
|
|
fork(s, flag);
|
|
SOCLOSE(s);
|
|
}
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
#endif
|
|
|
|
namespace
|
|
{
|
|
in_addr config_address;
|
|
|
|
class GetAddress
|
|
{
|
|
public:
|
|
static void init()
|
|
{
|
|
const char* config_option = Config::getRemoteBindAddress();
|
|
if (config_option)
|
|
{
|
|
int n = get_host_address(config_option, &config_address, 1);
|
|
if (n != 1)
|
|
{
|
|
// In case when config option is given with error,
|
|
// bind to loopback interface only
|
|
config_address.s_addr = htonl(INADDR_LOOPBACK);
|
|
// log warning
|
|
if (n == 0)
|
|
{
|
|
gds__log("Wrong RemoteBindAddress '%s' in firebird.conf - "
|
|
"binding to loopback interface", config_option);
|
|
}
|
|
else
|
|
{
|
|
gds__log("Host '%s' resolves to multiple interfaces - "
|
|
"binding to loopback interface", config_option);
|
|
}
|
|
}
|
|
}
|
|
else // use default to listen all
|
|
{
|
|
config_address.s_addr = INADDR_ANY;
|
|
}
|
|
}
|
|
|
|
static void cleanup() { }
|
|
};
|
|
}
|
|
|
|
static in_addr get_bind_address()
|
|
{
|
|
/**************************************
|
|
*
|
|
* g e t _ b i n d _ a d d r e s s
|
|
*
|
|
**************************************
|
|
*
|
|
* Functional description
|
|
* Return local address to bind sockets to.
|
|
*
|
|
**************************************/
|
|
static Firebird::InitMutex<GetAddress> instance;
|
|
|
|
instance.init();
|
|
|
|
return config_address;
|
|
}
|
|
|
|
|
|
#ifdef WIN_NT
|
|
// Windows does not have an inet_aton function.
|
|
bool inet_aton(const char* name, in_addr* address)
|
|
{
|
|
address->s_addr = inet_addr(name);
|
|
return address->s_addr != INADDR_NONE;
|
|
}
|
|
#endif
|
|
|
|
|
|
static int get_host_address(const char* name,
|
|
in_addr* const host_addr_arr,
|
|
const int arr_size)
|
|
{
|
|
/**************************************
|
|
*
|
|
* g e t _ h o s t _ a d d r e s s
|
|
*
|
|
**************************************
|
|
*
|
|
* Functional description
|
|
* Fills array with addresses up to arr_size (must be at least 1).
|
|
* Returns the required number of elements in array to be able to store
|
|
* all host addresses (may be less, equal or greater than arr_size).
|
|
*
|
|
**************************************/
|
|
if (inet_aton(name, &host_addr_arr[0]))
|
|
{
|
|
return 1;
|
|
}
|
|
|
|
const hostent* host = gethostbyname(name);
|
|
|
|
// On Windows NT/9x, gethostbyname can only accomodate
|
|
// 1 call at a time. In this case it returns the error
|
|
// WSAEINPROGRESS. On UNIX systems, this call may not succeed
|
|
// because of a temporary error. In this case, it returns
|
|
// h_error set to TRY_AGAIN. When these errors occur,
|
|
// retry the operation a few times.
|
|
// NOTE: This still does not guarantee success, but helps.
|
|
|
|
if (!host)
|
|
{
|
|
for (int retry = 0; H_ERRNO == INET_RETRY_ERRNO && retry < INET_RETRY_CALL; retry++)
|
|
{
|
|
if ( (host = gethostbyname(name)) )
|
|
break;
|
|
}
|
|
}
|
|
|
|
// We can't work with other types for now. Maybe AF_NETBIOS for MS, too?
|
|
if (host && host->h_addrtype == AF_INET)
|
|
{
|
|
const in_addr* const* list = reinterpret_cast<in_addr**>(host->h_addr_list);
|
|
int i = 0;
|
|
while (list[i] != NULL)
|
|
{
|
|
if (i < arr_size)
|
|
{
|
|
host_addr_arr[i] = *list[i];
|
|
}
|
|
++i;
|
|
}
|
|
return i;
|
|
}
|
|
|
|
// give up
|
|
return 0;
|
|
}
|
|
|
|
//____________________________________________________________
|
|
//
|
|
// Copy an array of p_cnct::p_cnct_repeat.
|
|
//
|
|
static void copy_p_cnct_repeat_array( p_cnct::p_cnct_repeat* pDest,
|
|
const p_cnct::p_cnct_repeat* pSource,
|
|
size_t nEntries)
|
|
{
|
|
for (size_t i = 0; i < nEntries; ++i) {
|
|
pDest[i] = pSource[i];
|
|
}
|
|
}
|
|
|
|
|
|
static rem_port* receive( rem_port* main_port, PACKET * packet)
|
|
{
|
|
/**************************************
|
|
*
|
|
* r e c e i v e
|
|
*
|
|
**************************************
|
|
*
|
|
* Functional description
|
|
* Receive a message from a port or clients of a port. If the process
|
|
* is a server and a connection request comes in, generate a new port
|
|
* block for the client.
|
|
*
|
|
**************************************/
|
|
|
|
// loop as long as we are receiving dummy packets, just
|
|
// throwing them away--note that if we are a server we won't
|
|
// be receiving them, but it is better to check for them at
|
|
// this level rather than try to catch them in all places where
|
|
// this routine is called
|
|
|
|
do {
|
|
if (!xdr_protocol(&main_port->port_receive, packet))
|
|
{
|
|
packet->p_operation = main_port->port_flags & PORT_partial_data ? op_partial : op_exit;
|
|
main_port->port_flags &= ~PORT_partial_data;
|
|
|
|
if (packet->p_operation == op_exit) {
|
|
main_port->port_state = rem_port::BROKEN;
|
|
}
|
|
break;
|
|
}
|
|
#ifdef DEBUG
|
|
{
|
|
static ULONG op_rec_count = 0;
|
|
op_rec_count++;
|
|
if (INET_trace & TRACE_operations)
|
|
{
|
|
fprintf(stdout, "%04u: OP Recd %5u opcode %d\n",
|
|
inet_debug_timer(),
|
|
op_rec_count, packet->p_operation);
|
|
fflush(stdout);
|
|
}
|
|
}
|
|
#endif
|
|
} while (packet->p_operation == op_dummy);
|
|
|
|
return main_port;
|
|
}
|
|
|
|
static bool select_multi(rem_port* main_port, UCHAR* buffer, SSHORT bufsize, SSHORT* length,
|
|
RemPortPtr& port)
|
|
{
|
|
/**************************************
|
|
*
|
|
* s e l e c t _ m u l t i
|
|
*
|
|
**************************************
|
|
*
|
|
* 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.
|
|
* If a connection request comes in, generate a new port
|
|
* block for the client.
|
|
*
|
|
**************************************/
|
|
|
|
for (;;)
|
|
{
|
|
select_port(main_port, &INET_select, port);
|
|
if (port == main_port && (port->port_server_flags & SRVR_multi_client))
|
|
{
|
|
if (INET_shutting_down)
|
|
{
|
|
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);
|
|
}
|
|
}
|
|
else if (port = select_accept(main_port))
|
|
{
|
|
if (!packet_receive(port, buffer, bufsize, length))
|
|
{
|
|
*length = 0;
|
|
}
|
|
return (*length) ? true : false;
|
|
}
|
|
|
|
continue;
|
|
}
|
|
if (port)
|
|
{
|
|
if (port->port_dummy_timeout < 0)
|
|
{
|
|
port->port_dummy_timeout = port->port_dummy_packet_interval;
|
|
if (port->port_flags & PORT_async || port->port_protocol < PROTOCOL_VERSION8)
|
|
{
|
|
continue;
|
|
}
|
|
*length = 0;
|
|
return true;
|
|
}
|
|
|
|
if (!packet_receive(port, buffer, bufsize, length))
|
|
{
|
|
if (port->port_flags & PORT_disconnect) {
|
|
continue;
|
|
}
|
|
*length = 0;
|
|
}
|
|
return (*length) ? true : false;
|
|
}
|
|
if (!select_wait(main_port, &INET_select))
|
|
{
|
|
port = NULL;
|
|
return false;
|
|
}
|
|
}
|
|
}
|
|
|
|
static rem_port* select_accept( rem_port* main_port)
|
|
{
|
|
/**************************************
|
|
*
|
|
* s e l e c t _ a c c e p t
|
|
*
|
|
**************************************
|
|
*
|
|
* Functional description
|
|
* Accept a new connection request.
|
|
*
|
|
**************************************/
|
|
struct sockaddr_in address;
|
|
|
|
rem_port* const port = alloc_port(main_port);
|
|
socklen_t l = sizeof(address);
|
|
inet_ports->registerPort(port);
|
|
|
|
port->port_handle = accept(main_port->port_handle, (struct sockaddr*) &address, &l);
|
|
if (port->port_handle == INVALID_SOCKET)
|
|
{
|
|
inet_error(port, "accept", isc_net_connect_err, INET_ERRNO);
|
|
disconnect(port);
|
|
return 0;
|
|
}
|
|
|
|
int optval = 1;
|
|
setsockopt(port->port_handle, SOL_SOCKET, SO_KEEPALIVE, (SCHAR*) &optval, sizeof(optval));
|
|
|
|
port->port_flags |= PORT_server;
|
|
|
|
if (main_port->port_server_flags & SRVR_thread_per_port)
|
|
{
|
|
port->port_server_flags = (SRVR_server | SRVR_inet | SRVR_thread_per_port);
|
|
return port;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
static void select_port(rem_port* main_port, Select* selct, RemPortPtr& port)
|
|
{
|
|
/**************************************
|
|
*
|
|
* s e l e c t _ p o r t
|
|
*
|
|
**************************************
|
|
*
|
|
* Functional description
|
|
* Select a descriptor that is ready to read
|
|
* and return the port block. Additionally,
|
|
* check if a port's keepalive timer has
|
|
* expired and return the port block so that
|
|
* a keepalive packet can be queued. Return
|
|
* NULL if none are active.
|
|
*
|
|
**************************************/
|
|
|
|
Firebird::MutexLockGuard guard(port_mutex);
|
|
|
|
for (port = main_port; port; port = port->port_next)
|
|
{
|
|
Select::HandleState result = selct->ok(port);
|
|
selct->unset(port->port_handle);
|
|
switch (result)
|
|
{
|
|
case Select::SEL_BAD:
|
|
if (port->port_state == rem_port::BROKEN)
|
|
continue;
|
|
return;
|
|
|
|
case Select::SEL_DISCONNECTED:
|
|
continue;
|
|
|
|
case Select::SEL_READY:
|
|
port->port_dummy_timeout = port->port_dummy_packet_interval;
|
|
return;
|
|
|
|
default:
|
|
break;
|
|
}
|
|
|
|
if (port->port_dummy_timeout < 0)
|
|
{
|
|
return;
|
|
}
|
|
}
|
|
}
|
|
|
|
static bool select_wait( rem_port* main_port, Select* selct)
|
|
{
|
|
/**************************************
|
|
*
|
|
* s e l e c t _ w a i t
|
|
*
|
|
**************************************
|
|
*
|
|
* Functional description
|
|
* Select interesting descriptors from
|
|
* port blocks and wait for something
|
|
* to read from them.
|
|
*
|
|
**************************************/
|
|
struct timeval timeout;
|
|
bool checkPorts = false;
|
|
|
|
for (;;)
|
|
{
|
|
selct->clear();
|
|
bool found = false;
|
|
|
|
// Use the time interval between select() calls to expire
|
|
// keepalive timers on all ports.
|
|
|
|
time_t delta_time;
|
|
if (selct->slct_time)
|
|
{
|
|
delta_time = time(NULL) - selct->slct_time;
|
|
selct->slct_time += delta_time;
|
|
}
|
|
else
|
|
{
|
|
delta_time = 0;
|
|
selct->slct_time = time(NULL);
|
|
}
|
|
|
|
{ // port_mutex scope
|
|
Firebird::MutexLockGuard guard(port_mutex);
|
|
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))
|
|
{
|
|
// Adjust down the port's keepalive timer.
|
|
|
|
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 =
|
|
#ifdef WIN_NT
|
|
false;
|
|
#else
|
|
(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)
|
|
{
|
|
// 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;
|
|
}
|
|
}
|
|
}
|
|
|
|
// 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;
|
|
}
|
|
}
|
|
}
|
|
checkPorts = false;
|
|
} // port_mutex scope
|
|
|
|
if (!found)
|
|
{
|
|
if (!INET_shutting_down && (main_port->port_server_flags & SRVR_multi_client)) {
|
|
gds__log("INET/select_wait: client rundown complete, server exiting");
|
|
}
|
|
return false;
|
|
}
|
|
|
|
for (;;)
|
|
{
|
|
// 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;
|
|
return false;
|
|
}
|
|
|
|
// Some platforms change the timeout in the select call.
|
|
// Reset timeout for each iteration to avoid problems.
|
|
timeout.tv_sec = SELECT_TIMEOUT;
|
|
timeout.tv_usec = 0;
|
|
|
|
selct->select(&timeout);
|
|
const int inetErrNo = INET_ERRNO;
|
|
|
|
//if (INET_shutting_down) {
|
|
// return false;
|
|
//}
|
|
|
|
if (selct->getCount() != -1)
|
|
{
|
|
// if selct->slct_count is zero it means that we timed out of
|
|
// select with nothing to read or accept, so clear the fd_set
|
|
// bit as this value is undefined on some platforms (eg. HP-UX),
|
|
// when the select call times out. Once these bits are cleared
|
|
// they can be used in select_port()
|
|
if (selct->getCount() == 0)
|
|
{
|
|
Firebird::MutexLockGuard guard(port_mutex);
|
|
for (rem_port* port = main_port; port; port = port->port_next)
|
|
{
|
|
selct->unset(port->port_handle);
|
|
}
|
|
}
|
|
return true;
|
|
}
|
|
if (INTERRUPT_ERROR(inetErrNo))
|
|
continue;
|
|
if (inetErrNo == NOTASOCKET)
|
|
{
|
|
checkPorts = true;
|
|
break;
|
|
}
|
|
|
|
gds__log("INET/select_wait: select failed, errno = %d", inetErrNo);
|
|
return false;
|
|
} // for (;;)
|
|
}
|
|
}
|
|
|
|
static int send_full( rem_port* port, PACKET * packet)
|
|
{
|
|
/**************************************
|
|
*
|
|
* s e n d _ f u l l
|
|
*
|
|
**************************************
|
|
*
|
|
* Functional description
|
|
* Send a packet across a port to another process.
|
|
*
|
|
**************************************/
|
|
|
|
if (!xdr_protocol(&port->port_send, packet))
|
|
return FALSE;
|
|
|
|
#ifdef DEBUG
|
|
{ // scope
|
|
static ULONG op_sent_count = 0;
|
|
op_sent_count++;
|
|
if (INET_trace & TRACE_operations)
|
|
{
|
|
fprintf(stdout, "%05u: OP Sent %5u opcode %d\n", inet_debug_timer(),
|
|
op_sent_count, packet->p_operation);
|
|
fflush(stdout);
|
|
}
|
|
} // end scope
|
|
#endif
|
|
|
|
return inet_write(&port->port_send /*, TRUE*/);
|
|
}
|
|
|
|
static int send_partial( rem_port* port, PACKET * packet)
|
|
{
|
|
/**************************************
|
|
*
|
|
* s e n d _ p a r t i a l
|
|
*
|
|
**************************************
|
|
*
|
|
* Functional description
|
|
* Send a packet across a port to another process.
|
|
*
|
|
**************************************/
|
|
|
|
#ifdef DEBUG
|
|
{ // scope
|
|
static ULONG op_sentp_count = 0;
|
|
op_sentp_count++;
|
|
if (INET_trace & TRACE_operations)
|
|
{
|
|
fprintf(stdout, "%05u: OP Sent %5u opcode %d (partial)\n", inet_debug_timer(),
|
|
op_sentp_count, packet->p_operation);
|
|
fflush(stdout);
|
|
}
|
|
} // end scope
|
|
#endif
|
|
|
|
return xdr_protocol(&port->port_send, packet);
|
|
}
|
|
|
|
|
|
static int xdrinet_create(XDR* xdrs, rem_port* port, UCHAR* buffer, USHORT length, enum xdr_op x_op)
|
|
{
|
|
/**************************************
|
|
*
|
|
* x d r i n e t _ c r e a t e
|
|
*
|
|
**************************************
|
|
*
|
|
* Functional description
|
|
* Initialize an XDR stream for Apollo mailboxes.
|
|
*
|
|
**************************************/
|
|
|
|
xdrs->x_public = (caddr_t) port;
|
|
xdrs->x_base = xdrs->x_private = reinterpret_cast<SCHAR*>(buffer);
|
|
xdrs->x_handy = length;
|
|
xdrs->x_ops = (xdr_t::xdr_ops*) &inet_ops;
|
|
xdrs->x_op = x_op;
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
#ifdef HAVE_SETITIMER
|
|
static void alarm_handler( int x)
|
|
{
|
|
/**************************************
|
|
*
|
|
* a l a r m _ h a n d l e r
|
|
*
|
|
**************************************
|
|
*
|
|
* Functional description
|
|
* Handle an alarm clock interrupt. If we were waiting on
|
|
* a semaphone, zap it.
|
|
*
|
|
**************************************/
|
|
}
|
|
#endif
|
|
|
|
static XDR_INT inet_destroy( XDR*)
|
|
{
|
|
/**************************************
|
|
*
|
|
* i n e t _ d e s t r o y
|
|
*
|
|
**************************************
|
|
*
|
|
* Functional description
|
|
* Destroy a stream. A no-op.
|
|
*
|
|
**************************************/
|
|
|
|
return (XDR_INT) 0;
|
|
}
|
|
|
|
static void inet_gen_error(rem_port* port, const Arg::StatusVector& v)
|
|
{
|
|
/**************************************
|
|
*
|
|
* i n e t _ g e n _ e r r o r
|
|
*
|
|
**************************************
|
|
*
|
|
* Functional description
|
|
* An error has occurred. Mark the port as broken.
|
|
* Format the status vector if there is one and
|
|
* save the status vector strings in a permanent place.
|
|
*
|
|
**************************************/
|
|
port->port_state = rem_port::BROKEN;
|
|
|
|
const char* node_name = port->port_connection ? port->port_connection->str_data : "(unknown)";
|
|
|
|
Arg::Gds error(isc_network_error);
|
|
error << Arg::Str(node_name) << v;
|
|
|
|
ISC_STATUS* status_vector = NULL;
|
|
if (port->port_context != NULL) {
|
|
status_vector = port->port_context->get_status_vector();
|
|
}
|
|
if (status_vector == NULL) {
|
|
status_vector = port->port_status_vector;
|
|
}
|
|
if (status_vector != NULL)
|
|
{
|
|
error.copyTo(status_vector);
|
|
REMOTE_save_status_strings(status_vector);
|
|
}
|
|
}
|
|
|
|
|
|
static bool_t inet_getbytes( XDR* xdrs, SCHAR* buff, u_int count)
|
|
{
|
|
/**************************************
|
|
*
|
|
* i n e t _ g e t b y t e s
|
|
*
|
|
**************************************
|
|
*
|
|
* Functional description
|
|
* Get a bunch of bytes from a memory stream if it fits.
|
|
*
|
|
**************************************/
|
|
#ifdef REM_SERVER
|
|
const rem_port* port = (rem_port*) xdrs->x_public;
|
|
if ((port->port_flags & PORT_server) && !(port->port_server_flags & SRVR_debug))
|
|
{
|
|
return REMOTE_getbytes(xdrs, buff, count);
|
|
}
|
|
#endif
|
|
|
|
SLONG bytecount = count;
|
|
|
|
// Use memcpy to optimize bulk transfers.
|
|
|
|
while (bytecount > (SLONG) sizeof(ISC_QUAD))
|
|
{
|
|
if (xdrs->x_handy >= bytecount)
|
|
{
|
|
memcpy(buff, xdrs->x_private, bytecount);
|
|
xdrs->x_private += bytecount;
|
|
xdrs->x_handy -= bytecount;
|
|
return TRUE;
|
|
}
|
|
|
|
if (xdrs->x_handy > 0)
|
|
{
|
|
memcpy(buff, xdrs->x_private, xdrs->x_handy);
|
|
xdrs->x_private += xdrs->x_handy;
|
|
buff += xdrs->x_handy;
|
|
bytecount -= xdrs->x_handy;
|
|
xdrs->x_handy = 0;
|
|
}
|
|
|
|
if (!inet_read(xdrs))
|
|
return FALSE;
|
|
}
|
|
|
|
// Scalar values and bulk transfer remainder fall thru
|
|
// to be moved byte-by-byte to avoid memcpy setup costs.
|
|
|
|
if (!bytecount)
|
|
return TRUE;
|
|
|
|
if (xdrs->x_handy >= bytecount)
|
|
{
|
|
xdrs->x_handy -= bytecount;
|
|
while (bytecount--)
|
|
*buff++ = *xdrs->x_private++;
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
while (--bytecount >= 0)
|
|
{
|
|
if (!xdrs->x_handy && !inet_read(xdrs))
|
|
return FALSE;
|
|
*buff++ = *xdrs->x_private++;
|
|
--xdrs->x_handy;
|
|
}
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
|
|
static bool_t inet_getlong( XDR* xdrs, SLONG* lp)
|
|
{
|
|
/**************************************
|
|
*
|
|
* i n e t _ g e t l o n g
|
|
*
|
|
**************************************
|
|
*
|
|
* Functional description
|
|
* Fetch a longword into a memory stream if it fits.
|
|
*
|
|
**************************************/
|
|
|
|
SLONG l;
|
|
|
|
if (!(*xdrs->x_ops->x_getbytes) (xdrs, reinterpret_cast<char*>(&l), 4))
|
|
return FALSE;
|
|
|
|
*lp = ntohl(l);
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
static u_int inet_getpostn( XDR* xdrs)
|
|
{
|
|
/**************************************
|
|
*
|
|
* i n e t _ g e t p o s t n
|
|
*
|
|
**************************************
|
|
*
|
|
* Functional description
|
|
* Get the current position (which is also current length) from stream.
|
|
*
|
|
**************************************/
|
|
|
|
return (u_int) (xdrs->x_private - xdrs->x_base);
|
|
}
|
|
|
|
static caddr_t inet_inline( XDR* xdrs, u_int bytecount)
|
|
{
|
|
/**************************************
|
|
*
|
|
* i n e t _ i n l i n e
|
|
*
|
|
**************************************
|
|
*
|
|
* Functional description
|
|
* Return a pointer to somewhere in the buffer.
|
|
*
|
|
**************************************/
|
|
|
|
if (bytecount > (u_int) xdrs->x_handy)
|
|
return FALSE;
|
|
|
|
return xdrs->x_base + bytecount;
|
|
}
|
|
|
|
static void inet_error(rem_port* port, const TEXT* function, ISC_STATUS operation, int status)
|
|
{
|
|
/**************************************
|
|
*
|
|
* i n e t _ e r r o r
|
|
*
|
|
**************************************
|
|
*
|
|
* Functional description
|
|
* An I/O error has occurred. Call
|
|
* inet_gen_error with the appropriate args
|
|
* to format the status vector if any.
|
|
*
|
|
**************************************/
|
|
if (status)
|
|
{
|
|
if (port->port_state != rem_port::BROKEN) {
|
|
gds__log("INET/inet_error: %s errno = %d", function, status);
|
|
}
|
|
|
|
inet_gen_error(port, Arg::Gds(operation) << SYS_ERR(status));
|
|
}
|
|
else
|
|
{
|
|
// No status value, just format the basic arguments.
|
|
inet_gen_error(port, Arg::Gds(operation));
|
|
}
|
|
}
|
|
|
|
static bool_t inet_putbytes( XDR* xdrs, const SCHAR* buff, u_int count)
|
|
{
|
|
/**************************************
|
|
*
|
|
* i n e t _ p u t b y t e s
|
|
*
|
|
**************************************
|
|
*
|
|
* Functional description
|
|
* Put a bunch of bytes to a memory stream if it fits.
|
|
*
|
|
**************************************/
|
|
SLONG bytecount = count;
|
|
|
|
// Use memcpy to optimize bulk transfers.
|
|
|
|
while (bytecount > (SLONG) sizeof(ISC_QUAD))
|
|
{
|
|
if (xdrs->x_handy >= bytecount)
|
|
{
|
|
memcpy(xdrs->x_private, buff, bytecount);
|
|
xdrs->x_private += bytecount;
|
|
xdrs->x_handy -= bytecount;
|
|
return TRUE;
|
|
}
|
|
|
|
if (xdrs->x_handy > 0)
|
|
{
|
|
memcpy(xdrs->x_private, buff, xdrs->x_handy);
|
|
xdrs->x_private += xdrs->x_handy;
|
|
buff += xdrs->x_handy;
|
|
bytecount -= xdrs->x_handy;
|
|
xdrs->x_handy = 0;
|
|
}
|
|
|
|
if (!inet_write(xdrs /*, 0*/))
|
|
return FALSE;
|
|
}
|
|
|
|
// Scalar values and bulk transfer remainder fall thru
|
|
// to be moved byte-by-byte to avoid memcpy setup costs.
|
|
|
|
if (!bytecount)
|
|
return TRUE;
|
|
|
|
if (xdrs->x_handy >= bytecount)
|
|
{
|
|
xdrs->x_handy -= bytecount;
|
|
while (bytecount--)
|
|
*xdrs->x_private++ = *buff++;
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
while (--bytecount >= 0)
|
|
{
|
|
if (xdrs->x_handy <= 0 && !inet_write(xdrs /*, 0*/))
|
|
return FALSE;
|
|
--xdrs->x_handy;
|
|
*xdrs->x_private++ = *buff++;
|
|
}
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
|
|
static bool_t inet_putlong( XDR* xdrs, const SLONG* lp)
|
|
{
|
|
/**************************************
|
|
*
|
|
* i n e t _ p u t l o n g
|
|
*
|
|
**************************************
|
|
*
|
|
* Functional description
|
|
* Fetch a longword into a memory stream if it fits.
|
|
*
|
|
**************************************/
|
|
const SLONG l = htonl(*lp);
|
|
return (*xdrs->x_ops->x_putbytes) (xdrs, reinterpret_cast<const char*>(&l), 4);
|
|
}
|
|
|
|
static bool_t inet_read( XDR* xdrs)
|
|
{
|
|
/**************************************
|
|
*
|
|
* i n e t _ r e a d
|
|
*
|
|
**************************************
|
|
*
|
|
* Functional description
|
|
* Read a buffer full of data. If we receive a bad packet,
|
|
* send the moral equivalent of a NAK and retry. ACK all
|
|
* partial packets. Don't ACK the last packet -- the next
|
|
* message sent will handle this.
|
|
*
|
|
**************************************/
|
|
rem_port* port = (rem_port*) xdrs->x_public;
|
|
char* p = xdrs->x_base;
|
|
const char* const end = p + INET_remote_buffer;
|
|
|
|
// If buffer is not completely empty, slide down what's left
|
|
|
|
if (xdrs->x_handy > 0)
|
|
{
|
|
memmove(p, xdrs->x_private, xdrs->x_handy);
|
|
p += xdrs->x_handy;
|
|
}
|
|
|
|
while (true)
|
|
{
|
|
SSHORT length = end - p;
|
|
if (!packet_receive(port, reinterpret_cast<UCHAR*>(p), length, &length))
|
|
{
|
|
return FALSE;
|
|
}
|
|
if (length >= 0)
|
|
{
|
|
p += length;
|
|
break;
|
|
}
|
|
p -= length;
|
|
if (!packet_send(port, 0, 0))
|
|
return FALSE;
|
|
}
|
|
|
|
xdrs->x_handy = (int) ((SCHAR *) p - xdrs->x_base);
|
|
xdrs->x_private = xdrs->x_base;
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
static bool_t inet_setpostn( XDR* xdrs, u_int bytecount)
|
|
{
|
|
/**************************************
|
|
*
|
|
* i n e t _ s e t p o s t n
|
|
*
|
|
**************************************
|
|
*
|
|
* Functional description
|
|
* Set the current position (which is also current length) from stream.
|
|
*
|
|
**************************************/
|
|
|
|
if (bytecount > (u_int) xdrs->x_handy)
|
|
return FALSE;
|
|
|
|
xdrs->x_private = xdrs->x_base + bytecount;
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
static rem_port* inet_try_connect(PACKET* packet,
|
|
Rdb* rdb,
|
|
const Firebird::PathName& file_name,
|
|
const TEXT* node_name,
|
|
ISC_STATUS* status_vector,
|
|
Firebird::ClumpletReader& dpb)
|
|
{
|
|
/**************************************
|
|
*
|
|
* i n e t _ t r y _ c o n n e c t
|
|
*
|
|
**************************************
|
|
*
|
|
* Functional description
|
|
* Given a packet with formatted protocol infomation,
|
|
* set header information and try the connection.
|
|
*
|
|
* If a connection is established, return a port block, otherwise
|
|
* return NULL.
|
|
*
|
|
**************************************/
|
|
P_CNCT* cnct = &packet->p_cnct;
|
|
packet->p_operation = op_connect;
|
|
cnct->p_cnct_operation = op_attach;
|
|
cnct->p_cnct_cversion = CONNECT_VERSION2;
|
|
cnct->p_cnct_client = ARCHITECTURE;
|
|
cnct->p_cnct_file.cstr_length = (USHORT) file_name.length();
|
|
cnct->p_cnct_file.cstr_address = reinterpret_cast<const UCHAR*>(file_name.c_str());
|
|
|
|
// If we can't talk to a server, punt. Let somebody else generate
|
|
// an error. status_vector will have the network error info.
|
|
|
|
rem_port* port = INET_connect(node_name, packet, status_vector, FALSE, &dpb);
|
|
if (!port)
|
|
{
|
|
delete rdb;
|
|
return NULL;
|
|
}
|
|
|
|
// Get response packet from server.
|
|
|
|
rdb->rdb_port = port;
|
|
port->port_context = rdb;
|
|
if (!port->receive(packet))
|
|
{
|
|
inet_error(port, "receive in try_connect", isc_net_connect_err, INET_ERRNO);
|
|
disconnect(port);
|
|
delete rdb;
|
|
return NULL;
|
|
}
|
|
|
|
return port;
|
|
}
|
|
|
|
static bool_t inet_write( XDR* xdrs /*, bool_t end_flag*/)
|
|
{
|
|
/**************************************
|
|
*
|
|
* i n e t _ w r i t e
|
|
*
|
|
**************************************
|
|
*
|
|
* Functional description
|
|
* Write a buffer full of data.
|
|
* Obsolete: If the end_flag isn't set, indicate
|
|
* that the buffer is a fragment, and reset the XDR for another buffer
|
|
* load.
|
|
*
|
|
**************************************/
|
|
// Encode the data portion of the packet
|
|
|
|
rem_port* port = (rem_port*) xdrs->x_public;
|
|
const char* p = xdrs->x_base;
|
|
SSHORT length = xdrs->x_private - p;
|
|
|
|
// Send data in manageable hunks. If a packet is partial, indicate
|
|
// that with a negative length. A positive length marks the end.
|
|
|
|
//p = xdrs->x_base; redundant
|
|
|
|
while (length)
|
|
{
|
|
const SSHORT l = (SSHORT) MIN(length, INET_remote_buffer);
|
|
length -= l;
|
|
if (!packet_send(port, p, (SSHORT) (length ? -l : l)))
|
|
return FALSE;
|
|
p += l;
|
|
}
|
|
|
|
xdrs->x_private = xdrs->x_base;
|
|
xdrs->x_handy = INET_remote_buffer;
|
|
|
|
return TRUE;
|
|
|
|
#ifdef PIGGYBACK
|
|
// CVC: Screwed logic here: if I initialize l2 to zero, nothing useful executes.
|
|
SCHAR aux_buffer[BUFFER_SIZE];
|
|
SSHORT l2 = 0;
|
|
#error Assign l2 some meaningful value before running this.
|
|
// If the other end has not piggy-backed the next packet, we're done.
|
|
|
|
if (!l2)
|
|
return TRUE;
|
|
|
|
// We've got a piggy-backed response. If the packet is partial,
|
|
// send an ACK for part we did receive.
|
|
|
|
char* p2 = aux_buffer;
|
|
|
|
while (l2 < 0)
|
|
{
|
|
if (!packet_send(port, 0, 0))
|
|
return FALSE;
|
|
p2 -= l2;
|
|
length = aux_buffer + sizeof(aux_buffer) - p2;
|
|
if (!packet_receive(port, p2, length, &l2))
|
|
{
|
|
p2 += l2;
|
|
continue;
|
|
}
|
|
}
|
|
|
|
length = p2 - aux_buffer + l2;
|
|
|
|
// Now we're got a encode glump ready to stuff into the read buffer.
|
|
// Unfortunately, if we just add it to the read buffer, we will shortly
|
|
// overflow the buffer. To avoid this, "scrumpf down" the active bits
|
|
// in the read buffer, then add out stuff at the end.
|
|
|
|
xdrs = &port->port_receive;
|
|
p2 = xdrs->x_base;
|
|
|
|
if (xdrs->x_handy && p2 != xdrs->x_private) {
|
|
memmove(p2, xdrs->x_private, xdrs->x_handy);
|
|
}
|
|
|
|
p2 += xdrs->x_handy;
|
|
|
|
xdrs->x_private = xdrs->x_base;
|
|
|
|
// xdrs->x_handy += JAP_decode (aux_buffer, length, p2);
|
|
|
|
return TRUE;
|
|
#endif
|
|
}
|
|
|
|
#ifdef DEBUG
|
|
static void packet_print(const TEXT* string, const UCHAR* packet, int length, ULONG counter)
|
|
{
|
|
/**************************************
|
|
*
|
|
* p a c k e t _ p r i n t
|
|
*
|
|
**************************************
|
|
*
|
|
* Functional description
|
|
* Print a summary of packet.
|
|
*
|
|
**************************************/
|
|
int sum = 0;
|
|
for (int l = length; l > 0; --l)
|
|
sum += *packet++;
|
|
|
|
fprintf(stdout, "%05u: PKT %s\t(%u): length = %4d, checksum = %d\n",
|
|
inet_debug_timer(), string, counter, length, sum);
|
|
fflush(stdout);
|
|
}
|
|
#endif
|
|
|
|
static bool packet_receive(rem_port* port, UCHAR* buffer, SSHORT buffer_length, SSHORT* length)
|
|
{
|
|
/**************************************
|
|
*
|
|
* p a c k e t _ r e c e i v e
|
|
*
|
|
**************************************
|
|
*
|
|
* Functional description
|
|
* Receive a packet and pass on it's goodness. If it's good,
|
|
* return true and the reported length of the packet, and update
|
|
* the receive sequence number. If it's bad, return false. If it's
|
|
* a duplicate message, just ignore it.
|
|
*
|
|
**************************************/
|
|
|
|
if (port->port_flags & PORT_disconnect) {
|
|
return false;
|
|
}
|
|
|
|
timeval timeout;
|
|
timeout.tv_usec = 0;
|
|
timeval* time_ptr = NULL;
|
|
|
|
if (port->port_protocol == 0)
|
|
{
|
|
// If the protocol is 0 we are still in the process of establishing
|
|
// a connection. Add a time out to the wait.
|
|
timeout.tv_sec = port->port_connect_timeout;
|
|
time_ptr = &timeout;
|
|
}
|
|
else if (port->port_protocol >= PROTOCOL_VERSION8 && port->port_dummy_packet_interval > 0)
|
|
{
|
|
// Set the time interval for sending dummy packets to the client
|
|
timeout.tv_sec = port->port_dummy_packet_interval;
|
|
time_ptr = &timeout;
|
|
}
|
|
|
|
// On Linux systems (and possibly others too) select will eventually
|
|
// change timout values so save it here for later reuse.
|
|
// Thanks to Brad Pepers who reported this bug FSG 3 MAY 2001
|
|
const timeval savetime = timeout;
|
|
|
|
const SOCKET ph = port->port_handle;
|
|
|
|
// Unsed to send a dummy packet, but too big to be defined in the loop.
|
|
PACKET packet;
|
|
|
|
int n = 0;
|
|
int inetErrNo;
|
|
|
|
for (;;)
|
|
{
|
|
// Implement an error-detection protocol to ensure that the client
|
|
// is still there. Use the select() call with a timeout to wait on
|
|
// the connection for an incoming packet. If none comes within a
|
|
// suitable time interval, write a dummy packet on the connection.
|
|
// If the client is not there, an error will be returned on the write.
|
|
// If the client is there, the dummy packet will be ignored by all
|
|
// InterBase clients V4 or greater. This protocol will detect when
|
|
// clients are lost abnormally through reboot or network disconnect.
|
|
|
|
// Don't send op_dummy packets on aux port; the server won't
|
|
// read them because it only writes to aux ports.
|
|
|
|
if ( !(port->port_flags & PORT_async) )
|
|
{
|
|
Select slct;
|
|
slct.set(ph);
|
|
|
|
int slct_count;
|
|
for (;;)
|
|
{
|
|
slct.select(time_ptr);
|
|
slct_count = slct.getCount();
|
|
inetErrNo = INET_ERRNO;
|
|
|
|
// restore original timeout value FSG 3 MAY 2001
|
|
timeout = savetime;
|
|
|
|
if (slct_count != -1 || !INTERRUPT_ERROR(inetErrNo))
|
|
{
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (slct_count == -1)
|
|
{
|
|
if (!(port->port_flags & PORT_disconnect)) {
|
|
inet_error(port, "select in packet_receive", isc_net_read_err, inetErrNo);
|
|
}
|
|
return false;
|
|
}
|
|
|
|
if (!slct_count && port->port_protocol >= PROTOCOL_VERSION8)
|
|
{
|
|
#ifdef DEBUG
|
|
if (INET_trace & TRACE_operations)
|
|
{
|
|
fprintf(stdout, "%05u: OP Sent: op_dummy\n", inet_debug_timer());
|
|
fflush(stdout);
|
|
}
|
|
#endif
|
|
packet.p_operation = op_dummy;
|
|
if (!send_full(port, &packet))
|
|
{
|
|
return false;
|
|
}
|
|
continue;
|
|
}
|
|
|
|
if (!slct_count && port->port_protocol == 0)
|
|
{
|
|
return false;
|
|
}
|
|
}
|
|
|
|
n = recv(port->port_handle, reinterpret_cast<char*>(buffer), buffer_length, 0);
|
|
inetErrNo = INET_ERRNO;
|
|
|
|
if (n != -1 || !INTERRUPT_ERROR(inetErrNo))
|
|
break;
|
|
}
|
|
|
|
if (n <= 0 && (port->port_flags & PORT_disconnect)) {
|
|
return false;
|
|
}
|
|
|
|
if (n == -1)
|
|
{
|
|
inet_error(port, "read", isc_net_read_err, inetErrNo);
|
|
return false;
|
|
}
|
|
|
|
if (!n)
|
|
{
|
|
port->port_state = rem_port::BROKEN;
|
|
return false;
|
|
}
|
|
|
|
#ifdef DEBUG
|
|
{ // scope
|
|
INET_count_recv++;
|
|
INET_bytes_recv += n;
|
|
if (INET_trace & TRACE_packets)
|
|
packet_print("receive", buffer, n, INET_count_recv);
|
|
INET_force_error--;
|
|
if (INET_force_error == 0)
|
|
{
|
|
INET_force_error = 1;
|
|
inet_error(port, "simulated error - read", isc_net_read_err, 0);
|
|
return false;
|
|
}
|
|
} // end scope
|
|
#endif
|
|
|
|
*length = n;
|
|
|
|
return true;
|
|
}
|
|
|
|
|
|
static bool packet_send( rem_port* port, const SCHAR* buffer, SSHORT buffer_length)
|
|
{
|
|
/**************************************
|
|
*
|
|
* p a c k e t _ s e n d
|
|
*
|
|
**************************************
|
|
*
|
|
* Functional description
|
|
* Send some data on it's way.
|
|
*
|
|
**************************************/
|
|
|
|
const char* data = buffer;
|
|
SSHORT length = buffer_length;
|
|
|
|
while (length)
|
|
{
|
|
#ifdef DEBUG
|
|
if (INET_trace & TRACE_operations)
|
|
{
|
|
fprintf(stdout, "Before Send\n");
|
|
fflush(stdout);
|
|
}
|
|
#endif
|
|
SSHORT n = send(port->port_handle, data, length, FB_SEND_FLAGS);
|
|
#ifdef DEBUG
|
|
if (INET_trace & TRACE_operations)
|
|
{
|
|
fprintf(stdout, "After Send n is %d\n", n);
|
|
fflush(stdout);
|
|
}
|
|
#endif
|
|
if (n == length) {
|
|
break;
|
|
}
|
|
|
|
if (n == -1)
|
|
{
|
|
if (INTERRUPT_ERROR(INET_ERRNO)) {
|
|
continue;
|
|
}
|
|
|
|
inet_error(port, "send", isc_net_write_err, INET_ERRNO);
|
|
return false;
|
|
}
|
|
|
|
data += n;
|
|
length -= n;
|
|
}
|
|
|
|
#ifdef HAVE_SETITIMER
|
|
struct itimerval internal_timer, client_timer;
|
|
struct sigaction internal_handler, client_handler;
|
|
#endif // HAVE_SETITIMER
|
|
|
|
if ((port->port_flags & PORT_async) && !(port->port_flags & PORT_no_oob))
|
|
{
|
|
int count = 0;
|
|
SSHORT n;
|
|
int inetErrNo = 0;
|
|
const char* b = buffer;
|
|
while ((n = send(port->port_handle, b, 1, MSG_OOB | FB_SEND_FLAGS)) == -1 &&
|
|
(INET_ERRNO == ENOBUFS || INTERRUPT_ERROR(INET_ERRNO)))
|
|
{
|
|
inetErrNo = INET_ERRNO;
|
|
|
|
if (count++ > 20) {
|
|
break;
|
|
}
|
|
|
|
#ifndef HAVE_SETITIMER
|
|
#ifdef WIN_NT
|
|
SleepEx(50, TRUE);
|
|
#else
|
|
sleep(1);
|
|
#endif
|
|
} // end of while() loop for systems without setitimer.
|
|
#else // HAVE_SETITIMER
|
|
if (count == 1)
|
|
{
|
|
// Wait in a loop until the lock becomes available
|
|
|
|
internal_timer.it_interval.tv_sec = 0;
|
|
internal_timer.it_interval.tv_usec = 0;
|
|
internal_timer.it_value.tv_sec = 0;
|
|
internal_timer.it_value.tv_usec = 0;
|
|
setitimer(ITIMER_REAL, &internal_timer, &client_timer);
|
|
internal_handler.sa_handler = alarm_handler;
|
|
sigemptyset(&internal_handler.sa_mask);
|
|
internal_handler.sa_flags = SA_RESTART;
|
|
sigaction(SIGALRM, &internal_handler, &client_handler);
|
|
}
|
|
|
|
internal_timer.it_value.tv_sec = 0;
|
|
internal_timer.it_value.tv_usec = 50000;
|
|
setitimer(ITIMER_REAL, &internal_timer, NULL);
|
|
pause();
|
|
} // end of while() loop for systems with setitimer
|
|
|
|
if (count)
|
|
{
|
|
// Restore user's outstanding alarm request and handler
|
|
|
|
internal_timer.it_value.tv_sec = 0;
|
|
internal_timer.it_value.tv_usec = 0;
|
|
setitimer(ITIMER_REAL, &internal_timer, NULL);
|
|
sigaction(SIGALRM, &client_handler, NULL);
|
|
setitimer(ITIMER_REAL, &client_timer, NULL);
|
|
}
|
|
#endif // HAVE_SETITIMER
|
|
|
|
if (n == -1)
|
|
{
|
|
inet_error(port, "send/oob", isc_net_write_err, inetErrNo);
|
|
return false;
|
|
}
|
|
}
|
|
|
|
#ifdef DEBUG
|
|
{ // scope
|
|
INET_count_send++;
|
|
INET_bytes_send += buffer_length;
|
|
if (INET_trace & TRACE_packets)
|
|
packet_print("send", (const UCHAR*) buffer, buffer_length, INET_count_send);
|
|
INET_force_error--;
|
|
if (INET_force_error == 0)
|
|
{
|
|
INET_force_error = 1;
|
|
inet_error(port, "simulated error - send", isc_net_write_err, 0);
|
|
return false;
|
|
}
|
|
} // end scope
|
|
#endif
|
|
|
|
return true;
|
|
}
|
|
|
|
static bool setNoNagleOption(rem_port* port)
|
|
{
|
|
/**************************************
|
|
*
|
|
* s e t N o N a g l e O p t i o n
|
|
*
|
|
**************************************
|
|
*
|
|
* Functional description
|
|
* Set TCP_NODELAY, return false
|
|
* in case of unexpected error
|
|
*
|
|
**************************************/
|
|
if (Config::getTcpNoNagle())
|
|
{
|
|
int optval = TRUE;
|
|
int n = setsockopt(port->port_handle, IPPROTO_TCP, TCP_NODELAY,
|
|
(SCHAR*) &optval, sizeof(optval));
|
|
|
|
if (n == -1)
|
|
{
|
|
return false;
|
|
}
|
|
}
|
|
return true;
|
|
}
|
|
|
|
void setStopMainThread(FPTR_INT func)
|
|
{
|
|
/**************************************
|
|
*
|
|
* s e t S t o p M a i n T h r e a d
|
|
*
|
|
**************************************
|
|
*
|
|
* Functional description
|
|
* Set function called by main thread
|
|
* in order to check for shutdown.
|
|
*
|
|
**************************************/
|
|
tryStopMainThread = func;
|
|
}
|