/*****************************************************************************/ /* HTTP2.c HTTP/2 is a replacement for how HTTP is expressed "on the wire". It is not a ground-up rewrite of the protocol; HTTP methods, status codes and semantics are the same, and it should be possible to use the same APIs as HTTP/1.x (possibly with some small additions) to represent the protocol. The focus of the protocol is on performance; specifically, end-user perceived latency, network and server resource usage. One major goal is to allow the use of a single connection from browsers to a Web site. Some useful (and used) sites: https://tools.ietf.org/html/rfc7540 https://tools.ietf.org/html/rfc7541 https://en.wikipedia.org/wiki/HTTP/2 https://http2.github.io/ https://http2.github.io/faq/ http://http2-explained.haxx.se/ http://chimera.labs.oreilly.com/books/1230000000545/ch12.html https://insouciant.org/tech/http-slash-2-considerations-and-tradeoffs/ http://undertow.io/blog/2015/04/27/An-in-depth-overview-of-HTTP2.html https://blog.newrelic.com/2016/02/17/http2-production/ https://blog.newrelic.com/2016/02/09/http2-best-practices-web-performance/ https://blog.cloudflare.com/tools-for-debugging-testing-and-using-http-2/ https://nghttp2.org/ https://nghttp2.org/documentation/nghttp.1.html https://nghttp2.org/documentation/h2load.1.html https://nghttp2.org/blog/2014/11/29/test-your-http-slash-2-server-with-nghttp-client/ chrome://net-internals#http2 HTTP/2 PROCESSING ----------------- These are indicative processing flows only. Not all functions involved or encountered are shown. Too many if-buts-maybees. Use WATCH. Request Processing ~~~~~~~~~~~~~~~~~~ : | RequestGet() !match the HTTP/2 request preface | Http2Preface() !either of these two functions are Http2SwitchResponse() !entry points to HTTP/2 processing | Http2Create() !create HTTP/2 structure and dispose | !of initiating request structure | Http2ClientRead() <--+ <--+ !receive frame from client | | | | {control frames} | !HTTP/2 internal processing | | : +--{not header frames}--+ : !frame processed by type | . HpackHeadersFrame() !request headers frame | Http2RequestBegin() !initialise request processing | Http2RequestProcess() !begin processing the request | RequestParseDictionary() !dictionary contains request | : !as per any other request | Http2RequestEnd() !end the request HTTP/2 specifics | : Read Frame Processing ~~~~~~~~~~~~~~~~~~~~~ : Http2NetClientRead() !initiate HTTP/2 connection read loop | NetIoRead() !asynchronous read | : | Http2NetClientReadAst() <-- + !asynchronous read completion | | !validate frame : | : !switch/case on frame type : !call function to process frame | : NetIoRead() : !asynchronous read | | +----------------------------+ Write Frame Processing ~~~~~~~~~~~~~~~~~~~~~~ : NetIoWrite() !request standard network call | Http2NetIoWrite() !HTTP/2 write | Http2NetQueueWrite() <----- + !put the write on a queue | | +-->!add I/O to a queue --> : !return after putting in queue | : !check queue(s) for I/O : !no queued I/O then end processing | : NetIoWrite() >------------+ : : : Http2NetWriteHeaderAst() <-+ : !write frame header then data | : : NetIoWrite() >------------+ : : : Http2NetWriteDataAst() <--+ : !write frame header plus data | | NetIoWrite() >---------------+ WATCHing via HTTP/2 ------------------- If a WATCHing request and one instantiated by Http2RequestBegin() share the HTTP/2 connection then none of the HTTP/2 WATCH points will be reported because down that particular rabbit hole is found only madness. HTTP/2 DoS Mitigations ---------------------- https://tools.ietf.org/html/rfc7540#section-10.5 https://www.kb.cert.org/vuls/id/605641/ https://github.com/Netflix/security-bulletins/blob/master/advisories/third-party/2019-002.md WASD places explicit (and reasoanble) limits on some HTTP/2 behaviours. Though exercising these mitigations is beyond the scope of the WASD test-bench. When mitigations activate the connection is dropped. Fingers crossed. 1) CVE-2019-9511, also known as Data Dribble WASD's asynchronous buffering and lack of stream prioritising mitigates. 2) CVE-2019-9512, also known as Ping Flood Http2Ping() checks for unreasonable client ping requests per second. 3) CVE-2019-9513, also known as Resource Loop Stream prioritising is not implemented by WASD. 4) CVE-2019-9514, also known as Reset Flood Http2ResetStream() checks for unreasonable server stream resets per second. 5) CVE-2019-9515, also known as Settings Flood Http2Settings() checks for unreasonable client setting frames per second. 6) CVE-2019-9516, also known as 0-Length Headers Leak HpackHeadersFrame() checks for zero-sized client headers. 7) CVE-2019-9517, also known as Internal Data Buffering The use of the TCP sliding window in this DoS puts it beyond WASD's control. WASD's asynchronous I/O should limit buffering impacts. 8) CVE-2019-9518, also known as Empty Frame Flooding Http2NetClientReadAst() checks for unreasonable client frame sizes. TESTING ------- Needless to say the major browsers (Chrome, Edge, FireFox, Safari) all contributed to end-use development. Indispensible were the |nghttp| and the associated |h2load| tools running on a Linux Mint (17.3) VM. Many thanks to the developer(s) of this package. And of course for someone educated in computing during the (19)70s, the availability of VM technology for such purposes is just brilliant! "But you know, we were happy in those days, though we were poor." o exercise the basic HTTP/2 functionality nghttp -nv https://klaatu.private/ o exercise starting HTTP/2 for "http" URIs (RFC 7450 3.2) nghttp -nvu http://klaatu.private/ o exercise starting HTTP/2 (for "http") with prior knowledge (RFC 7450 3.4) nghttp -nv http://klaatu.private/ o headers with continuation frames (RFC 7450 6.10) nghttp --continuation https://klaatu.private/ o basic HTTP/2 loading (number, clients and streams bumped) h2load --requests=1000 --clients=5 --max-concurrent-streams=5 https://klaatu.private/ o HTTP/2 loading with defined URIs h2load --requests=1000 --clients=5 --threads=5 --max-concurrent-streams=5 --input-file=urls.txt o HTTP/1.1 loading with defined URIs h2load --h1 --requests=100 --clients=2 --threads=2 --max-concurrent-streams=5 --input-file=urls.txt The HTTP/2 and HTTP/1.1 with --input-file can be concurrently used in two separate sessions to simultaneously exercise the server against the two protocols. The -w17 -W17 (for example) switches can exercise flow control. When module WATCHing is compiled-in, defining the WASD_HTTP2_SUBTLE_BREAK logical name enables code to break request processing in specific but subtle ways to ensure the server's continued stability in the presence of network or client issues. It should be used under load from soemthing akin to |h2load|. FOR LOCAL REFERENCE ------------------- +-----------------------------------------------+ | Length (24) | +---------------+---------------+---------------+ | Type (8) | Flags (8) | +-+-------------+---------------+-------------------------------+ |R| Stream Identifier (31) | +=+=============================================================+ | Frame Payload (0...) ... +---------------------------------------------------------------+ VERSION HISTORY --------------- 25-AUG-2021 MGD Http2CloseConnection() mung active flow control bugfix; Http2Supervisor() idle connection 03-APR-2021 MGD bugfix; Http2CloseConnection() once aborted always aborted 17-AUG-2020 MGD HTTP2_DEFAULT_WINDOW_SIZE from 1048575 to 131070 05-APR-2020 MGD Http2Report() remove proxy requests from HTTP/2-HTTP/1 ratio 31-JAN-2020 MGD fiddle with the code (perhaps optimisations, even bugfixes) 13-JAN-2019 MGD bugfix; Http2Priority() exclusive bit 17-NOV-2019 MGD bugfix; Http2CloseConnection() return after RequestEnd()ing 12-OCT-2019 MGD mitigate potential HTTP/2 DoS vulnerabilities https://www.kb.cert.org/vuls/id/605641/ https://tools.ietf.org/html/rfc7540#section-10.5 18-APR-2018 MGD bugfix; Http2ResetStream() rundown the request in-line 07-APR-2018 MGD bugfix; Http2ResetStream() explicitly rundown the request 23-DEC-2017 MGD bugfix; window update and flow control management 02-AUG-2015 MGD initial */ /*****************************************************************************/ #ifdef WASD_VMS_V7 # undef __VMS_VER # define __VMS_VER 70000000 # undef __CRTL_VER # define __CRTL_VER 70000000 #else # 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 #endif #include #include #include "wasd.h" #define WASD_MODULE "HTTP2" /******************/ /* global storage */ /******************/ BOOL Http2Enabled; int Http2CurrentConnected, Http2CurrentProcessing, Http2FlowControlCurrent, Http2PingTimerSeconds; uint Http2InitialWindowSize, Http2MaxConcurrentStreams, Http2MaxFrameSize, Http2MaxHeaderListSize, Http2MaxHeaderTableSize, Http2StreamIdent; /* used by VM.c */ const int Http2StructSize = sizeof(HTTP2_STRUCT); char Http2ClientPreface [] = "PRI * HTTP/2.0\r\n\r\nSM\r\n\r\n"; int Http2ClientPrefaceLength = sizeof(Http2ClientPreface)-1; char *Http2ErrorArray [HTTP2_ERROR_COUNT] = { "graceful shutdown", "protocol error detected", "implementation fault", "flow-control limits exceeded", "settings not acknowledged", "frame received for closed stream", "frame size incorrect", "stream not processed", "stream cancelled", "compression state not updated", "TCP connection error for CONNECT method", "processing capacity exceeded", "negotiated TLS parameters not acceptable", "use HTTP/1.1 for the request" }; LIST_HEAD Http2List; /********************/ /* external storage */ /********************/ extern int NetReadBufferSize; extern ulong HttpdTickSecond; extern char ErrorSanityCheck []; #define acptr AccountingPtr extern ACCOUNTING_STRUCT *AccountingPtr; extern CONFIG_STRUCT Config; extern LIST_HEAD RequestList; extern PROXY_ACCOUNTING_STRUCT *ProxyAccountingPtr; extern WATCH_STRUCT Watch; /*****************************************************************************/ /* Initialise all things HTTP/2. */ void Http2Init () { /*********/ /* begin */ /*********/ if (WATCH_MODULE(WATCH_MOD_HTTP2)) WatchThis (WATCHALL, WATCH_MOD_HTTP2, "Http2Init()"); Http2Enabled = Config.cfHttp2.Enabled; FaoToStdout ("%HTTPD-I-HTTP2, !AZ\n", Http2Enabled ? "enabled" : "disabled"); Http2InitialWindowSize = Config.cfHttp2.InitialWindowSize; if (!Http2InitialWindowSize) Http2InitialWindowSize = HTTP2_DEFAULT_WINDOW_SIZE; else if (Http2InitialWindowSize < HTTP2_INITIAL_WINDOW_SIZE) Http2InitialWindowSize = HTTP2_INITIAL_WINDOW_SIZE; else if (Http2InitialWindowSize > HTTP2_MAX_WINDOW_SIZE) Http2InitialWindowSize = HTTP2_MAX_WINDOW_SIZE; Http2MaxConcurrentStreams = Config.cfHttp2.MaxConcurrentStreams; if (!Http2MaxConcurrentStreams) Http2MaxConcurrentStreams = HTTP2_INITIAL_MAX_CONC_STREAMS; if (Http2MaxConcurrentStreams < HTTP2_MIN_INITIAL_MAX_CONC_STREAMS) Http2MaxConcurrentStreams = HTTP2_MIN_INITIAL_MAX_CONC_STREAMS; Http2MaxFrameSize = Config.cfHttp2.MaxFrameSize; if (Http2MaxFrameSize < HTTP2_INITIAL_MAX_FRAME_SIZE) Http2MaxFrameSize = HTTP2_INITIAL_MAX_FRAME_SIZE; else if (Http2MaxFrameSize > HTTP2_MAX_FRAME_SIZE) Http2MaxFrameSize = HTTP2_MAX_FRAME_SIZE; if (Http2Enabled && NetReadBufferSize < Http2MaxFrameSize) { NetReadBufferSize = Http2MaxFrameSize; FaoToStdout ("%HTTPD-W-HTTP2, network read buffer size \ increased to !UL bytes\n", NetReadBufferSize); } Http2MaxHeaderListSize = Config.cfHttp2.MaxHeaderListSize; if (Http2MaxHeaderListSize < HTTP2_INITIAL_HEAD_LIST_SIZE) Http2MaxHeaderListSize = HTTP2_INITIAL_HEAD_LIST_SIZE; else if (Http2MaxHeaderListSize > HTTP2_MAX_HEAD_LIST_SIZE) Http2MaxHeaderListSize = HTTP2_MAX_HEAD_LIST_SIZE; Http2MaxHeaderTableSize = Config.cfHttp2.MaxHeaderTableSize; if (Http2MaxHeaderTableSize < HTTP2_INITIAL_HEAD_TAB_SIZE) Http2MaxHeaderTableSize = HTTP2_INITIAL_HEAD_TAB_SIZE; else if (Http2MaxHeaderTableSize > HTTP2_MAX_HEAD_TAB_SIZE) Http2MaxHeaderTableSize = HTTP2_MAX_HEAD_TAB_SIZE; Http2PingTimerSeconds = Config.cfHttp2.PingTimerSeconds; if (Http2PingTimerSeconds == 0) Http2PingTimerSeconds = HTTP2_PING_SECONDS_DEFAULT; else if (Http2PingTimerSeconds < 0) Http2PingTimerSeconds = 0; /* "NONE" */ else if (Http2PingTimerSeconds > HTTP2_PING_SECONDS_MAX) Http2PingTimerSeconds = HTTP2_PING_SECONDS_MAX; if (!Http2Enabled) return; VmHttp2Init (); } /*****************************************************************************/ /* Called by WatchSetWatch(). */ void Http2SetWatch ( HTTP2_STRUCT *h2ptr, int item ) { h2ptr->WatchItem = item; if (h2ptr->NetIoPtr) if (h2ptr->NetIoPtr->SesolaPtr) SesolaSetWatch (h2ptr->NetIoPtr->SesolaPtr, item); else h2ptr->NetIoPtr->WatchItem = item; } /*****************************************************************************/ /* This is a TLS/SSL (https:) encrypted connection that has specified HTTP/2 via the TLS ALPN negotiation and transmitted the HTTP/2 client connection preface (RFC 7540 3.3) --OR-- a clear-text (http:) connection prior-knowledge (RFC 7540 3.4) connection preface. Create an HTTP/2 structure and then dispose of the request structure used to initiate the network connection. */ BOOL Http2Preface (REQUEST_STRUCT *rqptr) { int count, idx; HTTP2_STRUCT *h2ptr; /*********/ /* begin */ /*********/ if (WATCHMOD (rqptr, WATCH_MOD_HTTP2)) WatchThis (WATCHITM(rqptr), WATCH_MOD_HTTP2, "Http2Preface() preface:!UL read:!UL http2:!UL", Http2ClientPrefaceLength, rqptr->NetIoPtr->ReadCount, rqptr->NetIoPtr->ReadCount - Http2ClientPrefaceLength); if (WATCHING (rqptr, WATCH_CONNECT)) WatchThis (WATCHITM(rqptr), WATCH_CONNECT, "HTTP/2 connection preface (h2)"); if (!Http2Enabled) return (false); if (WATCHING (rqptr, WATCH_CONNECT)) WatchThis (WATCHITM(rqptr), WATCH_CONNECT, "HTTP/2 protocol"); h2ptr = Http2Create (rqptr); /* if HTTP/2 traffic has been pipelined with the preface */ if ((count = rqptr->NetIoPtr->ReadCount - Http2ClientPrefaceLength) > 0) { if (count > h2ptr->ReadBufferSize) ErrorExitVmsStatus (SS$_BUGCHECK, ErrorSanityCheck, FI_LI); memcpy (h2ptr->ReadBufferPtr, rqptr->rqNet.ReadBufferPtr + Http2ClientPrefaceLength, count); h2ptr->NetIoPtr->ReadStatus = SS$_NORMAL; h2ptr->NetIoPtr->ReadCount = count; SysDclAst (Http2NetClientReadAst, h2ptr); } else Http2NetClientRead (h2ptr); InstanceMutexLock (INSTANCE_MUTEX_HTTPD); NetUpdateConnected (rqptr, -1); NetUpdateConnected (h2ptr, +1); InstanceMutexUnLock (INSTANCE_MUTEX_HTTPD); RequestEnd5 (rqptr); return (true); } /****************************************************************************/ /* This is an unencrypted connection (http:) with request header indicating a desire to upgrade to HTTP/2 (RFC 7540 3.2). Major browser communities indicate no interest in providing HTTP/2 over clear-text connections so it's a bit of a niche but supported by the RFC. Generate an HTTP/2 101 Switching Protocols response and then handle the original request in the HTTP/2 environment. Exercise using nghttp -nvu http:/// */ BOOL Http2SwitchResponse (REQUEST_STRUCT *rqptr) { static char Http2Switching [] = "HTTP/1.1 101 Switching Protocols\r\n\ Upgrade: h2c\r\n\ Connection: Upgrade\r\n\ \r\n"; int retval, SettingsLength; char *cptr; uchar SettingsBuffer [256]; HTTP2_STRUCT *h2ptr; HTTP2_STREAM_STRUCT *s2ptr; NETIO_STRUCT *ioptr; /*********/ /* begin */ /*********/ if (WATCHMOD (rqptr, WATCH_MOD_RESPONSE)) WatchThis (WATCHITM(rqptr), WATCH_MOD_RESPONSE, "Http2SwitchResponse()"); if (WATCHING (rqptr, WATCH_CONNECT)) WatchThis (WATCHITM(rqptr), WATCH_CONNECT, "HTTP/2 upgrade"); if (!Http2Enabled) return (false); if (rqptr->rqHeader.UpgradeHttp2onHttp && rqptr->ServicePtr->RequestScheme != SCHEME_HTTP) { rqptr->rqResponse.HttpStatus = 400; ErrorGeneral (rqptr, MsgFor(rqptr,MSG_REQUEST_FORMAT), FI_LI); RequestEnd (rqptr); return (true); } if (rqptr->rqHeader.Http2SettingsPtr != NULL) { /* span the length of base-64 acceptable characters */ for (cptr = rqptr->rqHeader.Http2SettingsPtr; isalnum(*cptr) || *cptr == '+' || *cptr == '/' || *cptr == '='; *cptr++); SettingsLength = sizeof(SettingsBuffer); retval = base64_decode (SettingsBuffer, &SettingsLength, rqptr->rqHeader.Http2SettingsPtr, cptr - rqptr->rqHeader.Http2SettingsPtr); } if (rqptr->rqHeader.Http2SettingsPtr == NULL || retval != 0) { rqptr->rqResponse.HttpStatus = 400; ErrorGeneral (rqptr, MsgFor(rqptr,MSG_REQUEST_FORMAT), FI_LI); RequestEnd (rqptr); return (true); } /****************/ /* 101 response */ /****************/ /* no update to status code counters as this is an intermediate response */ rqptr->rqResponse.HttpStatus = 101; rqptr->rqResponse.HeaderSent = true; /* blocking write (for the convenience) */ NetWrite (rqptr, NULL, Http2Switching, sizeof(Http2Switching)-1); rqptr->rqResponse.HttpStatus = 0; rqptr->rqResponse.HeaderSent = false; /******************/ /* HTTP/2 request */ /******************/ if (WATCHING (rqptr, WATCH_CONNECT)) WatchThis (WATCHITM(rqptr), WATCH_CONNECT, "HTTP/2 protocol"); /* these that got us here are definitely no longer the case */ DictRemove (rqptr->rqDictPtr, DICT_TYPE_REQUEST, "connection", 10); DictRemove (rqptr->rqDictPtr, DICT_TYPE_REQUEST, "http2-settings", 14); DictRemove (rqptr->rqDictPtr, DICT_TYPE_REQUEST, "upgrade", 7); /* and the associated flags */ rqptr->rqHeader.UpgradeHttp2onHttp = false; rqptr->rqHeader.ConnectionHttp2Settings = false; rqptr->rqHeader.ConnectionUpgrade = false; /* see Http2RequestBegin() for parallels with what's done here */ h2ptr = Http2Create (rqptr); h2ptr->ExpectingH2cPreface = true; /* attach the request to the HTTP/2 stream */ Http2RequestBegin2 (rqptr, h2ptr, 1); Http2RequestProcess (rqptr); Http2NetClientRead (h2ptr); /* adjust the connected from the request to the HTTP/2 connection */ InstanceMutexLock (INSTANCE_MUTEX_HTTPD); NetUpdateProcessing (rqptr, -1); NetUpdateConnected (rqptr, -1); NetUpdateConnected (h2ptr, +1); InstanceMutexUnLock (INSTANCE_MUTEX_HTTPD); return (true); } /*****************************************************************************/ /* This is where the real fun begins. Create and initialise an HTTP/2 structure, using some data from the request structure. Return a pointer to the structure. */ HTTP2_STRUCT* Http2Create (REQUEST_STRUCT *rqptr) { HTTP2_STRUCT *h2ptr; /*********/ /* begin */ /*********/ if (WATCHMOD (rqptr, WATCH_MOD_HTTP2)) WatchThis (WATCHITM(rqptr), WATCH_MOD_HTTP2, "Http2Create()"); h2ptr = VmHttp2Get (rqptr->ConnectNumber); /* add to the HTTP/2 connection list */ ListAddHead (&Http2List, h2ptr, LIST_ENTRY_TYPE_HTTP2); h2ptr->NetIoPtr = rqptr->NetIoPtr; h2ptr->WatchItem = rqptr->WatchItem; h2ptr->NetIoPtr->WatchItem = h2ptr->WatchItem; h2ptr->ConnectTime64 = rqptr->rqTime.BeginTime64; /* always use original client data (see MapUrl_SetClientAddress()) */ if (rqptr->ClientResetPtr) h2ptr->ClientPtr = rqptr->ClientResetPtr; else h2ptr->ClientPtr = rqptr->ClientPtr; /* use the same service of the upgrade request */ h2ptr->ServicePtr = rqptr->ServicePtr; /* intialise server protocol parameters */ h2ptr->ServerInitialWindowSize = Http2InitialWindowSize; h2ptr->ServerMaxConcStreams = Http2MaxConcurrentStreams; h2ptr->ServerMaxFrameSize = Http2MaxFrameSize; h2ptr->ServerMaxHeaderListSize = Http2MaxHeaderListSize; h2ptr->ServerMaxHeaderTableSize = Http2MaxHeaderTableSize; h2ptr->ServerPushPromise = 0; /* intialise client protocol parameters */ h2ptr->ClientInitialWindowSize = HTTP2_INITIAL_WINDOW_SIZE; h2ptr->ClientMaxConcStreams = HTTP2_INITIAL_MAX_CONC_STREAMS; h2ptr->ClientMaxFrameSize = HTTP2_INITIAL_MAX_FRAME_SIZE; h2ptr->ClientMaxHeaderListSize = HTTP2_INITIAL_HEAD_LIST_SIZE; h2ptr->ClientMaxHeaderTableSize = HTTP2_INITIAL_HEAD_TAB_SIZE; h2ptr->ClientPushPromise = 0; /* HPACK initialisation */ h2ptr->HpackClientTable.max = Http2MaxHeaderTableSize; h2ptr->HpackServerTable.max = Http2MaxHeaderTableSize; h2ptr->HpackClientTable.h2ptr = h2ptr->HpackServerTable.h2ptr = h2ptr; /* initial flow control */ h2ptr->ReadWindowSize == h2ptr->ServerInitialWindowSize; h2ptr->WriteWindowSize = h2ptr->ClientInitialWindowSize; /* send server settings */ Http2Settings (h2ptr, 0, NULL, 0); /* set the initial connection flow-control window size */ h2ptr->ReadWindowSize = h2ptr->ServerInitialWindowSize * h2ptr->ServerMaxConcStreams; Http2WindowUpdate (h2ptr, 0, h2ptr->ReadWindowSize, NULL, 0); /* buffer for multiplexed reads */ h2ptr->ReadBufferSize = h2ptr->ServerMaxFrameSize + 128; h2ptr->ReadBufferPtr = VmGet2Heap (h2ptr, h2ptr->ReadBufferSize); return (h2ptr); } /*****************************************************************************/ /* Close the HTTP/2 protocol connection with the client. If there are still requests associated with the connection wait for them to end (based on delivered CANCELed I/O). If there's still HTTP/2 I/O in progress wait for it to rundown. Then check for any connection management writes still queued. Finally a check for straggling connection I/O in progress. After that the connection is removed from the list and the associated memory released in an indepedent AST. This function should only be called the once by HTTP/2 processing code when the channel number is non-zero. After that the Http2Supervisor() calls it every second until all the above conditions are met. Once removed from the connection list it's gone! */ void Http2CloseConnection (HTTP2_STRUCT *h2ptr) { BOOL OneShot; int idx; HTTP2_STREAM_STRUCT *s2ptr; REQUEST_STRUCT *rqeptr; /*********/ /* begin */ /*********/ if (WATCHMOD (h2ptr, WATCH_MOD_HTTP2)) WatchThis (WATCHITM(h2ptr), WATCH_MOD_HTTP2, "Http2CloseConnection() !&F chan:!UL list:!UL read:!&B write:!&B", Http2CloseConnection, h2ptr->NetIoPtr->Channel, LIST_GET_COUNT (&h2ptr->StreamList), NETIO_READ_IN_PROGRESS(h2ptr->NetIoPtr), NETIO_WRITE_IN_PROGRESS(h2ptr->NetIoPtr)); h2ptr->NetIoPtr->VmsStatus = SS$_VCCLOSED; /* if still stream(s) attached to the connection */ if (LIST_NOT_EMPTY (&h2ptr->StreamList)) return; /* if still queued write(s) waiting */ for (idx = HTTP2_WRITE_QUEUE_MAX; idx <= HTTP2_WRITE_QUEUE_LOW; idx++) if (LIST_NOT_EMPTY (&h2ptr->QueuedWriteList[idx])) { Http2NetQueueWrite (h2ptr, NULL); return; } /* any last gasp I/O still in progress */ if (NETIO_WRITE_IN_PROGRESS(h2ptr->NetIoPtr)) return; if (NETIO_READ_IN_PROGRESS(h2ptr->NetIoPtr)) { sys$cancel (h2ptr->NetIoPtr->Channel); return; } if (h2ptr->WriteInUseCount) { char *cptr = FaoToMemory (NULL, "WriteInUseCount:!SL", h2ptr->WriteInUseCount); ErrorNoticed (NULL, 0, cptr, FI_LI); VmFree (cptr, FI_LI); if (h2ptr->WriteInUseCount > 0) return; } /***********/ /* finally */ /***********/ if (WATCHPNT(h2ptr)) { if (WATCH_CATEGORY(WATCH_HTTP2)) WatchThis (WATCHITM(h2ptr), WATCH_HTTP2, "FRAMES tx:!UL rx:!UL REQUESTS tx:!UL rx:!UL total:!UL peak:!UL", h2ptr->FrameCountTx, h2ptr->FrameCountRx, h2ptr->FrameRequestCountTx, h2ptr->FrameRequestCountRx, h2ptr->RequestCount, h2ptr->RequestPeak); if (WATCH_CATEGORY(WATCH_HTTP2)) WatchThis (WATCHITM(h2ptr), WATCH_HTTP2, "HTTP/2 closed"); else if (WATCH_CATEGORY(WATCH_CONNECT)) WatchThis (WATCHITM(h2ptr), WATCH_CONNECT, "HTTP/2 closed"); } NetIoEnd (h2ptr->NetIoPtr); InstanceMutexLock (INSTANCE_MUTEX_HTTPD); NetUpdateConnected (h2ptr, -1); InstanceMutexUnLock (INSTANCE_MUTEX_HTTPD); /* remove from the HTTP/2 connection list */ ListRemove (&Http2List, h2ptr); VmFree2Heap (h2ptr, FI_LI); /* if this HTTP/2 connection was being (one-shot) WATCHed */ OneShot = h2ptr->WatchItem & WATCH_ITEM_HTTP2_FLAG; VmHttp2Free (h2ptr, FI_LI); if (OneShot && Watch.RequestPtr) { Watch.RequestPtr->RequestState = REQUEST_STATE_ENDING; SysDclAst (RequestEnd, Watch.RequestPtr); } } /*****************************************************************************/ /* An HTTP/2 error has been encountered. Signal this to the client as a non-fatal stream error or a fatal connection error. Return zero to continue, non-zero to abort the connection. */ int Http2Error ( HTTP2_STRUCT *h2ptr, uint ident, uint error ) { int retval; char *cptr; /*********/ /* begin */ /*********/ if (WATCHMOD (h2ptr, WATCH_MOD_HTTP2)) WatchThis (WATCHITM(h2ptr), WATCH_MOD_HTTP2, "Http2Error() !SL", error); if (WATCHING (h2ptr, WATCH_HTTP2)) Http2WatchError (h2ptr, error); switch (error) { /* stream oriented non-fatal errors */ case HTTP2_ERROR_NONE : case HTTP2_ERROR_REFUSED : case HTTP2_ERROR_CANCEL : case HTTP2_ERROR_CALM : case HTTP2_ERROR_HTTP11 : retval = Http2ResetStream (h2ptr, ident, 0, NULL, 0); if (retval >= 0) return (0); Http2GoAway (h2ptr, -(int)retval, NULL, 0); return (HTTP2_ERROR_INTERNAL); break; /* connection breaking errors */ case HTTP2_ERROR_PROTOCOL : case HTTP2_ERROR_INTERNAL : case HTTP2_ERROR_FLOW : case HTTP2_ERROR_TIMEOUT : case HTTP2_ERROR_CLOSED : case HTTP2_ERROR_SIZE : case HTTP2_ERROR_COMPRESS : case HTTP2_ERROR_CONNECT : case HTTP2_ERROR_SECURITY : Http2GoAway (h2ptr, error, NULL, 0); return (error); break; default : Http2GoAway (h2ptr, HTTP2_ERROR_INTERNAL, NULL, 0); return (HTTP2_ERROR_INTERNAL); } } /*****************************************************************************/ /* Send and receive GOAWAY frames. */ int Http2GoAway ( HTTP2_STRUCT *h2ptr, uint error, uchar *bptr, uint blen ) { uint size, stream; uchar *cptr, *czptr, *sptr, *zptr; uchar buf [256]; HTTP2_WRITE_STRUCT *w2ptr; /*********/ /* begin */ /*********/ if (WATCHMOD (h2ptr, WATCH_MOD_HTTP2)) WatchThis (WATCHITM(h2ptr), WATCH_MOD_HTTP2, "Http2GoAway() bptr:!8XL blen:!UL", blen); if (bptr != NULL) { /**********************/ /* goaway from client */ /**********************/ if (blen < 8) return (-(HTTP2_ERROR_SIZE)); if (WATCHING (h2ptr, WATCH_HTTP2)) Http2WatchGoaway (h2ptr, bptr, blen); HTTP2_GET_32 (bptr, stream); stream &= 0x7fffffff; HTTP2_GET_32 (bptr, error); zptr = (sptr = buf) + sizeof(buf)-1; if (blen > 8) { czptr = (cptr = bptr) + blen - 8; while (cptr < czptr && sptr < zptr) *sptr++ = *cptr++; } *sptr = '\0'; if (stream) return (-(HTTP2_ERROR_PROTOCOL)); h2ptr->GoAwayLastStreamIdent = stream; } else { /********************/ /* goaway to client */ /********************/ h2ptr->GoAwayIdent = h2ptr->LastStreamIdent; if (!bptr) bptr = ""; for (cptr = bptr; *cptr; cptr++); size = cptr - bptr; if (size <= (HTTP2_WRITE_PAYLOAD - 8)) size = 0; w2ptr = Http2GetWriteStruct (h2ptr, size, FI_LI); sptr = bptr; bptr = w2ptr->payload; HTTP2_PUT_32 (bptr, h2ptr->GoAwayIdent); HTTP2_PUT_32 (bptr, error); while (*bptr) *bptr++ = *sptr++; *bptr = '\0'; HTTP2_PLACE_24 (w2ptr->length, bptr - w2ptr->payload); HTTP2_PLACE_8 (w2ptr->type, HTTP2_FRAME_GOAWAY); HTTP2_PLACE_8 (w2ptr->flags, 0); HTTP2_PLACE_32 (w2ptr->ident, 0); w2ptr->Http2Ptr = h2ptr; w2ptr->AstParam = h2ptr; /* send the goaway frame and once sent close the connection */ w2ptr->AstFunction = Http2CloseConnection; w2ptr->HeaderPtr = w2ptr->header; w2ptr->DataPtr = w2ptr->payload; w2ptr->DataLength = bptr - w2ptr->payload; if (WATCHING (h2ptr, WATCH_HTTP2)) Http2WatchGoaway (h2ptr, w2ptr->DataPtr, w2ptr->DataLength); Http2NetQueueWrite (h2ptr, w2ptr); } /* if not gone after this period then close the connection anyway */ h2ptr->GoAwaySecond = HttpdTickSecond + HTTP2_TIMEOUT_GOAWAY_SECONDS; return (0); } /*****************************************************************************/ /* Send and receive PING frames. */ int Http2Ping ( HTTP2_STRUCT *h2ptr, uint flags, uchar *bptr, uint blen ) { uchar *sptr; int64 ResultTime64; HTTP2_WRITE_STRUCT *w2ptr; /*********/ /* begin */ /*********/ if (WATCHMOD (h2ptr, WATCH_MOD_HTTP2)) WatchThis (WATCHITM(h2ptr), WATCH_MOD_HTTP2, "Http2Ping() blen:!UL", blen); if (bptr != NULL) { /********************/ /* ping from client */ /********************/ if (blen != 8) return (-(HTTP2_ERROR_PROTOCOL)); if (flags & HTTP2_FLAG_PING_ACK) { /*******************/ /* ack from client */ /*******************/ /* client acknowledging server ping */ if (*(INT64PTR)bptr == h2ptr->PingTime64) { /* the number of 100 nano-seconds (up to 100 seconds) */ sys$gettim (&ResultTime64); ResultTime64 -= h2ptr->PingTime64; h2ptr->PingMicroSeconds = ResultTime64 / 10; } else h2ptr->PingMicroSeconds = -1; if (WATCHING (h2ptr, WATCH_HTTP2)) WatchThis (WATCHITM(h2ptr), WATCH_HTTP2, (int)h2ptr->PingMicroSeconds == -1 ? "PING client ERROR" : "PING !UL.!3ZLmS", h2ptr->PingMicroSeconds / 1000, h2ptr->PingMicroSeconds % 1000); h2ptr->PingBackTickSecond = 0; h2ptr->PingTime64 = 0; return (0); } /*****************/ /* ack to client */ /*****************/ if (WATCHING (h2ptr, WATCH_HTTP2)) WatchThis (WATCHITM(h2ptr), WATCH_HTTP2, "PING server ACKNOWLEDGE"); /* mitigate CVE-2019-9512 */ if (h2ptr->PingLimitCount++ > HTTP2_PING_LIMIT_COUNT) { if (WATCHING (h2ptr, WATCH_HTTP2)) WatchThis (WATCHITM(h2ptr), WATCH_HTTP2, "LIMIT client ping to !UL/S (DoS?)", HTTP2_PING_LIMIT_COUNT); return (-(HTTP2_ERROR_CALM)); } w2ptr = Http2GetWriteStruct (h2ptr, 0, FI_LI); /* replay the client payload */ sptr = w2ptr->payload; while (blen--) *sptr++ = *bptr++; HTTP2_PLACE_24 (w2ptr->length, 8); HTTP2_PLACE_8 (w2ptr->type, HTTP2_FRAME_PING); HTTP2_PLACE_8 (w2ptr->flags, HTTP2_FLAG_PING_ACK); HTTP2_PLACE_32 (w2ptr->ident, 0); w2ptr->Http2Ptr = h2ptr; w2ptr->HeaderPtr = w2ptr->header; w2ptr->DataPtr = w2ptr->payload; w2ptr->DataLength = 8; } else { /******************/ /* ping to client */ /******************/ /* only one outstanding with the specified timeout */ if (h2ptr->PingBackTickSecond) if (HttpdTickSecond < h2ptr->PingBackTickSecond) return (0); w2ptr = Http2GetWriteStruct (h2ptr, 0, FI_LI); /* generate and note the ping time */ sys$gettim (&h2ptr->PingTime64); *(INT64PTR)w2ptr->payload = h2ptr->PingTime64; h2ptr->PingBackTickSecond = HttpdTickSecond + HTTP2_PING_RESPONSE_MAX; HTTP2_PLACE_24 (w2ptr->length, 8); HTTP2_PLACE_8 (w2ptr->type, HTTP2_FRAME_PING); HTTP2_PLACE_8 (w2ptr->flags, 0); HTTP2_PLACE_32 (w2ptr->ident, 0); w2ptr->Http2Ptr = h2ptr; w2ptr->HeaderPtr = w2ptr->header; w2ptr->DataPtr = w2ptr->payload; w2ptr->DataLength = 8; if (WATCHING (h2ptr, WATCH_HTTP2)) WatchThis (WATCHITM(h2ptr), WATCH_HTTP2, "PING to client"); } Http2NetQueueWrite (h2ptr, w2ptr); return (0); } /*****************************************************************************/ /* HTTP/2 priority/dependency is not currently implemented. Weight is weight plus one, 1..256. */ int Http2Priority ( HTTP2_STRUCT *h2ptr, uint flags, uchar *bptr, uint blen ) { uint stream, value, weight; /*********/ /* begin */ /*********/ if (WATCHMOD (h2ptr, WATCH_MOD_HTTP2)) WatchThis (WATCHITM(h2ptr), WATCH_MOD_HTTP2, "Http2Priority() blen:!UL", blen); if (blen != 5) return (-(HTTP2_ERROR_SIZE)); HTTP2_GET_32 (bptr, stream); HTTP2_GET_8 (bptr, weight); return (0); } /*****************************************************************************/ /* Mark the stream as closed. When the server closes a stream (as when a request times out) send a RST_STREAM frame to the client. */ int Http2ResetStream ( HTTP2_STRUCT *h2ptr, uint ident, uint error, uchar *bptr, uint blen ) { HTTP2_STREAM_STRUCT *s2ptr; HTTP2_WRITE_STRUCT *w2ptr; REQUEST_STRUCT *rqeptr; /*********/ /* begin */ /*********/ if (WATCHMOD (h2ptr, WATCH_MOD_HTTP2)) WatchThis (WATCHITM(h2ptr), WATCH_MOD_HTTP2, "Http2ResetStream() blen:!UL ident:!UL error:!UL", blen, ident, error); if (ident == 0) return (-(HTTP2_ERROR_PROTOCOL)); /* locate the request corresponding to the stream */ for (s2ptr = LIST_GET_HEAD(&h2ptr->StreamList); s2ptr != NULL; s2ptr = LIST_GET_NEXT(s2ptr)) { rqeptr = (REQUEST_STRUCT*)s2ptr->RequestPtr; if (rqeptr->Http2Stream.Ident == ident) break; } if (s2ptr == NULL) { /* no stream found with that ident */ if (ident > h2ptr->LastStreamIdent) return (-(HTTP2_ERROR_PROTOCOL)); /* could be a hangover from a previous (closed) stream so ignore */ return (HTTP2_ERROR_NONE); } if (bptr != NULL) { /*******************/ /* reset by client */ /*******************/ if (blen != 4) return (-(HTTP2_ERROR_SIZE)); /* RFC 7540 6.4 */ if (s2ptr->State == HTTP2_STATE_IDLE) return (-(HTTP2_ERROR_PROTOCOL)); /* mitigate CVE-2019-9514 */ if (h2ptr->StreamResetLimitCount++ > HTTP2_STREAM_RESET_LIMIT_COUNT) { if (WATCHING (h2ptr, WATCH_HTTP2)) WatchThis (WATCHITM(h2ptr), WATCH_HTTP2, "LIMIT server stream resets to !UL/S (DoS?)", HTTP2_STREAM_RESET_LIMIT_COUNT); return (-(HTTP2_ERROR_CALM)); } s2ptr->State = HTTP2_STATE_CLOSED_REM; RequestAbort (rqeptr); } else { /*******************/ /* reset by server */ /*******************/ if (s2ptr->State == HTTP2_STATE_CLOSED_REM || s2ptr->State == HTTP2_STATE_CLOSED_LOC) s2ptr->State = HTTP2_STATE_CLOSED; else s2ptr->State = HTTP2_STATE_CLOSED_LOC; if (h2ptr->NetIoPtr->VmsStatus) s2ptr->State = HTTP2_STATE_CLOSED; if (s2ptr->State != HTTP2_STATE_CLOSED) { w2ptr = Http2GetWriteStruct (h2ptr, 0, FI_LI); HTTP2_PLACE_24 (w2ptr->length, 4); HTTP2_PLACE_8 (w2ptr->type, HTTP2_FRAME_RST_STREAM); HTTP2_PLACE_8 (w2ptr->flags, 0); HTTP2_PLACE_32 (w2ptr->ident, ident); w2ptr->Http2Ptr = h2ptr; w2ptr->HeaderPtr = w2ptr->header; w2ptr->DataPtr = w2ptr->payload; w2ptr->DataLength = 4; HTTP2_PLACE_32 (w2ptr->payload, error); Http2NetQueueWrite (h2ptr, w2ptr); } } return (HTTP2_ERROR_NONE); } /*****************************************************************************/ /* Send and receive HTTP/2 settings. */ int Http2Settings ( HTTP2_STRUCT *h2ptr, uint flags, uchar *bptr, uint blen ) { uint count, setting, value; uchar *aptr; HTTP2_WRITE_STRUCT *w2ptr; /*********/ /* begin */ /*********/ if (WATCHMOD (h2ptr, WATCH_MOD_HTTP2)) WatchThis (WATCHITM(h2ptr), WATCH_MOD_HTTP2, "Http2Settings() flags:0x!2XL len:!UL (!UL)", flags, blen, blen / 6); if (bptr != NULL) { /***************/ /* from client */ /***************/ /* all setting are in multiples of 6 octets */ if (blen % 6) return (-(HTTP2_ERROR_SIZE)); count = blen / 6; if (flags & HTTP2_FLAG_SETTINGS_ACK) { /**************/ /* client ack */ /**************/ /* payload must be empty */ if (count) return (-(HTTP2_ERROR_PROTOCOL)); return (0); } /*********************/ /* client setting(s) */ /*********************/ if (WATCHING (h2ptr, WATCH_HTTP2)) Http2WatchSettings (h2ptr, "client", bptr, blen); /* mitigate CVE-2019-9515 */ if (h2ptr->SettingsLimitCount++ > HTTP2_SETTINGS_LIMIT_COUNT) { if (WATCHING (h2ptr, WATCH_HTTP2)) WatchThis (WATCHITM(h2ptr), WATCH_HTTP2, "LIMIT client settings to !UL/S (DoS?)", HTTP2_SETTINGS_LIMIT_COUNT); return (-(HTTP2_ERROR_CALM)); } while (count--) { HTTP2_GET_8 (bptr, setting); HTTP2_GET_32 (bptr, value); /* process client requested settings */ switch (setting) { case HTTP2_SETTING_MAX_HEAD_TABLE_SIZE : h2ptr->ClientMaxHeaderTableSize = value; h2ptr->HpackClientTable.max = value; break; case HTTP2_SETTING_ENABLE_PUSH : h2ptr->ClientPushPromise = value; break; case HTTP2_SETTING_MAX_CONC_STREAMS : h2ptr->ClientMaxConcStreams = value; break; case HTTP2_SETTING_INIT_WIN_SIZE : Http2FlowControl (h2ptr, value); break; case HTTP2_SETTING_MAX_FRAME_SIZE : h2ptr->ClientMaxFrameSize = value; break; case HTTP2_SETTING_MAX_HEAD_LIST_SIZE : h2ptr->ClientMaxHeaderListSize = value; break; default : /* ignore unknown settings RFC 7540 6.5.2 */ } } /*****************/ /* ack to client */ /*****************/ if (WATCHING (h2ptr, WATCH_HTTP2)) WatchThis (WATCHITM(h2ptr), WATCH_HTTP2, "SETTINGS server ACKNOWLEDGE"); w2ptr = Http2GetWriteStruct (h2ptr, 0, FI_LI); HTTP2_PLACE_24 (w2ptr->length, 0); HTTP2_PLACE_8 (w2ptr->type, HTTP2_FRAME_SETTINGS); HTTP2_PLACE_8 (w2ptr->flags, HTTP2_FLAG_SETTINGS_ACK); HTTP2_PLACE_32 (w2ptr->ident, 0); w2ptr->Http2Ptr = h2ptr; w2ptr->HeaderPtr = w2ptr->header; w2ptr->DataPtr = w2ptr->payload; w2ptr->DataLength = 0; } else { /*************/ /* to client */ /*************/ w2ptr = Http2GetWriteStruct (h2ptr, 0, FI_LI); bptr = w2ptr->payload; HTTP2_PUT_16 (bptr, HTTP2_SETTING_MAX_HEAD_TABLE_SIZE); HTTP2_PUT_32 (bptr, h2ptr->ServerMaxHeaderTableSize); HTTP2_PUT_16 (bptr, HTTP2_SETTING_MAX_CONC_STREAMS); HTTP2_PUT_32 (bptr, h2ptr->ServerMaxConcStreams); HTTP2_PUT_16 (bptr, HTTP2_SETTING_INIT_WIN_SIZE); HTTP2_PUT_32 (bptr, h2ptr->ServerInitialWindowSize); HTTP2_PUT_16 (bptr, HTTP2_SETTING_MAX_FRAME_SIZE); HTTP2_PUT_32 (bptr, h2ptr->ServerMaxFrameSize); HTTP2_PUT_16 (bptr, HTTP2_SETTING_MAX_HEAD_LIST_SIZE); HTTP2_PUT_32 (bptr, h2ptr->ServerMaxHeaderListSize); HTTP2_PLACE_24 (w2ptr->length, bptr - w2ptr->payload); HTTP2_PLACE_8 (w2ptr->type, HTTP2_FRAME_SETTINGS); HTTP2_PLACE_8 (w2ptr->flags, 0); HTTP2_PLACE_32 (w2ptr->ident, 0); w2ptr->Http2Ptr = h2ptr; w2ptr->HeaderPtr = w2ptr->header; w2ptr->DataPtr = w2ptr->payload; w2ptr->DataLength = bptr - w2ptr->payload; if (WATCHING (h2ptr, WATCH_HTTP2)) Http2WatchSettings (h2ptr, "server", w2ptr->DataPtr, w2ptr->DataLength); } Http2NetQueueWrite (h2ptr, w2ptr); return (0); } /*****************************************************************************/ /* Client setting change to initial window size initiates connection-wide flow control window adjustment. The resulting window size can be negative (RFC 7540 6.9.2). Blocked flow control may now resume, or vice-versa. */ void Http2FlowControl ( HTTP2_STRUCT *h2ptr, uint value ) { int adjust; HTTP2_STREAM_STRUCT *s2ptr; /*********/ /* begin */ /*********/ if (WATCHMOD (h2ptr, WATCH_MOD_HTTP2)) WatchThis (WATCHITM(h2ptr), WATCH_MOD_HTTP2, "Http2FlowControl() !UL", value); adjust = -(h2ptr->ClientInitialWindowSize - value); for (s2ptr = LIST_GET_HEAD(&h2ptr->StreamList); s2ptr != NULL; s2ptr = LIST_GET_NEXT(s2ptr)) s2ptr->WriteWindowSize += adjust; h2ptr->ClientInitialWindowSize = value; /* resume writing if flow control was blocking and now permits */ Http2NetQueueWrite (h2ptr, NULL); } /*****************************************************************************/ /* Flow control feedback from the client. */ int Http2WindowUpdate ( HTTP2_STRUCT *h2ptr, uint ident, uint update, uchar *bptr, uint blen ) { uint size; HTTP2_STREAM_STRUCT *s2ptr; REQUEST_STRUCT *rqeptr; HTTP2_WRITE_STRUCT *w2ptr; /*********/ /* begin */ /*********/ if (WATCHMOD (h2ptr, WATCH_MOD_HTTP2)) WatchThis (WATCHITM(h2ptr), WATCH_MOD_HTTP2, "Http2WindowUpdate() blen:!UL ident:!UL", blen, ident); if (bptr != NULL) { /***************/ /* from client */ /***************/ if (blen != 4) return (-(HTTP2_ERROR_SIZE)); HTTP2_GET_32 (bptr, update); if (WATCHING (h2ptr, WATCH_HTTP2) && !WATCHING1S(h2ptr) && !WATCHING2(h2ptr)) WatchThis (WATCHITM(h2ptr), WATCH_HTTP2, "WINDOW client ident:!UL update:!UL", ident, update); if (update == 0 || update & 0x80000000) return (-(HTTP2_ERROR_PROTOCOL)); if (ident) { /**********/ /* stream */ /**********/ /* locate the request corresponding to the stream */ for (s2ptr = LIST_GET_HEAD(&h2ptr->StreamList); s2ptr != NULL; s2ptr = LIST_GET_NEXT(s2ptr)) { rqeptr = (REQUEST_STRUCT*)s2ptr->RequestPtr; if (rqeptr->Http2Stream.Ident == ident) break; } if (s2ptr == NULL) { /* no stream found with that ident */ if (ident > h2ptr->LastStreamIdent) return (-(HTTP2_ERROR_PROTOCOL)); /* could be a hangover from a previous (closed) stream so ignore */ return (HTTP2_ERROR_NONE); } size = (s2ptr->WriteWindowSize += update); } else { /**************/ /* connection */ /**************/ size = (h2ptr->WriteWindowSize += update); } /* the most significant bit indicates overflow */ if (size & 0x80000000) return (-(HTTP2_ERROR_PROTOCOL)); /* resume writing if flow control was blocking and now permits */ if (!NETIO_WRITE_IN_PROGRESS(h2ptr->NetIoPtr)) Http2NetQueueWrite (h2ptr, NULL); } else { /*************/ /* to client */ /*************/ if (WATCHING (h2ptr, WATCH_HTTP2) && !WATCHING1S(h2ptr) && !WATCHING2(h2ptr)) WatchThis (WATCHITM(h2ptr), WATCH_HTTP2, "WINDOW server ident:!UL update:!UL", ident, update); if (update == 0 || update & 0x80000000) { /* note and ignore */ ErrorNoticed (NULL, SS$_BUGCHECK, ErrorSanityCheck, FI_LI); return (0); } w2ptr = Http2GetWriteStruct (h2ptr, 0, FI_LI); HTTP2_PLACE_24 (w2ptr->length, 4); HTTP2_PLACE_8 (w2ptr->type, HTTP2_FRAME_WINDOW_UPDATE); HTTP2_PLACE_8 (w2ptr->flags, 0); HTTP2_PLACE_32 (w2ptr->ident, ident); HTTP2_PLACE_32 (w2ptr->payload, update); w2ptr->Http2Ptr = h2ptr; w2ptr->HeaderPtr = w2ptr->header; w2ptr->DataPtr = w2ptr->payload; w2ptr->DataLength = 4; Http2NetQueueWrite (h2ptr, w2ptr); } return (0); } /*****************************************************************************/ /* Allocate a write struct while performing some sanity checking. */ HTTP2_WRITE_STRUCT* Http2GetWriteStruct ( HTTP2_STRUCT *h2ptr, int size, char *module, int line ) { HTTP2_WRITE_STRUCT *w2ptr; /*********/ /* begin */ /*********/ if (++h2ptr->WriteInUseCount > 256) /* arbitrary */ { char *cptr = FaoToMemory (NULL, "WriteInUseCount:!SL", h2ptr->WriteInUseCount); ErrorNoticed (NULL, SS$_BUGCHECK, cptr, module, line); VmFree (cptr, FI_LI); } w2ptr = VmGet2Heap (h2ptr, sizeof(HTTP2_WRITE_STRUCT) + size); return (w2ptr); } /*****************************************************************************/ /* Free a write struct while performing some sanity checking. */ void Http2FreeWriteStruct ( HTTP2_STRUCT *h2ptr, HTTP2_WRITE_STRUCT *w2ptr, char *module, int line ) { /*********/ /* begin */ /*********/ if (--h2ptr->WriteInUseCount < 0) { HttpdStackTrace ("Http2FreeWriteStruct()", FI_LI); char *cptr = FaoToMemory (NULL, "WriteInUseCount:!SL", h2ptr->WriteInUseCount); ErrorNoticed (NULL, SS$_BUGCHECK, cptr, module, line); VmFree (cptr, FI_LI); } VmFreeFrom2Heap (h2ptr, w2ptr, module, line); } /*****************************************************************************/ /* Return a pointer to a string respresenting the supplied error code. */ char* Http2ErrorString (int error) { /*********/ /* begin */ /*********/ if (error > HTTP2_ERROR_COUNT) return ("*UNKNOWN*"); return (Http2ErrorArray[error]); } /*****************************************************************************/ /* Scan the HTTP/2 connection list for connections that have been idle for the timeout period and close them down elegantly. */ BOOL Http2Supervisor () { HTTP2_STRUCT *h2ptr, *next2; /*********/ /* begin */ /*********/ /** if (WATCH_MODULE(WATCH_MOD_HTTP2)) WatchThis (WATCHALL, WATCH_MOD_HTTP2, "Http2Supervisor()"); **/ /* process the connection list from least to most recent */ for (h2ptr = LIST_GET_HEAD(&Http2List); h2ptr != NULL; h2ptr = next2) { /* get (any) next in list in case the current connection is closed */ next2 = LIST_GET_NEXT(h2ptr); if (LIST_GET_HEAD(&h2ptr->StreamList)) h2ptr->IdleSecond = 0; else if (!h2ptr->IdleSecond) { if (Config.cfTimeout.Http2Idle) h2ptr->IdleSecond = HttpdTickSecond + Config.cfTimeout.Http2Idle; else h2ptr->IdleSecond = HttpdTickSecond + HTTP2_TIMEOUT_IDLE_SECONDS; } if (h2ptr->IdleSecond && h2ptr->IdleSecond < HttpdTickSecond) { if (WATCHING (h2ptr, WATCH_HTTP2)) WatchThis (WATCHITM(h2ptr), WATCH_HTTP2, "TIMEOUT idle"); Http2CloseConnection (h2ptr); continue; } if (h2ptr->GoAwaySecond && h2ptr->GoAwaySecond < HttpdTickSecond) { if (WATCHING (h2ptr, WATCH_HTTP2)) WatchThis (WATCHITM(h2ptr), WATCH_HTTP2, "TIMEOUT goaway"); Http2CloseConnection (h2ptr); continue; } if (h2ptr->NetIoPtr->VmsStatus) { /* with each call check if the connection can be closed */ Http2CloseConnection (h2ptr); continue; } if (h2ptr->GoAwayLastStreamIdent) { /* connection has been told to goaway */ Http2CloseConnection (h2ptr); continue; } if (h2ptr->PingBackTickSecond && h2ptr->PingBackTickSecond < HttpdTickSecond) { if (WATCHING (h2ptr, WATCH_HTTP2)) WatchThis (WATCHITM(h2ptr), WATCH_HTTP2, "TIMEOUT ping"); h2ptr->PingBackTickSecond = 0; } else if (Http2PingTimerSeconds && (h2ptr->PingSendTickSecond == 0 || h2ptr->PingSendTickSecond < HttpdTickSecond)) { /* send a ping every so-many seconds */ if (WATCHING (h2ptr, WATCH_HTTP2)) WatchThis (WATCHITM(h2ptr), WATCH_HTTP2, "TIME to ping"); Http2Ping (h2ptr, 0, NULL, 0); h2ptr->PingSendTickSecond = HttpdTickSecond + Http2PingTimerSeconds; } /* reset DoS limit counters */ h2ptr->EmptyFrameLimitCount = h2ptr->PingLimitCount = h2ptr->SettingsLimitCount = h2ptr->StreamResetLimitCount = h2ptr->ZeroHeaderLimitCount = 0; } return (LIST_NOT_EMPTY(&Http2List)); } /*****************************************************************************/ /* */ void Http2Report (REQUEST_STRUCT *rqptr) { static char PageBeginFao [] = "

\n\ \n\
\n\ \n\ \ \ \ \ \n\ \ \ \ \ \n\ \ \ \n\ \n\ \n\ \ \ \n\ \n\ \n\ \ \ \n\ \n\ \n\ \ \n\ \n\ \n\ \n\ \n\ \n\ \
HTTP/2HTTP/1.n
Requests  /Total:!&L(!UL%)!&L(!UL%)
/Peak:!&L!&L
Bytes  /Min:!&,@SQ!&,@SQ
/Max:!&,@SQ!&,@SQ
/Ave:!&L!&L
Duration  /Min:!AZ!AZ
/Max:!AZ!AZ
/Ave:!AZ!AZ
Bytes/Sec  /Min:!&L!&L
/Max:!&L!&L
/Ave:!&L!&L
\ Frames  /Total:!&,@SQ
/Request:!&,@SQ(!UL%)
/Average:!&L
/Flow Cntrl:!&,@SQ(!UL%)
/Rx:!&,@SQ
/Tx:!&,@SQ
\n\
\n"; /* the final column just adds a little white-space on the page far right */ static char Http2TableFao [] = "

\n\ \ \ \ \ \ \ \n\ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \n\ \n"; /* the empty 99% column just forces the rest left */ static char Http2EntryFao [] = "\ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \n"; static char Http2EmptyFao [] = "\ \n"; static char PageEndFao [] = "
FramesBytesWrite QueueHPACKRequests
ServiceClientDurationRxTxFCRxTx0HNLPkRxTxTotPkCurWATCH
!3ZL!AZ//!AZ!AZ,!UL!AZ!&L!&L!UL!&,@SQ!&,@SQ!UL!UL!UL!UL!UL!UL%!UL%!&L!UL!UL!&@
000empty
\n\

\n\ \n\
\n\
\n\ \n\
\n\
\n\
\n\ \n\
\n\
\n\ !AZ\ \n\ \n\ \n"; int idx, status, EntryCount, RequestHttpCount, RequestHttp1Count, RequestHttp2Count; int64 bytes64, Time64, ConnectTime64, FrameRequest64, FrameTotal64; ulong FaoVector [48]; ulong *vecptr; char *cptr; HTTP2_STRUCT *h2ptr; /*********/ /* begin */ /*********/ if (WATCHMOD (rqptr, WATCH_MOD_HTTP2)) WatchThis (WATCHITM(rqptr), WATCH_MOD_HTTP2, "Http2Report()"); AdminPageTitle (rqptr, "HTTP Report"); vecptr = FaoVector; InstanceMutexLock (INSTANCE_MUTEX_HTTPD); RequestHttp1Count = acptr->RequestHttp11Count + acptr->RequestHttp10Count; RequestHttp1Count -= acptr->DoProxyCount; RequestHttp2Count = acptr->RequestHttp2Count; RequestHttpCount = RequestHttp1Count + RequestHttp2Count; *vecptr++ = RequestHttp2Count; *vecptr++ = PercentOf32 (RequestHttp2Count, RequestHttpCount); *vecptr++ = RequestHttp1Count; *vecptr++ = PercentOf32 (RequestHttp1Count, RequestHttpCount); *vecptr++ = acptr->ProcessingPeak[HTTP2]; *vecptr++ = acptr->ProcessingPeak[HTTP1]; *vecptr++ = &acptr->BytesPerSecondMinBytes64[HTTP2]; *vecptr++ = &acptr->BytesPerSecondMinBytes64[HTTP1]; *vecptr++ = &acptr->BytesPerSecondMaxBytes64[HTTP2]; *vecptr++ = &acptr->BytesPerSecondMaxBytes64[HTTP1]; if (acptr->RequestHttp2Count) *vecptr++ = (ulong)(float)acptr->BytesPerSecondRawTotal64[HTTP2] / (float)acptr->RequestHttp2Count; else *vecptr++ = 0; if (acptr->RequestHttp11Count + acptr->RequestHttp10Count) *vecptr++ = (ulong)(float)acptr->BytesPerSecondRawTotal64[HTTP1] / (float)(acptr->RequestHttp11Count + acptr->RequestHttp10Count); else *vecptr++ = 0; /* this kludge steps over %CC-W-ALIGNCONFLICT */ bytes64 = acptr->ResponseDurationMin64[HTTP2]; *vecptr++ = DurationString (rqptr, &bytes64); bytes64 = acptr->ResponseDurationMin64[HTTP1]; *vecptr++ = DurationString (rqptr, &bytes64); bytes64 = acptr->ResponseDurationMax64[HTTP2]; *vecptr++ = DurationString (rqptr, &bytes64); bytes64 = acptr->ResponseDurationMax64[HTTP1]; *vecptr++ = DurationString (rqptr, &bytes64); bytes64 = acptr->ResponseDuration64[HTTP2]; *vecptr++ = AverageDurationString (rqptr, &bytes64, acptr->ResponseDurationCount[HTTP2]); bytes64 = acptr->ResponseDuration64[HTTP1]; *vecptr++ = AverageDurationString (rqptr, &bytes64, acptr->ResponseDurationCount[HTTP1]); *vecptr++ = acptr->BytesPerSecondMin[HTTP2]; *vecptr++ = acptr->BytesPerSecondMin[HTTP1]; *vecptr++ = acptr->BytesPerSecondMax[HTTP2]; *vecptr++ = acptr->BytesPerSecondMax[HTTP1]; *vecptr++ = acptr->BytesPerSecondAve[HTTP2]; *vecptr++ = acptr->BytesPerSecondAve[HTTP1]; FrameTotal64 = acptr->Http2FrameCountRx64; FrameTotal64 += acptr->Http2FrameCountTx64; *vecptr++ = &FrameTotal64; FrameRequest64 = acptr->Http2FrameRequestCountRx64; FrameRequest64 += acptr->Http2FrameRequestCountTx64; *vecptr++ = &FrameRequest64; *vecptr++ = PercentOf64 (&FrameRequest64, &FrameTotal64); if (acptr->RequestHttp2Count) *vecptr++ = (ulong)((float)FrameTotal64 / (float)acptr->RequestHttp2Count); else *vecptr++ = 0; *vecptr++ = &acptr->Http2FlowControlCount64; *vecptr++ = PercentOf64 (&acptr->Http2FlowControlCount64, &acptr->Http2FlowFrameCount64); *vecptr++ = &acptr->Http2FrameCountRx64; *vecptr++ = &acptr->Http2FrameCountTx64; InstanceMutexUnLock (INSTANCE_MUTEX_HTTPD); FaoCheck (sizeof(FaoVector), &FaoVector, vecptr, FI_LI); status = FaolToNet (rqptr, PageBeginFao, &FaoVector); if (VMSnok (status)) ErrorNoticed (rqptr, status, NULL, FI_LI); /***********************/ /* HTTP/2 list entries */ /***********************/ status = FaolToNet (rqptr, Http2TableFao, NULL); if (VMSnok (status)) ErrorNoticed (rqptr, status, NULL, FI_LI); EntryCount = 0; sys$gettim (&Time64); /* process the request list from least to most recent */ for (h2ptr = LIST_GET_HEAD(&Http2List); h2ptr != NULL; h2ptr = LIST_GET_NEXT(h2ptr)) { EntryCount++; vecptr = FaoVector; ConnectTime64 = h2ptr->ConnectTime64 - Time64; *vecptr++ = EntryCount % 2 ? " class=\"hlght\"" : ""; *vecptr++ = EntryCount; *vecptr++ = h2ptr->ServicePtr->RequestSchemeNamePtr; *vecptr++ = h2ptr->ServicePtr->ServerHostPort; *vecptr++ = h2ptr->ClientPtr->Lookup.HostName; *vecptr++ = h2ptr->ClientPtr->IpPort; *vecptr++ = DurationString (rqptr, &ConnectTime64); *vecptr++ = h2ptr->FrameCountRx; *vecptr++ = h2ptr->FrameCountTx; *vecptr++ = h2ptr->FlowControlCount; *vecptr++ = &h2ptr->BytesRawRx64; *vecptr++ = &h2ptr->BytesRawTx64; for (idx = HTTP2_WRITE_QUEUE_MAX; idx <= HTTP2_WRITE_QUEUE_LOW; idx++) *vecptr++ = LIST_GET_COUNT (&h2ptr->QueuedWriteList[idx]); *vecptr++ = h2ptr->QueuedWritePeak; *vecptr++ = ADMIN_REPORT_HPACK; *vecptr++ = h2ptr->ConnectNumber; if (h2ptr->HpackClientOutputCount) *vecptr++ = (h2ptr->HpackClientInputCount * 100) / h2ptr->HpackClientOutputCount; else *vecptr++ = 0; *vecptr++ = ADMIN_REPORT_HPACK; *vecptr++ = h2ptr->ConnectNumber; if (h2ptr->HpackServerInputCount) *vecptr++ = (h2ptr->HpackServerOutputCount * 100) / h2ptr->HpackServerInputCount; else *vecptr++ = 0; *vecptr++ = h2ptr->RequestCount; *vecptr++ = h2ptr->RequestPeak; *vecptr++ = h2ptr->RequestCurrent; if (rqptr->Http2Stream.Http2Ptr != NULL && rqptr->Http2Stream.Http2Ptr->ConnectNumber == h2ptr->ConnectNumber) *vecptr++ = "current"; else { *vecptr++ = "P\ +\ W"; *vecptr++ = ADMIN_REPORT_WATCH; *vecptr++ = h2ptr->ConnectNumber; *vecptr++ = ADMIN_REPORT_WATCH; *vecptr++ = h2ptr->ConnectNumber; *vecptr++ = h2ptr->ConnectNumber; *vecptr++ = ADMIN_REPORT_WATCH; *vecptr++ = h2ptr->ConnectNumber; } FaoCheck (sizeof(FaoVector), &FaoVector, vecptr, FI_LI); status = FaolToNet (rqptr, Http2EntryFao, &FaoVector); if (VMSnok (status)) ErrorNoticed (rqptr, status, NULL, FI_LI); } if (!EntryCount) FaolToNet (rqptr, Http2EmptyFao, NULL); /**************/ /* end report */ /**************/ vecptr = FaoVector; *vecptr++ = ADMIN_CONTROL_HTTP2_PURGE; *vecptr++ = ADMIN_CONTROL_HTTP2_PURGE_ALL, *vecptr++ = AdminRefresh(); FaolToNet (rqptr, PageEndFao, FaoVector); rqptr->rqResponse.PreExpired = PRE_EXPIRE_ADMIN; ResponseHeader200 (rqptr, "text/html", NULL); AdminEnd (rqptr); } /*****************************************************************************/ /* See NetTestBreak(). */ #if WATCH_MOD void Http2TestBreak (int every) { int count, total; HTTP2_STRUCT *h2ptr; HTTP2_STREAM_STRUCT *s2ptr; NETIO_STRUCT *ioptr; REQUEST_STRUCT *rqeptr; /*********/ /* begin */ /*********/ if (every < 0) every = 1; count = total = 0; for (h2ptr = LIST_GET_HEAD(&Http2List); h2ptr != NULL; h2ptr = LIST_GET_NEXT(h2ptr)) { if (++count % every) continue; ioptr = h2ptr->NetIoPtr; if (ioptr->Channel) { /* do not kill WATCHing request */ rqeptr = (REQUEST_STRUCT*)-1; for (s2ptr = LIST_GET_HEAD(&h2ptr->StreamList); s2ptr != NULL; s2ptr = LIST_GET_NEXT(s2ptr)) { rqeptr = (REQUEST_STRUCT*)s2ptr->RequestPtr; if (rqeptr == Watch.RequestPtr) break; } if (rqeptr == Watch.RequestPtr) continue; WatchThis (WATCHITM(h2ptr), WATCH_NETWORK, "BREAK2 !AZ", h2ptr->ServicePtr->ServerHostPort); sys$dassgn (ioptr->Channel); ioptr->Channel = 0; total++; for (s2ptr = LIST_GET_HEAD(&h2ptr->StreamList); s2ptr != NULL; s2ptr = LIST_GET_NEXT(s2ptr)) { rqeptr = (REQUEST_STRUCT*)s2ptr->RequestPtr; if (rqeptr->rqHeader.RequestUriPtr) WatchThis (WATCHITM(rqeptr), WATCH_NETWORK, "BREAK2 !AZ !AZ !AZ", rqeptr->ServicePtr->ServerHostPort, rqeptr->rqHeader.MethodName, rqeptr->rqHeader.RequestUriPtr); else WatchThis (WATCHITM(rqeptr), WATCH_NETWORK, "BREAK2 !AZ 0", rqeptr->ServicePtr->ServerHostPort); } } } FaoToStdout ("%HTTPD-I-HTTP2, !%T, !UL/!UL/!UL broken\n", 0, every, total, count); } #endif /* WATCH_MOD */ /*****************************************************************************/