1 /*
2  * Licensed to the Apache Software Foundation (ASF) under one
3  * or more contributor license agreements.  See the NOTICE file
4  * distributed with this work for additional information
5  * regarding copyright ownership.  The ASF licenses this file
6  * to you under the Apache License, Version 2.0 (the
7  * "License"); you may not use this file except in compliance
8  * with the License.  You may obtain a copy of the License at
9  *
10  *     http://www.apache.org/licenses/LICENSE-2.0
11  *
12  * Unless required by applicable law or agreed to in writing, software
13  * distributed under the License is distributed on an "AS IS" BASIS,
14  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15  * See the License for the specific language governing permissions and
16  * limitations under the License.
17  */
18 
19 /*
20 Regression test code for TS API HTTP hooks.  The code assumes there will only be one active transaction at a time.  It
21 verifies the event data parameter to the continuations triggered by the hooks is correct.
22 */
23 
24 #include <fstream>
25 #include <cstdlib>
26 
27 #include <ts/ts.h>
28 
29 // TSReleaseAssert() doesn't seem to produce any logging output for a debug build, so do both kinds of assert.
30 //
31 #define ALWAYS_ASSERT(EXPR) \
32   {                         \
33     TSAssert(EXPR);         \
34     TSReleaseAssert(EXPR);  \
35   }
36 
37 namespace
38 {
39 #define PINAME "test_hooks"
40 char PIName[] = PINAME;
41 
42 // NOTE:  It's important to flush this after writing so that a gold test using this plugin can examine the log before TS
43 // terminates.
44 //
45 std::fstream logFile;
46 
47 TSVConn activeVConn;
48 
49 TSHttpSsn activeSsn;
50 
51 TSHttpTxn activeTxn;
52 
53 int
transactionContFunc(TSCont,TSEvent event,void * eventData)54 transactionContFunc(TSCont, TSEvent event, void *eventData)
55 {
56   logFile << "Transaction: event=" << TSHttpEventNameLookup(event) << std::endl;
57 
58   TSDebug(PIName, "Transaction: event=%s(%d) eventData=%p", TSHttpEventNameLookup(event), event, eventData);
59 
60   switch (event) {
61   case TS_EVENT_HTTP_TXN_CLOSE: {
62     auto txn = static_cast<TSHttpTxn>(eventData);
63 
64     TSDebug(PIName, "Transaction: ssn=%p", TSHttpTxnSsnGet(txn));
65 
66     // Don't assume any order of continuation execution on the same hook.
67     ALWAYS_ASSERT((txn == activeTxn) or !activeTxn)
68 
69     ALWAYS_ASSERT(TSHttpTxnSsnGet(txn) == activeSsn)
70 
71     TSHttpTxnReenable(txn, TS_EVENT_HTTP_CONTINUE);
72   } break;
73 
74   case TS_EVENT_HTTP_READ_REQUEST_HDR: {
75     auto txn = static_cast<TSHttpTxn>(eventData);
76 
77     TSDebug(PIName, "Transaction: ssn=%p", TSHttpTxnSsnGet(txn));
78 
79     ALWAYS_ASSERT(txn == activeTxn)
80     ALWAYS_ASSERT(TSHttpTxnSsnGet(txn) == activeSsn)
81 
82     TSHttpTxnReenable(txn, TS_EVENT_HTTP_CONTINUE);
83   } break;
84 
85   default: {
86     ALWAYS_ASSERT(false)
87   } break;
88 
89   } // end switch
90 
91   return 0;
92 }
93 
94 TSCont tCont;
95 
96 int
sessionContFunc(TSCont,TSEvent event,void * eventData)97 sessionContFunc(TSCont, TSEvent event, void *eventData)
98 {
99   logFile << "Session: event=" << TSHttpEventNameLookup(event) << std::endl;
100 
101   TSDebug(PIName, "Session: event=%s(%d) eventData=%p", TSHttpEventNameLookup(event), event, eventData);
102 
103   switch (event) {
104   case TS_EVENT_HTTP_SSN_CLOSE: {
105     auto ssn = static_cast<TSHttpSsn>(eventData);
106 
107     // Don't assume any order of continuation execution on the same hook.
108     ALWAYS_ASSERT((ssn == activeSsn) or !activeSsn)
109 
110     TSHttpSsnReenable(ssn, TS_EVENT_HTTP_CONTINUE);
111   } break;
112 
113   case TS_EVENT_HTTP_TXN_START: {
114     auto txn = static_cast<TSHttpTxn>(eventData);
115 
116     // Don't assume any order of continuation execution on the same hook.
117     ALWAYS_ASSERT((txn == activeTxn) or !activeTxn)
118 
119     TSDebug(PIName, "Session: ssn=%p", TSHttpTxnSsnGet(txn));
120 
121     ALWAYS_ASSERT(TSHttpTxnSsnGet(txn) == activeSsn)
122 
123     TSHttpTxnHookAdd(txn, TS_HTTP_READ_REQUEST_HDR_HOOK, tCont);
124     TSHttpTxnHookAdd(txn, TS_HTTP_TXN_CLOSE_HOOK, tCont);
125 
126     TSHttpTxnReenable(txn, TS_EVENT_HTTP_CONTINUE);
127   } break;
128 
129   case TS_EVENT_HTTP_TXN_CLOSE: {
130     auto txn = static_cast<TSHttpTxn>(eventData);
131 
132     TSDebug(PIName, "Session: ssn=%p", TSHttpTxnSsnGet(txn));
133 
134     // Don't assume any order of continuation execution on the same hook.
135     ALWAYS_ASSERT((txn == activeTxn) or !activeTxn)
136 
137     ALWAYS_ASSERT(TSHttpTxnSsnGet(txn) == activeSsn)
138 
139     TSHttpTxnReenable(txn, TS_EVENT_HTTP_CONTINUE);
140   } break;
141 
142   case TS_EVENT_HTTP_READ_REQUEST_HDR: {
143     auto txn = static_cast<TSHttpTxn>(eventData);
144 
145     TSDebug(PIName, "Session: ssn=%p", TSHttpTxnSsnGet(txn));
146 
147     ALWAYS_ASSERT(txn == activeTxn)
148     ALWAYS_ASSERT(TSHttpTxnSsnGet(txn) == activeSsn)
149 
150     TSHttpTxnReenable(txn, TS_EVENT_HTTP_CONTINUE);
151   } break;
152 
153   default: {
154     ALWAYS_ASSERT(false)
155   } break;
156 
157   } // end switch
158 
159   return 0;
160 }
161 
162 TSCont sCont;
163 
164 int
globalContFunc(TSCont,TSEvent event,void * eventData)165 globalContFunc(TSCont, TSEvent event, void *eventData)
166 {
167   logFile << "Global: event=" << TSHttpEventNameLookup(event) << std::endl;
168 
169   TSDebug(PIName, "Global: event=%s(%d) eventData=%p", TSHttpEventNameLookup(event), event, eventData);
170 
171   switch (event) {
172   case TS_EVENT_VCONN_START: {
173     ALWAYS_ASSERT(!activeVConn)
174 
175     auto vConn = static_cast<TSVConn>(eventData);
176 
177     activeVConn = vConn;
178 
179     logFile << "Global: ssl flag=" << TSVConnIsSsl(vConn) << std::endl;
180 
181     TSVConnReenable(vConn);
182   } break;
183   case TS_EVENT_SSL_CERT:
184   case TS_EVENT_SSL_SERVERNAME: {
185     auto vConn = static_cast<TSVConn>(eventData);
186 
187     ALWAYS_ASSERT(vConn == activeVConn)
188 
189     logFile << "Global: ssl flag=" << TSVConnIsSsl(vConn) << std::endl;
190 
191     TSVConnReenable(vConn);
192   } break;
193 
194   case TS_EVENT_VCONN_CLOSE: {
195     auto vConn = static_cast<TSVConn>(eventData);
196 
197     ALWAYS_ASSERT(vConn == activeVConn)
198 
199     logFile << "Global: ssl flag=" << TSVConnIsSsl(vConn) << std::endl;
200 
201     TSVConnReenable(vConn);
202 
203     activeVConn = nullptr;
204   } break;
205 
206   case TS_EVENT_HTTP_SSN_START: {
207     ALWAYS_ASSERT(!activeSsn)
208 
209     auto ssn = static_cast<TSHttpSsn>(eventData);
210 
211     activeSsn = ssn;
212 
213     TSHttpSsnHookAdd(ssn, TS_HTTP_READ_REQUEST_HDR_HOOK, sCont);
214     TSHttpSsnHookAdd(ssn, TS_HTTP_SSN_CLOSE_HOOK, sCont);
215     TSHttpSsnHookAdd(ssn, TS_HTTP_TXN_START_HOOK, sCont);
216     TSHttpSsnHookAdd(ssn, TS_HTTP_TXN_CLOSE_HOOK, sCont);
217 
218     TSHttpSsnReenable(ssn, TS_EVENT_HTTP_CONTINUE);
219   } break;
220 
221   case TS_EVENT_HTTP_SSN_CLOSE: {
222     auto ssn = static_cast<TSHttpSsn>(eventData);
223 
224     ALWAYS_ASSERT(ssn == activeSsn)
225 
226     activeSsn = nullptr;
227 
228     TSHttpSsnReenable(ssn, TS_EVENT_HTTP_CONTINUE);
229   } break;
230 
231   case TS_EVENT_HTTP_TXN_START: {
232     ALWAYS_ASSERT(!activeTxn)
233 
234     auto txn = static_cast<TSHttpTxn>(eventData);
235 
236     TSDebug(PIName, "Global: ssn=%p", TSHttpTxnSsnGet(txn));
237 
238     activeTxn = txn;
239 
240     ALWAYS_ASSERT(TSHttpTxnSsnGet(txn) == activeSsn)
241 
242     TSHttpTxnReenable(txn, TS_EVENT_HTTP_CONTINUE);
243   } break;
244 
245   case TS_EVENT_HTTP_TXN_CLOSE: {
246     auto txn = static_cast<TSHttpTxn>(eventData);
247 
248     TSDebug(PIName, "Global: ssn=%p", TSHttpTxnSsnGet(txn));
249 
250     ALWAYS_ASSERT(txn == activeTxn)
251     ALWAYS_ASSERT(TSHttpTxnSsnGet(txn) == activeSsn)
252 
253     activeTxn = nullptr;
254 
255     TSHttpTxnReenable(txn, TS_EVENT_HTTP_CONTINUE);
256   } break;
257 
258   case TS_EVENT_HTTP_READ_REQUEST_HDR: {
259     auto txn = static_cast<TSHttpTxn>(eventData);
260 
261     TSDebug(PIName, "Global: ssn=%p", TSHttpTxnSsnGet(txn));
262 
263     ALWAYS_ASSERT(txn == activeTxn)
264     ALWAYS_ASSERT(TSHttpTxnSsnGet(txn) == activeSsn)
265 
266     TSHttpTxnReenable(txn, TS_EVENT_HTTP_CONTINUE);
267   } break;
268 
269   default: {
270     ALWAYS_ASSERT(false)
271   } break;
272 
273   } // end switch
274 
275   return 0;
276 }
277 
278 TSCont gCont;
279 
280 } // end anonymous namespace
281 
282 void
TSPluginInit(int argc,const char * argv[])283 TSPluginInit(int argc, const char *argv[])
284 {
285   TSPluginRegistrationInfo info;
286 
287   info.plugin_name   = PIName;
288   info.vendor_name   = "Apache Software Foundation";
289   info.support_email = "dev@trafficserver.apache.org";
290 
291   if (TSPluginRegister(&info) != TS_SUCCESS) {
292     TSError(PINAME ": Plugin registration failed");
293 
294     return;
295   }
296 
297   const char *fileSpec = std::getenv("OUTPUT_FILE");
298 
299   if (nullptr == fileSpec) {
300     TSError(PINAME ": Environment variable OUTPUT_FILE not found.");
301 
302     return;
303   }
304 
305   // Disable output buffering for logFile, so that explicit flushing is not necessary.
306   logFile.rdbuf()->pubsetbuf(nullptr, 0);
307 
308   logFile.open(fileSpec, std::ios::out);
309   if (!logFile.is_open()) {
310     TSError(PINAME ": could not open log file \"%s\"", fileSpec);
311 
312     return;
313   }
314 
315   // Mutex to protext the logFile object.
316   //
317   TSMutex mtx = TSMutexCreate();
318 
319   gCont = TSContCreate(globalContFunc, mtx);
320 
321   // Setup the global hook
322   TSHttpHookAdd(TS_HTTP_READ_REQUEST_HDR_HOOK, gCont);
323   TSHttpHookAdd(TS_HTTP_SSN_START_HOOK, gCont);
324   TSHttpHookAdd(TS_HTTP_SSN_CLOSE_HOOK, gCont);
325   TSHttpHookAdd(TS_HTTP_TXN_START_HOOK, gCont);
326   TSHttpHookAdd(TS_HTTP_TXN_CLOSE_HOOK, gCont);
327   TSHttpHookAdd(TS_SSL_CERT_HOOK, gCont);
328   TSHttpHookAdd(TS_SSL_SERVERNAME_HOOK, gCont);
329   // NOTE: as of January 2019 these two hooks are only triggered for TLS connections.  It seems that, at trafficserver
330   // startup, spurious data on the TLS TCP port may cause trafficserver to attempt (and fail) to create a TLS
331   // connection.  If this happens, it will result in TS_VCONN_START_HOOK being triggered, and then TS_VCONN_CLOSE_HOOK
332   // will be triggered when the connection closes due to failure.
333   //
334   TSHttpHookAdd(TS_VCONN_START_HOOK, gCont);
335   TSHttpHookAdd(TS_VCONN_CLOSE_HOOK, gCont);
336 
337   // TSHttpHookAdd(TS_SSL_SESSION_HOOK, gCont); -- Event is TS_EVENT_SSL_SESSION_NEW -- Event data is TSHttpSsn
338   // TSHttpHookAdd(TS_SSL_SERVER_VERIFY_HOOK, gCont);
339   // TSHttpHookAdd(TS_SSL_VERIFY_CLIENT_HOOK, gCont);
340   // TSHttpHookAdd(TS_VCONN_OUTBOUND_START_HOOK, gCont);
341   // TSHttpHookAdd(TS_VCONN_OUTBOUND_CLOSE_HOOK, gCont);
342 
343   sCont = TSContCreate(sessionContFunc, mtx);
344 
345   tCont = TSContCreate(transactionContFunc, mtx);
346 }
347 
348 namespace
349 {
350 class Cleanup
351 {
352 public:
~Cleanup()353   ~Cleanup()
354   {
355     // In practice it is not strictly necessary to destroy remaining continuations on program exit.
356 
357     if (tCont) {
358       TSContDestroy(tCont);
359     }
360     if (sCont) {
361       TSContDestroy(sCont);
362     }
363     if (gCont) {
364       TSContDestroy(gCont);
365     }
366   }
367 };
368 
369 // Do any needed cleanup for this source file at program termination time.
370 //
371 Cleanup cleanup;
372 
373 } // namespace
374