archie/tcl-dp/generic/dpTcp.c
2024-05-27 16:13:40 +02:00

1350 lines
35 KiB
C
Raw Permalink Blame History

This file contains invisible Unicode characters

This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

/*
* generic/dpTcp.c --
*
****************************************************************
* This file implements the generic code for a tcp channel
* driver in TCL 7.6 ONLY!
*
* The TCP code for Tcl 8.0 is in win/dpWinTCP.c and
* unix/dpUnixTCP.c
****************************************************************
*
* These are channels that are created by evaluating "dp_connect
* tcp".
*
* This file was drerived from generic/dpUdp.c. See the
* header of that file for more detailed information about
* the way DP uses sockets.
*
* Copyright (c) 1995-1996 Cornell University.
*
* See the file "license.terms" for information on usage and redistribution
* of this file, and for a DISCLAIMER OF ALL WARRANTIES.
*
*/
#include "generic/dpInt.h"
#include "generic/dpPort.h"
/*
* The maximum length of the queue of pending connections to a
* Tcp server socket.
*/
#define DP_LISTEN_LIMIT 100
/*
* The default send and receive buffer size.
*/
#define DP_TCP_SENDBUFSIZE 8192
#define DP_TCP_RECVBUFSIZE 8192
typedef SocketState TcpState;
#define PEEK_MODE (1<<1) /* Read without consuming? */
#define ASYNC_CONNECT (1<<2) /* Asynchronous connection? */
#define IS_SERVER (1<<3) /* Is this a server Tcp socket? */
/*
* Procedures that are used in this file only.
*/
static int TcpBlockMode _ANSI_ARGS_((ClientData instanceData,
int mode));
static int TcpInput _ANSI_ARGS_((ClientData instanceData,
char *buf, int bufSize, int *errorCodePtr));
static int TcpOutput _ANSI_ARGS_((ClientData instanceData,
char *buf, int toWrite, int *errorCodePtr));
static int TcpClose _ANSI_ARGS_((ClientData instanceData,
Tcl_Interp *interp));
static int TcpSetOption _ANSI_ARGS_((ClientData instanceData,
Tcl_Interp *interp, char *optionName,
char *optionValue));
static int TcpGetOption _ANSI_ARGS_((ClientData instanceData,
char *optionName, Tcl_DString *dsPtr));
static void TcpWatch _ANSI_ARGS_((ClientData instanceData,
int mask));
static int TcpReady _ANSI_ARGS_((ClientData instanceData,
int mask));
static Tcl_File TcpGetFile _ANSI_ARGS_((ClientData instanceData,
int direction));
static Tcl_Channel CreateClientChannel _ANSI_ARGS_((Tcl_Interp *interp,
int destIpAddr, int destPort, int myIpAddr,
int myPort, int async));
static Tcl_Channel CreateServerChannel _ANSI_ARGS_((Tcl_Interp *interp,
int myIpAddr, int myPort));
static int SetDefaultOptions _ANSI_ARGS_((Tcl_Interp * interp,
Tcl_Channel chan));
static int DpTcpSetSocketOption _ANSI_ARGS_((TcpState *statePtr,
int option, int value));
static int DpTcpGetSocketOption _ANSI_ARGS_((TcpState *statePtr,
int option, int *valuePtr));
static int DpSetAddress _ANSI_ARGS_((TcpState *statePtr));
static Tcl_ChannelType tcpChannelType = {
"tcp", /* Name of channel */
TcpBlockMode, /* Proc to set blocking mode on socket */
TcpClose, /* Proc to close a socket */
TcpInput, /* Proc to get input from a socket */
TcpOutput, /* Proc to send output to a socket */
NULL, /* Can't seek on a socket! */
TcpSetOption, /* Proc to set a socket option */
TcpGetOption, /* Proc to set a socket option */
TcpWatch, /* Proc called to set event loop wait params */
TcpReady, /* Proc called to check if socket has input */
TcpGetFile /* Proc to return a handle assoc with socket */
};
static int tcpCount = 0; /* Number of tcp files opened -- used to
* generate unique ids for channels */
/*
*--------------------------------------------------------------
*
* DpOpenTcpChannel --
*
* Opens a new channel that uses the TCP protocol.
*
* Results:
* Returns a pointer to the newly created Tcl_Channel. This
* is the structure with all the function pointers Tcl needs
* to communicate with (read, write, close, etc) the channel.
*
* Side effects:
* A socket is created with the specified port. No other
* socket can use that port until this channel is closed.
*
*--------------------------------------------------------------
*/
Tcl_Channel
DpOpenTcpChannel(interp, argc, argv)
Tcl_Interp *interp; /* For error reporting; can be NULL. */
int argc; /* Number of arguments. */
char **argv; /* Argument strings. */
{
Tcl_Channel chan;
TcpState *statePtr = NULL;
char channelName[20];
int i;
/*
* The default values for the value-option pairs
*/
int async = 0;
int destIpAddr = 0;
int destPort = 0;
int myIpAddr = DP_INADDR_ANY;
int myPort = 0;
int isServer = 0;
/*
* Flags to indicate that a certain option has been set by the
* command line
*/
int setAsync = 0;
int setHost = 0;
int setMyPort = 0;
int setPort = 0;
for (i=0; i<argc; i+=2) {
int v = i+1;
size_t len = strlen(argv[i]);
if (strncmp(argv[i], "-async", len)==0) {
if (v==argc) {goto arg_missing;}
if (Tcl_GetBoolean(interp, argv[v], &async) != TCL_OK) {
return NULL;
}
setAsync = 1;
}
else if (strncmp(argv[i], "-host", len)==0) {
if (v==argc) {goto arg_missing;}
if (!DpHostToIpAddr (argv[v], &destIpAddr)) {
Tcl_AppendResult (interp, "Illegal value for -host \"",
argv[v], "\"", NULL);
return NULL;
}
setHost = 1;
}
else if (strncmp(argv[i], "-myaddr", len)==0) {
if (v==argc) {goto arg_missing;}
if (strcmp (argv[v], "any") == 0) {
myIpAddr = DP_INADDR_ANY;
} else if (!DpHostToIpAddr (argv[v], &myIpAddr)) {
Tcl_AppendResult (interp, "Illegal value for -myaddr \"",
argv[v], "\"", NULL);
return NULL;
}
}
else if (strncmp(argv[i], "-myport", len)==0) {
if (v==argc) {goto arg_missing;}
if (Tcl_GetInt(interp, argv[v], &myPort) != TCL_OK) {
return NULL;
}
setMyPort = 1;
}
else if (strncmp(argv[i], "-port", len)==0) {
if (v==argc) {goto arg_missing;}
if (Tcl_GetInt(interp, argv[v], &destPort) != TCL_OK) {
return NULL;
}
setPort = 1;
}
else if (strncmp(argv[i], "-server", len)==0) {
if (v==argc) {goto arg_missing;}
if (Tcl_GetBoolean(interp, argv[v], &isServer) != TCL_OK) {
return NULL;
}
}
else {
Tcl_AppendResult(interp, "unknown option \"",
argv[i], "\", must be -async, -host, -myaddr, -myport ",
"-port or -server", NULL);
return NULL;
}
}
/*
* Check the options that must or must not be specified, depending on
* the -server option.
*/
if (isServer) {
if (!setMyPort) {
Tcl_AppendResult(interp, "option -myport must be specified ",
"for servers",
NULL);
return NULL;
}
if (setAsync) {
Tcl_AppendResult(interp, "option -async is not valid for servers",
NULL);
return NULL;
}
if (setHost) {
Tcl_AppendResult(interp, "option -host is not valid for servers",
NULL);
return NULL;
}
if (setPort) {
Tcl_AppendResult(interp, "option -port is not valid for servers",
NULL);
return NULL;
}
} else {
if (!setPort) {
Tcl_AppendResult(interp, "option -port must be specified ",
"for clients",
NULL);
return NULL;
}
if (!setHost) {
Tcl_AppendResult(interp, "option -host must be specified",
NULL);
return NULL;
}
}
/*
* Create a new socket and wrap it in a channel.
*/
if (isServer) {
chan = CreateServerChannel(interp, myIpAddr, myPort);
} else {
chan = CreateClientChannel(interp, destIpAddr, destPort, myIpAddr,
myPort, async);
}
if (chan == NULL) {
return NULL;
}
Tcl_RegisterChannel(interp, chan);
if (SetDefaultOptions(interp, chan) != TCL_OK) {
DpClose(interp, chan);
return NULL;
}
return chan;
arg_missing:
Tcl_AppendResult(interp, "value for \"", argv[argc-1], "\" missing", NULL);
return NULL;
}
/*
*----------------------------------------------------------------------
*
* Dp_TcpAccept --
*
* This procedure blocks until a client is connected to a given
* Tcp server channel.
*
* Results:
* On success, returns the Tcl_Channel of the connection. On failure,
* returns NULL.
*
* Side effects:
* The first client on the "queue of connected clients" (maintained
* by the OS) is removed from the queue.
*
*----------------------------------------------------------------------
*/
Tcl_Channel
Dp_TcpAccept(interp, channelId)
Tcl_Interp *interp; /* For error reporting. */
char *channelId; /* Name of a server Tcp channel. */
{
struct sockaddr_in destSockAddr; /* Address of client host. */
TcpState *svrStatePtr = NULL; /* State of the server socket. */
TcpState *statePtr = NULL; /* State of the new socket. */
int sock;
Tcl_Channel chan;
int len, mode;
char channelName[20];
int argc, ip;
char *argv[2], *argRet;
char ipStr[16];
chan = Tcl_GetChannel(interp, channelId, &mode);
if (chan == NULL) {
return NULL;
}
if (Tcl_GetChannelType(chan) != &tcpChannelType) {
Tcl_AppendResult(interp, "\"", channelId,
"\" must be a TCP server channel", NULL);
return NULL;
}
svrStatePtr = (TcpState *)Tcl_GetChannelInstanceData(chan);
if (!(svrStatePtr->flags & IS_SERVER)) {
Tcl_AppendResult(interp, "\"", channelId,
"\" is a TCP client channel, not a server", NULL);
return NULL;
}
len = sizeof(destSockAddr);
sock = accept(svrStatePtr->sock, (struct sockaddr *)&destSockAddr, &len);
if (sock < 0) {
Tcl_AppendResult(interp, "couldn't accept from socket: ",
Tcl_PosixError(interp), (char *) NULL);
return NULL;
}
statePtr = (TcpState *) ckalloc(sizeof(TcpState));
statePtr->flags = 0;
statePtr->sock = sock;
statePtr->sockFile = Tcl_GetFile((ClientData)sock, DP_SOCKET);
statePtr->interp = interp;
statePtr->myIpAddr = 0;
statePtr->myPort = 0;
statePtr->destIpAddr = 0;
statePtr->destPort = 0;
sprintf(channelName, "tcp%d", tcpCount++);
chan = Tcl_CreateChannel(&tcpChannelType, channelName,
(ClientData)statePtr, TCL_READABLE | TCL_WRITABLE);
Tcl_RegisterChannel(interp, chan);
if (SetDefaultOptions(interp, chan) != TCL_OK) {
DpClose(interp, chan);
ckfree((char *)statePtr);
Tcl_AppendResult(interp, "unable to set default options on accepted socket: ",
Tcl_PosixError(interp), (char *) NULL);
return NULL;
}
/*
* Construct return value list { <chanID> <ipAdr> }
*/
DpTcpGetSocketOption(statePtr, DP_REMOTEIPADDR, &ip);
sprintf(ipStr, "%d.%d.%d.%d", (ip>>24)&0xFF, (ip>>16)&0xFF,
(ip>>8)&0xFF, ip&0xFF);
argc = 2;
argv[0] = channelName;
argv[1] = ipStr;
argRet = Tcl_Merge(argc, argv);
Tcl_AppendResult(interp, argRet, (char *) NULL);
ckfree((char *)argRet);
return chan;
}
/*
*--------------------------------------------------------------
*
* TcpBlockMode --
*
* Sets the tcp socket to blocking or non-blocking. Just
* a wrapper around the platform specific function.
*
* Results:
* Zero if the operation was successful, or a nonzero POSIX
* error code if the operation failed.
*
* Side effects:
* None
*
*--------------------------------------------------------------
*/
static int
TcpBlockMode (instanceData, mode)
ClientData instanceData; /* Pointer to tcpState struct */
int mode; /* TCL_MODE_BLOCKING or TCL_MODE_NONBLOCKING */
{
TcpState *statePtr = (TcpState *)instanceData;
if (mode == TCL_MODE_BLOCKING) {
return DpTcpSetSocketOption(statePtr, DP_BLOCK, 1);
} else {
return DpTcpSetSocketOption(statePtr, DP_BLOCK, 0);
}
}
/*
*--------------------------------------------------------------
*
* TcpClose --
*
* This function is called by the Tcl channel driver when the
* caller want to close the socket. It releases the instanceData
* and closes the scoket. All queued output will have been flushed
* to the device before this function is called.
*
* Results:
* Zero for success, otherwise a nonzero POSIX error code and,
* if interp is not NULL, an error message in interp->result
*
* Side effects:
* None
*
*--------------------------------------------------------------
*/
static int
TcpClose (instanceData, interp)
ClientData instanceData; /* (in) Pointer to tcpState struct */
Tcl_Interp *interp; /* (in) For error reporting */
{
TcpState *statePtr = (TcpState *)instanceData;
int result;
result = DppCloseSocket(statePtr->sock);
if ((result != 0) && (interp != NULL)) {
DppGetErrno();
Tcl_SetResult(interp, Tcl_PosixError(interp), TCL_STATIC);
}
ckfree((char *)statePtr);
return result;
}
/*
*--------------------------------------------------------------
*
* TcpInput --
*
* This function is called by the Tcl channel driver whenever the
* user wants to get input from the TCP socket. If the socket
* has some data available but less than requested by the bufSize
* argument, we only read as much data as is available and return
* without blocking. If the socket has no data available
* whatsoever and is blocking, we block until at least one byte
* of data can be read from the socket.
*
* Results:
* A nonnegative integer indicating how many bytes were read, or
* DP_SOCKET_ERROR in case of error (with errorCodePtr set to the
* POSIX error code).
*
* Side effects:
* None
*
*--------------------------------------------------------------
*/
static int
TcpInput (instanceData, buf, bufSize, errorCodePtr)
ClientData instanceData; /* (in) Pointer to tcpState struct */
char *buf; /* (in/out) Buffer to fill */
int bufSize; /* (in) Size of buffer */
int *errorCodePtr; /* (out) POSIX error code (if any) */
{
TcpState *statePtr = (TcpState *)instanceData;
int result;
result = recv(statePtr->sock, buf, bufSize, 0);
if (result < 0) {
if (DppGetErrno() == ECONNRESET) {
/*
* Turn ECONNRESET into a soft EOF condition.
*/
return 0;
}
*errorCodePtr = Tcl_GetErrno();
return -1;
}
return result;
}
/*
*--------------------------------------------------------------
*
* TcpOutput --
*
* This function is called by the Tcl channel driver whenever the
* user wants to send output to the TCP socket. The function
* writes toWrite bytes from buf to the socket.
*
* Results:
* On success, returns a nonnegative integer indicating how many
* bytes were written to the socket. The return value is normally
* the same as toWrite, but may be less in some cases such as if
* the output operation is interrupted by a signal.
*
* On failure, returns DP_SOCKET_ERROR;
*
* Side effects:
* None
*
*--------------------------------------------------------------
*/
static int
TcpOutput (instanceData, buf, toWrite, errorCodePtr)
ClientData instanceData; /* (in) Pointer to tcpState struct */
char *buf; /* (in) Buffer to write */
int toWrite; /* (in) Number of bytes to write */
int *errorCodePtr; /* (out) POSIX error code (if any) */
{
TcpState *statePtr = (TcpState *)instanceData;
int result;
result = send(statePtr->sock, buf, toWrite, 0);
if (result < 0) {
result = DP_SOCKET_ERROR;
*errorCodePtr = DppGetErrno();
}
return result;
}
/*
*--------------------------------------------------------------
*
* TcpSetOption --
*
* This function is called by the Tcl channel driver
* whenever Tcl evaluates and fconfigure call to set
* some property of the tcp socket (e.g., the buffer
* size). The valid options are "sendBuffer" and
* "recvBuffer"
*
* Results:
* Standard Tcl return value.
*
* Side effects:
* Depends on the option. Generally changes the maximum
* message size that can be sent/received.
*
*--------------------------------------------------------------
*/
static int
TcpSetOption (instanceData, interp, optionName, optionValue)
ClientData instanceData;
Tcl_Interp *interp;
char *optionName;
char *optionValue;
{
int option;
int value;
TcpState *statePtr = (TcpState *)instanceData;
/*
* Set the option specified by optionName
*/
if (optionName[0] == '-') {
option = DpTranslateOption(optionName+1);
} else {
option = -1;
}
switch (option) {
case DP_KEEP_ALIVE:
case DP_REUSEADDR:
if (Tcl_GetBoolean(interp, optionValue, &value) != TCL_OK) {
return TCL_ERROR;
}
return DpTcpSetSocketOption(statePtr, option, value);
case DP_LINGER:
if (Tcl_GetInt(interp, optionValue, &value) != TCL_OK) {
return TCL_ERROR;
}
if (value <=0) {
Tcl_AppendResult(interp, "linger value must be > 0", NULL);
return TCL_ERROR;
}
return DpTcpSetSocketOption(statePtr, option, value);
case DP_RECV_BUFFER_SIZE:
case DP_SEND_BUFFER_SIZE:
if (Tcl_GetInt(interp, optionValue, &value) != TCL_OK) {
return TCL_ERROR;
}
if (value <=0) {
Tcl_AppendResult(interp, "Buffer size must be > 0", NULL);
return TCL_ERROR;
}
return DpTcpSetSocketOption(statePtr, option, value);
default:
Tcl_AppendResult (interp, "bad option \"", optionName,
"\": must be -keepalive, -linger, -recvbuffer, -reuseaddr, ",
"-sendBuffer or a standard fconfigure option", NULL);
return TCL_ERROR;
}
}
/*
*--------------------------------------------------------------
*
* TcpGetOption --
*
* This function is called by the Tcl channel code to
* retrieve a parameter of the socket (e.g., a buffer size).
* The valid options are "sendBuffer" and "recvBuffer"
*
* Results:
* A standard Tcl result
*
* Side effects:
* None
*
*--------------------------------------------------------------
*/
static int
TcpGetOption (instanceData,
optionName, dsPtr)
ClientData instanceData;
char *optionName;
Tcl_DString *dsPtr;
{
int option;
int value, rc;
char str[256];
TcpState *statePtr = (TcpState *)instanceData;
/*
* If optionName is NULL, then return all options in option-value
* pairs.
*/
if (optionName == NULL) {
Tcl_DStringAppend (dsPtr, " -keepAlive ", -1);
TcpGetOption(instanceData, "-keepAlive", dsPtr);
Tcl_DStringAppend (dsPtr, " -linger ", -1);
TcpGetOption(instanceData, "-linger", dsPtr);
Tcl_DStringAppend (dsPtr, " -recvBuffer ", -1);
TcpGetOption(instanceData, "-recvBuffer", dsPtr);
Tcl_DStringAppend (dsPtr, " -reuseAddr ", -1);
TcpGetOption(instanceData, "-reuseAddr", dsPtr);
Tcl_DStringAppend (dsPtr, " -sendBuffer ", -1);
TcpGetOption(instanceData, "-sendBuffer", dsPtr);
Tcl_DStringAppend (dsPtr, " -myIpAddr ", -1);
TcpGetOption(instanceData, "-myIpAddr", dsPtr);
Tcl_DStringAppend (dsPtr, " -myport ", -1);
TcpGetOption(instanceData, "-myport", dsPtr);
Tcl_DStringAppend (dsPtr, " -destIpAddr ", -1);
TcpGetOption(instanceData, "-destIpAddr", dsPtr);
Tcl_DStringAppend (dsPtr, " -destport ", -1);
TcpGetOption(instanceData, "-destport", dsPtr);
return TCL_OK;
}
/*
* Retrive the value of the option specified by optionName
*/
if (optionName[0] == '-') {
option = DpTranslateOption(optionName+1);
} else {
option = -1;
}
switch (option) {
case DP_LINGER:
case DP_RECV_BUFFER_SIZE:
case DP_SEND_BUFFER_SIZE:
case DP_MYPORT:
case DP_REMOTEPORT:
if (DpTcpGetSocketOption(statePtr, option, &value) != 0) {
return TCL_ERROR;
}
sprintf (str, "%d", value);
Tcl_DStringAppend(dsPtr, str, -1);
break;
case DP_KEEP_ALIVE:
case DP_REUSEADDR:
if (DpTcpGetSocketOption(statePtr, option, &value) != 0) {
return TCL_ERROR;
}
if (value) {
/*
* Some systems returns a non-zero value
* (not necessarily 1) to indicate "true".
*/
value = 1;
}
sprintf (str, "%d", value);
Tcl_DStringAppend(dsPtr, str, -1);
break;
case DP_MYIPADDR:
case DP_REMOTEIPADDR:
if (DpTcpGetSocketOption(statePtr, option, &value) != 0) {
return TCL_ERROR;
}
sprintf(str, "%d.%d.%d.%d", (value>>24)&0xFF, (value>>16)&0xFF,
(value>>8)&0xFF, value&0xFF);
Tcl_DStringAppend(dsPtr, str, -1);
break;
default:
{
char errStr[128];
sprintf(errStr, "bad option \"%s\": must be -blocking,"
" -buffering, -buffersize, -eofchar, -translation,"
" or a channel type specific option", optionName);
Tcl_DStringAppend(dsPtr, errStr, -1);
}
Tcl_SetErrno (EINVAL);
return TCL_ERROR;
}
return TCL_OK;
}
/*
*--------------------------------------------------------------
*
* TcpWatch --
*
* Gives a short overview (a few sentences), what other
* functions are related to this one.
*
* All changes to the module made that decrease resource usage,
* but make the function harder to understand, modify, and debug.
*
* Results:
* None.
*
* Side effects:
* Global variables touched.
* I/O operations performed.
* Delayed effects.
*
*--------------------------------------------------------------
*/
static void
TcpWatch (instanceData, mask)
ClientData instanceData;
int mask;
{
TcpState *infoPtr = (TcpState *) instanceData;
Tcl_WatchFile(infoPtr->sockFile, mask);
}
/*
*--------------------------------------------------------------
*
* TcpReady --
*
* Called by the notifier to check whether events of interest are
* present on the channel.
*
* Results:
* Returns OR-ed combination of TCL_READABLE, TCL_WRITABLE and
* TCL_EXCEPTION to indicate which events of interest are present.
*
* Side effects:
* None.
*
*--------------------------------------------------------------
*/
static int
TcpReady (instanceData, mask)
ClientData instanceData;
int mask;
{
TcpState *statePtr = (TcpState *) instanceData;
return Tcl_FileReady(statePtr->sockFile, mask);
}
/*
*--------------------------------------------------------------
*
* TcpGetFile --
*
* Called from Tcl_GetChannelFile to retrieve the handle
* from inside a TCP socket based channel.
*
* Results:
* TCL_OK
*
* Side effects:
* None.
*
*--------------------------------------------------------------
*/
static Tcl_File
TcpGetFile(instanceData, direction)
ClientData instanceData;
int direction;
{
TcpState *statePtr = (TcpState *)instanceData;
return statePtr->sockFile;
}
/*
*----------------------------------------------------------------------
*
* CreateClientChannel --
*
* This function opens a new socket in client mode
* and initializes the TcpState structure.
*
* Results:
* Returns a new TcpState, or NULL with an error in interp->result,
* if interp is not NULL.
*
* Side effects:
* Opens a socket.
*
*----------------------------------------------------------------------
*/
static Tcl_Channel
CreateClientChannel(interp, destIpAddr, destPort, myIpAddr, myPort, async)
Tcl_Interp *interp; /* For error reporting; can be NULL. */
int destIpAddr; /* IP address of the host to connect to. */
int destPort; /* Port number to open. */
int myIpAddr; /* Client-side address */
int myPort; /* Client-side port */
int async; /* If nonzero and creating a client socket,
* attempt to do an async connect. Otherwise
* do a synchronous connect or bind. */
{
int status, sock, asyncConnect, origState, len;
struct sockaddr_in destSockAddr; /* socket address for host */
struct sockaddr_in mySockAddr; /* Socket address for client */
TcpState *statePtr = NULL;
char channelName[20];
sock = -1;
origState = 0;
memset((char *)&destSockAddr, 0, sizeof(destSockAddr));
destSockAddr.sin_family = AF_INET;
destSockAddr.sin_port = htons((short)destPort);
destSockAddr.sin_addr.s_addr = htonl(destIpAddr);
sock = socket(AF_INET, SOCK_STREAM, 0);
if (sock < 0) {
goto error;
}
asyncConnect = 0;
status = 0;
memset((char *)&mySockAddr, 0, sizeof(mySockAddr));
mySockAddr.sin_family = AF_INET;
mySockAddr.sin_port = htons((unsigned short)myPort);
if (myIpAddr == DP_INADDR_ANY) {
mySockAddr.sin_addr.s_addr = INADDR_ANY;
} else {
mySockAddr.sin_addr.s_addr = htonl(myIpAddr);
}
status = bind(sock, (struct sockaddr *) &mySockAddr,
sizeof(mySockAddr));
if (status < 0) {
goto error;
}
/*
* Attempt to connect. The connect may fail at present with an
* EINPROGRESS but at a later time it will complete. The caller
* will set up a file handler on the socket if she is interested in
* being informed when the connect completes.
*/
if (async) {
status = DppSetBlock(sock, 0);
if (status < 0) {
goto error;
}
} else {
status = 0;
}
if (status > -1) {
status = connect(sock, (struct sockaddr *) &destSockAddr,
sizeof(destSockAddr));
if (status < 0) {
if (DppGetErrno() == ASYNC_CONNECT_ERROR) {
asyncConnect = 1;
status = 0;
} else {
goto error;
}
}
}
/*
* Allocate a new TcpState for this socket.
*/
statePtr = (TcpState *)ckalloc(sizeof(TcpState));
statePtr->flags = 0;
if (asyncConnect) {
statePtr->flags = ASYNC_CONNECT;
}
statePtr->sock = sock;
statePtr->sockFile = Tcl_GetFile((ClientData)sock, DP_SOCKET);
statePtr->interp = interp;
/*
* These variables are set whem they are needed.
* A call to fconfigure will prompt the call to
* SetAddress().
*/
statePtr->myIpAddr = 0;
statePtr->myPort = 0;
statePtr->destIpAddr = 0;
statePtr->destPort = 0;
sprintf(channelName, "tcp%d", tcpCount++);
statePtr->channel = Tcl_CreateChannel(&tcpChannelType, channelName,
(ClientData)statePtr, TCL_READABLE | TCL_WRITABLE);
return statePtr->channel;
error:
/*
* Call DppGetErrno() to translate the error into a POSIX code.
*/
DppGetErrno();
if (interp != NULL) {
Tcl_AppendResult(interp, "couldn't open socket: ",
Tcl_PosixError(interp), (char *) NULL);
}
if (statePtr != NULL) {
ckfree((char*)statePtr);
}
if (sock != -1) {
close(sock);
}
return NULL;
}
/*
*----------------------------------------------------------------------
*
* CreateServerChannel --
*
* This function opens a new socket in server mode and
* initializes the TcpState structure.
*
* Results:
* Returns a new TcpState, or NULL with an error in interp->result,
* if interp is not NULL.
*
* Side effects:
* Opens a socket.
*
*----------------------------------------------------------------------
*/
static Tcl_Channel
CreateServerChannel(interp, myIpAddr, myPort)
Tcl_Interp *interp; /* For error reporting; can be NULL. */
int myIpAddr; /* Address of the server. */
int myPort; /* Port number to listen to */
{
int status, sock, len;
struct sockaddr_in mySockAddr; /* socket address to use. */
TcpState *statePtr = NULL;
char channelName[20];
sock = socket(AF_INET, SOCK_STREAM, 0);
if (sock < 0) {
goto error;
}
memset((char *)&mySockAddr, 0, sizeof(mySockAddr));
mySockAddr.sin_family = AF_INET;
if (myIpAddr == DP_INADDR_ANY) {
mySockAddr.sin_addr.s_addr = INADDR_ANY;
} else {
mySockAddr.sin_addr.s_addr = htonl(myIpAddr);
}
mySockAddr.sin_port = htons((unsigned short)myPort);
status = bind(sock, (struct sockaddr *) &mySockAddr,
sizeof(mySockAddr));
if (status >= 0) {
status = listen(sock, DP_LISTEN_LIMIT);
}
if (status < 0) {
goto error;
}
/*
* Allocate a new TcpState for this server socket.
*/
statePtr = (TcpState *)ckalloc(sizeof(TcpState));
statePtr->flags = IS_SERVER;
statePtr->sock = sock;
statePtr->sockFile = Tcl_GetFile((ClientData)sock, DP_SOCKET);
statePtr->interp = interp;
/*
* These are set in DpSetAddress.
*/
statePtr->destIpAddr = 0;
statePtr->destPort = 0;
statePtr->myIpAddr = 0;
statePtr->myPort = 0;
sprintf(channelName, "tcp%d", tcpCount++);
statePtr->channel = Tcl_CreateChannel(&tcpChannelType, channelName,
(ClientData)statePtr, TCL_READABLE | TCL_WRITABLE);
return statePtr->channel;
error:
/*
* Call DppGetErrno() to translate the error into a POSIX code.
*/
DppGetErrno();
if (interp != NULL) {
Tcl_AppendResult(interp, "error opening socket: ",
Tcl_PosixError(interp), (char *) NULL);
}
if (statePtr != NULL) {
ckfree((char*)statePtr);
}
if (sock != -1) {
close(sock);
}
return NULL;
}
/*
*----------------------------------------------------------------------
*
* SetDefaultOptions --
*
* Sets the default options for a Tcp socket.
*
* Results:
* A standard Tcl result.
*
* Side effects:
* Some options of this socket are changed.
*
*----------------------------------------------------------------------
*/
static int
SetDefaultOptions(interp, chan)
Tcl_Interp *interp;
Tcl_Channel chan;
{
TcpState *statePtr = (TcpState *) Tcl_GetChannelInstanceData(chan);
if (DpTcpSetSocketOption(statePtr, DP_RECV_BUFFER_SIZE,
DP_TCP_RECVBUFSIZE) != TCL_OK) {
goto error;
}
if (DpTcpSetSocketOption(statePtr, DP_REUSEADDR, 1) != TCL_OK) {
goto error;
}
if (DpTcpSetSocketOption(statePtr, DP_KEEP_ALIVE, 0) != TCL_OK) {
goto error;
}
if (DpTcpSetSocketOption(statePtr, DP_SEND_BUFFER_SIZE,
DP_TCP_SENDBUFSIZE) != TCL_OK) {
goto error;
}
if (Tcl_SetChannelOption(interp, chan, "-translation", "binary") !=
TCL_OK) {
return TCL_ERROR;
}
if (Tcl_SetChannelOption(interp, chan, "-buffering", "none") !=
TCL_OK) {
return TCL_ERROR;
}
if (Tcl_SetChannelOption(interp, chan, "-eofchar", "") != TCL_OK) {
return TCL_ERROR;
}
return TCL_OK;
error:
Tcl_AppendResult(interp, "couldn't set default options for socket: ",
Tcl_PosixError(interp), (char *) NULL);
return TCL_ERROR;
}
/*
*--------------------------------------------------------------
*
* DpTcpSetSocketOption --
*
* Sets a socket option. Note that we hardcode a linger
* time of 30 seconds.
*
* Results:
* Zero if the operation was successful, or a nonzero POSIX
* error code if the operation failed.
*
* Side effects:
* None
*
*--------------------------------------------------------------
*/
static int
DpTcpSetSocketOption(statePtr, option, value)
TcpState *statePtr; /* (in) TcpState structure */
int option; /* (in) Option to set */
int value; /* (in) new value for option */
{
int sock, result;
struct linger l;
sock = statePtr->sock;
switch (option) {
case DP_BLOCK:
result = DppSetBlock(sock, value);
break;
case DP_KEEP_ALIVE:
result = setsockopt(sock, SOL_SOCKET, SO_KEEPALIVE, (char *)&value,
sizeof(value));
break;
case DP_LINGER:
l.l_onoff = value;
l.l_linger = 30;
result = setsockopt(sock, SOL_SOCKET, SO_LINGER, (char *)&l,
sizeof(struct linger));
break;
case DP_RECV_BUFFER_SIZE:
result = setsockopt(sock, SOL_SOCKET, SO_RCVBUF, (char *)&value,
sizeof(value));
break;
case DP_REUSEADDR:
result = setsockopt(sock, SOL_SOCKET, SO_REUSEADDR, (char *)&value,
sizeof(value));
break;
case DP_SEND_BUFFER_SIZE:
result = setsockopt(sock, SOL_SOCKET, SO_SNDBUF, (char *)&value,
sizeof(value));
break;
default:
return EINVAL;
}
if (result != 0) {
return DppGetErrno();
}
return 0;
}
/*
*--------------------------------------------------------------
*
* DpTcpGetSocketOption --
*
* Sets a socket option. The allowable options for Tcp
* sockets are
* DP_SEND_BUFFER_SIZE (int)
* DP_RECV_BUFFER_SIZE (int)
* Note that we can't determine whether a socket is blocking,
* so DP_BLOCK is not allowed.
*
* Results:
* Zero if the operation was successful, or a nonzero POSIX
* error code if the operation failed.
*
* Side effects:
* None
*
*--------------------------------------------------------------
*/
static int
DpTcpGetSocketOption (statePtr, option, valuePtr)
TcpState *statePtr; /* (in) TcpState structure */
int option; /* (in) Option to set */
int *valuePtr; /* (out) current value of option */
{
int sock, result, len, len1;
struct linger l;
*valuePtr = 0;
sock = statePtr->sock;
len = sizeof(int);
len1 = 1;
if (statePtr->myPort == 0) {
result = DpSetAddress(statePtr);
if (result != TCL_OK) {
return DppGetErrno();
}
}
switch (option) {
case DP_KEEP_ALIVE:
result = getsockopt(sock, SOL_SOCKET, SO_KEEPALIVE, (char *)valuePtr,
&len);
break;
case DP_LINGER:
len = sizeof(struct linger);
result = getsockopt(sock, SOL_SOCKET, SO_LINGER, (char *)&l, &len);
*valuePtr = l.l_onoff;
break;
case DP_RECV_BUFFER_SIZE:
result = getsockopt(sock, SOL_SOCKET, SO_RCVBUF, (char *)valuePtr,
&len);
break;
case DP_REUSEADDR:
result = getsockopt(sock, SOL_SOCKET, SO_REUSEADDR, (char *)valuePtr,
&len);
break;
case DP_SEND_BUFFER_SIZE:
result = getsockopt(sock, SOL_SOCKET, SO_SNDBUF, (char *)valuePtr,
&len);
break;
case DP_MYPORT:
*valuePtr = statePtr->myPort;
return 0;
case DP_REMOTEPORT:
*valuePtr = statePtr->destPort;
return 0;
case DP_MYIPADDR:
*valuePtr = statePtr->myIpAddr;
return 0;
case DP_REMOTEIPADDR:
*valuePtr = statePtr->destIpAddr;
return 0;
default:
return EINVAL;
}
if (result != 0) {
return DppGetErrno();
}
return 0;
}
/*
*--------------------------------------------------------------
*
* DpSetAddress --
*
* Sets the local and remote address info of a socket.
*
* Results:
* TCL_OK or TCL_ERROR.
*
* Side effects:
* None
*
*--------------------------------------------------------------
*/
#ifndef MAXHOSTNAMELEN
#define MAXHOSTNAMELEN 64
#endif
static int
DpSetAddress(statePtr)
TcpState *statePtr;
{
struct sockaddr_in destSockAddr;
struct sockaddr_in mySockAddr;
struct hostent *h;
int len;
int *ipAddrPtr;
char host[MAXHOSTNAMELEN];
/*
* Set the destination port and addr only if
* we are not a server socket. They are set to
* zero in CreateServerChannel otherwise.
*/
len = sizeof(struct sockaddr_in);
if (!(statePtr->flags & IS_SERVER)) {
if (getpeername(statePtr->sock, (struct sockaddr *)&destSockAddr,
&len) != 0) {
return TCL_ERROR;
}
statePtr->destIpAddr = ntohl(destSockAddr.sin_addr.s_addr);
statePtr->destPort = ntohs((unsigned short)destSockAddr.sin_port);
}
/*
* Set the local port and IP address
*/
len = sizeof(struct sockaddr_in);
if (getsockname(statePtr->sock, (struct sockaddr *)&mySockAddr,
&len) != 0) {
return TCL_ERROR;
}
if (gethostname(host, sizeof(host)) != 0) {
return TCL_ERROR;
}
h = gethostbyname(host);
if (h == NULL) {
return TCL_ERROR;
}
ipAddrPtr = &(statePtr->myIpAddr);
memcpy ((char *)ipAddrPtr, (char *) h->h_addr_list[0],
(size_t) h->h_length);
*ipAddrPtr = ntohl(*ipAddrPtr);
statePtr->myPort = ntohs((unsigned short)mySockAddr.sin_port);
return TCL_OK;
}