/* HTTCP.c ** TCP SPECIFIC CODE ** ** (c) COPYRIGHT MIT 1995. ** Please first read the full copyright statement in the file COPYRIGH. ** @(#) $Id: HTTCP.c,v 2.108 1998/05/04 19:37:25 frystyk Exp $ ** ** This code is in common between client and server sides. ** ** 16 Jan 92 TBL Fix strtol() undefined on CMU Mach. ** 25 Jun 92 JFG Added DECNET option through TCP socket emulation. ** 13 Sep 93 MD Added correct return of vmserrorno for HTInetStatus. ** Added decoding of vms error message for MULTINET. ** 31 May 94 HF Added cache on host id's; now use inet_ntoa() to ** HTInetString and some other fixes. Added HTDoConnect ** and HTDoAccept */ /* Library include files */ #include "wwwsys.h" #include "WWWUtil.h" #include "WWWCore.h" #include "HTReqMan.h" #include "HTNetMan.h" #include "HTTCP.h" /* Implemented here */ #include "HTHstMan.h" /* VMS stuff */ #ifdef VMS #ifndef MULTINET #define FD_SETSIZE 32 #else /* Multinet */ #define FD_SETSIZE 256 #endif /* Multinet */ #endif /* VMS */ /* Macros and other defines */ /* x seconds penalty on a multi-homed host if IP-address is down */ #define TCP_PENALTY 1200 /* x seconds penalty on a multi-homed host if IP-address is timed out */ #define TCP_DELAY 600 /* imperical study in socket call error codes */ #ifdef _WINSOCKAPI_ /* windows */ #define NETCALL_ERROR(ret) (ret == SOCKET_ERROR) #define NETCALL_DEADSOCKET(err) (err == WSAEBADF) #define NETCALL_WOULDBLOCK(err) (err == WSAEWOULDBLOCK) #else /* _WINSOCKAPI_ unix */ #define NETCALL_ERROR(ret) (ret < 0) #define NETCALL_DEADSOCKET(err) (err == EBADF) #if defined(EAGAIN) && defined(EALREADY) #define NETCALL_WOULDBLOCK(err) (err == EINPROGRESS || \ err == EALREADY || \ err == EAGAIN) #else /* (EAGAIN && EALREADY) */ #ifdef EALREADY #define NETCALL_WOULDBLOCK(err) (err == EINPROGRESS || err == EALREADY) #else /* EALREADY */ #ifdef EAGAIN #define NETCALL_WOULDBLOCK(err) (err == EINPROGRESS || err == EAGAIN) #else /* EAGAIN */ #define NETCALL_WOULDBLOCK(err) (err == EINPROGRESS) #endif /* !EAGAIN */ #endif /* !EALREADY */ #endif /* !(EAGAIN && EALREADY) */ #endif /* !_WINSOCKAPI_ done */ /* ------------------------------------------------------------------------- */ /* CONNECTION ESTABLISHMENT MANAGEMENT */ /* ------------------------------------------------------------------------- */ /* _makeSocket - create a socket, if !preemptive, set FIONBIO * returns 1: blocking * 0: non-blocking * -1: creation error */ PRIVATE int _makeSocket(HTHost * host, HTRequest * request, int preemptive, HTTransport * transport) { int status = 1; SOCKET sockfd; #ifdef DECNET if ((sockfd=socket(AF_DECnet, SOCK_STREAM, 0))==INVSOC) #else if ((sockfd=socket(AF_INET, SOCK_STREAM,IPPROTO_TCP))==INVSOC) #endif { HTRequest_addSystemError(request, ERR_FATAL, socerrno, NO, "socket"); host->tcpstate = TCP_ERROR; return -1; } if (PROT_TRACE) HTTrace("Socket...... Created %d\n", sockfd); /* Increase the number of sockets by one */ HTNet_increaseSocket(); /* ** If we have compiled without Nagle's algorithm then try and turn ** it off now */ #if defined(HT_NO_NAGLE) && defined(HAVE_SETSOCKOPT) && defined(TCP_NODELAY) { int disable = 1; status = setsockopt(sockfd, IPPROTO_TCP, TCP_NODELAY, (char *) &disable, sizeof(int)); if (status == -1) { if (PROT_TRACE) HTTrace("Socket...... Could not disable Nagle's algorithm - error %d\n", sockfd); } else { if (PROT_TRACE) HTTrace("Socket...... Turned off Nagle's algorithm\n"); } } #endif /* If non-blocking protocol then change socket status ** I use fcntl() so that I can ask the status before I set it. ** See W. Richard Stevens (Advan. Prog. in UNIX environment, p.364) ** Be CAREFULL with the old `O_NDELAY' - it will not work as read() ** returns 0 when blocking and NOT -1. FNDELAY is ONLY for BSD and ** does NOT work on SVR4 systems. O_NONBLOCK is POSIX. */ if (!preemptive) { #ifdef _WINSOCKAPI_ { /* begin windows scope */ long levents = FD_READ | FD_WRITE | FD_ACCEPT | FD_CONNECT | FD_CLOSE ; int rv = 0 ; u_long one = 1; status = ioctlsocket(sockfd, FIONBIO, &one) == SOCKET_ERROR ? -1 : 0; } /* end scope */ #else /* _WINSOCKAPI_ */ #if defined(VMS) { int enable = 1; status = IOCTL(sockfd, FIONBIO, &enable); } #else /* VMS */ if((status = fcntl(sockfd, F_GETFL, 0)) != -1) { #ifdef O_NONBLOCK status |= O_NONBLOCK; /* POSIX */ #else /* O_NONBLOCK */ #ifdef F_NDELAY status |= F_NDELAY; /* BSD */ #endif /* F_NDELAY */ #endif /* !O_NONBLOCK */ status = fcntl(sockfd, F_SETFL, status); } #endif /* !VMS */ #endif /* !_WINSOCKAPI_ */ if (PROT_TRACE) HTTrace("Socket...... %slocking socket\n", status == -1 ? "B" : "Non-b"); } else if (PROT_TRACE) HTTrace("Socket...... Blocking socket\n"); /* ** Associate the channel with the host and create an input and and output stream ** for this host/channel */ HTHost_setChannel(host, HTChannel_new(sockfd, NULL, YES)); HTHost_getInput(host, transport, NULL, 0); HTHost_getOutput(host, transport, NULL, 0); return status == -1 ? 1 : 0; } /* HTDoConnect() ** ** Note: Any port indication in URL, e.g., as `host:port' overwrites ** the default port value. ** ** returns HT_ERROR Error has occured or interrupted ** HT_OK if connected ** HT_WOULD_BLOCK if operation would have blocked */ PUBLIC int HTDoConnect (HTNet * net, char * url, u_short default_port) { HTHost * me = HTNet_host(net); HTRequest * request = HTNet_request(net); int preemptive = net->preemptive; int status = HT_OK; char * hostname = HTHost_name(me); /* Jump into the state machine */ while (1) { switch (me->tcpstate) { case TCP_BEGIN: { /* ** Add the net object to the host object found above. If the ** host is idle then we can start the request right away, ** otherwise we must wait until it is free. */ if ((status = HTHost_addNet(net->host, net)) == HT_PENDING) if (PROT_TRACE) HTTrace("HTDoConnect. Pending...\n"); /* ** If we are pending then return here, otherwise go to next state ** which is setting up a channel */ me->tcpstate = TCP_CHANNEL; if (PROT_TRACE) HTTrace("HTHost %p going to state TCP_CHANNEL.\n", me); if (status == HT_PENDING) return HT_PENDING; } break; case TCP_CHANNEL: /* ** The next state depends on whether we have a connection ** or not - if so then we can jump directly to connect() to ** test it - otherwise we must around DNS to get the name ** Resolved */ if (HTHost_channel(me) == NULL) { me->tcpstate = TCP_DNS; if (PROT_TRACE) HTTrace("HTHost %p going to state TCP_DNS.\n", me); } else { /* ** There is now one more using the channel */ HTChannel_upSemaphore(me->channel); /* ** We are now all set and can jump to connected mode */ me->tcpstate = TCP_CONNECTED; if (PROT_TRACE) HTTrace("HTHost %p going to state TCP_CONNECTED.\n", me); } hostname = HTHost_name(me); break; case TCP_DNS: if ((status = HTParseInet(me, hostname, request)) < 0) { if (PROT_TRACE) HTTrace("HTDoConnect. Can't locate `%s\'\n", hostname); HTRequest_addError(request, ERR_FATAL, NO, HTERR_NO_REMOTE_HOST, (void *) hostname, strlen(hostname), "HTDoConnect"); me->tcpstate = TCP_ERROR; if (PROT_TRACE) HTTrace("HTHost %p going to state TCP_ERROR.\n", me); break; } if (!HTHost_retry(me) && status > 1) /* If multiple homes */ HTHost_setRetry(me, status); me->tcpstate = TCP_NEED_SOCKET; if (PROT_TRACE) HTTrace("HTHost %p going to state TCP_NEED_SOCKET.\n", me); break; case TCP_NEED_SOCKET: if (_makeSocket(me, request, preemptive, net->transport) == -1) break; /* If multi-homed host then start timer on connection */ if (HTHost_retry(me)) me->connecttime = HTGetTimeInMillis(); /* Progress */ { HTAlertCallback *cbf = HTAlert_find(HT_PROG_CONNECT); if (cbf) (*cbf)(request, HT_PROG_CONNECT, HT_MSG_NULL, NULL, hostname, NULL); } me->tcpstate = TCP_NEED_CONNECT; if (PROT_TRACE) HTTrace("HTHost %p going to state TCP_NEED_CONNECT.\n", me); break; case TCP_NEED_CONNECT: status = connect(HTChannel_socket(me->channel), (struct sockaddr *) &me->sock_addr, sizeof(me->sock_addr)); /* * According to the Sun man page for connect: * EINPROGRESS The socket is non-blocking and the con- * nection cannot be completed immediately. * It is possible to select(2) for comple- * tion by selecting the socket for writ- * ing. * According to the Motorola SVR4 man page for connect: * EAGAIN The socket is non-blocking and the con- * nection cannot be completed immediately. * It is possible to select for completion * by selecting the socket for writing. * However, this is only possible if the * socket STREAMS module is the topmost * module on the protocol stack with a * write service procedure. This will be * the normal case. */ if (NETCALL_ERROR(status)) { if (NETCALL_WOULDBLOCK(socerrno)) { if (PROT_TRACE) HTTrace("HTDoConnect. WOULD BLOCK `%s'\n", hostname); HTHost_register(me, net, HTEvent_CONNECT); return HT_WOULD_BLOCK; } if (socerrno == EISCONN) { me->tcpstate = TCP_CONNECTED; if (PROT_TRACE) HTTrace("HTHost %p going to state TCP_CONNECTED.\n", me); break; } if (NETCALL_DEADSOCKET(socerrno)) /* We lost the socket */ { me->tcpstate = TCP_NEED_SOCKET; if (PROT_TRACE) HTTrace("HTHost %p going to state TCP_NEED_SOCKET.\n", me); break; } if (HTHost_retry(me)) { me->connecttime -= HTGetTimeInMillis(); /* Added EINVAL `invalid argument' as this is what I get back from a non-blocking connect where I should get `connection refused' on BSD. SVR4 gives SIG_PIPE */ #if defined(__svr4__) || defined (_WINSOCKAPI_) if (socerrno==ECONNREFUSED || socerrno==ETIMEDOUT || socerrno==ENETUNREACH || socerrno==EHOSTUNREACH || socerrno==EHOSTDOWN) #else if (socerrno==ECONNREFUSED || socerrno==ETIMEDOUT || socerrno==ENETUNREACH || socerrno==EHOSTUNREACH || socerrno==EHOSTDOWN || socerrno==EINVAL) #endif me->connecttime += TCP_DELAY; else me->connecttime += TCP_PENALTY; HTDNS_updateWeigths(me->dns, HTHost_home(me), me->connecttime); } me->tcpstate = TCP_ERROR; if (PROT_TRACE) HTTrace("HTHost %p going to state TCP_ERROR.\n", me); } else { me->tcpstate = TCP_CONNECTED; if (PROT_TRACE) HTTrace("HTHost %p going to state TCP_CONNECTED.\n", me); } break; case TCP_CONNECTED: HTHost_unregister(me, net, HTEvent_CONNECT); if (HTHost_retry(me)) { me->connecttime -= HTGetTimeInMillis(); HTDNS_updateWeigths(me->dns, HTHost_home(me), me->connecttime); } HTHost_setRetry(me, 0); me->tcpstate = TCP_IN_USE; if (PROT_TRACE) HTTrace("HTHost %p connected.\n", me); return HT_OK; break; /* once a host is connected, subsequent connections are immediately OK */ case TCP_IN_USE: if ((status = HTHost_addNet(net->host, net)) == HT_PENDING) { if (PROT_TRACE) HTTrace("HTDoConnect. Pending...\n"); return HT_PENDING; } HTChannel_upSemaphore(me->channel); return HT_OK; case TCP_NEED_BIND: case TCP_NEED_LISTEN: case TCP_ERROR: HTTrace("HTDoConnect. Connect failed %d\n", socerrno); if (HTChannel_socket(me->channel) != INVSOC) { /* HTEvent_unregister(HTChannel_socket(me->channel), (SockOps) FD_ALL); */ NETCLOSE(HTChannel_socket(me->channel)); /* HTChannel_setSocket(me->channel, INVSOC); */ #if 1 /* @@@ */ if (HTHost_isPersistent(me)) { /* Inherited socket */ HTHost_setPersistent(me, NO, HT_TP_SINGLE); me->tcpstate = TCP_NEED_SOCKET; if (PROT_TRACE) HTTrace("HTHost %p going to state TCP_NEED_SOCKET.\n", me); break; } #endif } /* Do we have more homes to try? */ HTHost_decreaseRetry(me); if (HTHost_retry(me) > 0) { HTRequest_addSystemError(request, ERR_NON_FATAL, socerrno, NO, "connect"); me->tcpstate = TCP_DNS; if (PROT_TRACE) HTTrace("HTHost %p going to state TCP_DNS.\n", me); break; } HTRequest_addSystemError(request, ERR_FATAL,socerrno,NO,"connect"); HTDNS_delete(hostname); HTHost_setRetry(me, 0); me->tcpstate = TCP_BEGIN; if (PROT_TRACE) HTTrace("HTHost %p going to state TCP_BEGIN.\n", me); return HT_ERROR; break; } } } /* HTDoAccept() ** ------------ ** This function makes a non-blocking accept which will turn up as ready ** read in the select. ** Returns ** HT_ERROR Error has occured or interrupted ** HT_OK if connected ** HT_WOULD_BLOCK if operation would have blocked */ PUBLIC int HTDoAccept (HTNet * net, HTNet ** accepted) { int status; int size = sizeof(net->host->sock_addr); HTRequest * request = HTNet_request(net); if (!request || HTNet_socket(net)==INVSOC) { if (PROT_TRACE) HTTrace("HTDoAccept.. Invalid socket\n"); return HT_ERROR; } /* Progress report */ { HTAlertCallback *cbf = HTAlert_find(HT_PROG_ACCEPT); if (cbf) (*cbf)(request, HT_PROG_ACCEPT, HT_MSG_NULL,NULL, NULL, NULL); } status = accept(HTNet_socket(net), (struct sockaddr *) &net->host->sock_addr, &size); if (NETCALL_ERROR(status)) { if (NETCALL_WOULDBLOCK(socerrno)) { if (PROT_TRACE) HTTrace("HTDoAccept.. WOULD BLOCK %d\n", HTNet_socket(net)); HTEvent_register(HTNet_socket(net), HTEvent_ACCEPT, &net->event); return HT_WOULD_BLOCK; } HTRequest_addSystemError(request, ERR_WARN, socerrno, YES, "accept"); if (PROT_TRACE) HTTrace("HTDoAccept.. Accept failed\n"); return HT_ERROR; } if (PROT_TRACE) HTTrace("Accepted.... socket %d\n", status); /* ** If accepted is the same as the net obejct then reuse it, else create ** a new object and leave the original alone */ if (*accepted == net) HTDoClose(net); else *accepted = HTNet_dup(net); HTNet_setSocket(*accepted, status); /* Create a channel for the new socket */ { HTHost * host = (*accepted)->host; HTChannel * ch = HTChannel_new(HTNet_socket(*accepted), NULL, NO); HTHost_setChannel(host, ch); } return HT_OK; } /* HTDoListen ** ---------- ** Listens on the specified port. 0 means that we chose it here ** If master==INVSOC then we listen on all local interfaces (using a ** wildcard). If !INVSOC then use this as the local interface ** returns HT_ERROR Error has occured or interrupted ** HT_OK if connected */ PUBLIC int HTDoListen (HTNet * net, u_short port, SOCKET master, int backlog) { int status; /* Jump into the state machine */ while (1) { switch (net->host->tcpstate) { case TCP_BEGIN: { SockA *sin = &net->host->sock_addr; memset((void *) sin, '\0', sizeof(SockA)); #ifdef DECNET sin->sdn_family = AF_DECnet; sin->sdn_objnum = port; #else sin->sin_family = AF_INET; if (master != INVSOC) { int len = sizeof(SockA); if (getsockname(master, (struct sockaddr *) sin, &len)<0) { HTRequest_addSystemError(net->request, ERR_FATAL, socerrno, NO, "getsockname"); net->host->tcpstate = TCP_ERROR; break; } } else sin->sin_addr.s_addr = INADDR_ANY; sin->sin_port = htons(port); #endif } if (PROT_TRACE) HTTrace("Socket...... Listen on port %d\n", port); net->host->tcpstate = TCP_NEED_SOCKET; break; case TCP_NEED_SOCKET: if (_makeSocket(net->host, net->request, net->preemptive, net->transport) == -1) break; net->host->tcpstate = TCP_NEED_BIND; break; case TCP_NEED_BIND: status = bind(HTNet_socket(net), (struct sockaddr *) &net->host->sock_addr, sizeof(net->host->sock_addr)); if (NETCALL_ERROR(status)) { if (PROT_TRACE) HTTrace("Socket...... Bind failed %d\n", socerrno); net->host->tcpstate = TCP_ERROR; } else net->host->tcpstate = TCP_NEED_LISTEN; break; case TCP_NEED_LISTEN: status = listen(HTNet_socket(net), backlog); if (NETCALL_ERROR(status)) net->host->tcpstate = TCP_ERROR; else net->host->tcpstate = TCP_CONNECTED; break; case TCP_CONNECTED: net->host->tcpstate = TCP_BEGIN; if (PROT_TRACE) HTTrace("Socket...... Bind and listen on port %d %s\n", (int) ntohs(net->host->sock_addr.sin_port), HTInetString(&net->host->sock_addr)); return HT_OK; break; case TCP_CHANNEL: case TCP_NEED_CONNECT: case TCP_DNS: case TCP_ERROR: if (PROT_TRACE) HTTrace("Socket...... Listen failed\n"); HTRequest_addSystemError(net->request, ERR_FATAL, socerrno, NO, "HTDoListen"); net->host->tcpstate = TCP_BEGIN; return HT_ERROR; break; } } } /* HTDoClose ** --------- ** Closes a file descriptor whatever means are available on the current ** platform. If we have unix file descriptors then use this otherwise use ** the ANSI C file descriptors ** ** returns HT_ERROR Error has occured or interrupted ** HT_OK if connected ** HT_WOULD_BLOCK if operation would have blocked */ PUBLIC int HTDoClose (HTNet * net) { int status = -1; if (net && HTNet_socket(net) != INVSOC) { if (PROT_TRACE) HTTrace("HTDoClose... Close %d\n", HTNet_socket(net)); status = NETCLOSE(HTNet_socket(net)); /* HTEvent_unregister(HTNet_socket(net), (SockOps) FD_ALL); */ HTNet_decreaseSocket(); HTNet_setSocket(net, INVSOC); /* ** As we have a socket available we check for whether ** we can start any pending requests. We do this by asking for ** pending Host objects. If none then use the current object */ HTHost_launchPending(net->host); } else if (PROT_TRACE) HTTrace("HTDoClose... No pending requests\n"); return status < 0 ? HT_ERROR : HT_OK; }