1 /** @file
2 
3   Traffic generator intercept plugin
4 
5   @section license License
6 
7   Licensed to the Apache Software Foundation (ASF) under one
8   or more contributor license agreements.  See the NOTICE file
9   distributed with this work for additional information
10   regarding copyright ownership.  The ASF licenses this file
11   to you under the Apache License, Version 2.0 (the
12   "License"); you may not use this file except in compliance
13   with the License.  You may obtain a copy of the License at
14 
15       http://www.apache.org/licenses/LICENSE-2.0
16 
17   Unless required by applicable law or agreed to in writing, software
18   distributed under the License is distributed on an "AS IS" BASIS,
19   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
20   See the License for the specific language governing permissions and
21   limitations under the License.
22  */
23 
24 #include <ts/ts.h>
25 #include <ts/remap.h>
26 #include <cerrno>
27 #include <cinttypes>
28 #include <iterator>
29 #include <cstdio>
30 #include <cstdlib>
31 #include <cstring>
32 #include <ctime>
33 #include <unistd.h>
34 
35 // Generator plugin
36 //
37 // The incoming URL must consist of 2 or more path components. The first
38 // component indicates cacheability, the second the number of bytes in the
39 // response body. Subsequent path components are ignored, so they can be used
40 // to uniqify cache keys (assuming that caching is enabled).
41 //
42 // Examples,
43 //
44 // /cache/100/6b1e2b1fa555b52124cb4e511acbae2a
45 //      -> return 100 bytes, cached
46 //
47 // /cache/21474836480/large/response
48 //      -> return 20G bytes, cached
49 //
50 // TODO Add query parameter options.
51 //
52 // It would be pretty useful to add support for query parameters to tweak how the response
53 // is handled. The following parameters seem useful:
54 //
55 //  - delay before sending the response headers
56 //  - delay before sending the response body
57 //  - turn off local caching of the response (ie. even for /cache/* URLs)
58 //  - force chunked encoding by omitting Content-Length
59 //  - specify the Cache-Control max-age
60 //
61 // We ought to scale the IO buffer size in proportion to the size of the response we are generating.
62 
63 #define PLUGIN "generator"
64 
65 #define VDEBUG(fmt, ...) TSDebug(PLUGIN, fmt, ##__VA_ARGS__)
66 
67 #if DEBUG
68 #define VERROR(fmt, ...) TSDebug(PLUGIN, fmt, ##__VA_ARGS__)
69 #else
70 #define VERROR(fmt, ...) TSError("[%s] %s: " fmt, PLUGIN, __FUNCTION__, ##__VA_ARGS__)
71 #endif
72 
73 #define VIODEBUG(vio, fmt, ...)                                                                                              \
74   VDEBUG("vio=%p vio.cont=%p, vio.cont.data=%p, vio.vc=%p " fmt, (vio), TSVIOContGet(vio), TSContDataGet(TSVIOContGet(vio)), \
75          TSVIOVConnGet(vio), ##__VA_ARGS__)
76 
77 static TSCont TxnHook;
78 static uint8_t GeneratorData[32 * 1024];
79 
80 static int StatCountBytes     = -1;
81 static int StatCountResponses = -1;
82 
83 static int GeneratorInterceptionHook(TSCont contp, TSEvent event, void *edata);
84 static int GeneratorTxnHook(TSCont contp, TSEvent event, void *edata);
85 
86 struct GeneratorRequest;
87 
88 union argument_type {
89   void *ptr;
90   intptr_t ecode;
91   TSVConn vc;
92   TSVIO vio;
93   TSHttpTxn txn;
94   GeneratorRequest *grq;
95 
argument_type(void * _p)96   argument_type(void *_p) : ptr(_p) {}
97 };
98 
99 // Return the length of a string literal (without the trailing NUL).
100 template <unsigned N>
101 unsigned
lengthof(const char (&)[N])102 lengthof(const char (&)[N])
103 {
104   return N - 1;
105 }
106 
107 // This structure represents the state of a streaming I/O request. It
108 // is directional (ie. either a read or a write). We need two of these
109 // for each TSVConn; one to push data into the TSVConn and one to pull
110 // data out.
111 struct IOChannel {
112   TSVIO vio = nullptr;
113   TSIOBuffer iobuf;
114   TSIOBufferReader reader;
115 
IOChannelIOChannel116   IOChannel() : iobuf(TSIOBufferSizedCreate(TS_IOBUFFER_SIZE_INDEX_32K)), reader(TSIOBufferReaderAlloc(iobuf)) {}
~IOChannelIOChannel117   ~IOChannel()
118   {
119     if (this->reader) {
120       TSIOBufferReaderFree(this->reader);
121     }
122 
123     if (this->iobuf) {
124       TSIOBufferDestroy(this->iobuf);
125     }
126   }
127 
128   void
readIOChannel129   read(TSVConn vc, TSCont contp)
130   {
131     this->vio = TSVConnRead(vc, contp, this->iobuf, INT64_MAX);
132   }
133 
134   void
writeIOChannel135   write(TSVConn vc, TSCont contp)
136   {
137     this->vio = TSVConnWrite(vc, contp, this->reader, INT64_MAX);
138   }
139 };
140 
141 struct GeneratorHttpHeader {
142   TSMBuffer buffer;
143   TSMLoc header;
144   TSHttpParser parser;
145 
GeneratorHttpHeaderGeneratorHttpHeader146   GeneratorHttpHeader()
147   {
148     this->buffer = TSMBufferCreate();
149     this->header = TSHttpHdrCreate(this->buffer);
150     this->parser = TSHttpParserCreate();
151   }
152 
~GeneratorHttpHeaderGeneratorHttpHeader153   ~GeneratorHttpHeader()
154   {
155     if (this->parser) {
156       TSHttpParserDestroy(this->parser);
157     }
158 
159     TSHttpHdrDestroy(this->buffer, this->header);
160     TSHandleMLocRelease(this->buffer, TS_NULL_MLOC, this->header);
161     TSMBufferDestroy(this->buffer);
162   }
163 };
164 
165 struct GeneratorRequest {
166   off_t nbytes   = 0; // Number of bytes to generate.
167   unsigned flags = 0;
168   unsigned delay = 0; // Milliseconds to delay before sending a response.
169   unsigned maxage;    // Max age for cache responses.
170   IOChannel readio;
171   IOChannel writeio;
172   GeneratorHttpHeader rqheader;
173 
174   enum {
175     CACHEABLE = 0x0001,
176     ISHEAD    = 0x0002,
177   };
178 
GeneratorRequestGeneratorRequest179   GeneratorRequest() : maxage(60 * 60 * 24) {}
180   ~GeneratorRequest() = default;
181 };
182 
183 // Destroy a generator request, including the per-txn continuation.
184 static void
GeneratorRequestDestroy(GeneratorRequest * grq,TSVIO vio,TSCont contp)185 GeneratorRequestDestroy(GeneratorRequest *grq, TSVIO vio, TSCont contp)
186 {
187   if (vio) {
188     TSVConnClose(TSVIOVConnGet(vio));
189   }
190 
191   TSContDestroy(contp);
192   delete grq;
193 }
194 
195 static off_t
GeneratorParseByteCount(const char * ptr,const char * end)196 GeneratorParseByteCount(const char *ptr, const char *end)
197 {
198   off_t nbytes = 0;
199 
200   for (; ptr < end; ++ptr) {
201     switch (*ptr) {
202     case '0':
203       nbytes = nbytes * 10 + 0;
204       break;
205     case '1':
206       nbytes = nbytes * 10 + 1;
207       break;
208     case '2':
209       nbytes = nbytes * 10 + 2;
210       break;
211     case '3':
212       nbytes = nbytes * 10 + 3;
213       break;
214     case '4':
215       nbytes = nbytes * 10 + 4;
216       break;
217     case '5':
218       nbytes = nbytes * 10 + 5;
219       break;
220     case '6':
221       nbytes = nbytes * 10 + 6;
222       break;
223     case '7':
224       nbytes = nbytes * 10 + 7;
225       break;
226     case '8':
227       nbytes = nbytes * 10 + 8;
228       break;
229     case '9':
230       nbytes = nbytes * 10 + 9;
231       break;
232     default:
233       return -1;
234     }
235   }
236 
237   return nbytes;
238 }
239 
240 static void
HeaderFieldDateSet(GeneratorHttpHeader & http,const char * field_name,int64_t field_len,time_t value)241 HeaderFieldDateSet(GeneratorHttpHeader &http, const char *field_name, int64_t field_len, time_t value)
242 {
243   TSMLoc field;
244 
245   TSMimeHdrFieldCreateNamed(http.buffer, http.header, field_name, field_len, &field);
246   TSMimeHdrFieldValueDateSet(http.buffer, http.header, field, value);
247   TSMimeHdrFieldAppend(http.buffer, http.header, field);
248   TSHandleMLocRelease(http.buffer, http.header, field);
249 }
250 
251 static void
HeaderFieldIntSet(GeneratorHttpHeader & http,const char * field_name,int64_t field_len,int64_t value)252 HeaderFieldIntSet(GeneratorHttpHeader &http, const char *field_name, int64_t field_len, int64_t value)
253 {
254   TSMLoc field;
255 
256   TSMimeHdrFieldCreateNamed(http.buffer, http.header, field_name, field_len, &field);
257   TSMimeHdrFieldValueInt64Set(http.buffer, http.header, field, -1, value);
258   TSMimeHdrFieldAppend(http.buffer, http.header, field);
259   TSHandleMLocRelease(http.buffer, http.header, field);
260 }
261 
262 static void
HeaderFieldStringSet(GeneratorHttpHeader & http,const char * field_name,int64_t field_len,const char * value)263 HeaderFieldStringSet(GeneratorHttpHeader &http, const char *field_name, int64_t field_len, const char *value)
264 {
265   TSMLoc field;
266 
267   TSMimeHdrFieldCreateNamed(http.buffer, http.header, field_name, field_len, &field);
268   TSMimeHdrFieldValueStringSet(http.buffer, http.header, field, -1, value, -1);
269   TSMimeHdrFieldAppend(http.buffer, http.header, field);
270   TSHandleMLocRelease(http.buffer, http.header, field);
271 }
272 
273 static int64_t
GeneratorGetRequestHeader(GeneratorHttpHeader & request,const char * field_name,int64_t field_len,int64_t default_value)274 GeneratorGetRequestHeader(GeneratorHttpHeader &request, const char *field_name, int64_t field_len, int64_t default_value)
275 {
276   TSMLoc field;
277 
278   field = TSMimeHdrFieldFind(request.buffer, request.header, field_name, field_len);
279   if (field != TS_NULL_MLOC) {
280     default_value = TSMimeHdrFieldValueInt64Get(request.buffer, request.header, field, -1);
281   }
282 
283   TSHandleMLocRelease(request.buffer, request.header, field);
284   return default_value;
285 }
286 
287 static void
GeneratorWriteResponseHeader(GeneratorRequest * grq,TSCont contp)288 GeneratorWriteResponseHeader(GeneratorRequest *grq, TSCont contp)
289 {
290   GeneratorHttpHeader response;
291 
292   VDEBUG("writing response header");
293 
294   TSReleaseAssert(TSHttpHdrTypeSet(response.buffer, response.header, TS_HTTP_TYPE_RESPONSE) == TS_SUCCESS);
295   TSReleaseAssert(TSHttpHdrVersionSet(response.buffer, response.header, TS_HTTP_VERSION(1, 1)) == TS_SUCCESS);
296 
297   TSReleaseAssert(TSHttpHdrStatusSet(response.buffer, response.header, TS_HTTP_STATUS_OK) == TS_SUCCESS);
298   TSHttpHdrReasonSet(response.buffer, response.header, TSHttpHdrReasonLookup(TS_HTTP_STATUS_OK), -1);
299 
300   // Set the Content-Length header.
301   HeaderFieldIntSet(response, TS_MIME_FIELD_CONTENT_LENGTH, TS_MIME_LEN_CONTENT_LENGTH, grq->nbytes);
302 
303   // Set the Cache-Control header.
304   if (grq->flags & GeneratorRequest::CACHEABLE) {
305     char buf[64];
306 
307     snprintf(buf, sizeof(buf), "max-age=%u, public", grq->maxage);
308     HeaderFieldStringSet(response, TS_MIME_FIELD_CACHE_CONTROL, TS_MIME_LEN_CACHE_CONTROL, buf);
309     HeaderFieldDateSet(response, TS_MIME_FIELD_LAST_MODIFIED, TS_MIME_LEN_LAST_MODIFIED, time(nullptr));
310   } else {
311     HeaderFieldStringSet(response, TS_MIME_FIELD_CACHE_CONTROL, TS_MIME_LEN_CACHE_CONTROL, "private");
312   }
313 
314   // Write the header to the IO buffer. Set the VIO bytes so that we can get a WRITE_COMPLETE
315   // event when this is done.
316   int hdrlen = TSHttpHdrLengthGet(response.buffer, response.header);
317 
318   TSHttpHdrPrint(response.buffer, response.header, grq->writeio.iobuf);
319   TSVIONBytesSet(grq->writeio.vio, hdrlen);
320   TSVIOReenable(grq->writeio.vio);
321 
322   TSStatIntIncrement(StatCountBytes, hdrlen);
323 }
324 
325 static bool
GeneratorParseRequest(GeneratorRequest * grq)326 GeneratorParseRequest(GeneratorRequest *grq)
327 {
328   TSMLoc url;
329   const char *path;
330   const char *end;
331   int pathsz;
332   unsigned count = 0;
333 
334   // First, make sure this is a GET request.
335   path = TSHttpHdrMethodGet(grq->rqheader.buffer, grq->rqheader.header, &pathsz);
336   if (path != TS_HTTP_METHOD_GET && path != TS_HTTP_METHOD_HEAD) {
337     VDEBUG("%.*s method is not supported", pathsz, path);
338     return false;
339   }
340 
341   if (path == TS_HTTP_METHOD_HEAD) {
342     grq->flags |= GeneratorRequest::ISHEAD;
343   }
344 
345   grq->delay  = GeneratorGetRequestHeader(grq->rqheader, "Generator-Delay", lengthof("Generator-Delay"), grq->delay);
346   grq->maxage = GeneratorGetRequestHeader(grq->rqheader, "Generator-MaxAge", lengthof("Generator-MaxAge"), grq->maxage);
347 
348   // Next, parse our parameters out of the URL.
349   TSReleaseAssert(TSHttpHdrUrlGet(grq->rqheader.buffer, grq->rqheader.header, &url) == TS_SUCCESS);
350   TSReleaseAssert(path = TSUrlPathGet(grq->rqheader.buffer, url, &pathsz));
351 
352   VDEBUG("requested path is %.*s", pathsz, path);
353 
354   end = path + pathsz;
355   while (path < end) {
356     const char *sep = path;
357     size_t nbytes;
358 
359     while (*sep != '/' && sep < end) {
360       ++sep;
361     }
362 
363     nbytes = std::distance(path, sep);
364     if (nbytes) {
365       VDEBUG("path component is %.*s", (int)nbytes, path);
366 
367       switch (count) {
368       case 0:
369         // First path component is "cache" or "nocache".
370         if (memcmp(path, "cache", 5) == 0) {
371           grq->flags |= GeneratorRequest::CACHEABLE;
372         } else if (memcmp(path, "nocache", 7) == 0) {
373           grq->flags &= ~GeneratorRequest::CACHEABLE;
374         } else {
375           VDEBUG("first component is %.*s, expecting 'cache' or 'nocache'", (int)nbytes, path);
376           goto fail;
377         }
378 
379         break;
380 
381       case 1:
382         // Second path component is a byte count.
383         grq->nbytes = GeneratorParseByteCount(path, sep);
384         VDEBUG("generator byte count is %lld", (long long)grq->nbytes);
385         if (grq->nbytes >= 0) {
386           // We don't care about any other path components.
387           TSHandleMLocRelease(grq->rqheader.buffer, grq->rqheader.header, url);
388           return true;
389         }
390 
391         goto fail;
392       }
393 
394       ++count;
395     }
396 
397     path = sep + 1;
398   }
399 
400 fail:
401   TSHandleMLocRelease(grq->rqheader.buffer, grq->rqheader.header, url);
402   return false;
403 }
404 
405 // Handle events from TSHttpTxnServerIntercept. The intercept
406 // starts with TS_EVENT_NET_ACCEPT, and then continues with
407 // TSVConn events.
408 static int
GeneratorInterceptionHook(TSCont contp,TSEvent event,void * edata)409 GeneratorInterceptionHook(TSCont contp, TSEvent event, void *edata)
410 {
411   argument_type arg(edata);
412 
413   VDEBUG("contp=%p, event=%s (%d), edata=%p", contp, TSHttpEventNameLookup(event), event, arg.ptr);
414 
415   switch (event) {
416   case TS_EVENT_NET_ACCEPT: {
417     // TS_EVENT_NET_ACCEPT will be delivered when the server intercept
418     // is set up by the core. We just need to allocate a generator
419     // request state and start reading the VC.
420     GeneratorRequest *grq = new GeneratorRequest();
421 
422     TSStatIntIncrement(StatCountResponses, 1);
423     VDEBUG("allocated server intercept generator grq=%p", grq);
424 
425     // This continuation was allocated in GeneratorTxnHook. Reset the
426     // data to keep track of this generator request.
427     TSContDataSet(contp, grq);
428 
429     // Start reading the request from the server intercept VC.
430     grq->readio.read(arg.vc, contp);
431     VIODEBUG(grq->readio.vio, "started reading generator request");
432 
433     return TS_EVENT_NONE;
434   }
435 
436   case TS_EVENT_NET_ACCEPT_FAILED: {
437     // TS_EVENT_NET_ACCEPT_FAILED will be delivered if the
438     // transaction is cancelled before we start tunnelling
439     // through the server intercept. One way that this can happen
440     // is if the intercept is attached early, and then we server
441     // the document out of cache.
442     argument_type cdata(TSContDataGet(contp));
443 
444     // There's nothing to do here except nuke the continuation
445     // that was allocated in GeneratorTxnHook().
446     VDEBUG("cancelling server intercept request for txn=%p", cdata.txn);
447 
448     TSContDestroy(contp);
449     return TS_EVENT_NONE;
450   }
451 
452   case TS_EVENT_VCONN_READ_READY: {
453     argument_type cdata           = TSContDataGet(contp);
454     GeneratorHttpHeader &rqheader = cdata.grq->rqheader;
455 
456     VDEBUG("reading vio=%p vc=%p, grq=%p", arg.vio, TSVIOVConnGet(arg.vio), cdata.grq);
457 
458     TSIOBufferBlock blk;
459     ssize_t consumed     = 0;
460     TSParseResult result = TS_PARSE_CONT;
461 
462     for (blk = TSIOBufferReaderStart(cdata.grq->readio.reader); blk; blk = TSIOBufferBlockNext(blk)) {
463       const char *ptr;
464       const char *end;
465       int64_t nbytes;
466 
467       ptr = TSIOBufferBlockReadStart(blk, cdata.grq->readio.reader, &nbytes);
468       if (ptr == nullptr || nbytes == 0) {
469         continue;
470       }
471 
472       end    = ptr + nbytes;
473       result = TSHttpHdrParseReq(rqheader.parser, rqheader.buffer, rqheader.header, &ptr, end);
474       switch (result) {
475       case TS_PARSE_ERROR:
476         // If we got a bad request, just shut it down.
477         VDEBUG("bad request on grq=%p, sending an error", cdata.grq);
478         GeneratorRequestDestroy(cdata.grq, arg.vio, contp);
479         return TS_EVENT_ERROR;
480 
481       case TS_PARSE_DONE:
482         // Check the response.
483         VDEBUG("parsed request on grq=%p, sending a response ", cdata.grq);
484         if (!GeneratorParseRequest(cdata.grq)) {
485           // We got a syntactically bad URL. It would be graceful to send
486           // a 400 response, but we are graceless and just fail the
487           // transaction.
488           GeneratorRequestDestroy(cdata.grq, arg.vio, contp);
489           return TS_EVENT_ERROR;
490         }
491 
492         // If this is a HEAD request, we don't need to send any bytes.
493         if (cdata.grq->flags & GeneratorRequest::ISHEAD) {
494           cdata.grq->nbytes = 0;
495         }
496 
497         // Start the vconn write.
498         cdata.grq->writeio.write(TSVIOVConnGet(arg.vio), contp);
499         TSVIONBytesSet(cdata.grq->writeio.vio, 0);
500 
501         if (cdata.grq->delay > 0) {
502           VDEBUG("delaying response by %ums", cdata.grq->delay);
503           TSContScheduleOnPool(contp, cdata.grq->delay, TS_THREAD_POOL_NET);
504           return TS_EVENT_NONE;
505         }
506 
507         GeneratorWriteResponseHeader(cdata.grq, contp);
508         return TS_EVENT_NONE;
509 
510       case TS_PARSE_CONT:
511         // We consumed the buffer we got minus the remainder.
512         consumed += (nbytes - std::distance(ptr, end));
513       }
514     }
515 
516     TSReleaseAssert(result == TS_PARSE_CONT);
517 
518     // Reenable the read VIO to get more events.
519     TSVIOReenable(arg.vio);
520     return TS_EVENT_NONE;
521   }
522 
523   case TS_EVENT_VCONN_WRITE_READY: {
524     argument_type cdata = TSContDataGet(contp);
525 
526     if (cdata.grq->nbytes) {
527       int64_t nbytes;
528 
529       if (cdata.grq->nbytes >= static_cast<ssize_t>(sizeof(GeneratorData))) {
530         nbytes = sizeof(GeneratorData);
531       } else {
532         nbytes = cdata.grq->nbytes % sizeof(GeneratorData);
533       }
534 
535       VIODEBUG(arg.vio, "writing %" PRId64 " bytes for grq=%p", nbytes, cdata.grq);
536       nbytes = TSIOBufferWrite(cdata.grq->writeio.iobuf, GeneratorData, nbytes);
537 
538       cdata.grq->nbytes -= nbytes;
539       TSStatIntIncrement(StatCountBytes, nbytes);
540 
541       // Update the number of bytes to write.
542       TSVIONBytesSet(arg.vio, TSVIONBytesGet(arg.vio) + nbytes);
543       TSVIOReenable(arg.vio);
544     }
545 
546     return TS_EVENT_NONE;
547   }
548 
549   case TS_EVENT_ERROR:
550   case TS_EVENT_VCONN_EOS: {
551     argument_type cdata = TSContDataGet(contp);
552 
553     VIODEBUG(arg.vio, "received EOS or ERROR for grq=%p", cdata.grq);
554     GeneratorRequestDestroy(cdata.grq, arg.vio, contp);
555     return event == TS_EVENT_ERROR ? TS_EVENT_ERROR : TS_EVENT_NONE;
556   }
557 
558   case TS_EVENT_VCONN_READ_COMPLETE:
559     // We read data forever, so we should never get a READ_COMPLETE.
560     VIODEBUG(arg.vio, "unexpected TS_EVENT_VCONN_READ_COMPLETE");
561     return TS_EVENT_NONE;
562 
563   case TS_EVENT_VCONN_WRITE_COMPLETE: {
564     argument_type cdata = TSContDataGet(contp);
565 
566     // If we still have bytes to write, kick off a new write operation, otherwise
567     // we are done and we can shut down the VC.
568     if (cdata.grq->nbytes) {
569       cdata.grq->writeio.write(TSVIOVConnGet(arg.vio), contp);
570       TSVIONBytesSet(cdata.grq->writeio.vio, cdata.grq->nbytes);
571     } else {
572       VIODEBUG(arg.vio, "TS_EVENT_VCONN_WRITE_COMPLETE %" PRId64 " todo", TSVIONTodoGet(arg.vio));
573       GeneratorRequestDestroy(cdata.grq, arg.vio, contp);
574     }
575 
576     return TS_EVENT_NONE;
577   }
578 
579   case TS_EVENT_TIMEOUT: {
580     // Our response delay expired, so write the headers now, which
581     // will also trigger the read+write event flow.
582     argument_type cdata = TSContDataGet(contp);
583     GeneratorWriteResponseHeader(cdata.grq, contp);
584     return TS_EVENT_NONE;
585   }
586 
587   case TS_EVENT_VCONN_INACTIVITY_TIMEOUT:
588     VERROR("unexpected event %s (%d) edata=%p", TSHttpEventNameLookup(event), event, arg.ptr);
589     return TS_EVENT_ERROR;
590 
591   default:
592     VERROR("unexpected event %s (%d) edata=%p", TSHttpEventNameLookup(event), event, arg.ptr);
593     return TS_EVENT_ERROR;
594   }
595 }
596 
597 // Handle events that occur on the TSHttpTxn.
598 static int
GeneratorTxnHook(TSCont contp,TSEvent event,void * edata)599 GeneratorTxnHook(TSCont contp, TSEvent event, void *edata)
600 {
601   argument_type arg(edata);
602 
603   VDEBUG("contp=%p, event=%s (%d), edata=%p", contp, TSHttpEventNameLookup(event), event, edata);
604 
605   switch (event) {
606   case TS_EVENT_HTTP_CACHE_LOOKUP_COMPLETE: {
607     int status;
608 
609     TSReleaseAssert(TSHttpTxnCacheLookupStatusGet(arg.txn, &status) == TS_SUCCESS);
610     if (status != TS_CACHE_LOOKUP_HIT_FRESH) {
611       // This transaction is going to be a cache miss, so intercept it.
612       VDEBUG("intercepting origin server request for txn=%p", arg.txn);
613       TSHttpTxnServerIntercept(TSContCreate(GeneratorInterceptionHook, TSMutexCreate()), arg.txn);
614     }
615 
616     break;
617   }
618 
619   default:
620     VERROR("unexpected event %s (%d)", TSHttpEventNameLookup(event), event);
621     break;
622   }
623 
624   TSHttpTxnReenable(arg.txn, TS_EVENT_HTTP_CONTINUE);
625   return TS_EVENT_NONE;
626 }
627 
628 static void
GeneratorInitialize()629 GeneratorInitialize()
630 {
631   TxnHook = TSContCreate(GeneratorTxnHook, nullptr);
632   memset(GeneratorData, 'x', sizeof(GeneratorData));
633 
634   if (TSStatFindName("generator.response_bytes", &StatCountBytes) == TS_ERROR) {
635     StatCountBytes = TSStatCreate("generator.response_bytes", TS_RECORDDATATYPE_COUNTER, TS_STAT_NON_PERSISTENT, TS_STAT_SYNC_SUM);
636   }
637 
638   if (TSStatFindName("generator.response_count", &StatCountResponses) == TS_ERROR) {
639     StatCountResponses =
640       TSStatCreate("generator.response_count", TS_RECORDDATATYPE_COUNTER, TS_STAT_NON_PERSISTENT, TS_STAT_SYNC_COUNT);
641   }
642 }
643 
644 void
TSPluginInit(int,const char * [])645 TSPluginInit(int /* argc */, const char * /* argv */[])
646 {
647   TSPluginRegistrationInfo info;
648 
649   info.plugin_name   = (char *)PLUGIN;
650   info.vendor_name   = (char *)"Apache Software Foundation";
651   info.support_email = (char *)"dev@trafficserver.apache.org";
652 
653   if (TSPluginRegister(&info) != TS_SUCCESS) {
654     VERROR("plugin registration failed\n");
655   }
656 
657   GeneratorInitialize();
658 
659   // Wait until after the cache lookup to decide whether to
660   // intercept a request. For cache hits we will never intercept.
661   TSHttpHookAdd(TS_HTTP_CACHE_LOOKUP_COMPLETE_HOOK, TxnHook);
662 }
663 
664 TSReturnCode
TSRemapInit(TSRemapInterface *,char *,int)665 TSRemapInit(TSRemapInterface * /* api_info */, char * /* errbuf */, int /* errbuf_size */)
666 {
667   GeneratorInitialize();
668   return TS_SUCCESS;
669 }
670 
671 TSRemapStatus
TSRemapDoRemap(void *,TSHttpTxn txn,TSRemapRequestInfo *)672 TSRemapDoRemap(void * /* ih */, TSHttpTxn txn, TSRemapRequestInfo * /* rri ATS_UNUSED */)
673 {
674   TSHttpTxnHookAdd(txn, TS_HTTP_CACHE_LOOKUP_COMPLETE_HOOK, TxnHook);
675   return TSREMAP_NO_REMAP; // This plugin never rewrites anything.
676 }
677 
678 TSReturnCode
TSRemapNewInstance(int,char * [],void ** ih,char *,int)679 TSRemapNewInstance(int /* argc */, char * /* argv */[], void **ih, char * /* errbuf ATS_UNUSED */, int /* errbuf_size ATS_UNUSED */)
680 {
681   *ih = nullptr;
682   return TS_SUCCESS;
683 }
684 
685 void
TSRemapDeleteInstance(void *)686 TSRemapDeleteInstance(void * /* ih */)
687 {
688 }
689