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 "License");
7  * you may not use this file except in compliance with the
8  * 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,
13  * software distributed under the License is distributed on an
14  * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
15  * KIND, either express or implied.  See the License for the
16  * specific language governing permissions and limitations
17  * under the License.
18  */
19 
20 #include <iostream>
21 #include <sstream>
22 #include <cstdio>
23 #include <cstring>
24 #include "ts/ts.h"
25 #include "ts/remap.h"
26 
27 #include "money_trace.h"
28 
29 /**
30  * Allocate transaction data structure.
31  */
32 static struct txndata *
allocTransactionData()33 allocTransactionData()
34 {
35   LOG_DEBUG("allocating transaction state data.");
36   struct txndata *txn_data           = static_cast<struct txndata *>(TSmalloc(sizeof(struct txndata)));
37   txn_data->client_request_mt_header = nullptr;
38   txn_data->new_span_mt_header       = nullptr;
39   return txn_data;
40 }
41 
42 /**
43  * free any previously allocated transaction data.
44  */
45 static void
freeTransactionData(struct txndata * txn_data)46 freeTransactionData(struct txndata *txn_data)
47 {
48   LOG_DEBUG("de-allocating transaction state data.");
49 
50   if (txn_data != nullptr) {
51     LOG_DEBUG("freeing transaction data.");
52     TSfree(txn_data->client_request_mt_header);
53     TSfree(txn_data->new_span_mt_header);
54     TSfree(txn_data);
55   }
56 }
57 
58 /**
59  * The TS_EVENT_HTTP_CACHE_LOOKUP_COMPLETE event callback.
60  *
61  * If there is a cache hit only schedule a TS_HTTP_SEND_RESPONSE_HDR_HOOK
62  * continuation to send back the money trace header in the response to the
63  * client.
64  *
65  * If there is a cache miss, a new money trace header is created and a
66  * TS_HTTP_SEND_REQUES_HDR_HOOK continuation is scheduled to add the
67  * new money trace header to the parent request.
68  */
69 static void
mt_cache_lookup_check(TSCont contp,TSHttpTxn txnp,struct txndata * txn_data)70 mt_cache_lookup_check(TSCont contp, TSHttpTxn txnp, struct txndata *txn_data)
71 {
72   MT generator;
73   int cache_result = 0;
74   char *new_mt_header;
75 
76   if (TS_SUCCESS != TSHttpTxnCacheLookupStatusGet(txnp, &cache_result)) {
77     LOG_ERROR("Unable to get cache status.");
78   } else {
79     switch (cache_result) {
80     case TS_CACHE_LOOKUP_MISS:
81     case TS_CACHE_LOOKUP_SKIPPED:
82       new_mt_header = const_cast<char *>(generator.moneyTraceHdr(txn_data->client_request_mt_header));
83       if (new_mt_header != nullptr) {
84         LOG_DEBUG("cache miss, built a new money trace header: %s.", new_mt_header);
85         txn_data->new_span_mt_header = new_mt_header;
86       } else {
87         LOG_DEBUG("failed to build a new money trace header.");
88       }
89       TSHttpTxnHookAdd(txnp, TS_HTTP_SEND_REQUEST_HDR_HOOK, contp);
90 
91       // fall through to the default as we always need to send the original
92       // money trace header received from the client back to the client in the
93       // response
94       // fallthrough
95 
96     default:
97       TSHttpTxnHookAdd(txnp, TS_HTTP_SEND_RESPONSE_HDR_HOOK, contp);
98       break;
99     }
100   }
101 }
102 
103 /**
104  * remap entry point, called to check for the existence of a money trace
105  * header.  If there is one, schedule the continuation to call back and
106  * process on TS_HTTP_CACHE_LOOKUP_COMPLETE_HOOK and TS_HTTP_TXN_CLOSE_HOOK.
107  */
108 static void
mt_check_request_header(TSHttpTxn txnp)109 mt_check_request_header(TSHttpTxn txnp)
110 {
111   int length               = 0;
112   struct txndata *txn_data = nullptr;
113   TSMBuffer bufp;
114   TSMLoc hdr_loc = nullptr, field_loc = nullptr;
115   TSCont contp;
116 
117   // check for a money trace header.  If there is one, schedule appropriate continuations.
118   if (TS_SUCCESS == TSHttpTxnClientReqGet(txnp, &bufp, &hdr_loc)) {
119     field_loc = TSMimeHdrFieldFind(bufp, hdr_loc, MIME_FIELD_MONEY_TRACE, MIME_LEN_MONEY_TRACE);
120     if (TS_NULL_MLOC != field_loc) {
121       const char *hdr_value = TSMimeHdrFieldValueStringGet(bufp, hdr_loc, field_loc, 0, &length);
122       if (!hdr_value || length <= 0) {
123         LOG_DEBUG("ignoring, corrupt money trace header.");
124       } else {
125         if (nullptr == (contp = TSContCreate(transaction_handler, nullptr))) {
126           LOG_ERROR("failed to create the transaction handler continuation");
127         } else {
128           txn_data                                   = allocTransactionData();
129           txn_data->client_request_mt_header         = TSstrndup(hdr_value, length);
130           txn_data->client_request_mt_header[length] = '\0'; // workaround for bug in core.
131           LOG_DEBUG("found money trace header: %s, length: %d", txn_data->client_request_mt_header, length);
132           TSContDataSet(contp, txn_data);
133           TSHttpTxnHookAdd(txnp, TS_HTTP_CACHE_LOOKUP_COMPLETE_HOOK, contp);
134           TSHttpTxnHookAdd(txnp, TS_HTTP_TXN_CLOSE_HOOK, contp);
135         }
136       }
137     } else {
138       LOG_DEBUG("no money trace header was found in the request.");
139     }
140   } else {
141     LOG_DEBUG("failed to retrieve the client request.");
142   }
143   TSHandleMLocRelease(bufp, hdr_loc, field_loc);
144 }
145 
146 /**
147  * The TS_EVENT_HTTP_SEND_RESPONSE_HDR callback.
148  *
149  * Adds the money trace header received in the client request to the
150  * client response headers.
151  */
152 static void
mt_send_client_response(TSHttpTxn txnp,struct txndata * txn_data)153 mt_send_client_response(TSHttpTxn txnp, struct txndata *txn_data)
154 {
155   TSMBuffer bufp;
156   TSMLoc hdr_loc = nullptr, field_loc = nullptr;
157 
158   if (txn_data->client_request_mt_header == nullptr) {
159     LOG_DEBUG("no client request header to return.");
160     return;
161   }
162 
163   // send back the money trace header received in the request
164   // back in the response to the client.
165   if (TS_SUCCESS != TSHttpTxnClientRespGet(txnp, &bufp, &hdr_loc)) {
166     LOG_DEBUG("could not get the server response headers.");
167     return;
168   } else {
169     if (TS_SUCCESS ==
170         TSMimeHdrFieldCreateNamed(bufp, hdr_loc, MIME_FIELD_MONEY_TRACE, strlen(MIME_FIELD_MONEY_TRACE), &field_loc)) {
171       if (TS_SUCCESS == TSMimeHdrFieldValueStringSet(bufp, hdr_loc, field_loc, -1, txn_data->client_request_mt_header,
172                                                      strlen(txn_data->client_request_mt_header))) {
173         LOG_DEBUG("response header added: %s: %s", MIME_FIELD_MONEY_TRACE, txn_data->client_request_mt_header);
174         TSMimeHdrFieldAppend(bufp, hdr_loc, field_loc);
175       }
176     } else {
177       LOG_DEBUG("failed to create money trace response header.");
178     }
179   }
180   TSHandleMLocRelease(bufp, hdr_loc, field_loc);
181 
182   return;
183 }
184 
185 /**
186  * The TS_EVENT_HTTP_SEND_REQUEST_HDR callback.
187  *
188  * When a parent request is made, this function adds the new
189  * money trace header to the parent request headers.
190  */
191 static void
mt_send_server_request(TSHttpTxn txnp,struct txndata * txn_data)192 mt_send_server_request(TSHttpTxn txnp, struct txndata *txn_data)
193 {
194   TSMBuffer bufp;
195   TSMLoc hdr_loc = nullptr, field_loc = nullptr;
196 
197   if (txn_data->new_span_mt_header == nullptr) {
198     LOG_DEBUG("there is no new mt request header to send.");
199     return;
200   }
201 
202   if (TS_SUCCESS == TSHttpTxnServerReqGet(txnp, &bufp, &hdr_loc)) {
203     field_loc = TSMimeHdrFieldFind(bufp, hdr_loc, MIME_FIELD_MONEY_TRACE, MIME_LEN_MONEY_TRACE);
204     if (TS_NULL_MLOC != field_loc) {
205       if (TS_SUCCESS == TSMimeHdrFieldValueStringSet(bufp, hdr_loc, field_loc, -1, txn_data->new_span_mt_header,
206                                                      strlen(txn_data->new_span_mt_header))) {
207         LOG_DEBUG("server request header updated: %s: %s", MIME_FIELD_MONEY_TRACE, txn_data->new_span_mt_header);
208       }
209     } else {
210       LOG_DEBUG("unable to retrieve the money trace header location from the server request headers.");
211       return;
212     }
213   }
214   TSHandleMLocRelease(bufp, hdr_loc, field_loc);
215 
216   return;
217 }
218 
219 /**
220  * Remap initialization.
221  */
222 TSReturnCode
TSRemapInit(TSRemapInterface * api_info,char * errbuf,int errbuf_size)223 TSRemapInit(TSRemapInterface *api_info, char *errbuf, int errbuf_size)
224 {
225   if (!api_info) {
226     strncpy(errbuf, "[tsremap_init] - Invalid TSRemapInterface argument", errbuf_size - 1);
227     return TS_ERROR;
228   }
229 
230   if (api_info->tsremap_version < TSREMAP_VERSION) {
231     snprintf(errbuf, errbuf_size, "[TSRemapInit] - Incorrect API version %ld.%ld", api_info->tsremap_version >> 16,
232              (api_info->tsremap_version & 0xffff));
233     return TS_ERROR;
234   }
235 
236   LOG_DEBUG("cache_range_requests remap is successfully initialized.");
237 
238   return TS_SUCCESS;
239 }
240 
241 /**
242  * not used, one instance per remap and no parameters are used.
243  */
244 TSReturnCode
TSRemapNewInstance(int argc,char * argv[],void ** ih,char *,int)245 TSRemapNewInstance(int argc, char *argv[], void **ih, char * /*errbuf */, int /* errbuf_size */)
246 {
247   return TS_SUCCESS;
248 }
249 
250 /**
251  * not used, one instance per remap
252  */
253 void
TSRemapDeleteInstance(void * ih)254 TSRemapDeleteInstance(void *ih)
255 {
256   LOG_DEBUG("no op");
257 }
258 
259 /**
260  * Remap entry point.
261  */
262 TSRemapStatus
TSRemapDoRemap(void * ih,TSHttpTxn txnp,TSRemapRequestInfo *)263 TSRemapDoRemap(void *ih, TSHttpTxn txnp, TSRemapRequestInfo * /* rri */)
264 {
265   mt_check_request_header(txnp);
266   return TSREMAP_NO_REMAP;
267 }
268 
269 /**
270  * Transaction event handler.
271  */
272 static int
transaction_handler(TSCont contp,TSEvent event,void * edata)273 transaction_handler(TSCont contp, TSEvent event, void *edata)
274 {
275   TSHttpTxn txnp           = static_cast<TSHttpTxn>(edata);
276   struct txndata *txn_data = static_cast<struct txndata *>(TSContDataGet(contp));
277 
278   switch (event) {
279   case TS_EVENT_HTTP_CACHE_LOOKUP_COMPLETE:
280     LOG_DEBUG("transaction cache lookup complete.");
281     mt_cache_lookup_check(contp, txnp, txn_data);
282     break;
283   case TS_EVENT_HTTP_SEND_REQUEST_HDR:
284     LOG_DEBUG("updating send request headers.");
285     mt_send_server_request(txnp, txn_data);
286     break;
287   case TS_EVENT_HTTP_SEND_RESPONSE_HDR:
288     LOG_DEBUG("updating send response headers.");
289     mt_send_client_response(txnp, txn_data);
290     break;
291   case TS_EVENT_HTTP_TXN_CLOSE:
292     LOG_DEBUG("handling transaction close.");
293     freeTransactionData(txn_data);
294     TSContDestroy(contp);
295     break;
296   default:
297     TSAssert(!"Unexpected event");
298     break;
299   }
300   TSHttpTxnReenable(txnp, TS_EVENT_HTTP_CONTINUE);
301 
302   return TS_SUCCESS;
303 }
304 
305 const char *
moneyTraceHdr(const char * mt_request_hdr)306 MT::moneyTraceHdr(const char *mt_request_hdr)
307 {
308   char copy[8192] = {'\0'};
309   char *toks[3], *p = nullptr, *saveptr = nullptr;
310   std::ostringstream temp_str;
311   std::string mt_header_str;
312   int numtoks = 0;
313 
314   if (mt_request_hdr == nullptr) {
315     LOG_DEBUG("an empty header was passed in.");
316     return nullptr;
317   } else {
318     strncpy(copy, mt_request_hdr, 8191);
319   }
320 
321   // parse the money header.
322   p = strtok_r(copy, ";", &saveptr);
323   if (p != nullptr) {
324     toks[numtoks++] = p;
325     // copy the traceid
326   } else {
327     LOG_DEBUG("failed to parse the money_trace_header: %s", mt_request_hdr);
328     return nullptr;
329   }
330   do {
331     p = strtok_r(nullptr, ";", &saveptr);
332     if (p != nullptr) {
333       toks[numtoks++] = p;
334     }
335   } while (p != nullptr && numtoks < 3);
336 
337   if (numtoks != 3 || toks[0] == nullptr || toks[1] == nullptr || toks[2] == nullptr) {
338     LOG_DEBUG("failed to parse the money_trace_header: %s", mt_request_hdr);
339     return nullptr;
340   }
341 
342   if (strncmp(toks[0], "trace-id", strlen("trace-id")) == 0 && strncmp(toks[2], "span-id", strlen("span-id")) == 0 &&
343       (p = strchr(toks[2], '=')) != nullptr) {
344     p++;
345     if (strncmp("0x", p, 2) == 0) {
346       temp_str << toks[0] << ";parent-id=" << p << ";span-id=0x" << std::hex << spanId() << std::ends;
347     } else {
348       temp_str << toks[0] << ";parent-id=" << p << ";span-id=" << spanId() << std::ends;
349     }
350   } else {
351     LOG_DEBUG("invalid money_trace_header: %s", mt_request_hdr);
352     return nullptr;
353   }
354 
355   mt_header_str = temp_str.str();
356 
357   return TSstrndup(mt_header_str.c_str(), mt_header_str.length());
358 }
359