xref: /trafficserver/proxy/http/HttpTunnel.h (revision e281fd6f)
1 /** @file
2 
3   A brief file description
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 /****************************************************************************
25 
26    HttpTunnel.h
27 
28    Description:
29 
30 
31 ****************************************************************************/
32 
33 #pragma once
34 
35 #include "tscore/ink_platform.h"
36 #include "P_EventSystem.h"
37 
38 // Get rid of any previous definition first... /leif
39 #ifdef MAX_PRODUCERS
40 #undef MAX_PRODUCERS
41 #endif
42 #ifdef MAX_CONSUMERS
43 #undef MAX_CONSUMERS
44 #endif
45 #define MAX_PRODUCERS 2
46 #define MAX_CONSUMERS 4
47 
48 #define HTTP_TUNNEL_EVENT_DONE (HTTP_TUNNEL_EVENTS_START + 1)
49 #define HTTP_TUNNEL_EVENT_PRECOMPLETE (HTTP_TUNNEL_EVENTS_START + 2)
50 #define HTTP_TUNNEL_EVENT_CONSUMER_DETACH (HTTP_TUNNEL_EVENTS_START + 3)
51 
52 #define HTTP_TUNNEL_STATIC_PRODUCER (VConnection *)!0
53 
54 // YTS Team, yamsat Plugin
55 #define ALLOCATE_AND_WRITE_TO_BUF 1
56 #define WRITE_TO_BUF 2
57 
58 struct HttpTunnelProducer;
59 class HttpSM;
60 class HttpPagesHandler;
61 typedef int (HttpSM::*HttpSMHandler)(int event, void *data);
62 
63 struct HttpTunnelConsumer;
64 struct HttpTunnelProducer;
65 typedef int (HttpSM::*HttpProducerHandler)(int event, HttpTunnelProducer *p);
66 typedef int (HttpSM::*HttpConsumerHandler)(int event, HttpTunnelConsumer *c);
67 
68 enum HttpTunnelType_t { HT_HTTP_SERVER, HT_HTTP_CLIENT, HT_CACHE_READ, HT_CACHE_WRITE, HT_TRANSFORM, HT_STATIC, HT_BUFFER_READ };
69 
70 enum TunnelChunkingAction_t {
71   TCA_CHUNK_CONTENT,
72   TCA_DECHUNK_CONTENT,
73   TCA_PASSTHRU_CHUNKED_CONTENT,
74   TCA_PASSTHRU_DECHUNKED_CONTENT
75 };
76 
77 struct ChunkedHandler {
78   enum ChunkedState {
79     CHUNK_READ_CHUNK = 0,
80     CHUNK_READ_SIZE_START,
81     CHUNK_READ_SIZE,
82     CHUNK_READ_SIZE_CRLF,
83     CHUNK_READ_TRAILER_BLANK,
84     CHUNK_READ_TRAILER_CR,
85     CHUNK_READ_TRAILER_LINE,
86     CHUNK_READ_ERROR,
87     CHUNK_READ_DONE,
88     CHUNK_WRITE_CHUNK,
89     CHUNK_WRITE_DONE,
90     CHUNK_FLOW_CONTROL
91   };
92 
93   static int const DEFAULT_MAX_CHUNK_SIZE = 4096;
94 
95   enum Action { ACTION_DOCHUNK = 0, ACTION_DECHUNK, ACTION_PASSTHRU, ACTION_UNSET };
96 
97   Action action = ACTION_UNSET;
98 
99   IOBufferReader *chunked_reader = nullptr;
100   MIOBuffer *dechunked_buffer    = nullptr;
101   int64_t dechunked_size         = 0;
102 
103   IOBufferReader *dechunked_reader = nullptr;
104   MIOBuffer *chunked_buffer        = nullptr;
105   int64_t chunked_size             = 0;
106 
107   bool truncation    = false;
108   int64_t skip_bytes = 0;
109 
110   ChunkedState state     = CHUNK_READ_CHUNK;
111   int64_t cur_chunk_size = 0;
112   int64_t bytes_left     = 0;
113   int last_server_event  = VC_EVENT_NONE;
114 
115   // Parsing Info
116   int running_sum = 0;
117   int num_digits  = 0;
118 
119   /// @name Output data.
120   //@{
121   /// The maximum chunk size.
122   /// This is the preferred size as well, used whenever possible.
123   int64_t max_chunk_size;
124   /// Caching members to avoid using printf on every chunk.
125   /// It holds the header for a maximal sized chunk which will cover
126   /// almost all output chunks.
127   char max_chunk_header[16];
128   int max_chunk_header_len = 0;
129   //@}
130   ChunkedHandler();
131 
132   void init(IOBufferReader *buffer_in, HttpTunnelProducer *p);
133   void init_by_action(IOBufferReader *buffer_in, Action action);
134   void clear();
135 
136   /// Set the max chunk @a size.
137   /// If @a size is zero it is set to @c DEFAULT_MAX_CHUNK_SIZE.
138   void set_max_chunk_size(int64_t size);
139 
140   // Returns true if complete, false otherwise
141   bool process_chunked_content();
142   bool generate_chunked_content();
143 
144 private:
145   void read_size();
146   void read_chunk();
147   void read_trailer();
148   int64_t transfer_bytes();
149 };
150 
151 struct HttpTunnelConsumer {
152   HttpTunnelConsumer();
153 
154   LINK(HttpTunnelConsumer, link);
155   HttpTunnelProducer *producer      = nullptr;
156   HttpTunnelProducer *self_producer = nullptr;
157 
158   HttpTunnelType_t vc_type       = HT_HTTP_CLIENT;
159   VConnection *vc                = nullptr;
160   IOBufferReader *buffer_reader  = nullptr;
161   HttpConsumerHandler vc_handler = nullptr;
162   VIO *write_vio                 = nullptr;
163 
164   int64_t skip_bytes    = 0; // bytes to skip at beginning of stream
165   int64_t bytes_written = 0; // total bytes written to the vc
166   int handler_state     = 0; // state used the handlers
167 
168   bool alive         = false;
169   bool write_success = false;
170   const char *name   = nullptr;
171 
172   /** Check if this consumer is downstream from @a vc.
173       @return @c true if any producer in the tunnel eventually feeds
174       data to this consumer.
175   */
176   bool is_downstream_from(VConnection *vc);
177   /** Check if this is a sink (final data destination).
178       @return @c true if data exits the ATS process at this consumer.
179   */
180   bool is_sink() const;
181 };
182 
183 struct HttpTunnelProducer {
184   HttpTunnelProducer();
185 
186   DLL<HttpTunnelConsumer> consumer_list;
187   HttpTunnelConsumer *self_consumer = nullptr;
188   VConnection *vc                   = nullptr;
189   HttpProducerHandler vc_handler    = nullptr;
190   VIO *read_vio                     = nullptr;
191   MIOBuffer *read_buffer            = nullptr;
192   IOBufferReader *buffer_start      = nullptr;
193   HttpTunnelType_t vc_type          = HT_HTTP_SERVER;
194 
195   ChunkedHandler chunked_handler;
196   TunnelChunkingAction_t chunking_action = TCA_PASSTHRU_DECHUNKED_CONTENT;
197 
198   bool do_chunking         = false;
199   bool do_dechunking       = false;
200   bool do_chunked_passthru = false;
201 
202   int64_t init_bytes_done = 0; // bytes passed in buffer
203   int64_t nbytes          = 0; // total bytes (client's perspective)
204   int64_t ntodo           = 0; // what this vc needs to do
205   int64_t bytes_read      = 0; // total bytes read from the vc
206   int handler_state       = 0; // state used the handlers
207   int last_event          = 0; ///< Tracking for flow control restarts.
208 
209   int num_consumers = 0;
210 
211   bool alive        = false;
212   bool read_success = false;
213   /// Flag and pointer for active flow control throttling.
214   /// If this is set, it points at the source producer that is under flow control.
215   /// If @c NULL then data flow is not being throttled.
216   HttpTunnelProducer *flow_control_source = nullptr;
217   const char *name                        = nullptr;
218 
219   /** Get the largest number of bytes any consumer has not consumed.
220       Use @a limit if you only need to check if the backlog is at least @a limit.
221       @return The actual backlog or a number at least @a limit.
222    */
223   uint64_t backlog(uint64_t limit = UINT64_MAX ///< More than this is irrelevant
224   );
225   /// Check if producer is original (to ATS) source of data.
226   /// @return @c true if this producer is the source of bytes from outside ATS.
227   bool is_source() const;
228   /// Throttle the flow.
229   void throttle();
230   /// Unthrottle the flow.
231   void unthrottle();
232   /// Check throttled state.
233   bool is_throttled() const;
234 
235   /// Update the handler_state member if it is still 0
236   void update_state_if_not_set(int new_handler_state);
237 
238   /** Set the flow control source producer for the flow.
239       This sets the value for this producer and all downstream producers.
240       @note This is the implementation for @c throttle and @c unthrottle.
241       @see throttle
242       @see unthrottle
243   */
244   void set_throttle_src(HttpTunnelProducer *srcp ///< Source producer of flow.
245   );
246 };
247 
248 class HttpTunnel : public Continuation
249 {
250   friend class HttpPagesHandler;
251   friend class CoreUtils;
252 
253   /** Data for implementing flow control across a tunnel.
254 
255       The goal is to bound the amount of data buffered for a
256       transaction flowing through the tunnel to (roughly) between the
257       @a high_water and @a low_water water marks. Due to the chunky nater of data
258       flow this always approximate.
259   */
260   struct FlowControl {
261     // Default value for high and low water marks.
262     static uint64_t const DEFAULT_WATER_MARK = 1 << 16;
263 
264     uint64_t high_water;    ///< Buffered data limit - throttle if more than this.
265     uint64_t low_water;     ///< Unthrottle if less than this buffered.
266     bool enabled_p = false; ///< Flow control state (@c false means disabled).
267 
268     /// Default constructor.
269     FlowControl();
270   };
271 
272 public:
273   HttpTunnel();
274 
275   void init(HttpSM *sm_arg, Ptr<ProxyMutex> &amutex);
276   void reset();
277   void kill_tunnel();
278   bool
is_tunnel_active() const279   is_tunnel_active() const
280   {
281     return active;
282   }
283   bool is_tunnel_alive() const;
284   bool has_cache_writer() const;
285 
286   HttpTunnelProducer *add_producer(VConnection *vc, int64_t nbytes, IOBufferReader *reader_start, HttpProducerHandler sm_handler,
287                                    HttpTunnelType_t vc_type, const char *name);
288 
289   void set_producer_chunking_action(HttpTunnelProducer *p, int64_t skip_bytes, TunnelChunkingAction_t action);
290   /// Set the maximum (preferred) chunk @a size of chunked output for @a producer.
291   void set_producer_chunking_size(HttpTunnelProducer *producer, int64_t size);
292 
293   HttpTunnelConsumer *add_consumer(VConnection *vc, VConnection *producer, HttpConsumerHandler sm_handler, HttpTunnelType_t vc_type,
294                                    const char *name, int64_t skip_bytes = 0);
295 
296   int deallocate_buffers();
297   DLL<HttpTunnelConsumer> *get_consumers(VConnection *vc);
298   HttpTunnelProducer *get_producer(VConnection *vc);
299   HttpTunnelConsumer *get_consumer(VConnection *vc);
300   HttpTunnelProducer *get_producer(HttpTunnelType_t type);
301   void tunnel_run(HttpTunnelProducer *p = nullptr);
302 
303   int main_handler(int event, void *data);
304   void consumer_reenable(HttpTunnelConsumer *c);
305   bool consumer_handler(int event, HttpTunnelConsumer *c);
306   bool producer_handler(int event, HttpTunnelProducer *p);
307   int producer_handler_dechunked(int event, HttpTunnelProducer *p);
308   int producer_handler_chunked(int event, HttpTunnelProducer *p);
309   void local_finish_all(HttpTunnelProducer *p);
310   void chain_finish_all(HttpTunnelProducer *p);
311   void chain_abort_cache_write(HttpTunnelProducer *p);
312   void chain_abort_all(HttpTunnelProducer *p);
313   void abort_cache_write_finish_others(HttpTunnelProducer *p);
314   void append_message_to_producer_buffer(HttpTunnelProducer *p, const char *msg, int64_t msg_len);
315   int64_t final_consumer_bytes_to_write(HttpTunnelProducer *p, HttpTunnelConsumer *c);
316 
317   /** Mark a producer and consumer as the same underlying object.
318 
319       This is use to chain producer/consumer pairs together to
320       indicate the data flows through them sequentially. The primary
321       example is a transform which serves as a consumer on the server
322       side and a producer on the cache/client side.
323   */
324   void chain(HttpTunnelConsumer *c, ///< Flow goes in here
325              HttpTunnelProducer *p  ///< Flow comes back out here
326   );
327 
328   void close_vc(HttpTunnelProducer *p);
329   void close_vc(HttpTunnelConsumer *c);
330 
331 private:
332   void internal_error();
333   void finish_all_internal(HttpTunnelProducer *p, bool chain);
334   void update_stats_after_abort(HttpTunnelType_t t);
335   void producer_run(HttpTunnelProducer *p);
336 
337   HttpTunnelProducer *get_producer(VIO *vio);
338   HttpTunnelConsumer *get_consumer(VIO *vio);
339 
340   HttpTunnelProducer *alloc_producer();
341   HttpTunnelConsumer *alloc_consumer();
342 
343   int num_producers = 0;
344   int num_consumers = 0;
345   HttpTunnelConsumer consumers[MAX_CONSUMERS];
346   HttpTunnelProducer producers[MAX_PRODUCERS];
347   HttpSM *sm = nullptr;
348 
349   bool active = false;
350 
351   /// State data about flow control.
352   FlowControl flow_state;
353 
354 private:
355   int reentrancy_count = 0;
356   bool call_sm         = false;
357 };
358 
359 // void HttpTunnel::abort_cache_write_finish_others
360 //
361 //    Abort all downstream cache writes and finsish
362 //      all other local consumers
363 //
364 inline void
abort_cache_write_finish_others(HttpTunnelProducer * p)365 HttpTunnel::abort_cache_write_finish_others(HttpTunnelProducer *p)
366 {
367   chain_abort_cache_write(p);
368   local_finish_all(p);
369 }
370 
371 // void HttpTunnel::local_finish_all(HttpTunnelProducer* p)
372 //
373 //   After the producer has finished, causes direct consumers
374 //      to finish their writes
375 //
376 inline void
local_finish_all(HttpTunnelProducer * p)377 HttpTunnel::local_finish_all(HttpTunnelProducer *p)
378 {
379   finish_all_internal(p, false);
380 }
381 
382 // void HttpTunnel::chain_finish_all(HttpTunnelProducer* p)
383 //
384 //   After the producer has finished, cause everyone
385 //    downstream in the tunnel to send everything
386 //    that producer has placed in the buffer
387 //
388 inline void
chain_finish_all(HttpTunnelProducer * p)389 HttpTunnel::chain_finish_all(HttpTunnelProducer *p)
390 {
391   finish_all_internal(p, true);
392 }
393 
394 inline bool
is_tunnel_alive() const395 HttpTunnel::is_tunnel_alive() const
396 {
397   bool tunnel_alive = false;
398 
399   for (const auto &producer : producers) {
400     if (producer.alive == true) {
401       tunnel_alive = true;
402       break;
403     }
404   }
405   if (!tunnel_alive) {
406     for (const auto &consumer : consumers) {
407       if (consumer.alive == true) {
408         tunnel_alive = true;
409         break;
410       }
411     }
412   }
413 
414   return tunnel_alive;
415 }
416 
417 inline HttpTunnelProducer *
get_producer(VConnection * vc)418 HttpTunnel::get_producer(VConnection *vc)
419 {
420   for (int i = 0; i < MAX_PRODUCERS; i++) {
421     if (producers[i].vc == vc) {
422       return producers + i;
423     }
424   }
425   return nullptr;
426 }
427 
428 inline HttpTunnelProducer *
get_producer(HttpTunnelType_t type)429 HttpTunnel::get_producer(HttpTunnelType_t type)
430 {
431   for (int i = 0; i < MAX_PRODUCERS; i++) {
432     if (producers[i].vc_type == type) {
433       return producers + i;
434     }
435   }
436   return nullptr;
437 }
438 
439 inline HttpTunnelConsumer *
get_consumer(VConnection * vc)440 HttpTunnel::get_consumer(VConnection *vc)
441 {
442   /** Rare but persistent problem in which a @c INKVConnInternal is used by a consumer, released,
443       and then re-allocated for a different consumer. This causes two consumers to have the same VC
444       pointer resulting in this method returning the wrong consumer. Note this is a not a bad use of
445       the tunnel, but an unfortunate interaction with the FIFO free lists.
446 
447       It's not correct to check for the consumer being alive - at a minimum `HTTP_TUNNEL_EVENT_DONE`
448       is dispatched against a consumer after the consumer is not alive. Instead if a non-alive
449       consumer matches it is stored as a candidate and returned if no other match is found. If a
450       live matching consumer is found, it is immediately returned. It is never valid to have the
451       same VC in more than one active consumer. This should avoid a performance impact because in
452       the usual case the consumer will be alive.
453 
454       In the case of a deliberate dispatch of an event to a dead consumer that has a duplicate vc
455       address, this will select the last consumer which will be correct as the consumers are added
456       in order therefore the latter consumer will be the most recent / appropriate target.
457   */
458   HttpTunnelConsumer *zret = nullptr;
459   for (HttpTunnelConsumer &c : consumers) {
460     if (c.vc == vc) {
461       zret = &c;
462       if (c.alive) { // a match that's alive is always the best.
463         break;
464       }
465     }
466   }
467   return zret;
468 }
469 
470 inline HttpTunnelProducer *
get_producer(VIO * vio)471 HttpTunnel::get_producer(VIO *vio)
472 {
473   for (int i = 0; i < MAX_PRODUCERS; i++) {
474     if (producers[i].read_vio == vio) {
475       return producers + i;
476     }
477   }
478   return nullptr;
479 }
480 
481 inline HttpTunnelConsumer *
get_consumer(VIO * vio)482 HttpTunnel::get_consumer(VIO *vio)
483 {
484   if (vio) {
485     for (int i = 0; i < MAX_CONSUMERS; i++) {
486       if (consumers[i].alive && (consumers[i].write_vio == vio || consumers[i].vc == vio->vc_server)) {
487         return consumers + i;
488       }
489     }
490   }
491   return nullptr;
492 }
493 
494 inline void
append_message_to_producer_buffer(HttpTunnelProducer * p,const char * msg,int64_t msg_len)495 HttpTunnel::append_message_to_producer_buffer(HttpTunnelProducer *p, const char *msg, int64_t msg_len)
496 {
497   if (p == nullptr || p->read_buffer == nullptr) {
498     return;
499   }
500 
501   p->read_buffer->write(msg, msg_len);
502   p->nbytes += msg_len;
503   p->bytes_read += msg_len;
504 }
505 
506 inline bool
has_cache_writer() const507 HttpTunnel::has_cache_writer() const
508 {
509   for (const auto &consumer : consumers) {
510     if (consumer.vc_type == HT_CACHE_WRITE && consumer.vc != nullptr) {
511       return true;
512     }
513   }
514   return false;
515 }
516 
517 inline bool
is_downstream_from(VConnection * vc)518 HttpTunnelConsumer::is_downstream_from(VConnection *vc)
519 {
520   HttpTunnelProducer *p = producer;
521 
522   while (p) {
523     if (p->vc == vc) {
524       return true;
525     }
526     // The producer / consumer chain can contain a cycle in the case
527     // of a blind tunnel so give up if we find ourself (the original
528     // consumer).
529     HttpTunnelConsumer *c = p->self_consumer;
530 
531     p = (c && c != this) ? c->producer : nullptr;
532   }
533   return false;
534 }
535 
536 inline bool
is_sink() const537 HttpTunnelConsumer::is_sink() const
538 {
539   return HT_HTTP_CLIENT == vc_type || HT_CACHE_WRITE == vc_type;
540 }
541 
542 inline bool
is_source() const543 HttpTunnelProducer::is_source() const
544 {
545   // If a producer is marked as a client, then it's part of a bidirectional tunnel
546   // and so is an actual source of data.
547   return HT_HTTP_SERVER == vc_type || HT_CACHE_READ == vc_type || HT_HTTP_CLIENT == vc_type;
548 }
549 
550 inline void
update_state_if_not_set(int new_handler_state)551 HttpTunnelProducer::update_state_if_not_set(int new_handler_state)
552 {
553   if (this->handler_state == 0) {
554     this->handler_state = new_handler_state;
555   }
556 }
557 
558 inline bool
is_throttled() const559 HttpTunnelProducer::is_throttled() const
560 {
561   return nullptr != flow_control_source;
562 }
563 
564 inline void
throttle()565 HttpTunnelProducer::throttle()
566 {
567   if (!this->is_throttled()) {
568     this->set_throttle_src(this);
569   }
570 }
571 
572 inline void
unthrottle()573 HttpTunnelProducer::unthrottle()
574 {
575   if (this->is_throttled()) {
576     this->set_throttle_src(nullptr);
577   }
578 }
579 
FlowControl()580 inline HttpTunnel::FlowControl::FlowControl() : high_water(DEFAULT_WATER_MARK), low_water(DEFAULT_WATER_MARK) {}
581