/*****************************************************************************/ /* ProxyNet.c ************* ** CAUTION ** ************* THIS MODULE IS TASK-ORIENTED, NOT REQUEST-ORIENTED. That is, most of the functions take a pointer to proxy task rather than a pointer to request as do other modules. The reason is simple. Many of these functions are designed for use independently of a request. This module provides the essential networking functionality for proxy processing. It also maintains the pool of persistent proxy->origin server connections. VERSION HISTORY --------------- 19-JAN-2020 MGD more proxy persistent connection (per JPP) 11-AUG-2015 MGD restructure of network I/O abstractions 09-APR-2011 MGD ProxyNetLocalPort() for access logging purposes 08-NOV-2006 JPP bugfix; ProxyNetConnectPersist() rejects all further requests once ProxyConnectPersistMax has been hit 04-JUL-2006 MGD use PercentOf() for more accurate percentages 21-APR-2006 JPP client to origin server affinity 20-JUL-2005 JPP proxy to origin server failover (support multiple IP addresses per name in the host cache) 09-JUN-2005 MGD bugfix; ProxyEnd(rqptr) should be ProxyEnd(tkptr) in ProxyNetHostConnectAst() (jpp@esme.fr) 20-SEP-2004 MGD extracted functionality from PROXY.C */ /*****************************************************************************/ #ifdef WASD_VMS_V7 #undef _VMS__V6__SOURCE #define _VMS__V6__SOURCE #undef __VMS_VER #define __VMS_VER 70000000 #undef __CRTL_VER #define __CRTL_VER 70000000 #endif /* standard C header files */ #include #include #include #include /* VMS related header files */ #include #include #include #include #include #include /* application-related header files */ #include "wasd.h" #define WASD_MODULE "PROXYNET" /******************/ /* global storage */ /******************/ int ProxyNetConnectCount, ProxyNetConnectCountMax, ProxyNetConnectFreeCount; unsigned long ProxyNetConnectTimeoutDelta [2]; LIST_HEAD ProxyNetConnectList, ProxyNetConnectFreeList; /********************/ /* external storage */ /********************/ extern BOOL LoggingProxyLocalPort; extern int EfnWait, EfnNoWait, HttpdTickSecond, OptionEnabled, ProxyConnectPersistMax, ProxyConnectPersistSeconds, ProxyConnectTimeoutSeconds; extern int ToLowerCase[], ToUpperCase[]; extern unsigned long HttpdTime64[]; extern char ErrorSanityCheck[], SoftwareID[]; extern struct dsc$descriptor TcpIpDeviceDsc; extern ACCOUNTING_STRUCT *AccountingPtr; extern IPADDRESS TcpIpEmptyAddress; extern PROXY_ACCOUNTING_STRUCT *ProxyAccountingPtr; extern MSG_STRUCT Msgs; extern TCP_SOCKET_ITEM TcpIpSocket4, TcpIpSocket6; extern VMS_ITEM_LIST2 ReuseAddress, ReuseAddressSocketOption, TcpIpFullDuplexCloseOption; extern WATCH_STRUCT Watch; /****************************************************************************/ /* */ ProxyNetInit () { int status; /*********/ /* begin */ /*********/ if (WATCH_MODULE(WATCH_MOD_PROXY)) WatchThis (WATCHALL, WATCH_MOD_PROXY, "ProxyNetInit()"); if (ProxyConnectPersistMax < 0 || ProxyConnectPersistMax > 1000) ProxyConnectPersistMax = 0; if (ProxyConnectPersistSeconds < 0 || ProxyConnectPersistSeconds > 3600) ProxyConnectPersistSeconds = 0; if (ProxyConnectTimeoutSeconds < 0 || ProxyConnectTimeoutSeconds > 60) ProxyConnectTimeoutSeconds = 0; else if (ProxyConnectTimeoutSeconds) { /* intialize to one second delta then multiply by number of seconds */ ProxyNetConnectTimeoutDelta[0] = -10000000; ProxyNetConnectTimeoutDelta[1] = -1; status = lib$mult_delta_time (&ProxyConnectTimeoutSeconds, &ProxyNetConnectTimeoutDelta); if (VMSnok (status)) ErrorExitVmsStatus (status, NULL, FI_LI); } ProxyNetConnectSupervisor (0); } /****************************************************************************/ /* No need to resolve if this proxy server chains to another. Otherwise, allow TcpIpNameToAddress() to asynchronously resolve the name or numeric address into the IP address and then call ProxyNetHostConnect() when ready. */ ProxyNetResolveHost (PROXY_TASK *tkptr) { /*********/ /* begin */ /*********/ if (WATCHMOD (tkptr, WATCH_MOD_PROXY)) WatchThis (WATCHITM(tkptr), WATCH_MOD_PROXY, "ProxyNetResolveHost() !&Z", tkptr->RequestHostName); if (IPADDRESS_IS_RESET (&tkptr->ChainIpAddress)) { tkptr->ConnectPort = tkptr->RequestPort; tkptr->ConnectHostPortPtr = tkptr->RequestHostPort; if ((tkptr->ServicePtr->ProxyAffinity || tkptr->RequestPtr->rqPathSet.ProxyAffinity) && tkptr->RequestPtr->rqHeader.CookiePtr) ProxyNetCheckAffinityCookie (tkptr); else TcpIpNameToAddress (&tkptr->HostLookup, tkptr->RequestHostName, tkptr->ProxyLookupRetryCount, &ProxyNetHostConnect, tkptr); return; } /*********************************************************/ /* this proxy server requests from another proxy server! */ /*********************************************************/ if (WATCHING (tkptr, WATCH_PROXY)) WatchThis (WATCHITM(tkptr), WATCH_PROXY, "HOST-PROXY-CHAIN !AZ", tkptr->ChainHostPortPtr); tkptr->ConnectPort = tkptr->ChainIpPort; tkptr->ConnectHostPortPtr = tkptr->ChainHostPortPtr; tkptr->HostLookup.LookupIOsb.Status = SS$_NORMAL; ProxyNetHostConnect (tkptr); } /****************************************************************************/ /* Resolve host using TCP/IP lookup unless cookie indicates a preferred IP address. Remove the affinity cookie from the cookie field. If that is the only cookie data then remove the cookie field entirely from the request. */ ProxyNetCheckAffinityCookie (PROXY_TASK *tkptr) { int status, RequestHostNameLength; char ch; char *cptr, *sptr, *zptr; REQUEST_STRUCT *rqptr; /*********/ /* begin */ /*********/ if (WATCH_MODULE(WATCH_MOD_PROXY)) WatchThis (WATCHALL, WATCH_MOD_PROXY, "ProxyNetCheckAffinityCookie()"); rqptr = tkptr->RequestPtr; RequestHostNameLength = strlen(tkptr->RequestHostName); cptr = rqptr->rqHeader.CookiePtr; while (*cptr) { while (*cptr && *cptr != PROXY_AFFINITY_COOKIE_PREFIX[0]) cptr++; if (!*cptr) break; if (!MATCH0 (cptr, PROXY_AFFINITY_COOKIE_PREFIX, sizeof(PROXY_AFFINITY_COOKIE_PREFIX)-1)) { cptr++; continue; } /* note start of affinity cookie */ sptr = cptr; cptr += sizeof(PROXY_AFFINITY_COOKIE_PREFIX)-1; if (MATCH0 (cptr, tkptr->RequestHostName, RequestHostNameLength)) { cptr += RequestHostNameLength; if (*cptr == '=') { for (zptr = ++cptr; *zptr && *zptr != ';'; zptr++); ch = *zptr; *zptr = '\0'; if (WATCHING (tkptr, WATCH_PROXY)) WatchThis (WATCHITM(tkptr), WATCH_PROXY, "AFFINITY with !AZ", cptr); tkptr->HostLookup.LookupIOsb.Status = TcpIpStringToAddress (cptr, &tkptr->HostLookup.IpAddress); *zptr = ch; if (*zptr) zptr++; while (*zptr && ISLWS(*zptr)) zptr++; /* remove the affinity cookie */ while (*zptr) *sptr++ = *zptr++; *sptr = '\0'; /* ensure it's not now an empty field */ for (cptr = rqptr->rqHeader.CookiePtr; *cptr && ISLWS(*cptr); cptr++); if (!*cptr) { /* just an empty cookie jar! */ rqptr->rqHeader.CookiePtr = NULL; DictRemove (rqptr->rqDictPtr, DICT_TYPE_REQUEST, "cookie", 6); } /* try to validate IP address from cache */ /* (prevents forged cookies that would allow open proxy) */ status = TcpIpCacheAddresstoName (&tkptr->HostLookup, &tkptr->HostLookup.IpAddress); if (VMSok(status)) { if (!strcmp (tkptr->HostLookup.HostName, tkptr->RequestHostName)) { /* affinity cookie match */ tkptr->ProxyAffinityInUse = true; ProxyNetHostConnect(tkptr); return; } else if (WATCHING (tkptr, WATCH_PROXY)) WatchThis (WATCHITM(tkptr), WATCH_PROXY, "AFFINITY hint not correct"); } else { /* not found in cache, try loading it only once */ if (WATCHING (tkptr, WATCH_PROXY)) WatchThis (WATCHITM(tkptr), WATCH_PROXY, "AFFINITY hint not in cache"); if (!tkptr->ProxyAffinityInUse) { tkptr->ProxyAffinityInUse = true; TcpIpNameToAddress (&tkptr->HostLookup, tkptr->RequestHostName, tkptr->ProxyLookupRetryCount, &ProxyNetResolveHost, tkptr); return; } } } if (*cptr) cptr++; } } /* no applicable affinity cookie present */ TcpIpNameToAddress (&tkptr->HostLookup, tkptr->RequestHostName, tkptr->ProxyLookupRetryCount, &ProxyNetHostConnect, tkptr); } /****************************************************************************/ /* Called as an AST by TcpIpNameToAddress(). Check that the host name has been resolved. If not report the error. Create a socket and attempt to connect to the remote, proxied server host. AST to ProxyNetHostConnectAst(). */ ProxyNetHostConnect (PROXY_TASK *tkptr) { static BOOL UseFullDuplexClose = true; int status; unsigned long *gwcptr; void *BindSocketNamePtr; IO_SB IOsb; REQUEST_STRUCT *rqptr; SOCKADDRIN *sin4ptr; SOCKADDRIN6 *sin6ptr; TCP_SOCKET_ITEM *TcpSocketPtr; VMS_ITEM_LIST2 *il2ptr; /*********/ /* begin */ /*********/ if (WATCHMOD (tkptr, WATCH_MOD_PROXY)) WatchThis (WATCHITM(tkptr), WATCH_MOD_PROXY, "ProxyNetHostConnect() !&Z !&S", tkptr->ConnectHostPortPtr, tkptr->HostLookup.LookupIOsb.Status); rqptr = tkptr->RequestPtr; if (VMSnok (tkptr->HostLookup.LookupIOsb.Status)) { /*********************************/ /* host address resolution error */ /*********************************/ if (WATCHING (tkptr, WATCH_PROXY)) WatchThis (WATCHITM(tkptr), WATCH_PROXY, "HOST-LOOKUP !AZ !&S", tkptr->RequestHostName, tkptr->HostLookup.LookupIOsb.Status); /* dispose of the non-connected channel/socket used for the lookup */ ProxyNetCloseSocket (tkptr); tkptr->ResponseStatusCode = 502; if (rqptr) { /* request associated with task */ rqptr->rqResponse.HttpStatus = 502; rqptr->rqResponse.ErrorTextPtr = tkptr->RequestHostPort; if (tkptr->HostLookup.LookupIOsb.Status == SS$_ENDOFFILE) ErrorGeneral (rqptr, MsgFor(rqptr,MSG_PROXY_HOST_UNKNOWN), FI_LI); else ErrorVmsStatus (rqptr, tkptr->HostLookup.LookupIOsb.Status, FI_LI); } ProxyEnd (tkptr); return; } /*****************/ /* host resolved */ /*****************/ IPADDRESS_COPY (&tkptr->RequestHostIpAddress, &tkptr->HostLookup.IpAddress) /* if not chaining use resolved address, otherwise the specified chain */ if (IPADDRESS_IS_RESET (&tkptr->ChainIpAddress)) IPADDRESS_COPY (&tkptr->ConnectIpAddress, &tkptr->HostLookup.IpAddress) else IPADDRESS_COPY (&tkptr->ConnectIpAddress, &tkptr->ChainIpAddress) if (ProxyConnectPersistMax) { /* search for an existing connection */ ProxyNetConnectSearch (tkptr); if (tkptr->NetIoPtr->Channel) { /***************************************/ /* persistent proxy->origin connection */ /***************************************/ /* peek to ensure it's still connected and there's nothing buffered */ static char ThrowAwayBuffer [1]; status = sys$qio (EfnNoWait, tkptr->NetIoPtr->Channel, IO$_READVBLK, &tkptr->ProxyConnectIOsb, &ProxyNetHostConnectAst, tkptr, ThrowAwayBuffer, sizeof(ThrowAwayBuffer), 0, TCPIP$C_MSG_PEEK|TCPIP$C_MSG_NBIO, 0, 0); if (VMSnok (status)) { /* leave it to the AST function to report! */ tkptr->ProxyConnectIOsb.Status = status; SysDclAst (ProxyNetHostConnectAst, tkptr); } return; } } /*******************/ /* connect to host */ /*******************/ if (WATCHING (tkptr, WATCH_PROXY)) WatchThis (WATCHITM(tkptr), WATCH_PROXY, "CONNECT !&I,!UL as !&I", &tkptr->ConnectIpAddress, tkptr->ConnectPort, &tkptr->BindIpAddress); tkptr->ConnectionCount = 1; /* assign a channel to the internet template device */ status = sys$assign (&TcpIpDeviceDsc, &tkptr->NetIoPtr->Channel, 0, 0); if (VMSnok (status)) { /* leave it to the AST function to report! */ tkptr->ProxyConnectIOsb.Status = status; SysDclAst (ProxyNetHostConnectAst, tkptr); return; } if (IPADDRESS_IS_SET (&tkptr->BindIpAddress)) { /* bind the proxy socket to a specific IP address */ if (IPADDRESS_IS_V4 (&tkptr->BindIpAddress)) { SOCKADDRESS_ZERO4 (&tkptr->ProxyBindSocketName) sin4ptr = &tkptr->ProxyBindSocketName.sa.v4; sin4ptr->SIN$W_FAMILY = TCPIP$C_AF_INET; sin4ptr->SIN$W_PORT = 0; IPADDRESS_SET4 (sin4ptr->SIN$L_ADDR, &tkptr->BindIpAddress) il2ptr = &tkptr->ProxyBindSocketNameItem; il2ptr->buf_len = sizeof(SOCKADDRIN); il2ptr->item = 0; il2ptr->buf_addr = sin4ptr; } else if (IPADDRESS_IS_V6 (&tkptr->BindIpAddress)) { SOCKADDRESS_ZERO6 (&tkptr->ProxyBindSocketName) sin6ptr = &tkptr->ProxyBindSocketName.sa.v6; sin6ptr->SIN6$B_FAMILY = TCPIP$C_AF_INET6; sin6ptr->SIN6$W_PORT = 0; IPADDRESS_SET6 (sin6ptr->SIN6$R_ADDR_OVERLAY.SIN6$T_ADDR, &tkptr->BindIpAddress) il2ptr = &tkptr->ProxyBindSocketNameItem; il2ptr->buf_len = sizeof(SOCKADDRIN); il2ptr->item = 0; il2ptr->buf_addr = sin6ptr; } else ErrorExitVmsStatus (SS$_BUGCHECK, ErrorSanityCheck, FI_LI); BindSocketNamePtr = (void*)il2ptr; } else BindSocketNamePtr = 0; if (IPADDRESS_IS_V4 (&tkptr->ConnectIpAddress)) TcpSocketPtr = &TcpIpSocket4; else if (IPADDRESS_IS_V6 (&tkptr->ConnectIpAddress)) TcpSocketPtr = &TcpIpSocket6; else ErrorExitVmsStatus (SS$_BUGCHECK, ErrorSanityCheck, FI_LI); /* make the channel a TCP, connection-oriented socket */ for (;;) { status = sys$qiow (EfnWait, tkptr->NetIoPtr->Channel, IO$_SETMODE, &tkptr->ProxyConnectIOsb, 0, 0, TcpSocketPtr, 0, BindSocketNamePtr, 0, UseFullDuplexClose ? &TcpIpFullDuplexCloseOption : 0, 0); if (WATCHMOD (tkptr, WATCH_MOD_PROXY)) WatchThis (WATCHITM(tkptr), WATCH_MOD_PROXY, "sys$qiow() !&S !&S", status, tkptr->ProxyConnectIOsb.Status); if (VMSok (status) && VMSok (tkptr->ProxyConnectIOsb.Status)) break; if (!UseFullDuplexClose) break; UseFullDuplexClose = false; /* Multinet 3.2 UCX driver barfs on FULL_DUPLEX_CLOSE, try without */ if (VMSok (status) && VMSnok (tkptr->ProxyConnectIOsb.Status)) { /* assign a new channel before retrying */ sys$dassgn (tkptr->NetIoPtr->Channel); status = sys$assign (&TcpIpDeviceDsc, &tkptr->NetIoPtr->Channel, 0, 0); if (VMSnok (status)) { /* leave it to the AST function to report! */ tkptr->ProxyConnectIOsb.Status = status; SysDclAst (ProxyNetHostConnectAst, tkptr); return; } } } /* it's a $QIOW so the IO status block is valid */ if (VMSok (status) && VMSnok (tkptr->ProxyConnectIOsb.Status)) status = tkptr->ProxyConnectIOsb.Status; if (VMSnok (status)) { /* leave it to the AST function to report! */ tkptr->ProxyConnectIOsb.Status = status; SysDclAst (ProxyNetHostConnectAst, tkptr); return; } if (IPADDRESS_IS_V4 (&tkptr->ConnectIpAddress)) { SOCKADDRESS_ZERO4 (&tkptr->ProxySocketName); sin4ptr = &tkptr->ProxySocketName.sa.v4; sin4ptr->SIN$W_FAMILY = TCPIP$C_AF_INET; sin4ptr->SIN$W_PORT = htons (tkptr->ConnectPort); IPADDRESS_SET4 (sin4ptr->SIN$L_ADDR, &tkptr->ConnectIpAddress) il2ptr = &tkptr->ProxySocketNameItem; il2ptr->buf_len = sizeof(SOCKADDRIN); il2ptr->item = TCPIP$C_SOCK_NAME; il2ptr->buf_addr = sin4ptr; } else if (IPADDRESS_IS_V6 (&tkptr->ConnectIpAddress)) { SOCKADDRESS_ZERO6 (&tkptr->ProxySocketName); sin6ptr = &tkptr->ProxySocketName.sa.v6; sin6ptr->SIN6$B_FAMILY = TCPIP$C_AF_INET6; sin6ptr->SIN6$W_PORT = htons (tkptr->ConnectPort); IPADDRESS_SET6 (sin6ptr->SIN6$R_ADDR_OVERLAY.SIN6$T_ADDR, &tkptr->ConnectIpAddress) il2ptr = &tkptr->ProxySocketNameItem; il2ptr->buf_len = sizeof(SOCKADDRIN6); il2ptr->item = TCPIP$C_SOCK_NAME; il2ptr->buf_addr = sin6ptr; } else ErrorExitVmsStatus (SS$_BUGCHECK, ErrorSanityCheck, FI_LI); if (ProxyConnectTimeoutSeconds) { status = sys$setimr (0, &ProxyNetConnectTimeoutDelta, &ProxyNetHostConnectTimeoutAst, tkptr, 0); if (VMSnok (status)) { /* leave it to the AST function to report! */ tkptr->ProxyConnectIOsb.Status = status; SysDclAst (ProxyNetHostConnectAst, tkptr); return; } } status = sys$qio (EfnNoWait, tkptr->NetIoPtr->Channel, IO$_ACCESS, &tkptr->ProxyConnectIOsb, &ProxyNetHostConnectAst, tkptr, 0, 0, &tkptr->ProxySocketNameItem, 0, 0, 0); if (VMSnok (status)) { /* leave it to the AST function to report! */ tkptr->ProxyConnectIOsb.Status = status; SysDclAst (ProxyNetHostConnectAst, tkptr); return; } } /****************************************************************************/ /* Called as an AST from ProxyNetHostConnect(). The remote host didn't respond. */ ProxyNetHostConnectTimeoutAst (PROXY_TASK *tkptr) { sys$cancel (tkptr->NetIoPtr->Channel); tkptr->ProxyConnectIOsb.Status = SS$_TIMEOUT; } /****************************************************************************/ /* Called as an AST from ProxyNetHostConnect(). The remote server host connect attempt has completed and either been successful or returned an error status. If an error then explain it and end proxy processing. If successful then begin processing the request scheme. */ ProxyNetHostConnectAst (PROXY_TASK *tkptr) { int status; char *cptr; REQUEST_STRUCT *rqptr; /*********/ /* begin */ /*********/ if (WATCHMOD (tkptr, WATCH_MOD_PROXY)) WatchThis (WATCHITM(tkptr), WATCH_MOD_PROXY, "ProxyNetHostConnectAst() !&F !&Z !&S", ProxyNetHostConnectAst, tkptr->ConnectHostPortPtr, tkptr->ProxyConnectIOsb.Status); /* First cancel the host connection timer */ sys$cantim (tkptr,0); rqptr = tkptr->RequestPtr; if (tkptr->ConnectionCount > 1) { /***************************************/ /* persistent proxy->origin connection */ /***************************************/ /* of course shouldn't be able to read anything at all at this stage!! */ if (VMSnok (tkptr->ProxyConnectIOsb.Status)) { if (tkptr->ProxyConnectIOsb.Status == SS$_SUSPENDED) { /* this means the non-blocking peek found nothing buffered */ tkptr->ProxyConnectIOsb.Status = SS$_NORMAL; } else { /* assume just a remote disconnection, close and restart */ ProxyNetCloseSocket (tkptr); ProxyNetHostConnect (tkptr); return; } } else { /* so now that we did consider it an error, close and restart */ ProxyNetCloseSocket (tkptr); ProxyNetHostConnect (tkptr); return; } } /*************************/ /* IP version accounting */ /*************************/ /* after any connection persistence has been taken into account */ if (IPADDRESS_IS_V4 (&tkptr->ConnectIpAddress)) { InstanceMutexLock (INSTANCE_MUTEX_HTTPD); ProxyAccountingPtr->ConnectIpv4Count++; /* [ipv4=0,ipv6=1][ipv4=0,ipv6=1] */ if (IPADDRESS_IS_V4 (&tkptr->ServicePtr->ServerIpAddress)) ProxyAccountingPtr->GatewayIpvCount[0][0]++; else ProxyAccountingPtr->GatewayIpvCount[1][0]++; InstanceMutexUnLock (INSTANCE_MUTEX_HTTPD); } else if (IPADDRESS_IS_V6 (&tkptr->ConnectIpAddress)) { InstanceMutexLock (INSTANCE_MUTEX_HTTPD); ProxyAccountingPtr->ConnectIpv6Count++; /* [ipv4=0,ipv6=1][ipv4=0,ipv6=1] */ if (IPADDRESS_IS_V4 (&tkptr->ServicePtr->ServerIpAddress)) ProxyAccountingPtr->GatewayIpvCount[0][1]++; else ProxyAccountingPtr->GatewayIpvCount[1][1]++; InstanceMutexUnLock (INSTANCE_MUTEX_HTTPD); } else ErrorExitVmsStatus (SS$_BUGCHECK, ErrorSanityCheck, FI_LI); if (VMSnok (tkptr->ProxyConnectIOsb.Status)) { /*****************/ /* connect error */ /*****************/ if (WATCHING (tkptr, WATCH_PROXY)) WatchThis (WATCHITM(rqptr), WATCH_PROXY, "CONNECT !&S", tkptr->ProxyConnectIOsb.Status); /* dispose of the non-connected channel/socket used for the lookup */ ProxyNetCloseSocket (tkptr); /* if we have some affinity hint, use it */ if (tkptr->ProxyAffinityInUse) { ProxyNetResolveHost (tkptr); return; } /* invalidate any entry (still) in the host name/address cache */ TcpIpCacheSetEntry (tkptr->RequestHostName, strlen(tkptr->RequestHostName), NULL); /* if we have an other address cached for this host, retry */ status = TcpIpCacheNameToAddress (&tkptr->HostLookup, tkptr->RequestHostName, strlen(tkptr->RequestHostName)); if (VMSok(status)) { ProxyNetHostConnect (tkptr); return; } tkptr->ResponseStatusCode = 502; if (rqptr->ServicePtr->ProxyTunnel == PROXY_TUNNEL_RAW || rqptr->ServicePtr->ProxyTunnel == PROXY_TUNNEL_FIREWALL) { /* can end up here with chained CONNECT requests */ ProxyEnd (tkptr); return; } if (rqptr) { /* request associated with task */ rqptr->rqResponse.HttpStatus = 502; switch (tkptr->ProxyConnectIOsb.Status) { case PROXY_ERROR_CONNECT_REFUSED : if (IPADDRESS_IS_SET (&tkptr->ChainIpAddress)) cptr = MsgFor(rqptr,MSG_PROXY_CHAIN_REFUSED); else cptr = MsgFor(rqptr,MSG_PROXY_CONNECT_REFUSED); ErrorGeneral (rqptr, cptr, FI_LI); break; case PROXY_ERROR_HOST_UNREACHABLE : if (IPADDRESS_IS_SET (&tkptr->ChainIpAddress)) cptr = MsgFor(rqptr,MSG_PROXY_CHAIN_UNREACHABLE); else cptr = MsgFor(rqptr,MSG_PROXY_HOST_UNREACHABLE); ErrorGeneral (rqptr, cptr, FI_LI); break; case PROXY_ERROR_HOST_TIMEOUT : if (IPADDRESS_IS_SET (&tkptr->ChainIpAddress)) cptr = MsgFor(rqptr,MSG_PROXY_CHAIN_UNREACHABLE); else cptr = MsgFor(rqptr,MSG_PROXY_HOST_UNREACHABLE); tkptr->ResponseStatusCode = 504; ErrorGeneral (rqptr, cptr, FI_LI); break; default : if (IPADDRESS_IS_SET (&tkptr->ChainIpAddress)) { rqptr->rqResponse.ErrorTextPtr = MsgFor(rqptr,MSG_PROXY_CHAIN_FAILURE); rqptr->rqResponse.ErrorOtherTextPtr = tkptr->ChainHostPortPtr; } else rqptr->rqResponse.ErrorTextPtr = tkptr->RequestHostPort; ErrorVmsStatus (rqptr, tkptr->ProxyConnectIOsb.Status, FI_LI); } } ProxyEnd (tkptr); return; } if (LoggingProxyLocalPort) rqptr->ProxyLocalPort = ProxyNetLocalPort (tkptr); /* it's successful if the remote host accepts it */ if (rqptr) rqptr->rqResponse.HttpStatus = 200; /* once we start reading into the buffer the request header is kaput */ rqptr->rqHeader.RequestHeaderPtrInvalid = true; if (tkptr->ProxyTunnel) { /**************/ /* tunnelling */ /**************/ if (IPADDRESS_IS_SET (&tkptr->ChainIpAddress)) ProxyTunnelChainConnect (tkptr); else if (tkptr->ProxyTunnel == PROXY_TUNNEL_HTTPS) SesolaNetClientBegin (tkptr); else ProxyTunnelBegin (tkptr); return; } /*************************************/ /* HTTP "GET", "POST", etc., methods */ /*************************************/ /* if affinity required and wasn't previouly set, do it now */ if ((tkptr->ServicePtr->ProxyAffinity || rqptr->rqPathSet.ProxyAffinity) && !tkptr->ProxyAffinityInUse) ProxyNetSetAffinityCookie (tkptr); if (tkptr->ConnectPort == DEFAULT_HTTPS_PORT || tkptr->RequestScheme == PROXY_SCHEME_HTTPSSL) { if (tkptr->ServicePtr->SSLclientEnabled) { if (tkptr->ConnectionCount > 1) ProxyWriteRequest (tkptr); else SesolaNetClientBegin (tkptr); } else { if (WATCHING (rqptr, WATCH_PROXY)) WatchThis (WATCHITM(rqptr), WATCH_PROXY, "CLIENT SSL not configured"); rqptr->rqResponse.HttpStatus = 503; ErrorGeneral (rqptr, MsgFor(rqptr,MSG_PROXY_GATEWAY), FI_LI); ProxyEnd (tkptr); return; } } else if (tkptr->RequestScheme == PROXY_SCHEME_HTTP) ProxyWriteRequest (tkptr); else if (tkptr->RequestScheme == PROXY_SCHEME_FTP) ProxyFtpLifeCycle (tkptr); else ErrorExitVmsStatus (SS$_BUGCHECK, ErrorSanityCheck, FI_LI); } /*****************************************************************************/ /* Get the local port of the outgoing connection (only for access log purposes). */ int ProxyNetLocalPort (PROXY_TASK *tkptr) { int status, SocketNameLength; IO_SB IOsb; REQUEST_STRUCT *rqptr; SOCKADDRESS SocketName; VMS_ITEM_LIST3 SocketNameItem; VMS_ITEM_LIST3 *il3ptr; /*********/ /* begin */ /*********/ rqptr = tkptr->RequestPtr; if (WATCHMOD (rqptr, WATCH_MOD_PROXY)) WatchThis (WATCHITM(rqptr), WATCH_MOD_PROXY, "ProxyNetLocalPort()"); il3ptr = &SocketNameItem; if (IPADDRESS_IS_V4 (&tkptr->ConnectIpAddress)) { il3ptr->buf_len = sizeof(SOCKADDRIN); il3ptr->item = 0; il3ptr->buf_addr = &SocketName.sa.v4; il3ptr->ret_len = &SocketNameLength; } else if (IPADDRESS_IS_V6 (&tkptr->ConnectIpAddress)) { il3ptr->buf_len = sizeof(SOCKADDRIN6); il3ptr->item = 0; il3ptr->buf_addr = &SocketName.sa.v6; il3ptr->ret_len = &SocketNameLength; } else ErrorExitVmsStatus (SS$_BUGCHECK, ErrorSanityCheck, FI_LI); status = sys$qiow (EfnWait, tkptr->NetIoPtr->Channel, IO$_SENSEMODE, &IOsb, 0, 0, 0, 0, il3ptr, 0, 0, 0); if (VMSok (status)) status = IOsb.Status; if (VMSnok (status)) { ErrorNoticed (rqptr, status, "ProxyNetLocalPort()", FI_LI); return (0); } if (IPADDRESS_IS_V4 (&tkptr->ConnectIpAddress)) return (ntohs(SocketName.sa.v4.SIN$W_PORT)); else return (ntohs(SocketName.sa.v6.SIN6$W_PORT)); } /*****************************************************************************/ /* Create a proxy cookie to set client affinity to the origin server. */ ProxyNetSetAffinityCookie (PROXY_TASK *tkptr) { int idx; unsigned short Length; char *cptr; char Buffer [256]; REQUEST_STRUCT *rqptr; /*********/ /* begin */ /*********/ rqptr = tkptr->RequestPtr; if (WATCHMOD (rqptr, WATCH_MOD_PROXY)) WatchThis (WATCHITM(rqptr), WATCH_MOD_PROXY, "ProxyNetSetAffinityCookie()"); /* set proxy preferred origin server cookie */ FaoToBuffer (Buffer, sizeof(Buffer), &Length, "!AZ!AZ=!&I; path=/;", PROXY_AFFINITY_COOKIE_PREFIX, tkptr->RequestHostName, &tkptr->ConnectIpAddress); cptr = VmGetHeap (rqptr, Length+1); for (idx = 0; idx < RESPONSE_COOKIE_MAX; idx++) { if (!rqptr->rqResponse.CookiePtr[idx]) { memcpy (rqptr->rqResponse.CookiePtr[idx] = cptr, Buffer, Length+1); break; } } if (WATCHING (rqptr, WATCH_PROXY)) WatchThis (WATCHITM(rqptr), WATCH_PROXY, "AFFINITY cookie set to !&I", &tkptr->ConnectIpAddress); } /*****************************************************************************/ /* Wrapper for AST parameter |tkptr|. */ int ProxyNetWrite ( PROXY_TASK *tkptr, PROXY_AST AstFunction, char *DataPtr, int DataLength ) { /*********/ /* begin */ /*********/ if (WATCHMOD (tkptr, WATCH_MOD_PROXY)) WatchThis (WATCHITM(tkptr), WATCH_MOD_PROXY, "ProxyNetWrite() !&F !&A !&X !UL", &ProxyNetWrite, AstFunction, DataPtr, DataLength); return (NetIoWrite (tkptr->NetIoPtr, AstFunction, tkptr, DataPtr, DataLength)); } /*****************************************************************************/ /* Wrapper for AST parameter |tkptr|. */ int ProxyNetRead ( PROXY_TASK *tkptr, PROXY_AST AstFunction, char *DataPtr, int DataSize ) { /*********/ /* begin */ /*********/ if (WATCHMOD (tkptr, WATCH_MOD_PROXY)) WatchThis (WATCHITM(tkptr), WATCH_MOD_PROXY, "ProxyNetRead() !&F !&A !&X !UL", &ProxyNetRead, AstFunction, DataPtr, DataSize); return (NetIoRead (tkptr->NetIoPtr, AstFunction, tkptr, DataPtr, DataSize)); } /****************************************************************************/ /* Just shut the socket down, bang! */ int ProxyNetCloseSocket (PROXY_TASK *tkptr) { int status; /*********/ /* begin */ /*********/ if (WATCHMOD (tkptr, WATCH_MOD_PROXY)) WatchThis (WATCHITM(tkptr), WATCH_MOD_PROXY, "ProxyNetCloseSocket() !&F", &ProxyNetCloseSocket); if (!tkptr->NetIoPtr) return (SS$_NORMAL); if (!tkptr->NetIoPtr->Channel) return (SS$_NORMAL); status = sys$dassgn (tkptr->NetIoPtr->Channel); tkptr->NetIoPtr->Channel = 0; if (WATCHING (tkptr, WATCH_PROXY)) { if (VMSok(status)) WatchThis (WATCHITM(tkptr), WATCH_PROXY, "CLOSE !AZ,!UL", tkptr->RequestHostName, tkptr->RequestPort); else WatchThis (WATCHITM(tkptr), WATCH_PROXY, "CLOSE !AZ,!UL !&S", tkptr->RequestHostName, tkptr->RequestPort, status); } return (status); } /*****************************************************************************/ /* */ BOOL ProxyNetInProgress (PROXY_TASK *tkptr) { /*********/ /* begin */ /*********/ if (WATCHMOD (tkptr, WATCH_MOD_PROXY)) WatchThis (WATCHITM(tkptr), WATCH_MOD_PROXY, "ProxyNetInProgress() inprog:!&B body:!&B", NETIO_IN_PROGRESS (tkptr->NetIoPtr), tkptr->QueuedBodyRead); if (NETIO_IN_PROGRESS (tkptr->NetIoPtr)) return (true); if (tkptr->QueuedBodyRead) return (true); return (false); } /****************************************************************************/ /* Create a persistent connection structure (using process memory) and add it to the list. Copy the channel number from the proxy task socket into the structure (zeroing the task channel), along with the IP address and port of the connection. */ ProxyNetConnectPersist (PROXY_TASK *tkptr) { int status; PROXY_CONNECT *pcptr; /*********/ /* begin */ /*********/ if (WATCHMOD (tkptr, WATCH_MOD_PROXY)) WatchThis (WATCHITM(tkptr), WATCH_MOD_PROXY, "ProxyNetConnectPersist() !&I,!UL !UL", &tkptr->ConnectIpAddress, tkptr->ConnectPort, tkptr->ConnectionCount); if (ProxyNetConnectFreeCount && ProxyNetConnectCount >= ProxyConnectPersistMax) { InstanceGblSecIncrLong (&ProxyAccountingPtr->ConnectPersistFull); return; } if (pcptr = ProxyNetConnectFreeList.HeadPtr) { ListRemove (&ProxyNetConnectFreeList, pcptr); memset (pcptr, 0, sizeof(PROXY_CONNECT)); if (ProxyNetConnectFreeCount) ProxyNetConnectFreeCount--; } else pcptr = (PROXY_CONNECT*)VmGet(sizeof(PROXY_CONNECT)); /* move the network I/O structure from the task */ pcptr->NetIoPtr = tkptr->NetIoPtr; tkptr->NetIoPtr = NULL; pcptr->SSLclientEnabled = tkptr->ServicePtr->SSLclientEnabled; pcptr->EntryTickSecond = HttpdTickSecond; IPADDRESS_COPY (&pcptr->ConnectIpAddress, &tkptr->ConnectIpAddress); pcptr->ConnectPort = tkptr->ConnectPort; pcptr->ConnectionCount = tkptr->ConnectionCount; ListAddTail (&ProxyNetConnectList, pcptr, LIST_ENTRY_TYPE_PROXY); ProxyNetConnectCount++; InstanceMutexLock (INSTANCE_MUTEX_HTTPD); if (ProxyNetConnectCount > ProxyAccountingPtr->ConnectPersistPeak) ProxyAccountingPtr->ConnectPersistPeak = ProxyNetConnectCount; InstanceMutexUnLock (INSTANCE_MUTEX_HTTPD); } /****************************************************************************/ /* Search through any list of persistent connections comparing the requests IP address and port to that stored in each list entry. If one matched copy the channel and persistence count to the proxy task and remove the entry from the list. Returns with the task connection socket channel non-zero if it found a match or still as zero if not. */ ProxyNetConnectSearch (PROXY_TASK *tkptr) { int status; LIST_ENTRY *leptr; PROXY_CONNECT *pcptr; /*********/ /* begin */ /*********/ if (WATCHMOD (tkptr, WATCH_MOD_PROXY)) WatchThis (WATCHITM(tkptr), WATCH_MOD_PROXY, "ProxyNetConnectSearch() !&I,!UL !UL", &tkptr->ConnectIpAddress, tkptr->ConnectPort, ProxyNetConnectCount); /* important these be zeroed to indicate search failure */ tkptr->NetIoPtr->Channel = tkptr->ConnectionCount = 0; if (!ProxyNetConnectCount) return; for (leptr = ProxyNetConnectList.HeadPtr; leptr; leptr = leptr->NextPtr) { pcptr = (PROXY_CONNECT*)leptr; if (WATCHMOD (tkptr, WATCH_MOD_PROXY)) WatchThis (WATCHITM(tkptr), WATCH_MOD_PROXY, "!&I,!UL !UL !UL !&B !&B", &pcptr->ConnectIpAddress, pcptr->ConnectPort, pcptr->ConnectionCount, pcptr->NetIoPtr->Channel, pcptr->SSLclientEnabled, tkptr->ServicePtr->SSLclientEnabled); if (!IPADDRESS_IS_SAME (&pcptr->ConnectIpAddress, &tkptr->ConnectIpAddress)) continue; if (pcptr->ConnectPort != tkptr->ConnectPort) continue; if (pcptr->SSLclientEnabled != tkptr->ServicePtr->SSLclientEnabled) continue; /*******/ /* hit */ /*******/ /* move the network I/O structure to the task */ tkptr->NetIoPtr = pcptr->NetIoPtr; pcptr->NetIoPtr; if (tkptr->NetIoPtr->SesolaPtr) SesolaNetSetProxyTask (tkptr); tkptr->ConnectionCount = pcptr->ConnectionCount + 1; ListRemove (&ProxyNetConnectList, pcptr); if (ProxyNetConnectCount) ProxyNetConnectCount--; ListAddTail (&ProxyNetConnectFreeList, pcptr, LIST_ENTRY_TYPE_PROXY); ProxyNetConnectFreeCount++; InstanceMutexLock (INSTANCE_MUTEX_HTTPD); ProxyAccountingPtr->ConnectPersistCount++; if (tkptr->ConnectionCount > ProxyAccountingPtr->ConnectPersistMax) ProxyAccountingPtr->ConnectPersistMax = tkptr->ConnectionCount; InstanceMutexUnLock (INSTANCE_MUTEX_HTTPD); if (WATCHING (tkptr, WATCH_PROXY)) WatchThis (WATCHITM(tkptr), WATCH_PROXY, "PERSISTENT !UL !AZ,!UL", tkptr->ConnectionCount, tkptr->RequestHostName, tkptr->RequestPort); return; } if (WATCHMOD (tkptr, WATCH_MOD_PROXY)) WatchThis (WATCHITM(tkptr), WATCH_MOD_PROXY, "NOT FOUND"); } /****************************************************************************/ /* Called by HttpdTick() once a second. Scan the list of persistent proxy->origin connections looking for those that have timed-out. For each timeout just close the socket by deassigning the channel and remove and free the entry from the list. If 'TimeoutSeconds' is negative use the configuration parameter. If zero then timeout all of the connections (purge the list). If a positive integer use this as the timeout period. */ int ProxyNetConnectSupervisor (int TimeoutPeriod) { int status, TimeoutSeconds; LIST_ENTRY *leptr; PROXY_CONNECT *pcptr; /*********/ /* begin */ /*********/ /** if (WATCH_MODULE(WATCH_MOD_PROXY)) WatchThis (WATCHALL, WATCH_MOD_PROXY, "ProxyNetConnectSupervisor() !SL !UL !UL !UL", TimeoutPeriod, ProxyConnectPersistSeconds, HttpdTickSecond, ProxyNetConnectCount); **/ if (!ProxyNetConnectCount) return (false); if (!TimeoutPeriod) TimeoutSeconds = 0; else if (TimeoutPeriod < 0) TimeoutSeconds = ProxyConnectPersistSeconds; else TimeoutSeconds = TimeoutPeriod; for (leptr = ProxyNetConnectList.HeadPtr; leptr; leptr = leptr->NextPtr) { pcptr = (PROXY_CONNECT*)leptr; if (WATCH_MODULE(WATCH_MOD_PROXY)) WatchThis (WATCHALL, WATCH_MOD_PROXY, "!&I,!UL !UL+!UL(!&B) !UL !UL", &pcptr->ConnectIpAddress, pcptr->ConnectPort, pcptr->EntryTickSecond, TimeoutSeconds, pcptr->EntryTickSecond + TimeoutSeconds <= HttpdTickSecond, pcptr->ConnectionCount, pcptr->NetIoPtr->Channel); if (pcptr->EntryTickSecond + TimeoutSeconds < HttpdTickSecond) { if (pcptr->NetIoPtr) { NetIoEnd (pcptr->NetIoPtr); pcptr->NetIoPtr = NULL; } ListRemove (&ProxyNetConnectList, pcptr); if (ProxyNetConnectCount) ProxyNetConnectCount--; ListAddTail (&ProxyNetConnectFreeList, pcptr, LIST_ENTRY_TYPE_PROXY); ProxyNetConnectFreeCount++; } } if (ProxyNetConnectCount) return (true); return (false); } /*****************************************************************************/ /* Return a report on proxy->origin server persistent connections. */ ProxyNetPersistentReport (REQUEST_STRUCT *rqptr) { static char BeginPageFao [] = "

\n\ \n\
\n\ \n\ \n\ \n\ \ \n\ \n\ \n\ \n\ \ \n\
Persist:!AZ
Limit:!UL
Current:!UL(!UL)
Full:!UL
Peak:!UL
Max:!UL
Count:!UL(!UL%)
\n\
\n\ \

\n\ \ \ \ \ \ \ \n"; static char ItemFao [] = "\ \ \ \ \ \n"; static char ItemOddFao [] = "\ \ \ \ \ \n"; static char EmptyListFao [] = "\ \n"; static char EndPageFao [] = "
AddressPortCountPersist
!3ZL!&I!UL!UL!AZ
!3ZL!&I  !UL  !UL  !AZ
000empty
\n\ !AZ\ \n\ \n\ \n"; int status, CountItem; unsigned short Length; unsigned long FaoVector [32]; unsigned long *vecptr; LIST_ENTRY *leptr; PROXY_CONNECT *pcptr; /*********/ /* begin */ /*********/ if (WATCHMOD (rqptr, WATCH_MOD_PROXY)) WatchThis (WATCHITM(rqptr), WATCH_MOD_PROXY, "ProxyNetPersistentReport()"); AdminPageTitle (rqptr, "Proxy Connection Report"); InstanceMutexLock (INSTANCE_MUTEX_HTTPD); vecptr = FaoVector; *vecptr++ = MetaConShowSeconds (rqptr, ProxyConnectPersistSeconds); *vecptr++ = ProxyConnectPersistMax; *vecptr++ = ProxyNetConnectCount; *vecptr++ = ProxyNetConnectFreeCount; *vecptr++ = ProxyAccountingPtr->ConnectPersistFull; *vecptr++ = ProxyAccountingPtr->ConnectPersistPeak; *vecptr++ = ProxyAccountingPtr->ConnectPersistMax; *vecptr++ = ProxyAccountingPtr->ConnectPersistCount; *vecptr++ = PercentOf (ProxyAccountingPtr->ConnectPersistCount, ProxyAccountingPtr->ConnectIpv4Count + ProxyAccountingPtr->ConnectIpv6Count); InstanceMutexUnLock (INSTANCE_MUTEX_HTTPD); status = FaolToNet (rqptr, BeginPageFao, &FaoVector); if (VMSnok (status)) ErrorNoticed (rqptr, status, NULL, FI_LI); CountItem = 0; for (leptr = ProxyNetConnectList.HeadPtr; leptr; leptr = leptr->NextPtr) { pcptr = (PROXY_CONNECT*)leptr; vecptr = FaoVector; *vecptr++ = ++CountItem; *vecptr++ = &pcptr->ConnectIpAddress; *vecptr++ = pcptr->ConnectPort; *vecptr++ = pcptr->ConnectionCount; *vecptr++ = MetaConShowSeconds (rqptr, HttpdTickSecond - pcptr->EntryTickSecond); if (CountItem & 1) status = FaolToNet (rqptr, ItemOddFao, &FaoVector); else status = FaolToNet (rqptr, ItemFao, &FaoVector); if (VMSnok (status)) ErrorNoticed (rqptr, status, NULL, FI_LI); } if (!ProxyNetConnectList.HeadPtr) { status = FaolToNet (rqptr, EmptyListFao, NULL); if (VMSnok (status)) ErrorNoticed (rqptr, status, NULL, FI_LI); } status = FaoToNet (rqptr, EndPageFao, AdminRefresh()); if (VMSnok (status)) ErrorNoticed (rqptr, status, NULL, FI_LI); rqptr->rqResponse.PreExpired = PRE_EXPIRE_ADMIN; ResponseHeader200 (rqptr, "text/html", &rqptr->NetWriteBufferDsc); AdminEnd (rqptr); } /****************************************************************************/