diff --git a/configure.ac b/configure.ac index f298398e984..28ce52ff670 100644 --- a/configure.ac +++ b/configure.ac @@ -2065,6 +2065,7 @@ AC_CONFIG_FILES([ proxy/http2/Makefile proxy/logging/Makefile proxy/shared/Makefile + proxy/l4r/Makefile rc/Makefile rc/trafficserver rc/trafficserver.conf diff --git a/iocore/eventsystem/I_Thread.h b/iocore/eventsystem/I_Thread.h index b3ba652f31c..2bef11b3857 100644 --- a/iocore/eventsystem/I_Thread.h +++ b/iocore/eventsystem/I_Thread.h @@ -142,6 +142,8 @@ class Thread ProxyAllocator ioDataAllocator; ProxyAllocator ioAllocator; ProxyAllocator ioBlockAllocator; + ProxyAllocator l4rClientSessionAllocator; + ProxyAllocator l4rServerSessionAllocator; public: /** Start the underlying thread. diff --git a/proxy/Makefile.am b/proxy/Makefile.am index c6fe8ac95d3..1102db2d457 100644 --- a/proxy/Makefile.am +++ b/proxy/Makefile.am @@ -18,7 +18,7 @@ include $(top_srcdir)/build/tidy.mk -SUBDIRS = hdrs shared http http2 logging config +SUBDIRS = hdrs shared http http2 logging config l4r noinst_LIBRARIES = libproxy.a diff --git a/proxy/ProtocolProbeSessionAccept.cc b/proxy/ProtocolProbeSessionAccept.cc index 8d332941d6d..8df03e81779 100644 --- a/proxy/ProtocolProbeSessionAccept.cc +++ b/proxy/ProtocolProbeSessionAccept.cc @@ -90,7 +90,11 @@ struct ProtocolProbeTrampoline : public Continuation, public ProtocolProbeSessio goto done; } - if (proto_is_http2(reader)) { + // Hardcode port 8000 to L4R + if (netvc->get_local_port() == 8000) { + key = PROTO_L4R; + } + else if (proto_is_http2(reader)) { key = PROTO_HTTP2; } else { key = PROTO_HTTP; diff --git a/proxy/ProtocolProbeSessionAccept.h b/proxy/ProtocolProbeSessionAccept.h index 8ba84201760..002def85e36 100644 --- a/proxy/ProtocolProbeSessionAccept.h +++ b/proxy/ProtocolProbeSessionAccept.h @@ -32,6 +32,7 @@ struct ProtocolProbeSessionAcceptEnums { enum ProtoGroupKey { PROTO_HTTP, ///< HTTP group (0.9-1.1) PROTO_HTTP2, ///< HTTP 2 group + PROTO_L4R, ///< Layer 4 routing N_PROTO_GROUPS ///< Size value. }; }; diff --git a/proxy/ProxyClientSession.cc b/proxy/ProxyClientSession.cc index e24f43b9916..a8dcf169a00 100644 --- a/proxy/ProxyClientSession.cc +++ b/proxy/ProxyClientSession.cc @@ -45,13 +45,17 @@ ProxyError::str(char *buf, size_t buf_len) return len; } -ProxyClientSession::ProxyClientSession() : VConnection(nullptr) +ProxyClientSession::ProxyClientSession() : BasicProxyClientSession() +{ +} + +BasicProxyClientSession::BasicProxyClientSession() : VConnection(nullptr) { ink_zero(this->user_args); } void -ProxyClientSession::set_session_active() +BasicProxyClientSession::set_session_active() { if (!m_active) { m_active = true; @@ -60,7 +64,7 @@ ProxyClientSession::set_session_active() } void -ProxyClientSession::clear_session_active() +BasicProxyClientSession::clear_session_active() { if (m_active) { m_active = false; @@ -69,7 +73,7 @@ ProxyClientSession::clear_session_active() } int64_t -ProxyClientSession::next_connection_id() +BasicProxyClientSession::next_connection_id() { return ink_atomic_increment(&next_cs_id, 1); } @@ -102,7 +106,7 @@ is_valid_hook(TSHttpHookID hookid) } void -ProxyClientSession::free() +BasicProxyClientSession::free() { if (schedule_event) { schedule_event->cancel(); @@ -113,7 +117,7 @@ ProxyClientSession::free() } int -ProxyClientSession::state_api_callout(int event, void *data) +BasicProxyClientSession::state_api_callout(int event, void *data) { Event *e = static_cast(data); if (e == schedule_event) { @@ -144,7 +148,7 @@ ProxyClientSession::state_api_callout(int event, void *data) plugin_mutex = hook->m_cont->mutex; plugin_lock = MUTEX_TAKE_TRY_LOCK(hook->m_cont->mutex, mutex->thread_holding); if (!plugin_lock) { - SET_HANDLER(&ProxyClientSession::state_api_callout); + SET_HANDLER(&BasicProxyClientSession::state_api_callout); if (!schedule_event) { // Don't bother to schedule is there is already one out. schedule_event = mutex->thread_holding->schedule_in(this, HRTIME_MSECONDS(10)); } @@ -179,7 +183,7 @@ ProxyClientSession::state_api_callout(int event, void *data) } void -ProxyClientSession::do_api_callout(TSHttpHookID id) +BasicProxyClientSession::do_api_callout(TSHttpHookID id) { ink_assert(id == TS_HTTP_SSN_START_HOOK || id == TS_HTTP_SSN_CLOSE_HOOK); @@ -188,7 +192,7 @@ ProxyClientSession::do_api_callout(TSHttpHookID id) this->api_current = nullptr; if (this->hooks_on && this->has_hooks()) { - SET_HANDLER(&ProxyClientSession::state_api_callout); + SET_HANDLER(&BasicProxyClientSession::state_api_callout); this->state_api_callout(EVENT_NONE, nullptr); } else { this->handle_api_return(TS_EVENT_HTTP_CONTINUE); @@ -196,11 +200,11 @@ ProxyClientSession::do_api_callout(TSHttpHookID id) } void -ProxyClientSession::handle_api_return(int event) +BasicProxyClientSession::handle_api_return(int event) { TSHttpHookID hookid = this->api_hookid; - SET_HANDLER(&ProxyClientSession::state_api_callout); + SET_HANDLER(&BasicProxyClientSession::state_api_callout); this->api_hookid = TS_HTTP_LAST_HOOK; this->api_scope = API_HOOK_SCOPE_NONE; diff --git a/proxy/ProxyClientSession.h b/proxy/ProxyClientSession.h index 1730ef77ff1..0918203f67b 100644 --- a/proxy/ProxyClientSession.h +++ b/proxy/ProxyClientSession.h @@ -53,10 +53,10 @@ struct ProxyError { uint32_t code = 0; }; -class ProxyClientSession : public VConnection +class BasicProxyClientSession : public VConnection { public: - ProxyClientSession(); + BasicProxyClientSession(); virtual void destroy() = 0; virtual void free(); @@ -147,19 +147,6 @@ class ProxyClientSession : public VConnection // Initiate an API hook invocation. void do_api_callout(TSHttpHookID id); - // Override if your session protocol allows this. - virtual bool - is_transparent_passthrough_allowed() const - { - return false; - } - - virtual bool - is_chunked_encoding_supported() const - { - return false; - } - // Override if your session protocol cares. virtual void set_half_close_flag(bool flag) @@ -200,11 +187,11 @@ class ProxyClientSession : public VConnection } virtual void - attach_server_session(HttpServerSession *ssession, bool transaction_done = true) + attach_server_session(VConnection *ssession, bool transaction_done = true) { } - virtual HttpServerSession * + virtual VConnection * get_server_session() const { return nullptr; @@ -286,8 +273,8 @@ class ProxyClientSession : public VConnection ink_hrtime ssn_last_txn_time = 0; // noncopyable - ProxyClientSession(ProxyClientSession &) = delete; - ProxyClientSession &operator=(const ProxyClientSession &) = delete; + BasicProxyClientSession(BasicProxyClientSession &) = delete; + BasicProxyClientSession &operator=(const BasicProxyClientSession &) = delete; protected: // XXX Consider using a bitwise flags variable for the following flags, so @@ -317,3 +304,43 @@ class ProxyClientSession : public VConnection // aborts. bool m_active = false; }; + +class ProxyClientSession : public BasicProxyClientSession +{ +public: + ProxyClientSession(); + + // Override if your session protocol allows this. + virtual bool + is_transparent_passthrough_allowed() const + { + return false; + } + + virtual bool + is_chunked_encoding_supported() const + { + return false; + } + + virtual void + attach_server_session(HttpServerSession *ssession, bool transaction_done = true) + { + } + + virtual HttpServerSession * + get_server_session() const + { + return nullptr; + } + + // noncopyable + ProxyClientSession(ProxyClientSession &) = delete; + ProxyClientSession &operator=(const ProxyClientSession &) = delete; + +protected: + // XXX Consider using a bitwise flags variable for the following flags, so + // that we can make the best use of internal alignment padding. + +private: +}; diff --git a/proxy/ProxyClientTransaction.h b/proxy/ProxyClientTransaction.h index 12fcf30cade..cc512cfa0f6 100644 --- a/proxy/ProxyClientTransaction.h +++ b/proxy/ProxyClientTransaction.h @@ -289,3 +289,265 @@ class ProxyClientTransaction : public VConnection private: }; + +class BasicProxyClientTransaction : public VConnection +{ +public: + BasicProxyClientTransaction(); + + // do_io methods implemented by subclasses + + virtual void new_transaction(); + + virtual NetVConnection * + get_netvc() const + { + return (parent) ? parent->get_netvc() : nullptr; + } + + virtual void set_active_timeout(ink_hrtime timeout_in) = 0; + virtual void set_inactivity_timeout(ink_hrtime timeout_in) = 0; + virtual void cancel_inactivity_timeout() = 0; + + virtual void attach_server_session(VConnection *ssession, bool transaction_done = true); + + // See if we need to schedule on the primary thread for the transaction or change the thread that is associated with the VC. + // If we reschedule, the scheduled action is returned. Otherwise, NULL is returned + Action *adjust_thread(Continuation *cont, int event, void *data); + + int + get_transact_count() const + { + return parent ? parent->get_transact_count() : 0; + } + + virtual bool + is_first_transaction() const + { + return get_transact_count() == 1; + } + + // Ask your session if this is allowed + bool + is_transparent_passthrough_allowed() + { + return false; + } + + virtual bool + is_chunked_encoding_supported() const + { + return false; + } + + void + set_half_close_flag(bool flag) + { + if (parent) { + parent->set_half_close_flag(flag); + } + } + virtual bool + get_half_close_flag() const + { + return parent ? parent->get_half_close_flag() : false; + } + + // What are the debug and hooks_enabled used for? How are they set? + // Just calling through to parent session for now + bool + debug() const + { + return parent ? parent->debug() : false; + } + bool + hooks_enabled() const + { + return parent ? parent->hooks_enabled() : false; + } + + APIHook * + ssn_hook_get(TSHttpHookID id) const + { + return parent ? parent->ssn_hook_get(id) : nullptr; + } + + bool + has_hooks() const + { + return parent->has_hooks(); + } + + virtual void + set_session_active() + { + if (parent) { + parent->set_session_active(); + } + } + + virtual void + clear_session_active() + { + if (parent) { + parent->clear_session_active(); + } + } + + /// DNS resolution preferences. + HostResStyle + get_host_res_style() const + { + return host_res_style; + } + void + set_host_res_style(HostResStyle style) + { + host_res_style = style; + } + + const AclRecord * + get_acl_record() const + { + return parent ? parent->acl_record : nullptr; + } + + // Indicate we are done with this transaction + virtual void release(IOBufferReader *r); + + // outbound values Set via the server port definition. Really only used for Http1 at the moment + virtual in_port_t + get_outbound_port() const + { + return outbound_port; + } + virtual IpAddr + get_outbound_ip4() const + { + return outbound_ip4; + } + virtual IpAddr + get_outbound_ip6() const + { + return outbound_ip6; + } + virtual void + set_outbound_port(in_port_t port) + { + outbound_port = port; + } + virtual void + set_outbound_ip(const IpAddr &new_addr) + { + if (new_addr.isIp4()) { + outbound_ip4 = new_addr; + } else if (new_addr.isIp6()) { + outbound_ip6 = new_addr; + } else { + outbound_ip4.invalidate(); + outbound_ip6.invalidate(); + } + } + virtual bool + is_outbound_transparent() const + { + return false; + } + virtual void + set_outbound_transparent(bool flag) + { + } + + virtual bool + ignore_keep_alive() + { + return true; + } + + virtual void destroy(); + + virtual void transaction_done() = 0; + + BasicProxyClientSession * + get_parent() + { + return parent; + } + + virtual void + set_parent(BasicProxyClientSession *new_parent) + { + parent = new_parent; + host_res_style = parent->host_res_style; + } + virtual void + set_h2c_upgrade_flag() + { + } + + VConnection * + get_server_session() const + { + return parent ? parent->get_server_session() : nullptr; + } + + VConnection * + get_sm() const + { + return current_reader; + } + + virtual bool allow_half_open() const = 0; + + virtual const char * + get_protocol_string() + { + return parent ? parent->get_protocol_string() : nullptr; + } + + void + set_restart_immediate(bool val) + { + restart_immediate = true; + } + bool + get_restart_immediate() const + { + return restart_immediate; + } + + virtual int + populate_protocol(std::string_view *result, int size) const + { + return parent ? parent->populate_protocol(result, size) : 0; + } + + virtual const char * + protocol_contains(std::string_view tag_prefix) const + { + return parent ? parent->protocol_contains(tag_prefix) : nullptr; + } + + // This function must return a non-negative number that is different for two in-progress transactions with the same parent + // session. + // + virtual int get_transaction_id() const = 0; + void set_rx_error_code(ProxyError e); + void set_tx_error_code(ProxyError e); + +protected: + BasicProxyClientSession *parent; + VConnection *current_reader; + IOBufferReader *sm_reader; + + /// DNS resolution preferences. + HostResStyle host_res_style; + /// Local outbound address control. + in_port_t outbound_port{0}; + IpAddr outbound_ip4; + IpAddr outbound_ip6; + + bool restart_immediate; + +private: +}; diff --git a/proxy/http/HttpProxyServerMain.cc b/proxy/http/HttpProxyServerMain.cc index ab1779161d2..5b43f4a0427 100644 --- a/proxy/http/HttpProxyServerMain.cc +++ b/proxy/http/HttpProxyServerMain.cc @@ -177,6 +177,11 @@ make_net_accept_options(const HttpProxyPort *port, unsigned nthreads) static void MakeHttpProxyAcceptor(HttpProxyAcceptor &acceptor, HttpProxyPort &port, unsigned nthreads) { + // Do Layer 4 routing on port 8000 + if (port.m_port == 8000) { + return; + } + NetProcessor::AcceptOptions &net_opt = acceptor._net_opt; HttpSessionAccept::Options accept_opt; diff --git a/proxy/l4r/L4RClientSession.cc b/proxy/l4r/L4RClientSession.cc new file mode 100644 index 00000000000..96c3cb11cf2 --- /dev/null +++ b/proxy/l4r/L4RClientSession.cc @@ -0,0 +1,551 @@ +/** @file + + A brief file description + + @section license License + + Licensed to the Apache Software Foundation (ASF) under one + or more contributor license agreements. See the NOTICE file + distributed with this work for additional information + regarding copyright ownership. The ASF licenses this file + to you under the Apache License, Version 2.0 (the + "License"); you may not use this file except in compliance + with the License. You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + */ + +/**************************************************************************** + + L4RClientSession.cc + + Description: + + + ****************************************************************************/ + +#include +#include "L4RClientSession.h" +//#include "Http1ClientTransaction.h" +//#include "HttpSM.h" +//#include "HttpDebugNames.h" +//#include "HttpServerSession.h" +#include "Plugin.h" + +#define L4rSsnDebug(fmt, ...) SsnDebug(this, "l4r_cs", fmt, __VA_ARGS__) + +#define STATE_ENTER(state_name, event, vio) \ + do { \ + /*ink_assert (magic == HTTP_SM_MAGIC_ALIVE); REMEMBER (event, NULL, reentrancy_count); */ \ + L4rSsnDebug("[%" PRId64 "] [%s, %s]", con_id, #state_name, "xxx"); \ + } while (0) + +enum { + HTTP_CS_MAGIC_ALIVE = 0x0123F00D, + HTTP_CS_MAGIC_DEAD = 0xDEADF00D, +}; + +#ifdef USE_HTTP_DEBUG_LISTS + +// We have debugging list that we can use to find stuck +// client sessions +DList(L4RClientSession, debug_link) debug_cs_list2; +ink_mutex debug_cs_list2_mutex; + +#endif /* USE_HTTP_DEBUG_LISTS */ + +ClassAllocator l4rClientSessionAllocator("l4rClientSessionAllocator"); + +L4RClientSession::L4RClientSession() + : client_vc(nullptr), + magic(HTTP_CS_MAGIC_DEAD), + transact_count(0), + tcp_init_cwnd_set(false), + half_close(false), + conn_decrease(false), + read_buffer(nullptr), + sm_reader(nullptr), + read_state(HCS_INIT), + ka_vio(nullptr), + slave_ka_vio(nullptr), + bound_ss(nullptr), + released_transactions(0) +{ +} + +void +L4RClientSession::destroy() +{ + if (read_state != HCS_CLOSED) { + return; + } + if (!in_destroy) { + in_destroy = true; + + L4rSsnDebug("[%" PRId64 "] session destroy", con_id); + ink_release_assert(!client_vc); + ink_assert(read_buffer); + ink_release_assert(transact_count == released_transactions); + do_api_callout(TS_HTTP_SSN_CLOSE_HOOK); + } else { + Warning("l4r: Attempt to double ssn close"); + } +} + +void +L4RClientSession::release_transaction() +{ + released_transactions++; + if (transact_count == released_transactions) { + destroy(); + } +} + +void +L4RClientSession::free() +{ + magic = HTTP_CS_MAGIC_DEAD; + if (read_buffer) { + free_MIOBuffer(read_buffer); + read_buffer = nullptr; + } + +#ifdef USE_HTTP_DEBUG_LISTS + ink_mutex_acquire(&debug_cs_list2_mutex); + debug_cs_list2.remove(this); + ink_mutex_release(&debug_cs_list2_mutex); +#endif + + if (conn_decrease) { + //HTTP_DECREMENT_DYN_STAT(http_current_client_connections_stat); + conn_decrease = false; + } + + // Clean up the write VIO in case of inactivity timeout + this->do_io_write(nullptr, 0, nullptr); + + // Free the transaction resources + //this->trans.super_type::destroy(); + + super::free(); + THREAD_FREE(this, l4rClientSessionAllocator, this_thread()); +} + +void +L4RClientSession::new_connection(NetVConnection *new_vc, MIOBuffer *iobuf, IOBufferReader *reader, bool backdoor) +{ + ink_assert(new_vc != nullptr); + ink_assert(client_vc == nullptr); + client_vc = new_vc; + magic = HTTP_CS_MAGIC_ALIVE; + mutex = new_vc->mutex; + //trans.mutex = mutex; // Share this mutex with the transaction + ssn_start_time = Thread::get_hrtime(); + in_destroy = false; + + MUTEX_TRY_LOCK(lock, mutex, this_ethread()); + ink_assert(lock.is_locked()); + + // Disable hooks for backdoor connections. + this->hooks_on = !backdoor; + + // Unique client session identifier. + con_id = ProxyClientSession::next_connection_id(); + + schedule_event = nullptr; + + //HTTP_INCREMENT_DYN_STAT(http_current_client_connections_stat); + conn_decrease = true; + //HTTP_INCREMENT_DYN_STAT(http_total_client_connections_stat); + //if (static_cast(new_vc->attributes) == HttpProxyPort::TRANSPORT_SSL) { + // HTTP_INCREMENT_DYN_STAT(https_total_client_connections_stat); + //} + + /* inbound requests stat should be incremented here, not after the + * header has been read */ + //HTTP_INCREMENT_DYN_STAT(http_total_incoming_connections_stat); + + // check what type of socket address we just accepted + // by looking at the address family value of sockaddr_storage + // and logging to stat system +#if 0 + switch (new_vc->get_remote_addr()->sa_family) { + case AF_INET: + HTTP_INCREMENT_DYN_STAT(http_total_client_connections_ipv4_stat); + break; + case AF_INET6: + HTTP_INCREMENT_DYN_STAT(http_total_client_connections_ipv6_stat); + break; + default: + // don't do anything if the address family is not ipv4 or ipv6 + // (there are many other address families in + // but we don't have a need to report on all the others today) + break; + } +#endif + +#ifdef USE_HTTP_DEBUG_LISTS + ink_mutex_acquire(&debug_cs_list2_mutex); + debug_cs_list2.push(this); + ink_mutex_release(&debug_cs_list2_mutex); +#endif + + L4rSsnDebug("[%" PRId64 "] session born, netvc %p", con_id, new_vc); + + client_vc->set_tcp_congestion_control(CLIENT_SIDE); + + read_buffer = iobuf ? iobuf : new_MIOBuffer(/*HTTP_HEADER_BUFFER_SIZE_INDEX*/BUFFER_SIZE_INDEX_4K); + sm_reader = reader ? reader : read_buffer->alloc_reader(); + //trans.set_reader(sm_reader); + + // INKqa11186: Use a local pointer to the mutex as + // when we return from do_api_callout, the ClientSession may + // have already been deallocated. + EThread *ethis = this_ethread(); + Ptr lmutex = this->mutex; + MUTEX_TAKE_LOCK(lmutex, ethis); + do_api_callout(TS_HTTP_SSN_START_HOOK); + MUTEX_UNTAKE_LOCK(lmutex, ethis); + lmutex.clear(); +} + +VIO * +L4RClientSession::do_io_read(Continuation *c, int64_t nbytes, MIOBuffer *buf) +{ + return client_vc->do_io_read(c, nbytes, buf); +} + +VIO * +L4RClientSession::do_io_write(Continuation *c, int64_t nbytes, IOBufferReader *buf, bool owner) +{ + /* conditionally set the tcp initial congestion window + before our first write. */ + L4rSsnDebug("tcp_init_cwnd_set %d", (int)tcp_init_cwnd_set); + if (!tcp_init_cwnd_set) { + tcp_init_cwnd_set = true; + set_tcp_init_cwnd(); + } + if (client_vc) { + return client_vc->do_io_write(c, nbytes, buf, owner); + } else { + return nullptr; + } +} + +void +L4RClientSession::set_tcp_init_cwnd() +{ + //if (!trans.get_sm()) { + // return; + //} + //int desired_tcp_init_cwnd = trans.get_sm()->t_state.txn_conf->server_tcp_init_cwnd; + int desired_tcp_init_cwnd = 0; + L4rSsnDebug("desired TCP congestion window is %d", desired_tcp_init_cwnd); + if (desired_tcp_init_cwnd == 0) { + return; + } + if (get_netvc()->set_tcp_init_cwnd(desired_tcp_init_cwnd) != 0) { + L4rSsnDebug("set_tcp_init_cwnd(%d) failed", desired_tcp_init_cwnd); + } +} + +void +L4RClientSession::do_io_shutdown(ShutdownHowTo_t howto) +{ + client_vc->do_io_shutdown(howto); +} + +void +L4RClientSession::do_io_close(int alerrno) +{ + if (read_state == HCS_CLOSED) { + return; // Don't double call session close + } + if (read_state == HCS_ACTIVE_READER) { + clear_session_active(); + } + + // Prevent double closing + ink_release_assert(read_state != HCS_CLOSED); + + // If we have an attached server session, release + // it back to our shared pool + if (bound_ss) { + bound_ss->release(); + bound_ss = nullptr; + slave_ka_vio = nullptr; + } + // Completed the last transaction. Just shutdown already + if (transact_count == released_transactions) { + half_close = false; + } + //if (half_close && this->trans.get_sm()) { + // read_state = HCS_HALF_CLOSED; + // SET_HANDLER(&L4RClientSession::state_wait_for_close); + // L4rSsnDebug("[%" PRId64 "] session half close", con_id); + + // if (client_vc) { + // // We want the client to know that that we're finished + // // writing. The write shutdown accomplishes this. Unfortuantely, + // // the IO Core semantics don't stop us from getting events + // // on the write side of the connection like timeouts so we + // // need to zero out the write of the continuation with + // // the do_io_write() call (INKqa05309) + // client_vc->do_io_shutdown(IO_SHUTDOWN_WRITE); + + // ka_vio = client_vc->do_io_read(this, INT64_MAX, read_buffer); + // ink_assert(slave_ka_vio != ka_vio); + + // // Set the active timeout to the same as the inactive time so + // // that this connection does not hang around forever if + // // the ua hasn't closed + // client_vc->set_active_timeout(HRTIME_SECONDS(trans.get_sm()->t_state.txn_conf->keep_alive_no_activity_timeout_in)); + // } + + // // [bug 2610799] Drain any data read. + // // If the buffer is full and the client writes again, we will not receive a + // // READ_READY event. + // sm_reader->consume(sm_reader->read_avail()); + //} else { + { + read_state = HCS_CLOSED; + L4rSsnDebug("[%" PRId64 "] session closed", con_id); + //HTTP_SUM_DYN_STAT(http_transactions_per_client_con, transact_count); + //HTTP_DECREMENT_DYN_STAT(http_current_client_connections_stat); + conn_decrease = false; + if (client_vc) { + client_vc->do_io_close(); + client_vc = nullptr; + } + } + if (transact_count == released_transactions) { + this->destroy(); + } +} + +int +L4RClientSession::state_wait_for_close(int event, void *data) +{ + STATE_ENTER(&L4RClientSession::state_wait_for_close, event, data); + + ink_assert(data == ka_vio); + ink_assert(read_state == HCS_HALF_CLOSED); + + Event *e = static_cast(data); + if (e == schedule_event) { + schedule_event = nullptr; + } + + switch (event) { + case VC_EVENT_EOS: + case VC_EVENT_ERROR: + case VC_EVENT_ACTIVE_TIMEOUT: + case VC_EVENT_INACTIVITY_TIMEOUT: + half_close = false; + this->do_io_close(); + break; + case VC_EVENT_READ_READY: + // Drain any data read + sm_reader->consume(sm_reader->read_avail()); + break; + + default: + ink_release_assert(0); + break; + } + + return 0; +} + +int +L4RClientSession::state_slave_keep_alive(int event, void *data) +{ + STATE_ENTER(&L4RClientSession::state_slave_keep_alive, event, data); + + ink_assert(data == slave_ka_vio); + + Event *e = static_cast(data); + if (e == schedule_event) { + schedule_event = nullptr; + } + + switch (event) { + default: + case VC_EVENT_READ_COMPLETE: + // These events are bogus + ink_assert(0); + /* Fall Through */ + case VC_EVENT_ERROR: + case VC_EVENT_READ_READY: + case VC_EVENT_EOS: + // The server session closed or something is amiss + bound_ss->do_io_close(); + bound_ss = nullptr; + slave_ka_vio = nullptr; + break; + + case VC_EVENT_ACTIVE_TIMEOUT: + case VC_EVENT_INACTIVITY_TIMEOUT: + // Timeout - place the session on the shared pool + bound_ss->release(); + bound_ss = nullptr; + slave_ka_vio = nullptr; + break; + } + + return 0; +} + +int +L4RClientSession::state_keep_alive(int event, void *data) +{ + // Route the event. It is either for client vc or + // the origin server slave vc + if (data && data == slave_ka_vio) { + return state_slave_keep_alive(event, data); + } else { + ink_assert(data && data == ka_vio); + ink_assert(read_state == HCS_KEEP_ALIVE); + } + + STATE_ENTER(&L4RClientSession::state_keep_alive, event, data); + + switch (event) { + case VC_EVENT_READ_READY: + // New transaction, need to spawn of new sm to process + // request + new_transaction(); + break; + + case VC_EVENT_EOS: + this->do_io_close(); + break; + + case VC_EVENT_READ_COMPLETE: + default: + // These events are bogus + ink_assert(0); + // Fall through + case VC_EVENT_ERROR: + case VC_EVENT_ACTIVE_TIMEOUT: + case VC_EVENT_INACTIVITY_TIMEOUT: + // Keep-alive timed out + this->do_io_close(); + break; + } + + return 0; +} +void +L4RClientSession::reenable(VIO *vio) +{ + client_vc->reenable(vio); +} + +// Called from the Http1ClientTransaction::release +void +L4RClientSession::release(ProxyClientTransaction *trans) +{ +#if 0 + ink_assert(read_state == HCS_ACTIVE_READER || read_state == HCS_INIT); + + // Clean up the write VIO in case of inactivity timeout + this->do_io_write(nullptr, 0, nullptr); + + // Check to see there is remaining data in the + // buffer. If there is, spin up a new state + // machine to process it. Otherwise, issue an + // IO to wait for new data + bool more_to_read = this->sm_reader->is_read_avail_more_than(0); + if (more_to_read) { + trans->destroy(); + trans->set_restart_immediate(true); + L4rSsnDebug("[%" PRId64 "] data already in buffer, starting new transaction", con_id); + new_transaction(); + } else { + L4rSsnDebug("[%" PRId64 "] initiating io for next header", con_id); + trans->set_restart_immediate(false); + read_state = HCS_KEEP_ALIVE; + SET_HANDLER(&L4RClientSession::state_keep_alive); + ka_vio = this->do_io_read(this, INT64_MAX, read_buffer); + ink_assert(slave_ka_vio != ka_vio); + + if (client_vc) { + client_vc->cancel_active_timeout(); + client_vc->add_to_keep_alive_queue(); + } + trans->destroy(); + } +#endif +} + +void +L4RClientSession::new_transaction() +{ + // If the client connection terminated during API callouts we're done. + if (nullptr == client_vc) { + this->do_io_close(); // calls the SSN_CLOSE hooks to match the SSN_START hooks. + return; + } + + // Defensive programming, make sure nothing persists across + // connection re-use + half_close = false; + + read_state = HCS_ACTIVE_READER; + + //trans.set_parent(this); + transact_count++; + + client_vc->add_to_active_queue(); + //trans.new_transaction(); +} + +void +L4RClientSession::attach_server_session(VConnection *vcssession, bool transaction_done) +{ + // TODO: Remove this hack! + L4RServerSession *ssession = dynamic_cast(vcssession); + if (ssession) { + ink_assert(bound_ss == nullptr); + ssession->state = LSS_KA_CLIENT_SLAVE; + bound_ss = ssession; + L4rSsnDebug("[%" PRId64 "] attaching server session [%" PRId64 "] as slave", con_id, ssession->con_id); + ink_assert(ssession->get_reader()->read_avail() == 0); + ink_assert(ssession->get_netvc() != this->get_netvc()); + + // handling potential keep-alive here + clear_session_active(); + + // Since this our slave, issue an IO to detect a close and + // have it call the client session back. This IO also prevent + // the server net conneciton from calling back a dead sm + SET_HANDLER(&L4RClientSession::state_keep_alive); + slave_ka_vio = ssession->do_io_read(this, INT64_MAX, ssession->read_buffer); + ink_assert(slave_ka_vio != ka_vio); + + // Transfer control of the write side as well + ssession->do_io_write(this, 0, nullptr); + + if (transaction_done) { + //ssession->get_netvc()->set_inactivity_timeout( + // HRTIME_SECONDS(trans.get_sm()->t_state.txn_conf->keep_alive_no_activity_timeout_out)); + ssession->get_netvc()->set_inactivity_timeout(5); + ssession->get_netvc()->cancel_active_timeout(); + } else { + // we are serving from the cache - this could take a while. + ssession->get_netvc()->cancel_inactivity_timeout(); + ssession->get_netvc()->cancel_active_timeout(); + } + } else { + ink_assert(bound_ss != nullptr); + bound_ss = nullptr; + slave_ka_vio = nullptr; + } +} diff --git a/proxy/l4r/L4RClientSession.h b/proxy/l4r/L4RClientSession.h new file mode 100644 index 00000000000..0f63b886f0d --- /dev/null +++ b/proxy/l4r/L4RClientSession.h @@ -0,0 +1,199 @@ +/** @file + + A brief file description + + @section license License + + Licensed to the Apache Software Foundation (ASF) under one + or more contributor license agreements. See the NOTICE file + distributed with this work for additional information + regarding copyright ownership. The ASF licenses this file + to you under the Apache License, Version 2.0 (the + "License"); you may not use this file except in compliance + with the License. You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + */ + +/**************************************************************************** + + L4RClientSession.h + + Description: + + ****************************************************************************/ + +#pragma once + +//#include "libts.h" +#include "P_Net.h" +#include "InkAPIInternal.h" +#include "HTTP.h" +#include "HttpConfig.h" +#include "IPAllow.h" +#include "ProxyClientSession.h" +#include "L4RServerSession.h" +//#include "Http1ClientTransaction.h" + +#ifdef USE_HTTP_DEBUG_LISTS +extern ink_mutex debug_cs_list2_mutex; +#endif + +class HttpSM; +class HttpServerSession; + +class L4RClientSession : public BasicProxyClientSession +{ +public: + typedef BasicProxyClientSession super; ///< Parent type. + L4RClientSession(); + + // Implement ProxyClientSession interface. + void destroy() override; + void free() override; + void release_transaction(); + + void + start() override + { + // Troll for data to get a new transaction + //this->release(&trans); + } + + void new_connection(NetVConnection *new_vc, MIOBuffer *iobuf, IOBufferReader *reader, bool backdoor) override; + + // Implement VConnection interface. + VIO *do_io_read(Continuation *c, int64_t nbytes = INT64_MAX, MIOBuffer *buf = nullptr) override; + VIO *do_io_write(Continuation *c = nullptr, int64_t nbytes = INT64_MAX, IOBufferReader *buf = nullptr, + bool owner = false) override; + + void do_io_close(int lerrno = -1) override; + void do_io_shutdown(ShutdownHowTo_t howto) override; + void reenable(VIO *vio) override; + + void + set_half_close_flag(bool flag) override + { + half_close = flag; + } + + bool + get_half_close_flag() const override + { + return half_close; + } + + NetVConnection * + get_netvc() const override + { + return client_vc; + } + + void + release_netvc() override + { + // Make sure the vio's are also released to avoid + // later surprises in inactivity timeout + if (client_vc) { + client_vc->do_io_read(nullptr, 0, nullptr); + client_vc->do_io_write(nullptr, 0, nullptr); + client_vc->set_action(nullptr); + client_vc = nullptr; + } + } + + int + get_transact_count() const override + { + return transact_count; + } + + // Indicate we are done with a transaction + void release(ProxyClientTransaction *trans) override; + + void attach_server_session(VConnection *ssession, bool transaction_done = true) override; + + VConnection * + get_server_session() const override + { + return bound_ss; + } + + void + set_active_timeout(ink_hrtime timeout_in) override + { + if (client_vc) + client_vc->set_active_timeout(timeout_in); + } + + void + set_inactivity_timeout(ink_hrtime timeout_in) override + { + if (client_vc) + client_vc->set_inactivity_timeout(timeout_in); + } + + void + cancel_inactivity_timeout() override + { + if (client_vc) + client_vc->cancel_inactivity_timeout(); + } + + const char * + get_protocol_string() const override + { + return "http"; + } + +private: + L4RClientSession(L4RClientSession &); + + void new_transaction(); + + int state_keep_alive(int event, void *data); + int state_slave_keep_alive(int event, void *data); + int state_wait_for_close(int event, void *data); + void set_tcp_init_cwnd(); + + enum C_Read_State { + HCS_INIT, + HCS_ACTIVE_READER, + HCS_KEEP_ALIVE, + HCS_HALF_CLOSED, + HCS_CLOSED, + }; + + NetVConnection *client_vc; + int magic; + int transact_count; + bool tcp_init_cwnd_set; + bool half_close; + bool conn_decrease; + + MIOBuffer *read_buffer; + IOBufferReader *sm_reader; + + C_Read_State read_state; + + VIO *ka_vio; + VIO *slave_ka_vio; + + L4RServerSession *bound_ss; + + int released_transactions; + +public: + // Link debug_link; + LINK(L4RClientSession, debug_link); + + //Http1ClientTransaction trans; +}; + +extern ClassAllocator l4rClientSessionAllocator; diff --git a/proxy/l4r/L4RClientTransaction.cc b/proxy/l4r/L4RClientTransaction.cc new file mode 100644 index 00000000000..eaa64042e46 --- /dev/null +++ b/proxy/l4r/L4RClientTransaction.cc @@ -0,0 +1,75 @@ +/** @file + + L4RClientTransaction.cc - The Transaction class for L4R* + + @section license License + + Licensed to the Apache Software Foundation (ASF) under one + or more contributor license agreements. See the NOTICE file + distributed with this work for additional information + regarding copyright ownership. The ASF licenses this file + to you under the Apache License, Version 2.0 (the + "License"); you may not use this file except in compliance + with the License. You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + */ + +#include "L4RClientTransaction.h" +#include "Http1ClientSession.h" +#include "L4RSM.h" + +void +L4RClientTransaction::release(IOBufferReader *r) +{ + // Must set this inactivity count here rather than in the session because the state machine + // is not availble then + MgmtInt ka_in = current_reader->t_state.txn_conf->keep_alive_no_activity_timeout_in; + set_inactivity_timeout(HRTIME_SECONDS(ka_in)); + + parent->clear_session_active(); + parent->ssn_last_txn_time = Thread::get_hrtime(); + + // Make sure that the state machine is returning + // correct buffer reader + ink_assert(r == sm_reader); + if (r != sm_reader) { + this->do_io_close(); + } else { + super_type::release(r); + } +} + +void +L4RClientTransaction::set_parent(BasicProxyClientSession *new_parent) +{ + parent = new_parent; + L4RClientSession *l4r_parent = dynamic_cast(new_parent); + if (l4r_parent) { + outbound_port = l4r_parent->outbound_port; + outbound_ip4 = l4r_parent->outbound_ip4; + outbound_ip6 = l4r_parent->outbound_ip6; + outbound_transparent = l4r_parent->f_outbound_transparent; + } + super_type::set_parent(new_parent); +} + +void +L4RClientTransaction::transaction_done() +{ + if (parent) { + static_cast(parent)->release_transaction(); + } +} + +bool +L4RClientTransaction::allow_half_open() const +{ + return current_reader ? current_reader->t_state.txn_conf->allow_half_open > 0 : true; +} diff --git a/proxy/l4r/L4RClientTransaction.h b/proxy/l4r/L4RClientTransaction.h new file mode 100644 index 00000000000..2c6b4dc5a1a --- /dev/null +++ b/proxy/l4r/L4RClientTransaction.h @@ -0,0 +1,139 @@ +/** @file + + L4RClientTransaction.h - The Transaction class for L4R* + + @section license License + + Licensed to the Apache Software Foundation (ASF) under one + or more contributor license agreements. See the NOTICE file + distributed with this work for additional information + regarding copyright ownership. The ASF licenses this file + to you under the Apache License, Version 2.0 (the + "License"); you may not use this file except in compliance + with the License. You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + */ + +#pragma once + +#include "../ProxyClientTransaction.h" + +class Continuation; +class L4RClientSession; + +class L4RClientTransaction : public BasicProxyClientTransaction +{ +public: + using super_type = BasicProxyClientTransaction; + + L4RClientTransaction() {} + // Implement VConnection interface. + VIO * + do_io_read(Continuation *c, int64_t nbytes = INT64_MAX, MIOBuffer *buf = nullptr) override + { + return parent->do_io_read(c, nbytes, buf); + } + VIO * + do_io_write(Continuation *c = nullptr, int64_t nbytes = INT64_MAX, IOBufferReader *buf = nullptr, bool owner = false) override + { + return parent->do_io_write(c, nbytes, buf, owner); + } + + void + do_io_close(int lerrno = -1) override + { + parent->do_io_close(lerrno); + // this->destroy(); Parent owns this data structure. No need for separate destroy. + } + + // Don't destroy your elements. Rely on the Http1ClientSession to clean up the + // L4RClientTransaction class as necessary. The super::destroy() clears the + // mutex, which Http1ClientSession owns. + void + destroy() override + { + current_reader = nullptr; + } + + void + do_io_shutdown(ShutdownHowTo_t howto) override + { + parent->do_io_shutdown(howto); + } + + void + reenable(VIO *vio) override + { + parent->reenable(vio); + } + + void + set_reader(IOBufferReader *reader) + { + sm_reader = reader; + } + + void release(IOBufferReader *r) override; + + bool + ignore_keep_alive() override + { + return false; + } + + bool allow_half_open() const override; + + void set_parent(BasicProxyClientSession *new_parent); + + bool + is_outbound_transparent() const override + { + return outbound_transparent; + } + void + set_outbound_transparent(bool flag) override + { + outbound_transparent = flag; + } + + // Pass on the timeouts to the netvc + void + set_active_timeout(ink_hrtime timeout_in) override + { + if (parent) + parent->set_active_timeout(timeout_in); + } + void + set_inactivity_timeout(ink_hrtime timeout_in) override + { + if (parent) + parent->set_inactivity_timeout(timeout_in); + } + void + cancel_inactivity_timeout() override + { + if (parent) + parent->cancel_inactivity_timeout(); + } + void transaction_done() override; + + int + get_transaction_id() const override + { + // For HTTP/1 there is only one on-going transaction at a time per session/connection. Therefore, the transaction count can be + // presumed not to increase during the lifetime of a transaction, thus this function will return a consistent unique transaction + // identifier. + // + return get_transact_count(); + } + +protected: + bool outbound_transparent{false}; +}; diff --git a/proxy/l4r/L4RProxyServerMain.cc b/proxy/l4r/L4RProxyServerMain.cc new file mode 100644 index 00000000000..b1a191ae726 --- /dev/null +++ b/proxy/l4r/L4RProxyServerMain.cc @@ -0,0 +1,408 @@ +/** @file + + A brief file description + + @section license License + + Licensed to the Apache Software Foundation (ASF) under one + or more contributor license agreements. See the NOTICE file + distributed with this work for additional information + regarding copyright ownership. The ASF licenses this file + to you under the Apache License, Version 2.0 (the + "License"); you may not use this file except in compliance + with the License. You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + */ + +#include "ts/ink_config.h" +#include "P_Net.h" +#include "Main.h" +//#include "HttpConfig.h" +//#include "HttpSessionAccept.h" +//#include "ReverseProxy.h" +//#include "HttpSessionManager.h" +//#include "HttpUpdateSM.h" +#ifdef USE_HTTP_DEBUG_LISTS +//#include "Http1ClientSession.h" +#endif +//#include "HttpPages.h" +//#include "HttpTunnel.h" +#include "ts/Tokenizer.h" +#include "P_SSLNextProtocolAccept.h" +#include "ProtocolProbeSessionAccept.h" +//#include "http2/Http2SessionAccept.h" +//#include "HttpConnectionCount.h" +#include "L4RProxyServerMain.h" +#include "L4RSessionAccept.h" + +#include + +//HttpSessionAccept *plugin_http_accept = nullptr; +//HttpSessionAccept *plugin_http_transparent_accept = nullptr; + +//static SLL ssl_plugin_acceptors; +//static Ptr ssl_plugin_mutex; + +//std::mutex proxyServerMutex; +//std::condition_variable proxyServerCheck; +//bool et_net_threads_ready = false; + +extern int num_of_net_threads; +extern int num_accept_threads; + +/// Global BufferWriter format name functions. +namespace +{ +void +TS_bwf_thread(ts::BufferWriter &w, ts::BWFSpec const &spec) +{ + bwformat(w, spec, this_thread()); +} +void +TS_bwf_ethread(ts::BufferWriter &w, ts::BWFSpec const &spec) +{ + bwformat(w, spec, this_ethread()); +} +} // namespace + +// File / process scope initializations +static bool L4R_SERVER_INITIALIZED __attribute__((unused)) = []() -> bool { + ts::bwf_register_global("ts-thread", &TS_bwf_thread); + ts::bwf_register_global("ts-ethread", &TS_bwf_ethread); + return true; +}(); + +#if 0 +bool +ssl_register_protocol(const char *protocol, Continuation *contp) +{ + SCOPED_MUTEX_LOCK(lock, ssl_plugin_mutex, this_ethread()); + + for (SSLNextProtocolAccept *ssl = ssl_plugin_acceptors.head; ssl; ssl = ssl_plugin_acceptors.next(ssl)) { + if (!ssl->registerEndpoint(protocol, contp)) { + return false; + } + } + + return true; +} + +bool +ssl_unregister_protocol(const char *protocol, Continuation *contp) +{ + SCOPED_MUTEX_LOCK(lock, ssl_plugin_mutex, this_ethread()); + + for (SSLNextProtocolAccept *ssl = ssl_plugin_acceptors.head; ssl; ssl = ssl_plugin_acceptors.next(ssl)) { + // Ignore possible failure because we want to try to unregister + // from all SSL ports. + ssl->unregisterEndpoint(protocol, contp); + } + + return true; +} +#endif + +///////////////////////////////////////////////////////////////// +// +// main() +// +///////////////////////////////////////////////////////////////// + +/** Data about an acceptor. + + This is used to separate setting up the proxy ports and + starting to accept on them. + +*/ +struct L4RProxyAcceptor { + /// Accept continuation. + Continuation *_accept; + /// Options for @c NetProcessor. + NetProcessor::AcceptOptions _net_opt; + + /// Default constructor. + L4RProxyAcceptor() : _accept(nullptr) {} +}; + +/** Global acceptors. + + This is parallel to @c HttpProxyPort::global(), each generated + from the corresponding port descriptor. + + @internal We use @c Continuation instead of @c HttpAccept because + @c SSLNextProtocolAccept is a subclass of @c Cont instead of @c + HttpAccept. +*/ +std::vector L4RProxyAcceptors; + +// Called from InkAPI.cc +NetProcessor::AcceptOptions +make_net_accept_options2(const HttpProxyPort *port, unsigned nthreads) +{ + NetProcessor::AcceptOptions net; + + net.accept_threads = nthreads; + + REC_ReadConfigInteger(net.packet_mark, "proxy.config.net.sock_packet_mark_in"); + REC_ReadConfigInteger(net.packet_tos, "proxy.config.net.sock_packet_tos_in"); + REC_ReadConfigInteger(net.recv_bufsize, "proxy.config.net.sock_recv_buffer_size_in"); + REC_ReadConfigInteger(net.send_bufsize, "proxy.config.net.sock_send_buffer_size_in"); + REC_ReadConfigInteger(net.sockopt_flags, "proxy.config.net.sock_option_flag_in"); + +#ifdef TCP_FASTOPEN + REC_ReadConfigInteger(net.tfo_queue_length, "proxy.config.net.sock_option_tfo_queue_size_in"); +#endif + + if (port) { + net.f_inbound_transparent = port->m_inbound_transparent_p; + net.ip_family = port->m_family; + net.local_port = port->m_port; + + if (port->m_inbound_ip.isValid()) { + net.local_ip = port->m_inbound_ip; + }/* else if (AF_INET6 == port->m_family && HttpConfig::m_master.inbound_ip6.isIp6()) { + net.local_ip = HttpConfig::m_master.inbound_ip6; + } else if (AF_INET == port->m_family && HttpConfig::m_master.inbound_ip4.isIp4()) { + net.local_ip = HttpConfig::m_master.inbound_ip4; + }*/ + } + + return net; +} + +static void +MakeL4RProxyAcceptor(L4RProxyAcceptor &acceptor, HttpProxyPort &port, unsigned nthreads) +{ + // Do http/https routing if port is not 8000 + if (port.m_port != 8000) { + return; + } + + NetProcessor::AcceptOptions &net_opt = acceptor._net_opt; +#if 0 + HttpSessionAccept::Options accept_opt; +#endif + net_opt = make_net_accept_options2(&port, nthreads); +#if 0 + accept_opt.f_outbound_transparent = port.m_outbound_transparent_p; + accept_opt.transport_type = port.m_type; + accept_opt.setHostResPreference(port.m_host_res_preference); + accept_opt.setTransparentPassthrough(port.m_transparent_passthrough); + accept_opt.setSessionProtocolPreference(port.m_session_protocol_preference); + + if (port.m_outbound_ip4.isValid()) { + accept_opt.outbound_ip4 = port.m_outbound_ip4; + } else if (HttpConfig::m_master.outbound_ip4.isValid()) { + accept_opt.outbound_ip4 = HttpConfig::m_master.outbound_ip4; + } + + if (port.m_outbound_ip6.isValid()) { + accept_opt.outbound_ip6 = port.m_outbound_ip6; + } else if (HttpConfig::m_master.outbound_ip6.isValid()) { + accept_opt.outbound_ip6 = HttpConfig::m_master.outbound_ip6; + } +#endif + + // OK the way this works is that the fallback for each port is a protocol + // probe acceptor. For SSL ports, we can stack a NPN+ALPN acceptor in front + // of that, and these ports will fall back to the probe if no NPN+ALPN endpoint + // was negotiated. + + // XXX the protocol probe should be a configuration option. + + ProtocolProbeSessionAccept *probe = new ProtocolProbeSessionAccept(); + L4RSessionAccept *l4r = nullptr; // don't allocate this unless it will be used. + probe->proxyPort = &port; + + // Layer 4 routing + if (port.m_port == 8000) { + //detail::L4RSessionAcceptOptions opt; + l4r = new L4RSessionAccept; + probe->registerEndpoint(ProtocolProbeSessionAccept::PROTO_L4R, l4r); + } + +#if 0 + if (port.isSSL()) { + SSLNextProtocolAccept *ssl = new SSLNextProtocolAccept(probe, port.m_transparent_passthrough); + + // ALPN selects the first server-offered protocol, + // so make sure that we offer the newest protocol first. + // But since registerEndpoint prepends you want to + // register them backwards, so you'd want to register + // the least important protocol first: + // http/1.0, http/1.1, h2 + + // HTTP + if (port.m_session_protocol_preference.contains(TS_ALPN_PROTOCOL_INDEX_HTTP_1_0)) { + ssl->registerEndpoint(TS_ALPN_PROTOCOL_HTTP_1_0, http); + } + + if (port.m_session_protocol_preference.contains(TS_ALPN_PROTOCOL_INDEX_HTTP_1_1)) { + ssl->registerEndpoint(TS_ALPN_PROTOCOL_HTTP_1_1, http); + } + + // HTTP2 + if (port.m_session_protocol_preference.contains(TS_ALPN_PROTOCOL_INDEX_HTTP_2_0)) { + Http2SessionAccept *acc = new Http2SessionAccept(accept_opt); + + ssl->registerEndpoint(TS_ALPN_PROTOCOL_HTTP_2_0, acc); + } + + SCOPED_MUTEX_LOCK(lock, ssl_plugin_mutex, this_ethread()); + ssl_plugin_acceptors.push(ssl); + ssl->proxyPort = &port; + acceptor._accept = ssl; + } else { +#endif + acceptor._accept = probe; +#if 0 + } +#endif +} + +/// Do all pre-thread initialization / setup. +void +prep_L4RProxyServer() +{ + //httpSessionManager.init(); +} + +/** Set up all the accepts and sockets. + */ +void +init_accept_L4RProxyServer(int n_accept_threads) +{ + HttpProxyPort::Group &proxy_ports = HttpProxyPort::global(); + + //init_reverse_proxy(); + //http_pages_init(); + +#ifdef USE_HTTP_DEBUG_LISTS + ink_mutex_init(&debug_sm_list_mutex); + ink_mutex_init(&debug_cs_list_mutex); +#endif + + // Used to give plugins the ability to create http requests + // The equivalent of the connecting to localhost on the proxy + // port but without going through the operating system + // +#if 0 + if (plugin_http_accept == nullptr) { + plugin_http_accept = new HttpSessionAccept(); + } + + // Same as plugin_http_accept except outbound transparent. + if (!plugin_http_transparent_accept) { + HttpSessionAccept::Options ha_opt; + ha_opt.setOutboundTransparent(true); + plugin_http_transparent_accept = new HttpSessionAccept(ha_opt); + } + + if (!ssl_plugin_mutex) { + ssl_plugin_mutex = new_ProxyMutex(); + } +#endif + + // Do the configuration defined ports. + // Assign temporary empty objects of proxy ports size + L4RProxyAcceptors.assign(proxy_ports.size(), L4RProxyAcceptor()); + for (int i = 0, n = proxy_ports.size(); i < n; ++i) { + MakeL4RProxyAcceptor(L4RProxyAcceptors.at(i), proxy_ports[i], n_accept_threads); + } +} + +/** Increment the counter to keep track of how many et_net threads + * we have started. This function is scheduled at the start of each + * et_net thread using schedule_spawn(). We also check immediately + * after incrementing the counter to see whether all of the et_net + * threads have started such that we can notify main() to call + * start_HttpProxyServer(). + */ +void +init_L4RProxyServer(EThread *) +{ + if (eventProcessor.thread_group[ET_NET]._started == num_of_net_threads) { + std::unique_lock lock(proxyServerMutex); + et_net_threads_ready = true; + lock.unlock(); + proxyServerCheck.notify_one(); + } +} + +void +start_L4RProxyServer() +{ + static bool called_once = false; + HttpProxyPort::Group &proxy_ports = HttpProxyPort::global(); + + /////////////////////////////////// + // start accepting connections // + /////////////////////////////////// + + ink_assert(!called_once); + ink_assert(proxy_ports.size() == L4RProxyAcceptors.size()); + + for (int i = 0, n = proxy_ports.size(); i < n; ++i) { + L4RProxyAcceptor &acceptor = L4RProxyAcceptors[i]; + HttpProxyPort &port = proxy_ports[i]; + if (port.isSSL()) { + if (nullptr == sslNetProcessor.main_accept(acceptor._accept, port.m_fd, acceptor._net_opt)) { + return; + } + } else if (!port.isPlugin()) { + if (nullptr == netProcessor.main_accept(acceptor._accept, port.m_fd, acceptor._net_opt)) { + return; + } + } + // XXX although we make a good pretence here, I don't believe that NetProcessor::main_accept() ever actually returns + // NULL. It would be useful to be able to detect errors and spew them here though. + } + +#if TS_HAS_TESTS + if (is_action_tag_set("http_update_test")) { + //init_http_update_test(); + } +#endif + + // Set up stat page for http connection count +#if 0 + statPagesManager.register_http("connection_count", register_ShowConnectionCount); + + // Alert plugins that connections will be accepted. + APIHook *hook = lifecycle_hooks->get(TS_LIFECYCLE_PORTS_READY_HOOK); + while (hook) { + hook->invoke(TS_EVENT_LIFECYCLE_PORTS_READY, nullptr); + hook = hook->next(); + } +#endif +} + +void +start_L4RProxyServerBackDoor(int port, int accept_threads) +{ + NetProcessor::AcceptOptions opt; + //HttpSessionAccept::Options ha_opt; + + opt.local_port = port; + opt.accept_threads = accept_threads; + opt.localhost_only = true; + //ha_opt.backdoor = true; + opt.backdoor = true; + + // The backdoor only binds the loopback interface + netProcessor.main_accept(new L4RSessionAccept(/*ha_opt*/), NO_FD, opt); +} + +void +stop_L4RProxyServer() +{ + //sslNetProcessor.stop_accept(); + //netProcessor.stop_accept(); +} diff --git a/proxy/l4r/L4RProxyServerMain.h b/proxy/l4r/L4RProxyServerMain.h new file mode 100644 index 00000000000..cefb50fff56 --- /dev/null +++ b/proxy/l4r/L4RProxyServerMain.h @@ -0,0 +1,57 @@ +/** @file + + Main function to start the HTTP Proxy Server + + @section license License + + Licensed to the Apache Software Foundation (ASF) under one + or more contributor license agreements. See the NOTICE file + distributed with this work for additional information + regarding copyright ownership. The ASF licenses this file + to you under the Apache License, Version 2.0 (the + "License"); you may not use this file except in compliance + with the License. You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + */ + +#pragma once + +#include +#include +#include + +// We can use HttpProxyPort as there is nothing HTTP specific defined in it +struct HttpProxyPort; + +/// Perform any pre-thread start initialization. +void prep_L4RProxyServer(); + +/** Initialize all HTTP proxy port data structures needed to accept connections. + */ +void init_accept_L4RProxyServer(int n_accept_threads = 0); + +/** Checkes whether we can call start_HttpProxyServer(). + */ +void init_L4RProxyServer(EThread *); + +/** Start the proxy server. + The port data should have been created by @c prep_HttpProxyServer(). +*/ +void start_L4RProxyServer(); + +void stop_L4RProxyServer(); + +void start_L4RProxyServerBackDoor(int port, int accept_threads = 0); + +NetProcessor::AcceptOptions make_net_accept_options(const HttpProxyPort *port, unsigned nthreads); + +extern std::mutex proxyServerMutex; +extern std::condition_variable proxyServerCheck; +extern bool et_net_threads_ready; diff --git a/proxy/l4r/L4RSM.cc b/proxy/l4r/L4RSM.cc new file mode 100644 index 00000000000..74e9ff5f5ac --- /dev/null +++ b/proxy/l4r/L4RSM.cc @@ -0,0 +1,8014 @@ +/** @file + + L4R state machine + + @section license License + + Licensed to the Apache Software Foundation (ASF) under one + or more contributor license agreements. See the NOTICE file + distributed with this work for additional information + regarding copyright ownership. The ASF licenses this file + to you under the Apache License, Version 2.0 (the + "License"); you may not use this file except in compliance + with the License. You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + + */ + +#include "../ProxyClientTransaction.h" +#include "L4RSM.h" +//#include "HttpTransactHeaders.h" +#include "ProxyConfig.h" +#include "L4RServerSession.h" +//#include "HttpDebugNames.h" +//#include "HttpSessionManager.h" +#include "P_Cache.h" +#include "P_Net.h" +#include "StatPages.h" +#include "Log.h" +#include "LogAccessHttp.h" +#include "PluginVC.h" +#include "ReverseProxy.h" +//#include "RemapProcessor.h" +#include "Transform.h" +#include "P_SSLConfig.h" +//#include "HttpPages.h" +#include "IPAllow.h" +#include "ts/I_Layout.h" + +#include +#include +#include +#include + +#define DEFAULT_RESPONSE_BUFFER_SIZE_INDEX 6 // 8K +#define DEFAULT_REQUEST_BUFFER_SIZE_INDEX 6 // 8K +#define MIN_CONFIG_BUFFER_SIZE_INDEX 5 // 4K + +#define lsm_release_assert(EX) \ + { \ + if (!(EX)) { \ + this->dump_state_on_assert(); \ + _ink_assert(#EX, __FILE__, __LINE__); \ + } \ + } + +/* + * Comment this off if you don't + * want httpSM to use new_empty_MIOBuffer(..) call + */ + +#define USE_NEW_EMPTY_MIOBUFFER + +extern int cache_config_read_while_writer; +extern TunnelHashMap TunnelMap; // stores the name of the servers to tunnel to + +// We have a debugging list that can use to find stuck +// state machines +DList(L4RSM, debug_link) debug_sm_list; +ink_mutex debug_sm_list_mutex; + +static const int sub_header_size = sizeof("Content-type: ") - 1 + 2 + sizeof("Content-range: bytes ") - 1 + 4; +static const int boundary_size = 2 + sizeof("RANGE_SEPARATOR") - 1 + 2; + +namespace +{ +// Unique state machine identifier +std::atomic next_sm_id(0); +} // namespace + +ClassAllocator l4rSMAllocator("l4rSMAllocator"); + +L4RVCTable::L4RVCTable() +{ + memset(&vc_table, 0, sizeof(vc_table)); +} + +L4RVCTableEntry * +L4RVCTable::new_entry() +{ + for (int i = 0; i < vc_table_max_entries; i++) { + if (vc_table[i].vc == nullptr) { + return vc_table + i; + } + } + + ink_release_assert(0); + return nullptr; +} + +L4RVCTableEntry * +L4RVCTable::find_entry(VConnection *vc) +{ + for (int i = 0; i < vc_table_max_entries; i++) { + if (vc_table[i].vc == vc) { + return vc_table + i; + } + } + + return nullptr; +} + +L4RVCTableEntry * +L4RVCTable::find_entry(VIO *vio) +{ + for (int i = 0; i < vc_table_max_entries; i++) { + if (vc_table[i].read_vio == vio || vc_table[i].write_vio == vio) { + ink_assert(vc_table[i].vc != nullptr); + return vc_table + i; + } + } + + return nullptr; +} + +// bool L4RVCTable::remove_entry(HttpVCEntry* e) +// +// Deallocates all buffers from the associated +// entry and re-initializes it's other fields +// for reuse +// +void +L4RVCTable::remove_entry(L4RVCTableEntry *e) +{ + ink_assert(e->vc == nullptr || e->in_tunnel); + e->vc = nullptr; + e->eos = false; + if (e->read_buffer) { + free_MIOBuffer(e->read_buffer); + e->read_buffer = nullptr; + } + if (e->write_buffer) { + free_MIOBuffer(e->write_buffer); + e->write_buffer = nullptr; + } + e->read_vio = nullptr; + e->write_vio = nullptr; + e->vc_handler = nullptr; + e->vc_type = HTTP_UNKNOWN; + e->in_tunnel = false; +} + +// bool L4RVCTable::cleanup_entry(HttpVCEntry* e) +// +// Closes the associate vc for the entry, +// and the call remove_entry +// +void +L4RVCTable::cleanup_entry(L4RVCTableEntry *e) +{ + ink_assert(e->vc); + if (e->in_tunnel == false) { + // Update stats + switch (e->vc_type) { + case HTTP_UA_VC: + // proxy.process.http.current_client_transactions is decremented in L4RSM::destroy + break; + default: + // This covers: + // HTTP_UNKNOWN, HTTP_SERVER_VC, HTTP_TRANSFORM_VC, HTTP_CACHE_READ_VC, + // HTTP_CACHE_WRITE_VC, HTTP_RAW_SERVER_VC + break; + } + + e->vc->do_io_close(); + e->vc = nullptr; + } + remove_entry(e); +} + +void +L4RVCTable::cleanup_all() +{ + for (int i = 0; i < vc_table_max_entries; i++) { + if (vc_table[i].vc != nullptr) { + cleanup_entry(vc_table + i); + } + } +} + +#define SMDebug(tag, ...) SpecificDebug(debug_on, tag, __VA_ARGS__) + +#define REMEMBER(e, r) \ + { \ + history.push_back(MakeSourceLocation(), e, r); \ + } + +#ifdef STATE_ENTER +#undef STATE_ENTER +#endif +#define STATE_ENTER(state_name, event) \ + { \ + /*ink_assert (magic == HTTP_SM_MAGIC_ALIVE); */ REMEMBER(event, reentrancy_count); \ + SMDebug("l4r", "[%" PRId64 "] [%s, %s]", sm_id, #state_name, HttpDebugNames::get_event_name(event)); \ + } + +#define L4R_SM_SET_DEFAULT_HANDLER(_h) \ + { \ + REMEMBER(NO_EVENT, reentrancy_count); \ + default_handler = _h; \ + } + +L4RSM::L4RSM() : Continuation(nullptr) +{ + ink_zero(vc_table); + ink_zero(http_parser); +} + +void +L4RSM::cleanup() +{ + t_state.destroy(); + api_hooks.clear(); + http_parser_clear(&http_parser); + + // t_state.content_control.cleanup(); + + HttpConfig::release(t_state.http_config_param); + m_remap->release(); + + mutex.clear(); + tunnel.mutex.clear(); + cache_sm.mutex.clear(); + transform_cache_sm.mutex.clear(); + magic = HTTP_SM_MAGIC_DEAD; + debug_on = false; +} + +void +L4RSM::destroy() +{ + cleanup(); + l4rSMAllocator.free(this); +} + +void +L4RSM::init() +{ + milestones[TS_MILESTONE_SM_START] = Thread::get_hrtime(); + + magic = HTTP_SM_MAGIC_ALIVE; + + // Unique state machine identifier + sm_id = next_sm_id++; + t_state.state_machine_id = sm_id; + t_state.state_machine = this; + + t_state.http_config_param = HttpConfig::acquire(); + // Acquire a lease on the global remap / rewrite table (stupid global name ...) + m_remap = rewrite_table->acquire(); + + // Simply point to the global config for the time being, no need to copy this + // entire struct if nothing is going to change it. + t_state.txn_conf = &t_state.http_config_param->oride; + + t_state.init(); + + // Added to skip dns if the document is in cache. DNS will be forced if there is a ip based ACL in + // cache control or parent.config or if the doc_in_cache_skip_dns is disabled or if http caching is disabled + // TODO: This probably doesn't honor this as a per-transaction overridable config. + t_state.force_dns = (ip_rule_in_CacheControlTable() || t_state.parent_params->parent_table->ipMatch || + !(t_state.txn_conf->doc_in_cache_skip_dns) || !(t_state.txn_conf->cache_http)); + + http_parser.m_allow_non_http = t_state.http_config_param->parser_allow_non_http; + http_parser_init(&http_parser); + + SET_HANDLER(&L4RSM::main_handler); + +#ifdef USE_HTTP_DEBUG_LISTS + ink_mutex_acquire(&debug_sm_list_mutex); + debug_sm_list.push(this); + ink_mutex_release(&debug_sm_list_mutex); +#endif +} + +void +L4RSM::set_ua_half_close_flag() +{ + ua_txn->set_half_close_flag(true); +} + +inline void +L4RSM::do_api_callout() +{ + if (hooks_set) { + do_api_callout_internal(); + } else { + handle_api_return(); + } +} + +int +L4RSM::state_add_to_list(int event, void * /* data ATS_UNUSED */) +{ + // The list if for stat pages and general debugging + // The config variable exists mostly to allow us to + // measure an performance drop during benchmark runs + if (t_state.http_config_param->enable_http_info) { + STATE_ENTER(&L4RSM::state_add_to_list, event); + ink_assert(event == EVENT_NONE || event == EVENT_INTERVAL); + + int bucket = ((unsigned int)sm_id % HTTP_LIST_BUCKETS); + + MUTEX_TRY_LOCK(lock, L4RSMList[bucket].mutex, mutex->thread_holding); + // the client_vc`s timeout events can be triggered, so we should not + // reschedule the http_sm when the lock is not acquired. + // FIXME: the sm_list may miss some http_sms when the lock contention + if (lock.is_locked()) { + L4RSMList[bucket].sm_list.push(this); + } + } + + t_state.api_next_action = HttpTransact::SM_ACTION_API_SM_START; + do_api_callout(); + return EVENT_DONE; +} + +int +L4RSM::state_remove_from_list(int event, void * /* data ATS_UNUSED */) +{ + // The config parameters are guaranteed not change + // across the life of a transaction so it safe to + // check the config here and use it determine + // whether we need to strip ourselves off of the + // state page list + if (t_state.http_config_param->enable_http_info) { + STATE_ENTER(&L4RSM::state_remove_from_list, event); + ink_assert(event == EVENT_NONE || event == EVENT_INTERVAL); + + int bucket = ((unsigned int)sm_id % HTTP_LIST_BUCKETS); + + MUTEX_TRY_LOCK(lock, L4RSMList[bucket].mutex, mutex->thread_holding); + if (!lock.is_locked()) { + L4R_SM_SET_DEFAULT_HANDLER(&L4RSM::state_remove_from_list); + mutex->thread_holding->schedule_in(this, HTTP_LIST_RETRY); + return EVENT_DONE; + } + + L4RSMList[bucket].sm_list.remove(this); + } + + return this->kill_this_async_hook(EVENT_NONE, nullptr); +} + +int +L4RSM::kill_this_async_hook(int /* event ATS_UNUSED */, void * /* data ATS_UNUSED */) +{ + // In the base L4RSM, we don't have anything to + // do here. subclasses can override this function + // to do their own asynchronous cleanup + // So We're now ready to finish off the state machine + terminate_sm = true; + kill_this_async_done = true; + + return EVENT_DONE; +} + +void +L4RSM::start_sub_sm() +{ + tunnel.init(this, mutex); + cache_sm.init(this, mutex); + transform_cache_sm.init(this, mutex); +} + +void +L4RSM::attach_client_session(ProxyClientTransaction *client_vc, IOBufferReader *buffer_reader) +{ + milestones[TS_MILESTONE_UA_BEGIN] = Thread::get_hrtime(); + ink_assert(client_vc != nullptr); + + NetVConnection *netvc = client_vc->get_netvc(); + if (!netvc) { + return; + } + ua_txn = client_vc; + + // It seems to be possible that the ua_txn pointer will go stale before log entries for this HTTP transaction are + // generated. Therefore, collect information that may be needed for logging from the ua_txn object at this point. + // + _client_transaction_id = ua_txn->get_transaction_id(); + { + auto p = ua_txn->get_parent(); + if (p) { + _client_connection_id = p->connection_id(); + } + } + + // Collect log & stats information + client_tcp_reused = !(ua_txn->is_first_transaction()); + SSLNetVConnection *ssl_vc = dynamic_cast(netvc); + if (ssl_vc != nullptr) { + client_connection_is_ssl = true; + client_ssl_reused = ssl_vc->getSSLSessionCacheHit(); + const char *protocol = ssl_vc->getSSLProtocol(); + client_sec_protocol = protocol ? protocol : "-"; + const char *cipher = ssl_vc->getSSLCipherSuite(); + client_cipher_suite = cipher ? cipher : "-"; + if (!client_tcp_reused) { + // Copy along the TLS handshake timings + milestones[TS_MILESTONE_TLS_HANDSHAKE_START] = ssl_vc->sslHandshakeBeginTime; + milestones[TS_MILESTONE_TLS_HANDSHAKE_END] = ssl_vc->sslHandshakeEndTime; + } + } + const char *protocol_str = client_vc->get_protocol_string(); + client_protocol = protocol_str ? protocol_str : "-"; + + ink_release_assert(ua_txn->get_half_close_flag() == false); + mutex = client_vc->mutex; + HTTP_INCREMENT_DYN_STAT(http_current_client_transactions_stat); + if (ua_txn->debug()) { + debug_on = true; + } + + start_sub_sm(); + + // Allocate a user agent entry in the state machine's + // vc table + ua_entry = vc_table.new_entry(); + ua_entry->vc = client_vc; + ua_entry->vc_type = HTTP_UA_VC; + + ats_ip_copy(&t_state.client_info.src_addr, netvc->get_remote_addr()); + ats_ip_copy(&t_state.client_info.dst_addr, netvc->get_local_addr()); + t_state.client_info.dst_addr.port() = netvc->get_local_port(); + t_state.client_info.is_transparent = netvc->get_is_transparent(); + t_state.backdoor_request = !client_vc->hooks_enabled(); + t_state.client_info.port_attribute = static_cast(netvc->attributes); + + // Record api hook set state + hooks_set = client_vc->has_hooks(); + + // Setup for parsing the header + ua_buffer_reader = buffer_reader; + ua_entry->vc_handler = &L4RSM::state_read_client_request_header; + t_state.hdr_info.client_request.destroy(); + t_state.hdr_info.client_request.create(HTTP_TYPE_REQUEST); + http_parser_init(&http_parser); + + // Prepare raw reader which will live until we are sure this is HTTP indeed + if (is_transparent_passthrough_allowed()) { + ua_raw_buffer_reader = buffer_reader->clone(); + } + + // We first need to run the transaction start hook. Since + // this hook maybe asynchronous, we need to disable IO on + // client but set the continuation to be the state machine + // so if we get an timeout events the sm handles them + ua_entry->read_vio = client_vc->do_io_read(this, 0, buffer_reader->mbuf); + + ///////////////////////// + // set up timeouts // + ///////////////////////// + client_vc->set_inactivity_timeout(HRTIME_SECONDS(t_state.http_config_param->accept_no_activity_timeout)); + client_vc->set_active_timeout(HRTIME_SECONDS(t_state.txn_conf->transaction_active_timeout_in)); + + ++reentrancy_count; + // Add our state sm to the sm list + state_add_to_list(EVENT_NONE, nullptr); + // This is another external entry point and it is possible for the state machine to get terminated + // while down the call chain from @c state_add_to_list. So we need to use the reentrancy_count to + // prevent cleanup there and do it here as we return to the external caller. + if (terminate_sm == true && reentrancy_count == 1) { + kill_this(); + } else { + --reentrancy_count; + ink_assert(reentrancy_count >= 0); + } +} + +void +L4RSM::setup_client_read_request_header() +{ + ink_assert(ua_entry->vc_handler == &L4RSM::state_read_client_request_header); + + ua_entry->read_vio = ua_txn->do_io_read(this, INT64_MAX, ua_buffer_reader->mbuf); + // The header may already be in the buffer if this + // a request from a keep-alive connection + handleEvent(VC_EVENT_READ_READY, ua_entry->read_vio); +} + +void +L4RSM::setup_blind_tunnel_port() +{ + NetVConnection *netvc = ua_txn->get_netvc(); + SSLNetVConnection *ssl_vc = dynamic_cast(netvc); + int host_len; + if (ssl_vc && ssl_vc->GetSNIMapping()) { + if (!t_state.hdr_info.client_request.url_get()->host_get(&host_len)) { + // the URL object has not been created in the start of the transaction. Hence, we need to create the URL here + URL u; + + t_state.hdr_info.client_request.create(HTTP_TYPE_REQUEST); + t_state.hdr_info.client_request.method_set(HTTP_METHOD_CONNECT, HTTP_LEN_CONNECT); + t_state.hdr_info.client_request.url_create(&u); + u.scheme_set(URL_SCHEME_TUNNEL, URL_LEN_TUNNEL); + t_state.hdr_info.client_request.url_set(&u); + auto *hs = TunnelMap.find(ssl_vc->serverName); + if (hs != nullptr) { + t_state.hdr_info.client_request.url_get()->host_set(hs->hostname, hs->len); + if (hs->port > 0) { + t_state.hdr_info.client_request.url_get()->port_set(hs->port); + } else { + t_state.hdr_info.client_request.url_get()->port_set(t_state.state_machine->ua_txn->get_netvc()->get_local_port()); + } + } else { + t_state.hdr_info.client_request.url_get()->host_set(ssl_vc->serverName, strlen(ssl_vc->serverName)); + t_state.hdr_info.client_request.url_get()->port_set(t_state.state_machine->ua_txn->get_netvc()->get_local_port()); + } + } + } else { + char new_host[INET6_ADDRSTRLEN]; + ats_ip_ntop(t_state.state_machine->ua_txn->get_netvc()->get_local_addr(), new_host, sizeof(new_host)); + + t_state.hdr_info.client_request.url_get()->host_set(new_host, strlen(new_host)); + t_state.hdr_info.client_request.url_get()->port_set(t_state.state_machine->ua_txn->get_netvc()->get_local_port()); + } + call_transact_and_set_next_state(HttpTransact::HandleBlindTunnel); +} + +int +L4RSM::state_read_client_request_header(int event, void *data) +{ + STATE_ENTER(&L4RSM::state_read_client_request_header, event); + + ink_assert(ua_entry->read_vio == (VIO *)data); + ink_assert(server_entry == nullptr); + ink_assert(server_session == nullptr); + + int bytes_used = 0; + ink_assert(ua_entry->eos == false); + + NetVConnection *netvc = ua_txn->get_netvc(); + if (!netvc && event != VC_EVENT_EOS) { + return 0; + } + + switch (event) { + case VC_EVENT_READ_READY: + case VC_EVENT_READ_COMPLETE: + // More data to parse + break; + + case VC_EVENT_EOS: + ua_entry->eos = true; + if ((client_request_hdr_bytes > 0) && is_transparent_passthrough_allowed() && (ua_raw_buffer_reader != nullptr)) { + break; + } + // Fall through + case VC_EVENT_ERROR: + case VC_EVENT_INACTIVITY_TIMEOUT: + case VC_EVENT_ACTIVE_TIMEOUT: + // The user agent is hosed. Close it & + // bail on the state machine + vc_table.cleanup_entry(ua_entry); + ua_entry = nullptr; + set_ua_abort(HttpTransact::ABORTED, event); + terminate_sm = true; + return 0; + } + + // Reset the inactivity timeout if this is the first + // time we've been called. The timeout had been set to + // the accept timeout by the ProxyClientTransaction + // + if ((ua_buffer_reader->read_avail() > 0) && (client_request_hdr_bytes == 0)) { + milestones[TS_MILESTONE_UA_FIRST_READ] = Thread::get_hrtime(); + ua_txn->set_inactivity_timeout(HRTIME_SECONDS(t_state.txn_conf->transaction_no_activity_timeout_in)); + } + ///////////////////// + // tokenize header // + ///////////////////// + + ParseResult state = t_state.hdr_info.client_request.parse_req(&http_parser, ua_buffer_reader, &bytes_used, ua_entry->eos, + t_state.http_config_param->strict_uri_parsing); + + client_request_hdr_bytes += bytes_used; + + // Check to see if we are over the hdr size limit + if (client_request_hdr_bytes > t_state.txn_conf->request_hdr_max_size) { + SMDebug("http", "client header bytes were over max header size; treating as a bad request"); + state = PARSE_RESULT_ERROR; + } + + // We need to handle EOS as well as READ_READY because the client + // may have sent all of the data already followed by a fIN and that + // should be OK. + if (is_transparent_passthrough_allowed() && ua_raw_buffer_reader != nullptr) { + bool do_blind_tunnel = false; + // If we had a parse error and we're done reading data + // blind tunnel + if ((event == VC_EVENT_READ_READY || event == VC_EVENT_EOS) && state == PARSE_RESULT_ERROR) { + do_blind_tunnel = true; + + // If we had a GET request that has data after the + // get request, do blind tunnel + } else if (state == PARSE_RESULT_DONE && t_state.hdr_info.client_request.method_get_wksidx() == HTTP_WKSIDX_GET && + ua_buffer_reader->read_avail() > 0 && !t_state.hdr_info.client_request.is_keep_alive_set()) { + do_blind_tunnel = true; + } + if (do_blind_tunnel) { + SMDebug("http", "[%" PRId64 "] first request on connection failed parsing, switching to passthrough.", sm_id); + + t_state.transparent_passthrough = true; + http_parser_clear(&http_parser); + + // Turn off read eventing until we get the + // blind tunnel infrastructure set up + if (netvc) { + netvc->do_io_read(this, 0, nullptr); + } + + /* establish blind tunnel */ + setup_blind_tunnel_port(); + + // Setting half close means we will send the FIN when we've written all of the data. + if (event == VC_EVENT_EOS) { + this->set_ua_half_close_flag(); + t_state.client_info.keep_alive = HTTP_NO_KEEPALIVE; + } + return 0; + } + } + + // Check to see if we are done parsing the header + if (state != PARSE_RESULT_CONT || ua_entry->eos || (state == PARSE_RESULT_CONT && event == VC_EVENT_READ_COMPLETE)) { + if (ua_raw_buffer_reader != nullptr) { + ua_raw_buffer_reader->dealloc(); + ua_raw_buffer_reader = nullptr; + } + http_parser_clear(&http_parser); + ua_entry->vc_handler = &L4RSM::state_watch_for_client_abort; + milestones[TS_MILESTONE_UA_READ_HEADER_DONE] = Thread::get_hrtime(); + } + + switch (state) { + case PARSE_RESULT_ERROR: + SMDebug("http", "[%" PRId64 "] error parsing client request header", sm_id); + + // Disable further I/O on the client + ua_entry->read_vio->nbytes = ua_entry->read_vio->ndone; + + call_transact_and_set_next_state(HttpTransact::BadRequest); + break; + + case PARSE_RESULT_CONT: + if (ua_entry->eos) { + SMDebug("http_seq", "[%" PRId64 "] EOS before client request parsing finished", sm_id); + set_ua_abort(HttpTransact::ABORTED, event); + + // Disable further I/O on the client + ua_entry->read_vio->nbytes = ua_entry->read_vio->ndone; + + call_transact_and_set_next_state(HttpTransact::BadRequest); + break; + } else if (event == VC_EVENT_READ_COMPLETE) { + SMDebug("http_parse", "[%" PRId64 "] VC_EVENT_READ_COMPLETE and PARSE CONT state", sm_id); + break; + } else { + if (is_transparent_passthrough_allowed() && ua_raw_buffer_reader != nullptr && + ua_raw_buffer_reader->get_current_block()->write_avail() <= 0) { + // Disable passthrough regardless of eventual parsing failure or success -- otherwise + // we either have to consume some data or risk blocking the writer. + ua_raw_buffer_reader->dealloc(); + ua_raw_buffer_reader = nullptr; + } + ua_entry->read_vio->reenable(); + return VC_EVENT_CONT; + } + case PARSE_RESULT_DONE: + SMDebug("http", "[%" PRId64 "] done parsing client request header", sm_id); + + ua_txn->set_session_active(); + + if (t_state.hdr_info.client_request.version_get() == HTTPVersion(1, 1) && + (t_state.hdr_info.client_request.method_get_wksidx() == HTTP_WKSIDX_POST || + t_state.hdr_info.client_request.method_get_wksidx() == HTTP_WKSIDX_PUT) && + t_state.http_config_param->send_100_continue_response) { + int len = 0; + const char *expect = t_state.hdr_info.client_request.value_get(MIME_FIELD_EXPECT, MIME_LEN_EXPECT, &len); + // When receive an "Expect: 100-continue" request from client, ATS sends a "100 Continue" response to client + // immediately, before receive the real response from original server. + if ((len == HTTP_LEN_100_CONTINUE) && (strncasecmp(expect, HTTP_VALUE_100_CONTINUE, HTTP_LEN_100_CONTINUE) == 0)) { + int64_t alloc_index = buffer_size_to_index(len_100_continue_response); + if (ua_entry->write_buffer) { + free_MIOBuffer(ua_entry->write_buffer); + ua_entry->write_buffer = nullptr; + } + ua_entry->write_buffer = new_MIOBuffer(alloc_index); + IOBufferReader *buf_start = ua_entry->write_buffer->alloc_reader(); + + t_state.hdr_info.client_request.m_100_continue_required = true; + + SMDebug("http_seq", "send 100 Continue response to client"); + int64_t nbytes = ua_entry->write_buffer->write(str_100_continue_response, len_100_continue_response); + ua_entry->write_vio = ua_txn->do_io_write(this, nbytes, buf_start); + } + } + + if (t_state.hdr_info.client_request.method_get_wksidx() == HTTP_WKSIDX_TRACE || + (t_state.hdr_info.client_request.get_content_length() == 0 && + t_state.client_info.transfer_encoding != HttpTransact::CHUNKED_ENCODING)) { + // Enable further IO to watch for client aborts + ua_entry->read_vio->reenable(); + } else { + // Disable further I/O on the client since there could + // be body that we are tunneling POST/PUT/CONNECT or + // extension methods and we can't issue another + // another IO later for the body with a different buffer + ua_entry->read_vio->nbytes = ua_entry->read_vio->ndone; + } + + call_transact_and_set_next_state(HttpTransact::ModifyRequest); + + break; + default: + ink_assert(!"not reached"); + } + + return 0; +} + +void +L4RSM::wait_for_full_body() +{ + is_waiting_for_full_body = true; + L4R_SM_SET_DEFAULT_HANDLER(&L4RSM::tunnel_handler_post); + bool chunked = (t_state.client_info.transfer_encoding == HttpTransact::CHUNKED_ENCODING); + int64_t alloc_index; + HttpTunnelProducer *p = nullptr; + + // content length is undefined, use default buffer size + if (t_state.hdr_info.request_content_length == HTTP_UNDEFINED_CL) { + alloc_index = (int)t_state.txn_conf->default_buffer_size_index; + if (alloc_index < MIN_CONFIG_BUFFER_SIZE_INDEX || alloc_index > MAX_BUFFER_SIZE_INDEX) { + alloc_index = DEFAULT_REQUEST_BUFFER_SIZE_INDEX; + } + } else { + alloc_index = buffer_size_to_index(t_state.hdr_info.request_content_length); + } + MIOBuffer *post_buffer = new_MIOBuffer(alloc_index); + IOBufferReader *buf_start = post_buffer->alloc_reader(); + + this->_postbuf.init(post_buffer->clone_reader(buf_start)); + + // Note: Many browsers, Netscape and IE included send two extra + // bytes (CRLF) at the end of the post. We just ignore those + // bytes since the sending them is not spec + + // Next order of business if copy the remaining data from the + // header buffer into new buffer + int64_t post_bytes = chunked ? INT64_MAX : t_state.hdr_info.request_content_length; + client_request_body_bytes = post_buffer->write(ua_buffer_reader, chunked ? ua_buffer_reader->read_avail() : post_bytes); + + ua_buffer_reader->consume(client_request_body_bytes); + p = tunnel.add_producer(ua_entry->vc, post_bytes, buf_start, &L4RSM::tunnel_handler_post_ua, HT_BUFFER_READ, "ua post buffer"); + if (chunked) { + tunnel.set_producer_chunking_action(p, 0, TCA_PASSTHRU_CHUNKED_CONTENT); + } + ua_entry->in_tunnel = true; + ua_txn->set_inactivity_timeout(HRTIME_SECONDS(t_state.txn_conf->transaction_no_activity_timeout_in)); + tunnel.tunnel_run(p); +} + +int +L4RSM::state_watch_for_client_abort(int event, void *data) +{ + STATE_ENTER(&L4RSM::state_watch_for_client_abort, event); + + ink_assert(ua_entry->read_vio == (VIO *)data || ua_entry->write_vio == (VIO *)data); + ink_assert(ua_entry->vc == ua_txn); + + switch (event) { + /* EOS means that the client has initiated the connection shut down. + * Only half close the client connection so ATS can read additional + * data that may still be sent from the server and send it to the + * client. + */ + case VC_EVENT_EOS: { + // We got an early EOS. + NetVConnection *netvc = ua_txn->get_netvc(); + if (ua_txn->allow_half_open()) { + if (netvc) { + netvc->do_io_shutdown(IO_SHUTDOWN_READ); + } + ua_entry->eos = true; + } else { + ua_txn->do_io_close(); + ua_buffer_reader = nullptr; + vc_table.cleanup_entry(ua_entry); + ua_entry = nullptr; + tunnel.kill_tunnel(); + terminate_sm = true; // Just die already, the requester is gone + } + break; + } + case VC_EVENT_ERROR: + case VC_EVENT_ACTIVE_TIMEOUT: + case VC_EVENT_INACTIVITY_TIMEOUT: { + if (tunnel.is_tunnel_active()) { + // Check to see if the user agent is part of the tunnel. + // If so forward the event to the tunnel. Otherwise, + // kill the tunnel and fallthrough to the case + // where the tunnel is not active + HttpTunnelConsumer *c = tunnel.get_consumer(ua_txn); + if (c && c->alive) { + SMDebug("http", + "[%" PRId64 "] [watch_for_client_abort] " + "forwarding event %s to tunnel", + sm_id, HttpDebugNames::get_event_name(event)); + tunnel.handleEvent(event, c->write_vio); + return 0; + } else { + tunnel.kill_tunnel(); + } + } + // Disable further I/O on the client + if (ua_entry->read_vio) { + ua_entry->read_vio->nbytes = ua_entry->read_vio->ndone; + } + mark_server_down_on_client_abort(); + milestones[TS_MILESTONE_UA_CLOSE] = Thread::get_hrtime(); + set_ua_abort(HttpTransact::ABORTED, event); + + terminate_sm = true; + break; + } + case VC_EVENT_READ_COMPLETE: + // XXX Work around for TS-1233. + case VC_EVENT_READ_READY: + // Ignore. Could be a pipelined request. We'll get to it + // when we finish the current transaction + break; + case VC_EVENT_WRITE_READY: + // 100-continue handler + ink_assert(t_state.hdr_info.client_request.m_100_continue_required); + ua_entry->write_vio->reenable(); + break; + case VC_EVENT_WRITE_COMPLETE: + // 100-continue handler + ink_assert(t_state.hdr_info.client_request.m_100_continue_required); + if (ua_entry->write_buffer) { + ink_assert(ua_entry->write_vio && !ua_entry->write_vio->ntodo()); + free_MIOBuffer(ua_entry->write_buffer); + ua_entry->write_buffer = nullptr; + } + break; + default: + ink_release_assert(0); + break; + } + + return 0; +} + +void +L4RSM::setup_push_read_response_header() +{ + ink_assert(server_session == nullptr); + ink_assert(server_entry == nullptr); + ink_assert(ua_txn != nullptr); + ink_assert(t_state.method == HTTP_WKSIDX_PUSH); + + // Set the handler to read the pushed response hdr + ua_entry->vc_handler = &L4RSM::state_read_push_response_header; + + // We record both the total payload size as + // client_request_body_bytes and the bytes for the individual + // pushed hdr and body components + pushed_response_hdr_bytes = 0; + client_request_body_bytes = 0; + + // Note: we must use destroy() here since clear() + // does not free the memory from the header + t_state.hdr_info.server_response.destroy(); + t_state.hdr_info.server_response.create(HTTP_TYPE_RESPONSE); + http_parser_clear(&http_parser); + + // We already done the READ when we read the client + // request header + ink_assert(ua_entry->read_vio); + + // If there is anything in the buffer call the parsing routines + // since if the response is finished, we won't get any + // additional callbacks + int resp_hdr_state = VC_EVENT_CONT; + if (ua_buffer_reader->read_avail() > 0) { + if (ua_entry->eos) { + resp_hdr_state = state_read_push_response_header(VC_EVENT_EOS, ua_entry->read_vio); + } else { + resp_hdr_state = state_read_push_response_header(VC_EVENT_READ_READY, ua_entry->read_vio); + } + } + // It is possible that the entire PUSHed response header was already + // in the buffer. In this case we don't want to fire off any more + // IO since we are going to switch buffers when we go to tunnel to + // the cache + if (resp_hdr_state == VC_EVENT_CONT) { + ink_assert(ua_entry->eos == false); + ua_entry->read_vio = ua_txn->do_io_read(this, INT64_MAX, ua_buffer_reader->mbuf); + } +} + +int +L4RSM::state_read_push_response_header(int event, void *data) +{ + STATE_ENTER(&L4RSM::state_read_push_response_header, event); + ink_assert(ua_entry->read_vio == (VIO *)data); + ink_assert(t_state.current.server == nullptr); + + int64_t data_size = 0; + int64_t bytes_used = 0; + + // Not used here. + // bool parse_error = false; + // VIO* vio = (VIO*) data; + + switch (event) { + case VC_EVENT_EOS: + ua_entry->eos = true; + // Fall through + + case VC_EVENT_READ_READY: + case VC_EVENT_READ_COMPLETE: + // More data to parse + break; + + case VC_EVENT_ERROR: + case VC_EVENT_INACTIVITY_TIMEOUT: + case VC_EVENT_ACTIVE_TIMEOUT: + // The user agent is hosed. Send an error + set_ua_abort(HttpTransact::ABORTED, event); + call_transact_and_set_next_state(HttpTransact::HandleBadPushRespHdr); + return 0; + } + + int state = PARSE_RESULT_CONT; + while (ua_buffer_reader->read_avail() && state == PARSE_RESULT_CONT) { + const char *start = ua_buffer_reader->start(); + const char *tmp = start; + data_size = ua_buffer_reader->block_read_avail(); + ink_assert(data_size >= 0); + + ///////////////////// + // tokenize header // + ///////////////////// + state = + t_state.hdr_info.server_response.parse_resp(&http_parser, &tmp, tmp + data_size, false // Only call w/ eof when data exhausted + ); + + bytes_used = tmp - start; + + ink_release_assert(bytes_used <= data_size); + ua_buffer_reader->consume(bytes_used); + pushed_response_hdr_bytes += bytes_used; + client_request_body_bytes += bytes_used; + } + + // We are out of data. If we've received an EOS we need to + // call the parser with (eof == true) so it can determine + // whether to use the response as is or declare a parse error + if (ua_entry->eos) { + const char *end = ua_buffer_reader->start(); + state = t_state.hdr_info.server_response.parse_resp(&http_parser, &end, end, true // We are out of data after server eos + ); + ink_release_assert(state == PARSE_RESULT_DONE || state == PARSE_RESULT_ERROR); + } + // Don't allow 0.9 (unparsable headers) since TS doesn't + // cache 0.9 responses + if (state == PARSE_RESULT_DONE && t_state.hdr_info.server_response.version_get() == HTTPVersion(0, 9)) { + state = PARSE_RESULT_ERROR; + } + + if (state != PARSE_RESULT_CONT) { + // Disable further IO + ua_entry->read_vio->nbytes = ua_entry->read_vio->ndone; + http_parser_clear(&http_parser); + milestones[TS_MILESTONE_SERVER_READ_HEADER_DONE] = Thread::get_hrtime(); + } + + switch (state) { + case PARSE_RESULT_ERROR: + SMDebug("http", "[%" PRId64 "] error parsing push response header", sm_id); + call_transact_and_set_next_state(HttpTransact::HandleBadPushRespHdr); + break; + + case PARSE_RESULT_CONT: + ua_entry->read_vio->reenable(); + return VC_EVENT_CONT; + + case PARSE_RESULT_DONE: + SMDebug("http", "[%" PRId64 "] done parsing push response header", sm_id); + call_transact_and_set_next_state(HttpTransact::HandlePushResponseHdr); + break; + default: + ink_assert(!"not reached"); + } + + return VC_EVENT_DONE; +} + +////////////////////////////////////////////////////////////////////////////// +// +// L4RSM::state_http_server_open() +// +////////////////////////////////////////////////////////////////////////////// +int +L4RSM::state_raw_http_server_open(int event, void *data) +{ + STATE_ENTER(&L4RSM::state_raw_http_server_open, event); + ink_assert(server_entry == nullptr); + milestones[TS_MILESTONE_SERVER_CONNECT_END] = Thread::get_hrtime(); + NetVConnection *netvc = nullptr; + + pending_action = nullptr; + switch (event) { + case EVENT_INTERVAL: + // If we get EVENT_INTERNAL it means that we moved the transaction + // to a different thread in do_http_server_open. Since we didn't + // do any of the actual work in do_http_server_open, we have to + // go back and do it now. + do_http_server_open(true); + return 0; + + case NET_EVENT_OPEN: + + // Record the VC in our table + server_entry = vc_table.new_entry(); + server_entry->vc = netvc = (NetVConnection *)data; + server_entry->vc_type = HTTP_RAW_SERVER_VC; + t_state.current.state = HttpTransact::CONNECTION_ALIVE; + + netvc->set_inactivity_timeout(HRTIME_SECONDS(t_state.txn_conf->transaction_no_activity_timeout_out)); + netvc->set_active_timeout(HRTIME_SECONDS(t_state.txn_conf->transaction_active_timeout_out)); + break; + + case VC_EVENT_ERROR: + case NET_EVENT_OPEN_FAILED: + t_state.current.state = HttpTransact::OPEN_RAW_ERROR; + // use this value just to get around other values + t_state.hdr_info.response_error = HttpTransact::STATUS_CODE_SERVER_ERROR; + break; + + default: + ink_release_assert(0); + break; + } + + call_transact_and_set_next_state(HttpTransact::OriginServerRawOpen); + return 0; +} + +// int L4RSM::state_request_wait_for_transform_read(int event, void* data) +// +// We've done a successful transform open and issued a do_io_write +// to the transform. We are now ready for the transform to tell +// us it is now ready to be read from and it done modifying the +// server request header +// +int +L4RSM::state_request_wait_for_transform_read(int event, void *data) +{ + STATE_ENTER(&L4RSM::state_request_wait_for_transform_read, event); + int64_t size; + + switch (event) { + case TRANSFORM_READ_READY: + size = *((int64_t *)data); + if (size != INT64_MAX && size >= 0) { + // We got a content length so update our internal + // data as well as fix up the request header + t_state.hdr_info.transform_request_cl = size; + t_state.hdr_info.server_request.value_set_int64(MIME_FIELD_CONTENT_LENGTH, MIME_LEN_CONTENT_LENGTH, size); + setup_server_send_request_api(); + break; + } else { + // No content length from the post. This is a no go + // since http spec requires content length when + // sending a request message body. Change the event + // to an error and fall through + event = VC_EVENT_ERROR; + Log::error("Request transformation failed to set content length"); + } + // FALLTHROUGH + default: + state_common_wait_for_transform_read(&post_transform_info, &L4RSM::tunnel_handler_post, event, data); + break; + } + + return 0; +} + +// int L4RSM::state_response_wait_for_transform_read(int event, void* data) +// +// We've done a successful transform open and issued a do_io_write +// to the transform. We are now ready for the transform to tell +// us it is now ready to be read from and it done modifying the +// user agent response header +// +int +L4RSM::state_response_wait_for_transform_read(int event, void *data) +{ + STATE_ENTER(&L4RSM::state_response_wait_for_transform_read, event); + int64_t size = *((int64_t *)data); + + switch (event) { + case TRANSFORM_READ_READY: + if (size != INT64_MAX && size >= 0) { + // We got a content length so update our internal state + t_state.hdr_info.transform_response_cl = size; + t_state.hdr_info.transform_response.value_set_int64(MIME_FIELD_CONTENT_LENGTH, MIME_LEN_CONTENT_LENGTH, size); + } else { + t_state.hdr_info.transform_response_cl = HTTP_UNDEFINED_CL; + } + call_transact_and_set_next_state(HttpTransact::handle_transform_ready); + break; + default: + state_common_wait_for_transform_read(&transform_info, &L4RSM::tunnel_handler, event, data); + break; + } + + return 0; +} + +// int L4RSM::state_common_wait_for_transform_read(...) +// +// This function handles the overlapping cases between request and response +// transforms which prevents code duplication +// +int +L4RSM::state_common_wait_for_transform_read(HttpTransformInfo *t_info, L4RSMHandler tunnel_handler, int event, void *data) +{ + STATE_ENTER(&L4RSM::state_common_wait_for_transform_read, event); + HttpTunnelConsumer *c = nullptr; + + switch (event) { + case HTTP_TUNNEL_EVENT_DONE: + // There are three reasons why the the tunnel could signal completed + // 1) there was error from the transform write + // 2) there was an error from the data source + // 3) the transform write completed before it sent + // TRANSFORM_READ_READY which is legal and in which + // case we should just wait for the transform read ready + c = tunnel.get_consumer(t_info->vc); + ink_assert(c != nullptr); + ink_assert(c->vc == t_info->entry->vc); + + if (c->handler_state == HTTP_SM_TRANSFORM_FAIL) { + // Case 1 we failed to complete the write to the + // transform fall through to vc event error case + ink_assert(c->write_success == false); + } else if (c->producer->read_success == false) { + // Case 2 - error from data source + if (c->producer->vc_type == HT_HTTP_CLIENT) { + // Our source is the client. POST can't + // be truncated so forward to the tunnel + // handler to clean this mess up + ink_assert(t_info == &post_transform_info); + return (this->*tunnel_handler)(event, data); + } else { + // On the response side, we just forward as much + // as we can of truncated documents so + // just don't cache the result + ink_assert(t_info == &transform_info); + t_state.api_info.cache_transformed = false; + return 0; + } + } else { + // Case 3 - wait for transform read ready + return 0; + } + // FALLTHROUGH + case VC_EVENT_ERROR: + // Transform VC sends NULL on error conditions + if (!c) { + c = tunnel.get_consumer(t_info->vc); + ink_assert(c != nullptr); + } + vc_table.cleanup_entry(t_info->entry); + t_info->entry = nullptr; + // In Case 1: error due to transform write, + // we need to keep the original t_info->vc for transform_cleanup() + // to skip do_io_close(); otherwise, set it to NULL. + if (c->handler_state != HTTP_SM_TRANSFORM_FAIL) { + t_info->vc = nullptr; + } + if (c->producer->vc_type == HT_HTTP_CLIENT) { + /* Producer was the user agent and there was a failure transforming the POST. + Handling this is challenging and this isn't the best way but it at least + avoids a crash due to trying to send a response to a NULL'd out user agent. + The problem with not closing the user agent is handling draining of the + rest of the POST - the user agent may well not check for a response until that's + done in which case we can get a deadlock where the user agent never reads the + error response because the POST wasn't drained and the buffers filled up. + Draining has a potential bad impact on any pipelining which must be considered. + If we're not going to drain properly the next best choice is to shut down the + entire state machine since (1) there's no point in finishing the POST to the + origin and (2) there's no user agent connection to which to send the error response. + */ + terminate_sm = true; + } else { + tunnel.kill_tunnel(); + call_transact_and_set_next_state(HttpTransact::HandleApiErrorJump); + } + break; + default: + ink_release_assert(0); + } + + return 0; +} + +// int L4RSM::state_api_callback(int event, void *data) + +// InkAPI.cc calls us directly here to avoid problems +// with setting and changing the default_handler +// function. As such, this is an entry point +// and needs to handle the reentrancy counter and +// deallocation the state machine if necessary +// +int +L4RSM::state_api_callback(int event, void *data) +{ + ink_release_assert(magic == HTTP_SM_MAGIC_ALIVE); + + ink_assert(reentrancy_count >= 0); + reentrancy_count++; + + milestone_update_api_time(milestones, api_timer); + + STATE_ENTER(&L4RSM::state_api_callback, event); + + state_api_callout(event, data); + + // The sub-handler signals when it is time for the state + // machine to exit. We can only exit if we are not reentrantly + // called otherwise when the our call unwinds, we will be + // running on a dead state machine + // + // Because of the need for an api shutdown hook, kill_this() + // is also reentrant. As such, we don't want to decrement + // the reentrancy count until after we run kill_this() + // + if (terminate_sm == true && reentrancy_count == 1) { + kill_this(); + } else { + reentrancy_count--; + ink_assert(reentrancy_count >= 0); + } + + return VC_EVENT_CONT; +} + +int +L4RSM::state_api_callout(int event, void *data) +{ + // enum and variable for figuring out what the next action is after + // after we've finished the api state + enum AfterApiReturn_t { + API_RETURN_UNKNOWN = 0, + API_RETURN_CONTINUE, + API_RETURN_DEFERED_CLOSE, + API_RETURN_DEFERED_SERVER_ERROR, + API_RETURN_ERROR_JUMP, + API_RETURN_SHUTDOWN, + API_RETURN_INVALIDATE_ERROR + }; + AfterApiReturn_t api_next = API_RETURN_UNKNOWN; + + if (event != EVENT_NONE) { + STATE_ENTER(&L4RSM::state_api_callout, event); + } + + if (api_timer < 0) { + // This happens when either the plugin lock was missed and the hook rescheduled or + // the transaction got an event without the plugin calling TsHttpTxnReenable(). + // The call chain does not recurse here if @a api_timer < 0 which means this call + // is the first from an event dispatch in this case. + milestone_update_api_time(milestones, api_timer); + } + + switch (event) { + case HTTP_TUNNEL_EVENT_DONE: + // This is a reschedule via the tunnel. Just fall through + // + case EVENT_INTERVAL: + if (data != pending_action) { + pending_action->cancel(); + } + pending_action = nullptr; + // FALLTHROUGH + case EVENT_NONE: + if (cur_hook_id == TS_HTTP_TXN_START_HOOK && t_state.client_info.port_attribute == HttpProxyPort::TRANSPORT_BLIND_TUNNEL) { + /* Creating the request object early to set the host header and port for blind tunneling here for the +plugins required to work with sni_routing. +*/ + // Plugins triggered on txn_start_hook will get the host and port at that point + // We've received a request on a port which we blind forward + URL u; + + t_state.hdr_info.client_request.create(HTTP_TYPE_REQUEST); + t_state.hdr_info.client_request.method_set(HTTP_METHOD_CONNECT, HTTP_LEN_CONNECT); + t_state.hdr_info.client_request.url_create(&u); + u.scheme_set(URL_SCHEME_TUNNEL, URL_LEN_TUNNEL); + t_state.hdr_info.client_request.url_set(&u); + + NetVConnection *netvc = ua_txn->get_netvc(); + SSLNetVConnection *ssl_vc = dynamic_cast(netvc); + + if (ssl_vc && ssl_vc->GetSNIMapping()) { + auto *hs = TunnelMap.find(ssl_vc->serverName); + if (hs != nullptr) { + t_state.hdr_info.client_request.url_get()->host_set(hs->hostname, hs->len); + if (hs->port > 0) { + t_state.hdr_info.client_request.url_get()->port_set(hs->port); + } else { + t_state.hdr_info.client_request.url_get()->port_set(t_state.state_machine->ua_txn->get_netvc()->get_local_port()); + } + } else { + t_state.hdr_info.client_request.url_get()->host_set(ssl_vc->serverName, strlen(ssl_vc->serverName)); + t_state.hdr_info.client_request.url_get()->port_set(t_state.state_machine->ua_txn->get_netvc()->get_local_port()); + } + } + } + // FALLTHROUGH + case HTTP_API_CONTINUE: + if ((cur_hook_id >= 0) && (cur_hook_id < TS_HTTP_LAST_HOOK)) { + if (!cur_hook) { + if (cur_hooks == 0) { + cur_hook = http_global_hooks->get(cur_hook_id); + cur_hooks++; + } + } + // even if ua_txn is NULL, cur_hooks must + // be incremented otherwise cur_hooks is not set to 2 and + // transaction hooks (stored in api_hooks object) are not called. + if (!cur_hook) { + if (cur_hooks == 1) { + if (ua_txn) { + cur_hook = ua_txn->ssn_hook_get(cur_hook_id); + } + cur_hooks++; + } + } + if (!cur_hook) { + if (cur_hooks == 2) { + cur_hook = api_hooks.get(cur_hook_id); + cur_hooks++; + } + } + if (cur_hook) { + if (callout_state == HTTP_API_NO_CALLOUT) { + callout_state = HTTP_API_IN_CALLOUT; + } + + /* The MUTEX_TRY_LOCK macro was changed so + that it can't handle NULL mutex'es. The plugins + can use null mutexes so we have to do this manually. + We need to take a smart pointer to the mutex since + the plugin could release it's mutex while we're on + the callout + */ + bool plugin_lock; + Ptr plugin_mutex; + if (cur_hook->m_cont->mutex) { + plugin_mutex = cur_hook->m_cont->mutex; + plugin_lock = MUTEX_TAKE_TRY_LOCK(cur_hook->m_cont->mutex, mutex->thread_holding); + + if (!plugin_lock) { + api_timer = -Thread::get_hrtime_updated(); + L4R_SM_SET_DEFAULT_HANDLER(&L4RSM::state_api_callout); + ink_assert(pending_action == nullptr); + pending_action = mutex->thread_holding->schedule_in(this, HRTIME_MSECONDS(10)); + // Should @a callout_state be reset back to HTTP_API_NO_CALLOUT here? Because the default + // handler has been changed the value isn't important to the rest of the state machine + // but not resetting means there is no way to reliably detect re-entrance to this state with an + // outstanding callout. + return 0; + } + } else { + plugin_lock = false; + } + + SMDebug("http", "[%" PRId64 "] calling plugin on hook %s at hook %p", sm_id, HttpDebugNames::get_api_hook_name(cur_hook_id), + cur_hook); + + APIHook *hook = cur_hook; + cur_hook = cur_hook->next(); + + if (!api_timer) { + api_timer = Thread::get_hrtime_updated(); + } + hook->invoke(TS_EVENT_HTTP_READ_REQUEST_HDR + cur_hook_id, this); + if (api_timer > 0) { // true if the hook did not call TxnReenable() + milestone_update_api_time(milestones, api_timer); + api_timer = -Thread::get_hrtime_updated(); // set in order to track non-active callout duration + // which means that if we get back from the invoke with api_timer < 0 we're already + // tracking a non-complete callout from a chain so just let it ride. It will get cleaned + // up in state_api_callback when the plugin re-enables this transaction. + } + + if (plugin_lock) { + Mutex_unlock(plugin_mutex, mutex->thread_holding); + } + + return 0; + } + } + // Map the callout state into api_next + switch (callout_state) { + case HTTP_API_NO_CALLOUT: + case HTTP_API_IN_CALLOUT: + if (t_state.api_modifiable_cached_resp && t_state.api_update_cached_object == HttpTransact::UPDATE_CACHED_OBJECT_PREPARE) { + t_state.api_update_cached_object = HttpTransact::UPDATE_CACHED_OBJECT_CONTINUE; + } + api_next = API_RETURN_CONTINUE; + break; + case HTTP_API_DEFERED_CLOSE: + api_next = API_RETURN_DEFERED_CLOSE; + break; + case HTTP_API_DEFERED_SERVER_ERROR: + api_next = API_RETURN_DEFERED_SERVER_ERROR; + break; + default: + ink_release_assert(0); + } + break; + + case HTTP_API_ERROR: + if (callout_state == HTTP_API_DEFERED_CLOSE) { + api_next = API_RETURN_DEFERED_CLOSE; + } else if (cur_hook_id == TS_HTTP_TXN_CLOSE_HOOK) { + // If we are closing the state machine, we can't + // jump to an error state so just continue + api_next = API_RETURN_CONTINUE; + } else if (t_state.api_http_sm_shutdown) { + t_state.api_http_sm_shutdown = false; + t_state.cache_info.object_read = nullptr; + cache_sm.close_read(); + transform_cache_sm.close_read(); + release_server_session(); + terminate_sm = true; + api_next = API_RETURN_SHUTDOWN; + t_state.squid_codes.log_code = SQUID_LOG_TCP_DENIED; + } else if (t_state.api_modifiable_cached_resp && + t_state.api_update_cached_object == HttpTransact::UPDATE_CACHED_OBJECT_PREPARE) { + t_state.api_update_cached_object = HttpTransact::UPDATE_CACHED_OBJECT_ERROR; + api_next = API_RETURN_INVALIDATE_ERROR; + } else { + api_next = API_RETURN_ERROR_JUMP; + } + break; + + default: + ink_assert(false); + terminate_sm = true; + return 0; + } + + // Now that we're completed with the api state and figured out what + // to do next, do it + callout_state = HTTP_API_NO_CALLOUT; + api_timer = 0; + switch (api_next) { + case API_RETURN_CONTINUE: + handle_api_return(); + break; + case API_RETURN_DEFERED_CLOSE: + ink_assert(t_state.api_next_action == HttpTransact::SM_ACTION_API_SM_SHUTDOWN); + do_api_callout(); + break; + case API_RETURN_DEFERED_SERVER_ERROR: + ink_assert(t_state.api_next_action == HttpTransact::SM_ACTION_API_SEND_REQUEST_HDR); + ink_assert(t_state.current.state != HttpTransact::CONNECTION_ALIVE); + call_transact_and_set_next_state(HttpTransact::HandleResponse); + break; + case API_RETURN_ERROR_JUMP: + call_transact_and_set_next_state(HttpTransact::HandleApiErrorJump); + break; + case API_RETURN_SHUTDOWN: + break; + case API_RETURN_INVALIDATE_ERROR: + do_cache_prepare_update(); + break; + default: + case API_RETURN_UNKNOWN: + ink_release_assert(0); + } + + return 0; +} + +// void L4RSM::handle_api_return() +// +// Figures out what to do after calling api callouts +// have finished. This messy and I would like +// to come up with a cleaner way to handle the api +// return. The way we are doing things also makes a +// mess of set_next_state() +// +void +L4RSM::handle_api_return() +{ + switch (t_state.api_next_action) { + case HttpTransact::SM_ACTION_API_SM_START: + if (t_state.client_info.port_attribute == HttpProxyPort::TRANSPORT_BLIND_TUNNEL) { + setup_blind_tunnel_port(); + } else { + setup_client_read_request_header(); + } + return; + + case HttpTransact::SM_ACTION_API_CACHE_LOOKUP_COMPLETE: + case HttpTransact::SM_ACTION_API_READ_CACHE_HDR: + if (t_state.api_cleanup_cache_read && t_state.api_update_cached_object != HttpTransact::UPDATE_CACHED_OBJECT_PREPARE) { + t_state.api_cleanup_cache_read = false; + t_state.cache_info.object_read = nullptr; + t_state.request_sent_time = UNDEFINED_TIME; + t_state.response_received_time = UNDEFINED_TIME; + cache_sm.close_read(); + transform_cache_sm.close_read(); + } + // fallthrough + + case HttpTransact::SM_ACTION_API_PRE_REMAP: + case HttpTransact::SM_ACTION_API_POST_REMAP: + case HttpTransact::SM_ACTION_API_READ_REQUEST_HDR: + case HttpTransact::SM_ACTION_REQUEST_BUFFER_READ_COMPLETE: + case HttpTransact::SM_ACTION_API_OS_DNS: + case HttpTransact::SM_ACTION_API_READ_RESPONSE_HDR: + call_transact_and_set_next_state(nullptr); + return; + case HttpTransact::SM_ACTION_API_SEND_REQUEST_HDR: + setup_server_send_request(); + return; + case HttpTransact::SM_ACTION_API_SEND_RESPONSE_HDR: + // Set back the inactivity timeout + if (ua_txn) { + ua_txn->set_inactivity_timeout(HRTIME_SECONDS(t_state.txn_conf->transaction_no_activity_timeout_in)); + } + + // We only follow 3xx when redirect_in_process == false. Otherwise the redirection has already been launched (in + // SM_ACTION_SERVE_FROM_CACHE or SM_ACTION_SERVER_READ).redirect_in_process is set before this logic if we need more direction. + // This redirection is only used with the build_error_reponse. Then, the redirection_tries will be increased by + // state_read_server_reponse_header and never get into this logic again. + if (enable_redirection && !t_state.redirect_info.redirect_in_process && is_redirect_required() && + (redirection_tries <= t_state.txn_conf->number_of_redirections)) { + do_redirect(); + } else if (redirection_tries > t_state.txn_conf->number_of_redirections) { + t_state.squid_codes.subcode = SQUID_SUBCODE_NUM_REDIRECTIONS_EXCEEDED; + } + // we have further processing to do + // based on what t_state.next_action is + break; + case HttpTransact::SM_ACTION_API_SM_SHUTDOWN: + state_remove_from_list(EVENT_NONE, nullptr); + return; + default: + ink_release_assert("! Not reached"); + break; + } + + switch (t_state.next_action) { + case HttpTransact::SM_ACTION_TRANSFORM_READ: { + HttpTunnelProducer *p = setup_transfer_from_transform(); + perform_transform_cache_write_action(); + tunnel.tunnel_run(p); + break; + } + case HttpTransact::SM_ACTION_SERVER_READ: { + if (unlikely(t_state.did_upgrade_succeed)) { + // We've successfully handled the upgrade, let's now setup + // a blind tunnel. + IOBufferReader *initial_data = nullptr; + if (t_state.is_websocket) { + HTTP_INCREMENT_DYN_STAT(http_websocket_current_active_client_connections_stat); + if (server_session) { + initial_data = server_session->get_reader(); + } + + if (ua_txn) { + SMDebug("http_websocket", + "(client session) Setting websocket active timeout=%" PRId64 "s and inactive timeout=%" PRId64 "s", + t_state.txn_conf->websocket_active_timeout, t_state.txn_conf->websocket_inactive_timeout); + ua_txn->set_active_timeout(HRTIME_SECONDS(t_state.txn_conf->websocket_active_timeout)); + ua_txn->set_inactivity_timeout(HRTIME_SECONDS(t_state.txn_conf->websocket_inactive_timeout)); + } + + if (server_session) { + SMDebug("http_websocket", + "(server session) Setting websocket active timeout=%" PRId64 "s and inactive timeout=%" PRId64 "s", + t_state.txn_conf->websocket_active_timeout, t_state.txn_conf->websocket_inactive_timeout); + server_session->get_netvc()->set_active_timeout(HRTIME_SECONDS(t_state.txn_conf->websocket_active_timeout)); + server_session->get_netvc()->set_inactivity_timeout(HRTIME_SECONDS(t_state.txn_conf->websocket_inactive_timeout)); + } + } + + setup_blind_tunnel(true, initial_data); + } else { + HttpTunnelProducer *p = setup_server_transfer(); + perform_cache_write_action(); + tunnel.tunnel_run(p); + } + break; + } + case HttpTransact::SM_ACTION_SERVE_FROM_CACHE: { + HttpTunnelProducer *p = setup_cache_read_transfer(); + tunnel.tunnel_run(p); + break; + } + + case HttpTransact::SM_ACTION_INTERNAL_CACHE_WRITE: { + if (cache_sm.cache_write_vc) { + setup_internal_transfer(&L4RSM::tunnel_handler_cache_fill); + } else { + setup_internal_transfer(&L4RSM::tunnel_handler); + } + break; + } + + case HttpTransact::SM_ACTION_INTERNAL_CACHE_NOOP: + case HttpTransact::SM_ACTION_INTERNAL_CACHE_DELETE: + case HttpTransact::SM_ACTION_INTERNAL_CACHE_UPDATE_HEADERS: + case HttpTransact::SM_ACTION_SEND_ERROR_CACHE_NOOP: { + setup_internal_transfer(&L4RSM::tunnel_handler); + break; + } + + case HttpTransact::SM_ACTION_REDIRECT_READ: { + // Clean up from any communication with previous servers + release_server_session(); + + call_transact_and_set_next_state(HttpTransact::HandleRequest); + break; + } + case HttpTransact::SM_ACTION_SSL_TUNNEL: { + setup_blind_tunnel(true); + break; + } + default: { + ink_release_assert(!"Should not get here"); + } + } +} + +////////////////////////////////////////////////////////////////////////////// +// +// L4RSM::state_http_server_open() +// +////////////////////////////////////////////////////////////////////////////// +int +L4RSM::state_http_server_open(int event, void *data) +{ + SMDebug("http_track", "entered inside state_http_server_open"); + STATE_ENTER(&L4RSM::state_http_server_open, event); + // TODO decide whether to uncomment after finish testing redirect + // ink_assert(server_entry == NULL); + pending_action = nullptr; + milestones[TS_MILESTONE_SERVER_CONNECT_END] = Thread::get_hrtime(); + HttpServerSession *session; + + switch (event) { + case NET_EVENT_OPEN: + session = (TS_SERVER_SESSION_SHARING_POOL_THREAD == t_state.http_config_param->server_session_sharing_pool) ? + THREAD_ALLOC_INIT(httpServerSessionAllocator, mutex->thread_holding) : + httpServerSessionAllocator.alloc(); + session->sharing_pool = static_cast(t_state.http_config_param->server_session_sharing_pool); + session->sharing_match = static_cast(t_state.txn_conf->server_session_sharing_match); + // If origin_max_connections or origin_min_keep_alive_connections is + // set then we are metering the max and or min number + // of connections per host. Set enable_origin_connection_limiting + // to true in the server session so it will increment and decrement + // the connection count. + if (t_state.txn_conf->origin_max_connections > 0 || t_state.http_config_param->origin_min_keep_alive_connections > 0) { + SMDebug("http_ss", "[%" PRId64 "] max number of connections: %" PRIu64, sm_id, t_state.txn_conf->origin_max_connections); + session->enable_origin_connection_limiting = true; + } + /*UnixNetVConnection * vc = (UnixNetVConnection*)(ua_txn->client_vc); + UnixNetVConnection *server_vc = (UnixNetVConnection*)data; + printf("client fd is :%d , server fd is %d\n",vc->con.fd, + server_vc->con.fd); */ + session->attach_hostname(t_state.current.server->name); + session->new_connection(static_cast(data)); + session->state = HSS_ACTIVE; + + attach_server_session(session); + if (t_state.current.request_to == HttpTransact::PARENT_PROXY) { + session->to_parent_proxy = true; + HTTP_INCREMENT_DYN_STAT(http_current_parent_proxy_connections_stat); + HTTP_INCREMENT_DYN_STAT(http_total_parent_proxy_connections_stat); + + } else { + session->to_parent_proxy = false; + } + handle_http_server_open(); + return 0; + case EVENT_INTERVAL: // Delayed call from another thread + if (server_session == nullptr) { + do_http_server_open(); + } + break; + case VC_EVENT_ERROR: + case VC_EVENT_INACTIVITY_TIMEOUT: + case NET_EVENT_OPEN_FAILED: + t_state.current.state = HttpTransact::CONNECTION_ERROR; + // save the errno from the connect fail for future use (passed as negative value, flip back) + t_state.current.server->set_connect_fail(event == NET_EVENT_OPEN_FAILED ? -reinterpret_cast(data) : ECONNABORTED); + + /* If we get this error in transparent mode, then we simply can't bind to the 4-tuple to make the connection. There's no hope + of retries succeeding in the near future. The best option is to just shut down the connection without further comment. The + only known cause for this is outbound transparency combined with use client target address / source port, as noted in + TS-1424. If the keep alives desync the current connection can be attempting to rebind the 4 tuple simultaneously with the + shut down of an existing connection. Dropping the client side will cause it to pick a new source port and recover from this + issue. + */ + if (EADDRNOTAVAIL == t_state.current.server->connect_result && t_state.client_info.is_transparent) { + if (is_debug_tag_set("http_tproxy")) { + ip_port_text_buffer ip_c, ip_s; + Debug("http_tproxy", "Force close of client connect (%s->%s) due to EADDRNOTAVAIL [%" PRId64 "]", + ats_ip_nptop(&t_state.client_info.src_addr.sa, ip_c, sizeof ip_c), + ats_ip_nptop(&t_state.server_info.dst_addr.sa, ip_s, sizeof ip_s), sm_id); + } + t_state.client_info.keep_alive = HTTP_NO_KEEPALIVE; // part of the problem, clear it. + terminate_sm = true; + } else if (ENET_THROTTLING == t_state.current.server->connect_result) { + HTTP_INCREMENT_DYN_STAT(http_origin_connections_throttled_stat); + send_origin_throttled_response(); + } else { + call_transact_and_set_next_state(HttpTransact::HandleResponse); + } + return 0; + + default: + Error("[L4RSM::state_http_server_open] Unknown event: %d", event); + ink_release_assert(0); + return 0; + } + + return 0; +} + +int +L4RSM::state_read_server_response_header(int event, void *data) +{ + STATE_ENTER(&L4RSM::state_read_server_response_header, event); + ink_assert(server_entry->read_vio == (VIO *)data); + ink_assert(t_state.current.server->state == HttpTransact::STATE_UNDEFINED); + ink_assert(t_state.current.state == HttpTransact::STATE_UNDEFINED); + + int bytes_used = 0; + VIO *vio = (VIO *)data; + + switch (event) { + case VC_EVENT_EOS: + server_entry->eos = true; + + // Fall through + case VC_EVENT_READ_READY: + case VC_EVENT_READ_COMPLETE: + // More data to parse + break; + + case VC_EVENT_ERROR: + case VC_EVENT_INACTIVITY_TIMEOUT: + case VC_EVENT_ACTIVE_TIMEOUT: + // Error handling function + handle_server_setup_error(event, data); + return 0; + } + + // Reset the inactivity timeout if this is the first + // time we've been called. The timeout had been set to + // the connect timeout when we set up to read the header + // + if (server_response_hdr_bytes == 0) { + milestones[TS_MILESTONE_SERVER_FIRST_READ] = Thread::get_hrtime(); + + if (t_state.api_txn_no_activity_timeout_value != -1) { + server_session->get_netvc()->set_inactivity_timeout(HRTIME_MSECONDS(t_state.api_txn_no_activity_timeout_value)); + } else { + server_session->get_netvc()->set_inactivity_timeout(HRTIME_SECONDS(t_state.txn_conf->transaction_no_activity_timeout_out)); + } + + // For requests that contain a body, we can cancel the ua inactivity timeout. + if (ua_txn && t_state.hdr_info.request_content_length) { + ua_txn->cancel_inactivity_timeout(); + } + } + ///////////////////// + // tokenize header // + ///////////////////// + ParseResult state = + t_state.hdr_info.server_response.parse_resp(&http_parser, server_buffer_reader, &bytes_used, server_entry->eos); + + server_response_hdr_bytes += bytes_used; + + // Don't allow HTTP 0.9 (unparsable headers) on resued connections. + // And don't allow empty headers from closed connections + if ((state == PARSE_RESULT_DONE && t_state.hdr_info.server_response.version_get() == HTTPVersion(0, 9) && + server_session->transact_count > 1) || + (server_entry->eos && vio->ndone == 0)) { + state = PARSE_RESULT_ERROR; + } + // Check to see if we are over the hdr size limit + if (server_response_hdr_bytes > t_state.txn_conf->response_hdr_max_size) { + state = PARSE_RESULT_ERROR; + } + + if (state != PARSE_RESULT_CONT) { + // Disable further IO + server_entry->read_vio->nbytes = server_entry->read_vio->ndone; + http_parser_clear(&http_parser); + milestones[TS_MILESTONE_SERVER_READ_HEADER_DONE] = Thread::get_hrtime(); + } + + switch (state) { + case PARSE_RESULT_ERROR: { + // Many broken servers send really badly formed 302 redirects. + // Even if the parser doesn't like the redirect forward + // if it's got a Location header. We check the type of the + // response to make sure that the parser was able to parse + // something and didn't just throw up it's hands (INKqa05339) + bool allow_error = false; + if (t_state.hdr_info.server_response.type_get() == HTTP_TYPE_RESPONSE && + t_state.hdr_info.server_response.status_get() == HTTP_STATUS_MOVED_TEMPORARILY) { + if (t_state.hdr_info.server_response.field_find(MIME_FIELD_LOCATION, MIME_LEN_LOCATION)) { + allow_error = true; + } + } + + if (allow_error == false) { + SMDebug("http_seq", "Error parsing server response header"); + t_state.current.state = HttpTransact::PARSE_ERROR; + + // If the server closed prematurely on us, use the + // server setup error routine since it will forward + // error to a POST tunnel if any + if (event == VC_EVENT_EOS) { + handle_server_setup_error(VC_EVENT_EOS, data); + } else { + call_transact_and_set_next_state(HttpTransact::HandleResponse); + } + break; + } + // FALLTHROUGH (since we are allowing the parse error) + } + // fallthrough + + case PARSE_RESULT_DONE: + SMDebug("http_seq", "Done parsing server response header"); + + // Now that we know that we have all of the origin server + // response headers, we can reset the client inactivity + // timeout. This is unlikely to cause a recurrence of + // old bug because there will be no more retries now that + // the connection has been established. It is possible + // however. We do not need to reset the inactivity timeout + // if the request contains a body (noted by the + // request_content_length field) because it was never + // canceled. + // + + // we now reset the client inactivity timeout only + // when we are ready to send the response headers. In the + // case of transform plugin, this is after the transform + // outputs the 1st byte, which can take a long time if the + // plugin buffers the whole response. + // Also, if the request contains a body, we cancel the timeout + // when we read the 1st byte of the origin server response. + /* + if (ua_txn && !t_state.hdr_info.request_content_length) { + ua_txn->get_netvc()->set_inactivity_timeout(HRTIME_SECONDS( + HttpConfig::m_master.accept_no_activity_timeout)); + } + */ + + t_state.current.state = HttpTransact::CONNECTION_ALIVE; + t_state.transact_return_point = HttpTransact::HandleResponse; + t_state.api_next_action = HttpTransact::SM_ACTION_API_READ_RESPONSE_HDR; + + // if exceeded limit deallocate postdata buffers and disable redirection + if (!(enable_redirection && (redirection_tries <= t_state.txn_conf->number_of_redirections))) { + this->disable_redirect(); + } + + do_api_callout(); + break; + case PARSE_RESULT_CONT: + ink_assert(server_entry->eos == false); + server_entry->read_vio->reenable(); + return VC_EVENT_CONT; + + default: + ink_assert(!"not reached"); + } + + return 0; +} + +int +L4RSM::state_send_server_request_header(int event, void *data) +{ + STATE_ENTER(&L4RSM::state_send_server_request_header, event); + ink_assert(server_entry != nullptr); + ink_assert(server_entry->write_vio == (VIO *)data || server_entry->read_vio == (VIO *)data); + + int method; + + switch (event) { + case VC_EVENT_WRITE_READY: + server_entry->write_vio->reenable(); + break; + + case VC_EVENT_WRITE_COMPLETE: + // We are done sending the request header, deallocate + // our buffer and then decide what to do next + free_MIOBuffer(server_entry->write_buffer); + server_entry->write_buffer = nullptr; + method = t_state.hdr_info.server_request.method_get_wksidx(); + if (!t_state.api_server_request_body_set && method != HTTP_WKSIDX_TRACE && + (t_state.hdr_info.request_content_length > 0 || t_state.client_info.transfer_encoding == HttpTransact::CHUNKED_ENCODING)) { + if (post_transform_info.vc) { + setup_transform_to_server_transfer(); + } else { + do_setup_post_tunnel(HTTP_SERVER_VC); + // Start up read response in parallel in case of early error response + setup_server_read_response_header(); + } + } else { + // It's time to start reading the response + setup_server_read_response_header(); + } + + break; + + case VC_EVENT_READ_READY: + // We already did the read for the response header and + // we got some data. Wait for the request header + // send before dealing with it. However, we need to + // disable further IO here since the whole response + // may be in the buffer and we can not switch buffers + // on the io core later + ink_assert(server_entry->read_vio == (VIO *)data); + // setting nbytes to ndone would disable reads and remove it from the read queue. + // We can't do this in the epoll paradigm because we may be missing epoll errors that would + // prevent us from leaving this state. + // setup_server_read_response_header will trigger READ_READY to itself if there is data in the buffer. + + // server_entry->read_vio->nbytes = server_entry->read_vio->ndone; + + break; + + case VC_EVENT_EOS: + // EOS of stream comes from the read side. Treat it as + // as error if there is nothing in the read buffer. If + // there is something the server may have blasted back + // the response before receiving the request. Happens + // often with redirects + // + // If we are in the middle of an api callout, it + // means we haven't actually sent the request yet + // so the stuff in the buffer is garbage and we + // want to ignore it + // + server_entry->eos = true; + + // I'm not sure about the above comment, but if EOS is received on read and we are + // still in this state, we must have not gotten WRITE_COMPLETE. With epoll we might not receive EOS + // from both read and write sides of a connection so it should be handled correctly (close tunnels, + // deallocate, etc) here with handle_server_setup_error(). Otherwise we might hang due to not shutting + // down and never receiving another event again. + /*if (server_buffer_reader->read_avail() > 0 && callout_state == HTTP_API_NO_CALLOUT) { + break; + } */ + + // Nothing in the buffer + // proceed to error + // fallthrough + + case VC_EVENT_ERROR: + case VC_EVENT_ACTIVE_TIMEOUT: + case VC_EVENT_INACTIVITY_TIMEOUT: + handle_server_setup_error(event, data); + break; + + case VC_EVENT_READ_COMPLETE: + // new event expected due to TS-3189 + SMDebug("http_ss", "read complete due to 0 byte do_io_read"); + break; + + default: + ink_release_assert(0); + break; + } + + return 0; +} + +void +L4RSM::process_srv_info(HostDBInfo *r) +{ + SMDebug("dns_srv", "beginning process_srv_info"); + t_state.hostdb_entry = Ptr(r); + + /* we didn't get any SRV records, continue w normal lookup */ + if (!r || !r->is_srv || !r->round_robin) { + t_state.dns_info.srv_hostname[0] = '\0'; + t_state.dns_info.srv_lookup_success = false; + t_state.txn_conf->srv_enabled = false; + SMDebug("dns_srv", "No SRV records were available, continuing to lookup %s", t_state.dns_info.lookup_name); + } else { + HostDBRoundRobin *rr = r->rr(); + HostDBInfo *srv = nullptr; + if (rr) { + srv = rr->select_best_srv(t_state.dns_info.srv_hostname, &mutex->thread_holding->generator, ink_local_time(), + (int)t_state.txn_conf->down_server_timeout); + } + if (!srv) { + t_state.dns_info.srv_lookup_success = false; + t_state.dns_info.srv_hostname[0] = '\0'; + t_state.txn_conf->srv_enabled = false; + SMDebug("dns_srv", "SRV records empty for %s", t_state.dns_info.lookup_name); + } else { + t_state.dns_info.srv_lookup_success = true; + t_state.dns_info.srv_port = srv->data.srv.srv_port; + t_state.dns_info.srv_app = srv->app; + // t_state.dns_info.single_srv = (rr->good == 1); + ink_assert(srv->data.srv.key == makeHostHash(t_state.dns_info.srv_hostname)); + SMDebug("dns_srv", "select SRV records %s", t_state.dns_info.srv_hostname); + } + } + return; +} + +void +L4RSM::process_hostdb_info(HostDBInfo *r) +{ + // Increment the refcount to our item, since we are pointing at it + t_state.hostdb_entry = Ptr(r); + + sockaddr const *client_addr = nullptr; + bool use_client_addr = t_state.http_config_param->use_client_target_addr == 1 && t_state.client_info.is_transparent && + t_state.dns_info.os_addr_style == HttpTransact::DNSLookupInfo::OS_Addr::OS_ADDR_TRY_DEFAULT; + if (use_client_addr) { + NetVConnection *vc = t_state.state_machine->ua_txn ? t_state.state_machine->ua_txn->get_netvc() : nullptr; + if (vc) { + client_addr = vc->get_local_addr(); + // Regardless of whether the client address matches the DNS record or not, + // we want to use that address. Therefore, we copy over the client address + // info and skip the assignment from the DNS cache + ats_ip_copy(t_state.host_db_info.ip(), client_addr); + t_state.dns_info.os_addr_style = HttpTransact::DNSLookupInfo::OS_Addr::OS_ADDR_TRY_CLIENT; + t_state.dns_info.lookup_success = true; + // Leave ret unassigned, so we don't overwrite the host_db_info + } else { + use_client_addr = false; + } + } + + if (r && !r->is_failed()) { + ink_time_t now = ink_local_time(); + HostDBInfo *ret = nullptr; + t_state.dns_info.lookup_success = true; + t_state.dns_info.lookup_validated = true; + + HostDBRoundRobin *rr = r->round_robin ? r->rr() : nullptr; + if (rr) { + // if use_client_target_addr is set, make sure the client addr is in the results pool + if (use_client_addr && rr->find_ip(client_addr) == nullptr) { + SMDebug("http", "use_client_target_addr == 1. Client specified address is not in the pool, not validated."); + t_state.dns_info.lookup_validated = false; + } else { + // Since the time elapsed between current time and client_request_time + // may be very large, we cannot use client_request_time to approximate + // current time when calling select_best_http(). + ret = rr->select_best_http(&t_state.client_info.src_addr.sa, now, static_cast(t_state.txn_conf->down_server_timeout)); + // set the srv target`s last_failure + if (t_state.dns_info.srv_lookup_success) { + uint32_t last_failure = 0xFFFFFFFF; + for (int i = 0; i < rr->rrcount && last_failure != 0; ++i) { + if (last_failure > rr->info(i).app.http_data.last_failure) { + last_failure = rr->info(i).app.http_data.last_failure; + } + } + + if (last_failure != 0 && (uint32_t)(now - t_state.txn_conf->down_server_timeout) < last_failure) { + HostDBApplicationInfo app; + app.allotment.application1 = 0; + app.allotment.application2 = 0; + app.http_data.last_failure = last_failure; + hostDBProcessor.setby_srv(t_state.dns_info.lookup_name, 0, t_state.dns_info.srv_hostname, &app); + } + } + } + } else { + if (use_client_addr && !ats_ip_addr_eq(client_addr, &r->data.ip.sa)) { + SMDebug("http", "use_client_target_addr == 1. Comparing single addresses failed, not validated."); + t_state.dns_info.lookup_validated = false; + } else { + ret = r; + } + } + if (ret) { + t_state.host_db_info = *ret; + ink_release_assert(!t_state.host_db_info.reverse_dns); + ink_release_assert(ats_is_ip(t_state.host_db_info.ip())); + } + } else { + SMDebug("http", "[%" PRId64 "] DNS lookup failed for '%s'", sm_id, t_state.dns_info.lookup_name); + + if (!use_client_addr) { + t_state.dns_info.lookup_success = false; + } + t_state.host_db_info.app.allotment.application1 = 0; + t_state.host_db_info.app.allotment.application2 = 0; + ink_assert(!t_state.host_db_info.round_robin); + } + + milestones[TS_MILESTONE_DNS_LOOKUP_END] = Thread::get_hrtime(); + + if (is_debug_tag_set("http_timeout")) { + if (t_state.api_txn_dns_timeout_value != -1) { + int foo = (int)(milestones.difference_msec(TS_MILESTONE_DNS_LOOKUP_BEGIN, TS_MILESTONE_DNS_LOOKUP_END)); + SMDebug("http_timeout", "DNS took: %d msec", foo); + } + } +} + +////////////////////////////////////////////////////////////////////////////// +// +// L4RSM::state_hostdb_lookup() +// +////////////////////////////////////////////////////////////////////////////// +int +L4RSM::state_hostdb_lookup(int event, void *data) +{ + STATE_ENTER(&L4RSM::state_hostdb_lookup, event); + + // ink_assert (m_origin_server_vc == 0); + // REQ_FLAVOR_SCHEDULED_UPDATE can be transformed into + // REQ_FLAVOR_REVPROXY + ink_assert(t_state.req_flavor == HttpTransact::REQ_FLAVOR_SCHEDULED_UPDATE || + t_state.req_flavor == HttpTransact::REQ_FLAVOR_REVPROXY || ua_entry->vc != nullptr); + + switch (event) { + case EVENT_HOST_DB_LOOKUP: + pending_action = nullptr; + process_hostdb_info((HostDBInfo *)data); + call_transact_and_set_next_state(nullptr); + break; + case EVENT_SRV_LOOKUP: { + pending_action = nullptr; + process_srv_info((HostDBInfo *)data); + + char *host_name = t_state.dns_info.srv_lookup_success ? t_state.dns_info.srv_hostname : t_state.dns_info.lookup_name; + HostDBProcessor::Options opt; + opt.port = t_state.dns_info.srv_lookup_success ? t_state.dns_info.srv_port : t_state.server_info.dst_addr.host_order_port(); + opt.flags = (t_state.cache_info.directives.does_client_permit_dns_storing) ? HostDBProcessor::HOSTDB_DO_NOT_FORCE_DNS : + HostDBProcessor::HOSTDB_FORCE_DNS_RELOAD; + opt.timeout = (t_state.api_txn_dns_timeout_value != -1) ? t_state.api_txn_dns_timeout_value : 0; + opt.host_res_style = ua_txn->get_host_res_style(); + + Action *dns_lookup_action_handle = + hostDBProcessor.getbyname_imm(this, (process_hostdb_info_pfn)&L4RSM::process_hostdb_info, host_name, 0, opt); + if (dns_lookup_action_handle != ACTION_RESULT_DONE) { + ink_assert(!pending_action); + pending_action = dns_lookup_action_handle; + } else { + call_transact_and_set_next_state(nullptr); + } + } break; + case EVENT_HOST_DB_IP_REMOVED: + ink_assert(!"Unexpected event from HostDB"); + break; + default: + ink_assert(!"Unexpected event"); + } + + return 0; +} + +int +L4RSM::state_hostdb_reverse_lookup(int event, void *data) +{ + STATE_ENTER(&L4RSM::state_hostdb_reverse_lookup, event); + + // REQ_FLAVOR_SCHEDULED_UPDATE can be transformed into + // REQ_FLAVOR_REVPROXY + ink_assert(t_state.req_flavor == HttpTransact::REQ_FLAVOR_SCHEDULED_UPDATE || + t_state.req_flavor == HttpTransact::REQ_FLAVOR_REVPROXY || ua_entry->vc != nullptr); + + switch (event) { + case EVENT_HOST_DB_LOOKUP: + pending_action = nullptr; + if (data) { + t_state.request_data.hostname_str = ((HostDBInfo *)data)->hostname(); + } else { + SMDebug("http", "[%" PRId64 "] reverse DNS lookup failed for '%s'", sm_id, t_state.dns_info.lookup_name); + } + call_transact_and_set_next_state(nullptr); + break; + default: + ink_assert(!"Unexpected event"); + } + + return 0; +} + +////////////////////////////////////////////////////////////////////////////// +// +// L4RSM:state_mark_os_down() +// +////////////////////////////////////////////////////////////////////////////// +int +L4RSM::state_mark_os_down(int event, void *data) +{ + HostDBInfo *mark_down = nullptr; + + if (event == EVENT_HOST_DB_LOOKUP && data) { + HostDBInfo *r = (HostDBInfo *)data; + + if (r->round_robin) { + // Look for the entry we need mark down in the round robin + ink_assert(t_state.current.server != nullptr); + ink_assert(t_state.current.request_to == HttpTransact::ORIGIN_SERVER); + if (t_state.current.server) { + mark_down = r->rr()->find_ip(&t_state.current.server->dst_addr.sa); + } + } else { + // No longer a round robin, check to see if our address is the same + if (ats_ip_addr_eq(t_state.host_db_info.ip(), r->ip())) { + mark_down = r; + } + } + + if (mark_down) { + mark_host_failure(mark_down, t_state.request_sent_time); + } + } + // We either found our entry or we did not. Either way find + // the entry we should use now + return state_hostdb_lookup(event, data); +} + +////////////////////////////////////////////////////////////////////////// +// +// L4RSM::state_handle_stat_page() +// +////////////////////////////////////////////////////////////////////////// +int +L4RSM::state_handle_stat_page(int event, void *data) +{ + STATE_ENTER(&L4RSM::state_handle_stat_page, event); + switch (event) { + case STAT_PAGE_SUCCESS: + pending_action = nullptr; + + if (data) { + StatPageData *spd = (StatPageData *)data; + + t_state.internal_msg_buffer = spd->data; + if (spd->type) { + t_state.internal_msg_buffer_type = spd->type; + } else { + t_state.internal_msg_buffer_type = nullptr; // Defaults to text/html + } + t_state.internal_msg_buffer_size = spd->length; + t_state.internal_msg_buffer_fast_allocator_size = -1; + } + + call_transact_and_set_next_state(HttpTransact::HandleStatPage); + break; + + case STAT_PAGE_FAILURE: + pending_action = nullptr; + call_transact_and_set_next_state(HttpTransact::HandleStatPage); + break; + + default: + ink_release_assert(0); + break; + } + + return 0; +} + +///////////////////////////////////////////////////////////////////////////////// +// L4RSM::state_cache_open_write() +// +// This state is set by set_next_state() for a cache open write +// (SERVER_READ_CACHE_WRITE) +// +////////////////////////////////////////////////////////////////////////// +int +L4RSM::state_cache_open_write(int event, void *data) +{ + STATE_ENTER(&L4RSM : state_cache_open_write, event); + + // Make sure we are on the "right" thread + if (ua_txn) { + if (pending_action) { + pending_action->cancel(); + } + if ((pending_action = ua_txn->adjust_thread(this, event, data))) { + return 0; // Go away if we reschedule + } + } + + milestones[TS_MILESTONE_CACHE_OPEN_WRITE_END] = Thread::get_hrtime(); + pending_action = nullptr; + + switch (event) { + case CACHE_EVENT_OPEN_WRITE: + ////////////////////////////// + // OPEN WRITE is successful // + ////////////////////////////// + t_state.cache_info.write_lock_state = HttpTransact::CACHE_WL_SUCCESS; + break; + + case CACHE_EVENT_OPEN_WRITE_FAILED: + // Failed on the write lock and retrying the vector + // for reading + if (t_state.redirect_info.redirect_in_process) { + SMDebug("http_redirect", "[%" PRId64 "] CACHE_EVENT_OPEN_WRITE_FAILED during redirect follow", sm_id); + t_state.cache_open_write_fail_action = HttpTransact::CACHE_WL_FAIL_ACTION_DEFAULT; + t_state.cache_info.write_lock_state = HttpTransact::CACHE_WL_FAIL; + break; + } + if (t_state.txn_conf->cache_open_write_fail_action == HttpTransact::CACHE_WL_FAIL_ACTION_DEFAULT) { + t_state.cache_info.write_lock_state = HttpTransact::CACHE_WL_FAIL; + break; + } else { + t_state.cache_open_write_fail_action = t_state.txn_conf->cache_open_write_fail_action; + if (!t_state.cache_info.object_read || + (t_state.cache_open_write_fail_action == HttpTransact::CACHE_WL_FAIL_ACTION_ERROR_ON_MISS_OR_REVALIDATE)) { + // cache miss, set wl_state to fail + SMDebug("http", "[%" PRId64 "] cache object read %p, cache_wl_fail_action %d", sm_id, t_state.cache_info.object_read, + t_state.cache_open_write_fail_action); + t_state.cache_info.write_lock_state = HttpTransact::CACHE_WL_FAIL; + break; + } + } + // INTENTIONAL FALL THROUGH + // Allow for stale object to be served + case CACHE_EVENT_OPEN_READ: + // The write vector was locked and the cache_sm retried + // and got the read vector again. + cache_sm.cache_read_vc->get_http_info(&t_state.cache_info.object_read); + // ToDo: Should support other levels of cache hits here, but the cache does not support it (yet) + if (cache_sm.cache_read_vc->is_ram_cache_hit()) { + t_state.cache_info.hit_miss_code = SQUID_HIT_RAM; + } else { + t_state.cache_info.hit_miss_code = SQUID_HIT_DISK; + } + + ink_assert(t_state.cache_info.object_read != nullptr); + t_state.source = HttpTransact::SOURCE_CACHE; + // clear up CACHE_LOOKUP_MISS, let Freshness function decide + // hit status + t_state.cache_lookup_result = HttpTransact::CACHE_LOOKUP_NONE; + t_state.cache_info.write_lock_state = HttpTransact::CACHE_WL_READ_RETRY; + break; + + case HTTP_TUNNEL_EVENT_DONE: + // In the case where we have issued a cache write for the + // transformed copy, the tunnel from the origin server to + // the transform may complete while we are waiting for + // the cache write. If this is the case, forward the event + // to the transform read state as it will know how to + // handle it + if (t_state.next_action == HttpTransact::SM_ACTION_CACHE_ISSUE_WRITE_TRANSFORM) { + state_common_wait_for_transform_read(&transform_info, &L4RSM::tunnel_handler, event, data); + + return 0; + } + // Fallthrough + default: + ink_release_assert(0); + } + + // The write either succeeded or failed, notify transact + call_transact_and_set_next_state(nullptr); + + return 0; +} + +inline void +L4RSM::setup_cache_lookup_complete_api() +{ + t_state.api_next_action = HttpTransact::SM_ACTION_API_CACHE_LOOKUP_COMPLETE; + do_api_callout(); +} + +////////////////////////////////////////////////////////////////////////// +// +// L4RSM::state_cache_open_read() +// +// This state handles the result of CacheProcessor::open_read() +// that attempts to do cache lookup and open a particular cached +// object for reading. +// +////////////////////////////////////////////////////////////////////////// +int +L4RSM::state_cache_open_read(int event, void *data) +{ + STATE_ENTER(&L4RSM::state_cache_open_read, event); + milestones[TS_MILESTONE_CACHE_OPEN_READ_END] = Thread::get_hrtime(); + + ink_assert(server_entry == nullptr); + ink_assert(t_state.cache_info.object_read == nullptr); + + switch (event) { + case CACHE_EVENT_OPEN_READ: { + pending_action = nullptr; + + SMDebug("http", "[%" PRId64 "] cache_open_read - CACHE_EVENT_OPEN_READ", sm_id); + + ///////////////////////////////// + // lookup/open is successful. // + ///////////////////////////////// + ink_assert(cache_sm.cache_read_vc != nullptr); + t_state.source = HttpTransact::SOURCE_CACHE; + + cache_sm.cache_read_vc->get_http_info(&t_state.cache_info.object_read); + // ToDo: Should support other levels of cache hits here, but the cache does not support it (yet) + if (cache_sm.cache_read_vc->is_ram_cache_hit()) { + t_state.cache_info.hit_miss_code = SQUID_HIT_RAM; + } else { + t_state.cache_info.hit_miss_code = SQUID_HIT_DISK; + } + + ink_assert(t_state.cache_info.object_read != nullptr); + call_transact_and_set_next_state(HttpTransact::HandleCacheOpenRead); + break; + } + case CACHE_EVENT_OPEN_READ_FAILED: + pending_action = nullptr; + + SMDebug("http", "[%" PRId64 "] cache_open_read - CACHE_EVENT_OPEN_READ_FAILED with %s (%d)", sm_id, + InkStrerror(-(intptr_t)data), (int)(intptr_t)data); + + SMDebug("http", "[state_cache_open_read] open read failed."); + // Inform HttpTransact somebody else is updating the document + // HttpCacheSM already waited so transact should go ahead. + if ((intptr_t)data == -ECACHE_DOC_BUSY) { + t_state.cache_lookup_result = HttpTransact::CACHE_LOOKUP_DOC_BUSY; + } else { + t_state.cache_lookup_result = HttpTransact::CACHE_LOOKUP_MISS; + } + + ink_assert(t_state.transact_return_point == nullptr); + t_state.transact_return_point = HttpTransact::HandleCacheOpenRead; + setup_cache_lookup_complete_api(); + break; + + default: + ink_release_assert("!Unknown event"); + break; + } + + return 0; +} + +int +L4RSM::main_handler(int event, void *data) +{ + ink_release_assert(magic == HTTP_SM_MAGIC_ALIVE); + + L4RSMHandler jump_point = nullptr; + ink_assert(reentrancy_count >= 0); + reentrancy_count++; + + // Don't use the state enter macro since it uses history + // space that we don't care about + SMDebug("http", "[%" PRId64 "] [L4RSM::main_handler, %s]", sm_id, HttpDebugNames::get_event_name(event)); + + L4RVCTableEntry *vc_entry = nullptr; + + if (data != nullptr) { + // Only search the VC table if the event could have to + // do with a VIO to save a few cycles + + if (event < VC_EVENT_EVENTS_START + 100) { + vc_entry = vc_table.find_entry((VIO *)data); + } + } + + if (vc_entry) { + jump_point = vc_entry->vc_handler; + ink_assert(jump_point != (L4RSMHandler) nullptr); + ink_assert(vc_entry->vc != (VConnection *)nullptr); + (this->*jump_point)(event, data); + } else { + ink_assert(default_handler != (L4RSMHandler) nullptr); + (this->*default_handler)(event, data); + } + + // The sub-handler signals when it is time for the state + // machine to exit. We can only exit if we are not reentrantly + // called otherwise when the our call unwinds, we will be + // running on a dead state machine + // + // Because of the need for an api shutdown hook, kill_this() + // is also reentrant. As such, we don't want to decrement + // the reentrancy count until after we run kill_this() + // + if (terminate_sm == true && reentrancy_count == 1) { + kill_this(); + } else { + reentrancy_count--; + ink_assert(reentrancy_count >= 0); + } + + return (VC_EVENT_CONT); +} + +// void L4RSM::tunnel_handler_post_or_put() +// +// Handles the common cleanup tasks for Http post/put +// to prevent code duplication +// +void +L4RSM::tunnel_handler_post_or_put(HttpTunnelProducer *p) +{ + ink_assert(p->vc_type == HT_HTTP_CLIENT || (p->handler_state == HTTP_SM_POST_UA_FAIL && p->vc_type == HT_BUFFER_READ)); + HttpTunnelConsumer *c; + + // If there is a post transform, remove it's entry from the State + // Machine's VC table + // + // MUST NOT clear the vc pointer from post_transform_info + // as this causes a double close of the transform vc in transform_cleanup + // + if (post_transform_info.vc != nullptr) { + ink_assert(post_transform_info.entry->in_tunnel == true); + ink_assert(post_transform_info.vc == post_transform_info.entry->vc); + vc_table.cleanup_entry(post_transform_info.entry); + post_transform_info.entry = nullptr; + } + + switch (p->handler_state) { + case HTTP_SM_POST_SERVER_FAIL: + c = tunnel.get_consumer(server_entry->vc); + ink_assert(c->write_success == false); + break; + case HTTP_SM_POST_UA_FAIL: + // UA quit - shutdown the SM + ink_assert(p->read_success == false); + terminate_sm = true; + break; + case HTTP_SM_POST_SUCCESS: + // The post succeeded + ink_assert(p->read_success == true); + ink_assert(p->consumer_list.head->write_success == true); + tunnel.deallocate_buffers(); + tunnel.reset(); + // When the ua completed sending it's data we must have + // removed it from the tunnel + ink_release_assert(ua_entry->in_tunnel == false); + server_entry->in_tunnel = false; + + break; + default: + ink_release_assert(0); + } +} + +// int L4RSM::tunnel_handler_post(int event, void* data) +// +// Handles completion of any http request body tunnel +// Having 'post' in its name is a misnomer +// +int +L4RSM::tunnel_handler_post(int event, void *data) +{ + STATE_ENTER(&L4RSM::tunnel_handler_post, event); + + HttpTunnelProducer *p = ua_txn != nullptr ? tunnel.get_producer(ua_txn) : tunnel.get_producer(HT_HTTP_CLIENT); + if (!p) { + return 0; // Cannot do anything if there is no producer + } + + switch (event) { + case HTTP_TUNNEL_EVENT_DONE: // Tunnel done. + if (p->handler_state == HTTP_SM_POST_UA_FAIL) { + // post failed + switch (t_state.client_info.state) { + case HttpTransact::ACTIVE_TIMEOUT: + call_transact_and_set_next_state(HttpTransact::PostActiveTimeoutResponse); + return 0; + case HttpTransact::INACTIVE_TIMEOUT: + call_transact_and_set_next_state(HttpTransact::PostInactiveTimeoutResponse); + return 0; + default: + break; + } + } + break; + case VC_EVENT_WRITE_READY: // iocore may callback first before send. + return 0; + case VC_EVENT_EOS: // SSLNetVC may callback EOS during write error (6.0.x or early) + case VC_EVENT_ERROR: // Send HTTP 408 error + case VC_EVENT_WRITE_COMPLETE: // tunnel_handler_post_ua has sent HTTP 408 response + case VC_EVENT_INACTIVITY_TIMEOUT: // ua_txn timeout during sending the HTTP 408 response + case VC_EVENT_ACTIVE_TIMEOUT: // ua_txn timeout + if (ua_entry->write_buffer) { + free_MIOBuffer(ua_entry->write_buffer); + ua_entry->write_buffer = nullptr; + ua_entry->vc->do_io_write(this, 0, nullptr); + } + break; + case VC_EVENT_READ_READY: + case VC_EVENT_READ_COMPLETE: + default: + ink_assert(!"not reached"); + return 0; + } + + ink_assert(event == HTTP_TUNNEL_EVENT_DONE); + ink_assert(data == &tunnel); + // The tunnel calls this when it is done + + int p_handler_state = p->handler_state; + if (is_waiting_for_full_body && !this->is_postbuf_valid()) { + p_handler_state = HTTP_SM_POST_SERVER_FAIL; + } + if (p->vc_type != HT_BUFFER_READ) { + tunnel_handler_post_or_put(p); + } + + switch (p_handler_state) { + case HTTP_SM_POST_SERVER_FAIL: + handle_post_failure(); + break; + case HTTP_SM_POST_UA_FAIL: + break; + case HTTP_SM_POST_SUCCESS: + // It's time to start reading the response + if (is_waiting_for_full_body) { + is_waiting_for_full_body = false; + is_using_post_buffer = true; + client_request_body_bytes = this->postbuf_buffer_avail(); + + call_transact_and_set_next_state(HttpTransact::HandleRequestBufferDone); + break; + } + // Read reasponse already setup + break; + default: + ink_release_assert(0); + } + + return 0; +} + +int +L4RSM::tunnel_handler_cache_fill(int event, void *data) +{ + STATE_ENTER(&L4RSM::tunnel_handler_cache_fill, event); + + ink_assert(event == HTTP_TUNNEL_EVENT_DONE); + ink_assert(data == &tunnel); + + ink_release_assert(cache_sm.cache_write_vc); + + tunnel.deallocate_buffers(); + this->postbuf_clear(); + tunnel.reset(); + + setup_server_transfer_to_cache_only(); + tunnel.tunnel_run(); + + return 0; +} + +int +L4RSM::tunnel_handler_100_continue(int event, void *data) +{ + STATE_ENTER(&L4RSM::tunnel_handler_100_continue, event); + + ink_assert(event == HTTP_TUNNEL_EVENT_DONE); + ink_assert(data == &tunnel); + + // We're done sending the 100 continue. If we succeeded, + // we set up to parse the next server response. If we + // failed, shutdown the state machine + HttpTunnelConsumer *c = tunnel.get_consumer(ua_txn); + + if (c->write_success) { + // Note: we must use destroy() here since clear() + // does not free the memory from the header + t_state.hdr_info.client_response.destroy(); + tunnel.deallocate_buffers(); + this->postbuf_clear(); + tunnel.reset(); + + if (server_entry->eos) { + // if the server closed while sending the + // 100 continue header, handle it here so we + // don't assert later + SMDebug("http", + "[%" PRId64 "] tunnel_handler_100_continue - server already " + "closed, terminating connection", + sm_id); + + // Since 100 isn't a final (loggable) response header + // kill the 100 continue header and create an empty one + t_state.hdr_info.server_response.destroy(); + t_state.hdr_info.server_response.create(HTTP_TYPE_RESPONSE); + handle_server_setup_error(VC_EVENT_EOS, server_entry->read_vio); + } else { + setup_server_read_response_header(); + } + } else { + terminate_sm = true; + } + + return 0; +} + +int +L4RSM::tunnel_handler_push(int event, void *data) +{ + STATE_ENTER(&L4RSM::tunnel_handler_push, event); + + ink_assert(event == HTTP_TUNNEL_EVENT_DONE); + ink_assert(data == &tunnel); + + // Check to see if the client is still around + HttpTunnelProducer *ua = (ua_txn) ? tunnel.get_producer(ua_txn) : tunnel.get_producer(HT_HTTP_CLIENT); + + if (ua && !ua->read_success) { + // Client failed to send the body, it's gone. Kill the + // state machine + terminate_sm = true; + return 0; + } + + HttpTunnelConsumer *cache = ua->consumer_list.head; + ink_release_assert(cache->vc_type == HT_CACHE_WRITE); + bool cache_write_success = cache->write_success; + + // Reset tunneling state since we need to send a response + // to client as whether we succeeded + tunnel.deallocate_buffers(); + this->postbuf_clear(); + tunnel.reset(); + + if (cache_write_success) { + call_transact_and_set_next_state(HttpTransact::HandlePushTunnelSuccess); + } else { + call_transact_and_set_next_state(HttpTransact::HandlePushTunnelFailure); + } + + return 0; +} + +int +L4RSM::tunnel_handler(int event, void *data) +{ + STATE_ENTER(&L4RSM::tunnel_handler, event); + + ink_assert(event == HTTP_TUNNEL_EVENT_DONE); + // The tunnel calls this when it is done + terminate_sm = true; + + if (unlikely(t_state.is_websocket)) { + HTTP_DECREMENT_DYN_STAT(http_websocket_current_active_client_connections_stat); + } + + return 0; +} + +/**************************************************** + TUNNELING HANDLERS + ******************************************************/ + +bool +L4RSM::is_http_server_eos_truncation(HttpTunnelProducer *p) +{ + if ((p->do_dechunking || p->do_chunked_passthru) && p->chunked_handler.truncation) { + return true; + } + + ////////////////////////////////////////////////////////////// + // If we did not get or did not trust the origin server's // + // content-length, read_content_length is unset. The // + // only way the end of the document is signaled is the // + // origin server closing the connection. However, we // + // need to protect against the document getting truncated // + // because the origin server crashed. The following // + // tabled outlines when we mark the server read as failed // + // // + // No C-L : read success // + // Received byts < C-L : read failed (=> Cache Abort) // + // Received byts == C-L : read success // + // Received byts > C-L : read success // + ////////////////////////////////////////////////////////////// + int64_t cl = t_state.hdr_info.server_response.get_content_length(); + + if (cl != UNDEFINED_COUNT && cl > server_response_body_bytes) { + SMDebug("http", "[%" PRId64 "] server EOS after %" PRId64 " bytes, expected %" PRId64, sm_id, server_response_body_bytes, cl); + return true; + } else { + return false; + } +} + +int +L4RSM::tunnel_handler_server(int event, HttpTunnelProducer *p) +{ + STATE_ENTER(&L4RSM::tunnel_handler_server, event); + + milestones[TS_MILESTONE_SERVER_CLOSE] = Thread::get_hrtime(); + + bool close_connection = false; + + if (t_state.current.server->keep_alive == HTTP_KEEPALIVE && server_entry->eos == false && + plugin_tunnel_type == HTTP_NO_PLUGIN_TUNNEL && t_state.txn_conf->keep_alive_enabled_out == 1) { + close_connection = false; + } else { + close_connection = true; + } + + switch (event) { + case VC_EVENT_INACTIVITY_TIMEOUT: + case VC_EVENT_ACTIVE_TIMEOUT: + case VC_EVENT_ERROR: + t_state.squid_codes.log_code = SQUID_LOG_ERR_READ_TIMEOUT; + t_state.squid_codes.hier_code = SQUID_HIER_TIMEOUT_DIRECT; + /* fallthru */ + + case VC_EVENT_EOS: + + switch (event) { + case VC_EVENT_INACTIVITY_TIMEOUT: + t_state.current.server->state = HttpTransact::INACTIVE_TIMEOUT; + break; + case VC_EVENT_ACTIVE_TIMEOUT: + t_state.current.server->state = HttpTransact::ACTIVE_TIMEOUT; + break; + case VC_EVENT_ERROR: + t_state.current.server->state = HttpTransact::CONNECTION_ERROR; + break; + case VC_EVENT_EOS: + t_state.current.server->state = HttpTransact::TRANSACTION_COMPLETE; + break; + } + + close_connection = true; + + ink_assert(p->vc_type == HT_HTTP_SERVER); + + if (is_http_server_eos_truncation(p)) { + SMDebug("http", "[%" PRId64 "] [L4RSM::tunnel_handler_server] aborting HTTP tunnel due to server truncation", sm_id); + tunnel.chain_abort_all(p); + // UA session may not be in the tunnel yet, don't NULL out the pointer in that case. + // Note: This is a hack. The correct solution is for the UA session to signal back to the SM + // when the UA is about to be destroyed and clean up the pointer there. That should be done once + // the TS-3612 changes are in place (and similarly for the server session). + /*if (ua_entry->in_tunnel) + ua_txn = NULL; */ + + t_state.current.server->abort = HttpTransact::ABORTED; + t_state.client_info.keep_alive = HTTP_NO_KEEPALIVE; + t_state.current.server->keep_alive = HTTP_NO_KEEPALIVE; + t_state.squid_codes.log_code = SQUID_LOG_ERR_READ_ERROR; + } else { + SMDebug("http", "[%" PRId64 "] [L4RSM::tunnel_handler_server] finishing HTTP tunnel", sm_id); + p->read_success = true; + t_state.current.server->abort = HttpTransact::DIDNOT_ABORT; + // Appending reason to a response without Content-Length will result in + // the reason string being written to the client and a bad CL when reading from cache. + // I didn't find anywhere this appended reason is being used, so commenting it out. + /* + if (t_state.negative_caching && p->bytes_read == 0) { + int reason_len; + const char *reason = t_state.hdr_info.server_response.reason_get(&reason_len); + if (reason == NULL) + tunnel.append_message_to_producer_buffer(p, "Negative Response", sizeof("Negative Response") - 1); + else + tunnel.append_message_to_producer_buffer(p, reason, reason_len); + } + */ + tunnel.local_finish_all(p); + } + break; + + case HTTP_TUNNEL_EVENT_PRECOMPLETE: + case VC_EVENT_READ_COMPLETE: + // + // The transfer completed successfully + // If there is still data in the buffer, the server + // sent too much indicating a failed transfer + p->read_success = true; + t_state.current.server->state = HttpTransact::TRANSACTION_COMPLETE; + t_state.current.server->abort = HttpTransact::DIDNOT_ABORT; + + if (p->do_dechunking || p->do_chunked_passthru) { + if (p->chunked_handler.truncation) { + tunnel.abort_cache_write_finish_others(p); + // We couldn't read all chunks successfully: + // Disable keep-alive. + t_state.client_info.keep_alive = HTTP_NO_KEEPALIVE; + t_state.current.server->keep_alive = HTTP_NO_KEEPALIVE; + } else { + tunnel.local_finish_all(p); + } + } + break; + + case HTTP_TUNNEL_EVENT_CONSUMER_DETACH: + // All consumers are prematurely gone. Shutdown + // the server connection + p->read_success = true; + t_state.current.server->state = HttpTransact::TRANSACTION_COMPLETE; + t_state.current.server->abort = HttpTransact::DIDNOT_ABORT; + close_connection = true; + break; + + case VC_EVENT_READ_READY: + case VC_EVENT_WRITE_READY: + case VC_EVENT_WRITE_COMPLETE: + default: + // None of these events should ever come our way + ink_assert(0); + break; + } + + // turn off negative caching in case there are multiple server contacts + if (t_state.negative_caching) { + t_state.negative_caching = false; + } + + // If we had a ground fill, check update our status + if (background_fill == BACKGROUND_FILL_STARTED) { + background_fill = p->read_success ? BACKGROUND_FILL_COMPLETED : BACKGROUND_FILL_ABORTED; + HTTP_DECREMENT_DYN_STAT(http_background_fill_current_count_stat); + } + // We handled the event. Now either shutdown the connection or + // setup it up for keep-alive + ink_assert(server_entry->vc == p->vc); + ink_assert(p->vc_type == HT_HTTP_SERVER); + ink_assert(p->vc == server_session); + + if (close_connection) { + p->vc->do_io_close(); + p->read_vio = nullptr; + /* TS-1424: if we're outbound transparent and using the client + source port for the outbound connection we must effectively + propagate server closes back to the client. Part of that is + disabling KeepAlive if the server closes. + */ + if (ua_txn && ua_txn->is_outbound_transparent() && t_state.http_config_param->use_client_source_port) { + t_state.client_info.keep_alive = HTTP_NO_KEEPALIVE; + } + } else { + server_session->attach_hostname(t_state.current.server->name); + server_session->server_trans_stat--; + HTTP_DECREMENT_DYN_STAT(http_current_server_transactions_stat); + + // If the option to attach the server session to the client session is set + // and if the client is still around and the client is keep-alive, attach the + // server session to so the next ka request can use it. Server sessions will + // be placed into the shared pool if the next incoming request is for a different + // origin server + if (t_state.txn_conf->attach_server_session_to_client == 1 && ua_txn && t_state.client_info.keep_alive == HTTP_KEEPALIVE) { + Debug("http", "attaching server session to the client"); + ua_txn->attach_server_session(server_session); + } else { + // Release the session back into the shared session pool + server_session->get_netvc()->set_inactivity_timeout(HRTIME_SECONDS(t_state.txn_conf->keep_alive_no_activity_timeout_out)); + server_session->release(); + } + } + + // The server session has been released. Clean all pointer + server_entry->in_tunnel = true; // to avid cleaning in clenup_entry + vc_table.cleanup_entry(server_entry); + server_session = nullptr; // Because p->vc == server_session + server_entry = nullptr; + + return 0; +} + +// int L4RSM::tunnel_handler_100_continue_ua(int event, HttpTunnelConsumer* c) +// +// Used for tunneling the 100 continue response. The tunnel +// should not close or release the user agent unless there is +// an error since the real response is yet to come +// +int +L4RSM::tunnel_handler_100_continue_ua(int event, HttpTunnelConsumer *c) +{ + STATE_ENTER(&L4RSM::tunnel_handler_100_continue_ua, event); + + ink_assert(c->vc == ua_txn); + + switch (event) { + case VC_EVENT_EOS: + ua_entry->eos = true; + // FALL-THROUGH + case VC_EVENT_INACTIVITY_TIMEOUT: + case VC_EVENT_ACTIVE_TIMEOUT: + case VC_EVENT_ERROR: + set_ua_abort(HttpTransact::ABORTED, event); + c->vc->do_io_close(); + break; + case VC_EVENT_WRITE_COMPLETE: + // mark the vc as no longer in tunnel + // so we don't get hosed if the ua abort before + // real response header is received + ua_entry->in_tunnel = false; + c->write_success = true; + } + + return 0; +} + +bool +L4RSM::is_bg_fill_necessary(HttpTunnelConsumer *c) +{ + ink_assert(c->vc_type == HT_HTTP_CLIENT); + + if (c->producer->alive && // something there to read + // server_entry && server_entry->vc && // from an origin server + // server_session && server_session->get_netvc() && // which is still open and valid + c->producer->num_consumers > 1 // with someone else reading it + ) { + HttpTunnelProducer *p = nullptr; + + if (!server_entry || !server_entry->vc || !server_session || !server_session->get_netvc()) { + // return true if we have finished the reading from OS when client aborted + p = c->producer->self_consumer ? c->producer->self_consumer->producer : c->producer; + if (p->vc_type == HT_HTTP_SERVER && p->read_success) { + return true; + } else { + return false; + } + } + // If threshold is 0.0 or negative then do background + // fill regardless of the content length. Since this + // is floating point just make sure the number is near zero + if (t_state.txn_conf->background_fill_threshold <= 0.001) { + return true; + } + + int64_t ua_cl = t_state.hdr_info.client_response.get_content_length(); + + if (ua_cl > 0) { + int64_t ua_body_done = c->bytes_written - client_response_hdr_bytes; + float pDone = (float)ua_body_done / ua_cl; + + // If we got a good content length. Check to make sure that we haven't already + // done more the content length since that would indicate the content-length + // is bogus. If we've done more than the threshold, continue the background fill + if (pDone <= 1.0 && pDone > t_state.txn_conf->background_fill_threshold) { + return true; + } else { + SMDebug("http", "[%" PRId64 "] no background. Only %%%f of %%%f done [%" PRId64 " / %" PRId64 " ]", sm_id, pDone, + t_state.txn_conf->background_fill_threshold, ua_body_done, ua_cl); + } + } + } + + return false; +} + +int +L4RSM::tunnel_handler_ua(int event, HttpTunnelConsumer *c) +{ + bool close_connection = true; + HttpTunnelProducer *p = nullptr; + HttpTunnelConsumer *selfc = nullptr; + + STATE_ENTER(&L4RSM::tunnel_handler_ua, event); + ink_assert(c->vc == ua_txn); + milestones[TS_MILESTONE_UA_CLOSE] = Thread::get_hrtime(); + + switch (event) { + case VC_EVENT_EOS: + ua_entry->eos = true; + + // FALL-THROUGH + case VC_EVENT_INACTIVITY_TIMEOUT: + case VC_EVENT_ACTIVE_TIMEOUT: + case VC_EVENT_ERROR: + + // The user agent died or aborted. Check to + // see if we should setup a background fill + set_ua_abort(HttpTransact::ABORTED, event); + + if (is_bg_fill_necessary(c)) { + p = c->producer->self_consumer ? c->producer->self_consumer->producer : c->producer; + SMDebug("http", "[%" PRId64 "] Initiating background fill", sm_id); + // check whether to finish the reading. + background_fill = p->read_success ? BACKGROUND_FILL_COMPLETED : BACKGROUND_FILL_STARTED; + + // There is another consumer (cache write) so + // detach the user agent + if (background_fill == BACKGROUND_FILL_STARTED) { + HTTP_INCREMENT_DYN_STAT(http_background_fill_current_count_stat); + ink_assert(server_entry->vc == server_session); + ink_assert(c->is_downstream_from(server_session)); + server_session->get_netvc()->set_active_timeout(HRTIME_SECONDS(t_state.txn_conf->background_fill_active_timeout)); + } + + } else { + // No background fill + p = c->producer; + tunnel.chain_abort_all(c->producer); + selfc = p->self_consumer; + if (selfc) { + // This is the case where there is a transformation between ua and os + p = selfc->producer; + // if producer is the cache or OS, close the producer. + // Otherwise in case of large docs, producer iobuffer gets filled up, + // waiting for a consumer to consume data and the connection is never closed. + if (p->alive && ((p->vc_type == HT_CACHE_READ) || (p->vc_type == HT_HTTP_SERVER))) { + tunnel.chain_abort_all(p); + } + } + } + break; + + case VC_EVENT_WRITE_COMPLETE: + c->write_success = true; + t_state.client_info.abort = HttpTransact::DIDNOT_ABORT; + if (t_state.client_info.keep_alive == HTTP_KEEPALIVE) { + if (ua_txn->allow_half_open() && + (t_state.www_auth_content != HttpTransact::CACHE_AUTH_SERVE || ua_txn->get_server_session())) { + // successful keep-alive + close_connection = false; + } + // else { the authenticated server connection (cache + // authenticated feature) closed during the serve-from-cache. + // We want the client to issue a new connection for the + // session based authenticated mechanism like NTLM, instead + // of still using the existing client connection. } + } + break; + case VC_EVENT_WRITE_READY: + case VC_EVENT_READ_READY: + case VC_EVENT_READ_COMPLETE: + default: + // None of these events should ever come our way + ink_assert(0); + break; + } + + client_response_body_bytes = c->bytes_written - client_response_hdr_bytes; + + if (client_response_body_bytes < 0) { + client_response_body_bytes = 0; + } + + // attribute the size written to the client from various sources + // NOTE: responses that go through a range transform are attributed + // to their original sources + // all other transforms attribute the total number of input bytes + // to a source in L4RSM::tunnel_handler_transform_write + // + HttpTransact::Source_t original_source = t_state.source; + if (HttpTransact::SOURCE_TRANSFORM == original_source && t_state.range_setup != HttpTransact::RANGE_NONE) { + original_source = t_state.pre_transform_source; + } + + switch (original_source) { + case HttpTransact::SOURCE_HTTP_ORIGIN_SERVER: + server_response_body_bytes = client_response_body_bytes; + break; + case HttpTransact::SOURCE_CACHE: + cache_response_body_bytes = client_response_body_bytes; + break; + default: + break; + } + + ink_assert(ua_entry->vc == c->vc); + if (close_connection) { + // If the client could be pipelining or is doing a POST, we need to + // set the ua_txn into half close mode + + // only external POSTs should be subject to this logic; ruling out internal POSTs here + bool is_eligible_post_request = (t_state.method == HTTP_WKSIDX_POST); + if (is_eligible_post_request) { + NetVConnection *vc = ua_txn->get_netvc(); + if (vc) { + is_eligible_post_request &= !vc->get_is_internal_request(); + } + } + if ((is_eligible_post_request || t_state.client_info.pipeline_possible == true) && ua_txn->allow_half_open() && + c->producer->vc_type != HT_STATIC && event == VC_EVENT_WRITE_COMPLETE) { + ua_txn->set_half_close_flag(true); + } + + ua_txn->do_io_close(); + } else { + ink_assert(ua_buffer_reader != nullptr); + ua_txn->release(ua_buffer_reader); + ua_buffer_reader = nullptr; + // ua_txn = NULL; + } + + return 0; +} + +int +L4RSM::tunnel_handler_ua_push(int event, HttpTunnelProducer *p) +{ + STATE_ENTER(&L4RSM::tunnel_handler_ua_push, event); + + pushed_response_body_bytes += p->bytes_read; + client_request_body_bytes += p->bytes_read; + + switch (event) { + case VC_EVENT_INACTIVITY_TIMEOUT: + case VC_EVENT_ACTIVE_TIMEOUT: + case VC_EVENT_ERROR: + case VC_EVENT_EOS: + // Transfer terminated. Bail on the cache write. + set_ua_abort(HttpTransact::ABORTED, event); + p->vc->do_io_close(EHTTP_ERROR); + p->read_vio = nullptr; + tunnel.chain_abort_all(p); + break; + + case HTTP_TUNNEL_EVENT_PRECOMPLETE: + case VC_EVENT_READ_COMPLETE: + // + // The transfer completed successfully + p->read_success = true; + ua_entry->in_tunnel = false; + break; + + case VC_EVENT_READ_READY: + case VC_EVENT_WRITE_READY: + case VC_EVENT_WRITE_COMPLETE: + default: + // None of these events should ever come our way + ink_assert(0); + break; + } + + return 0; +} + +int +L4RSM::tunnel_handler_cache_read(int event, HttpTunnelProducer *p) +{ + STATE_ENTER(&L4RSM::tunnel_handler_cache_read, event); + + switch (event) { + case VC_EVENT_ERROR: + case VC_EVENT_EOS: + ink_assert(t_state.cache_info.object_read->valid()); + if (t_state.cache_info.object_read->object_size_get() != INT64_MAX || event == VC_EVENT_ERROR) { + // Abnormal termination + t_state.squid_codes.log_code = SQUID_LOG_TCP_SWAPFAIL; + p->vc->do_io_close(EHTTP_ERROR); + p->read_vio = nullptr; + tunnel.chain_abort_all(p); + HTTP_INCREMENT_DYN_STAT(http_cache_read_errors); + break; + } else { + tunnel.local_finish_all(p); + // fall through for the case INT64_MAX read with VC_EVENT_EOS + // callback (read successful) + } + // fallthrough + + case VC_EVENT_READ_COMPLETE: + case HTTP_TUNNEL_EVENT_PRECOMPLETE: + case HTTP_TUNNEL_EVENT_CONSUMER_DETACH: + p->read_success = true; + p->vc->do_io_close(); + p->read_vio = nullptr; + break; + default: + ink_release_assert(0); + break; + } + + HTTP_DECREMENT_DYN_STAT(http_current_cache_connections_stat); + return 0; +} + +int +L4RSM::tunnel_handler_cache_write(int event, HttpTunnelConsumer *c) +{ + STATE_ENTER(&L4RSM::tunnel_handler_cache_write, event); + + HttpTransact::CacheWriteStatus_t *status_ptr = + (c->producer->vc_type == HT_TRANSFORM) ? &t_state.cache_info.transform_write_status : &t_state.cache_info.write_status; + + switch (event) { + case VC_EVENT_ERROR: + case VC_EVENT_EOS: + // Abnormal termination + *status_ptr = HttpTransact::CACHE_WRITE_ERROR; + c->write_vio = nullptr; + c->vc->do_io_close(EHTTP_ERROR); + + HTTP_INCREMENT_DYN_STAT(http_cache_write_errors); + SMDebug("http", "[%" PRId64 "] aborting cache write due %s event from cache", sm_id, HttpDebugNames::get_event_name(event)); + // abort the producer if the cache_writevc is the only consumer. + if (c->producer->alive && c->producer->num_consumers == 1) { + tunnel.chain_abort_all(c->producer); + } + break; + case VC_EVENT_WRITE_COMPLETE: + // if we've never initiated a cache write + // abort the cache since it's finicky about a close + // in this case. This case can only occur + // we got a truncated header from the origin server + // but decided to accept it anyways + if (c->write_vio == nullptr) { + *status_ptr = HttpTransact::CACHE_WRITE_ERROR; + c->write_success = false; + c->vc->do_io_close(EHTTP_ERROR); + } else { + *status_ptr = HttpTransact::CACHE_WRITE_COMPLETE; + c->write_success = true; + c->vc->do_io_close(); + c->write_vio = nullptr; + } + break; + default: + // All other events indicate problems + ink_assert(0); + break; + } + + HTTP_DECREMENT_DYN_STAT(http_current_cache_connections_stat); + return 0; +} + +int +L4RSM::tunnel_handler_post_ua(int event, HttpTunnelProducer *p) +{ + STATE_ENTER(&L4RSM::tunnel_handler_post_ua, event); + client_request_body_bytes = p->init_bytes_done + p->bytes_read; + + switch (event) { + case VC_EVENT_INACTIVITY_TIMEOUT: + case VC_EVENT_ACTIVE_TIMEOUT: + if (client_response_hdr_bytes == 0) { + p->handler_state = HTTP_SM_POST_UA_FAIL; + set_ua_abort(HttpTransact::ABORTED, event); + + SMDebug("http_tunnel", "send 408 response to client to vc %p, tunnel vc %p", ua_txn->get_netvc(), p->vc); + + tunnel.chain_abort_all(p); + server_session = nullptr; + // Reset the inactivity timeout, otherwise the InactivityCop will callback again in the next second. + ua_txn->set_inactivity_timeout(HRTIME_SECONDS(t_state.txn_conf->transaction_no_activity_timeout_in)); + // if it is active timeout case, we need to give another chance to send 408 response; + ua_txn->set_active_timeout(HRTIME_SECONDS(t_state.txn_conf->transaction_active_timeout_in)); + + p->vc->do_io_write(this, 0, nullptr); + p->vc->do_io_shutdown(IO_SHUTDOWN_READ); + + return 0; + } + // fall through + case VC_EVENT_EOS: + // My reading of spec says that user agents can not terminate + // posts with a half close so this is an error + case VC_EVENT_ERROR: + // Did not complete post tunneling. Abort the + // server and close the ua + p->handler_state = HTTP_SM_POST_UA_FAIL; + set_ua_abort(HttpTransact::ABORTED, event); + + tunnel.chain_abort_all(p); + server_session = nullptr; + p->read_vio = nullptr; + p->vc->do_io_close(EHTTP_ERROR); + + // the in_tunnel status on both the ua & and + // it's consumer must already be set to true. Previously + // we were setting it again to true but incorrectly in + // the case of a transform + lsm_release_assert(ua_entry->in_tunnel == true); + if (p->consumer_list.head && p->consumer_list.head->vc_type == HT_TRANSFORM) { + lsm_release_assert(post_transform_info.entry->in_tunnel == true); + } else if (server_entry != nullptr) { + lsm_release_assert(server_entry->in_tunnel == true); + } + break; + + case VC_EVENT_READ_COMPLETE: + case HTTP_TUNNEL_EVENT_PRECOMPLETE: + p->handler_state = HTTP_SM_POST_SUCCESS; + p->read_success = true; + ua_entry->in_tunnel = false; + + if (p->do_dechunking || p->do_chunked_passthru) { + if (p->chunked_handler.truncation) { + tunnel.abort_cache_write_finish_others(p); + } else { + tunnel.local_finish_all(p); + } + } + + // Initiate another read to watch catch aborts and + // timeouts + ua_entry->vc_handler = &L4RSM::state_watch_for_client_abort; + ua_entry->read_vio = p->vc->do_io_read(this, INT64_MAX, ua_buffer_reader->mbuf); + break; + default: + ink_release_assert(0); + } + + return 0; +} + +// YTS Team, yamsat Plugin +// Tunnel handler to deallocate the tunnel buffers and +// set redirect_in_process=false +// Copy partial POST data to buffers. Check for the various parameters including +// the maximum configured post data size +int +L4RSM::tunnel_handler_for_partial_post(int event, void * /* data ATS_UNUSED */) +{ + STATE_ENTER(&L4RSM::tunnel_handler_for_partial_post, event); + tunnel.deallocate_buffers(); + tunnel.reset(); + + t_state.redirect_info.redirect_in_process = false; + is_using_post_buffer = false; + + if (post_failed) { + post_failed = false; + handle_post_failure(); + } else { + do_setup_post_tunnel(HTTP_SERVER_VC); + } + + return 0; +} + +int +L4RSM::tunnel_handler_post_server(int event, HttpTunnelConsumer *c) +{ + STATE_ENTER(&L4RSM::tunnel_handler_post_server, event); + + server_request_body_bytes = c->bytes_written; + + switch (event) { + case VC_EVENT_EOS: + case VC_EVENT_ERROR: + case VC_EVENT_INACTIVITY_TIMEOUT: + case VC_EVENT_ACTIVE_TIMEOUT: + // Did not complete post tunneling + // + // In the http case, we don't want to close + // the connection because the + // destroys the header buffer which may + // a response even though the tunnel failed. + + // Shutdown both sides of the connection. This prevents us + // from getting any further events and signals to client + // that POST data will not be forwarded to the server. Doing + // shutdown on the write side will likely generate a TCP + // reset to the client but if the proxy wasn't here this is + // exactly what would happen. + // we should wait to shutdown read side of the + // client to prevent sending a reset + server_entry->eos = true; + c->vc->do_io_shutdown(IO_SHUTDOWN_WRITE); + + // We may be reading from a transform. In that case, we + // want to close the transform + HttpTunnelProducer *ua_producer; + if (c->producer->vc_type == HT_TRANSFORM) { + if (c->producer->handler_state == HTTP_SM_TRANSFORM_OPEN) { + ink_assert(c->producer->vc == post_transform_info.vc); + c->producer->vc->do_io_close(); + c->producer->alive = false; + c->producer->self_consumer->alive = false; + } + ua_producer = c->producer->self_consumer->producer; + } else { + ua_producer = c->producer; + } + ink_assert(ua_producer->vc_type == HT_HTTP_CLIENT); + ink_assert(ua_producer->vc == ua_txn); + ink_assert(ua_producer->vc == ua_entry->vc); + + // Before shutting down, initiate another read + // on the user agent in order to get timeouts + // coming to the state machine and not the tunnel + ua_entry->vc_handler = &L4RSM::state_watch_for_client_abort; + + // YTS Team, yamsat Plugin + // When event is VC_EVENT_ERROR,and when redirection is enabled + // do not shut down the client read + if (enable_redirection) { + if (ua_producer->vc_type == HT_STATIC && event != VC_EVENT_ERROR && event != VC_EVENT_EOS) { + ua_entry->read_vio = ua_producer->vc->do_io_read(this, INT64_MAX, c->producer->read_buffer); + // ua_producer->vc->do_io_shutdown(IO_SHUTDOWN_READ); + t_state.client_info.pipeline_possible = false; + } else { + if (ua_producer->vc_type == HT_STATIC && t_state.redirect_info.redirect_in_process) { + post_failed = true; + } + } + } else { + ua_entry->read_vio = ua_producer->vc->do_io_read(this, INT64_MAX, c->producer->read_buffer); + // we should not shutdown read side of the client here to prevent sending a reset + // ua_producer->vc->do_io_shutdown(IO_SHUTDOWN_READ); + t_state.client_info.pipeline_possible = false; + } // end of added logic + + // We want to shutdown the tunnel here and see if there + // is a response on from the server. Mark the user + // agent as down so that tunnel concludes. + ua_producer->alive = false; + ua_producer->handler_state = HTTP_SM_POST_SERVER_FAIL; + ink_assert(tunnel.is_tunnel_alive() == false); + break; + + case VC_EVENT_WRITE_COMPLETE: + // Completed successfully + c->write_success = true; + break; + default: + ink_release_assert(0); + } + + return 0; +} + +int +L4RSM::tunnel_handler_ssl_producer(int event, HttpTunnelProducer *p) +{ + STATE_ENTER(&L4RSM::tunnel_handler_ssl_producer, event); + + switch (event) { + case VC_EVENT_EOS: + // The write side of this connection is still alive + // so half-close the read + if (p->self_consumer->alive) { + p->vc->do_io_shutdown(IO_SHUTDOWN_READ); + tunnel.local_finish_all(p); + break; + } + // FALL THROUGH - both sides of the tunnel are dea + case VC_EVENT_ERROR: + case VC_EVENT_INACTIVITY_TIMEOUT: + case VC_EVENT_ACTIVE_TIMEOUT: + // The other side of the connection is either already dead + // or rendered inoperative by the error on the connection + // Note: use tunnel close vc so the tunnel knows we are + // nuking the of the connection as well + tunnel.close_vc(p); + tunnel.local_finish_all(p); + + // Because we've closed the net vc this error came in, it's write + // direction is now dead as well. If that side still being fed data, + // we need to kill that pipe as well + if (p->self_consumer->producer->alive) { + p->self_consumer->producer->alive = false; + if (p->self_consumer->producer->self_consumer->alive) { + p->self_consumer->producer->vc->do_io_shutdown(IO_SHUTDOWN_READ); + } else { + tunnel.close_vc(p->self_consumer->producer); + } + } + break; + case VC_EVENT_READ_COMPLETE: + case HTTP_TUNNEL_EVENT_PRECOMPLETE: + // We should never get these event since we don't know + // how long the stream is + default: + ink_release_assert(0); + } + + // Update stats + switch (p->vc_type) { + case HT_HTTP_SERVER: + server_response_body_bytes += p->bytes_read; + break; + case HT_HTTP_CLIENT: + client_request_body_bytes += p->bytes_read; + break; + default: + // Covered here: + // HT_CACHE_READ, HT_CACHE_WRITE, + // HT_TRANSFORM, HT_STATIC. + break; + } + + return 0; +} + +int +L4RSM::tunnel_handler_ssl_consumer(int event, HttpTunnelConsumer *c) +{ + STATE_ENTER(&L4RSM::tunnel_handler_ssl_consumer, event); + + switch (event) { + case VC_EVENT_ERROR: + case VC_EVENT_EOS: + case VC_EVENT_INACTIVITY_TIMEOUT: + case VC_EVENT_ACTIVE_TIMEOUT: + // we need to mark the producer dead + // otherwise it can stay alive forever. + if (c->producer->alive) { + c->producer->alive = false; + if (c->producer->self_consumer->alive) { + c->producer->vc->do_io_shutdown(IO_SHUTDOWN_READ); + } else { + tunnel.close_vc(c->producer); + } + } + // Since we are changing the state of the self_producer + // we must have the tunnel shutdown the vc + tunnel.close_vc(c); + tunnel.local_finish_all(c->self_producer); + break; + + case VC_EVENT_WRITE_COMPLETE: + // If we get this event, it means that the producer + // has finished and we wrote the remaining data + // to the consumer + // + // If the read side of this connection has not yet + // closed, do a write half-close and then wait for + // read side to close so that we don't cut off + // pipelined responses with TCP resets + // + // ink_assert(c->producer->alive == false); + c->write_success = true; + if (c->self_producer->alive == true) { + c->vc->do_io_shutdown(IO_SHUTDOWN_WRITE); + } else { + c->vc->do_io_close(); + } + break; + + default: + ink_release_assert(0); + } + + // Update stats + switch (c->vc_type) { + case HT_HTTP_SERVER: + server_request_body_bytes += c->bytes_written; + break; + case HT_HTTP_CLIENT: + client_response_body_bytes += c->bytes_written; + break; + default: + // Handled here: + // HT_CACHE_READ, HT_CACHE_WRITE, HT_TRANSFORM, + // HT_STATIC + break; + } + + return 0; +} + +int +L4RSM::tunnel_handler_transform_write(int event, HttpTunnelConsumer *c) +{ + STATE_ENTER(&L4RSM::tunnel_handler_transform_write, event); + + HttpTransformInfo *i; + + // Figure out if this the request or response transform + // : use post_transform_info.entry because post_transform_info.vc + // is not set to NULL after the post transform is done. + if (post_transform_info.entry) { + i = &post_transform_info; + ink_assert(c->vc == i->entry->vc); + } else { + i = &transform_info; + ink_assert(c->vc == i->vc); + ink_assert(c->vc == i->entry->vc); + } + + switch (event) { + case VC_EVENT_ERROR: + // Transform error + tunnel.chain_abort_all(c->producer); + c->handler_state = HTTP_SM_TRANSFORM_FAIL; + c->vc->do_io_close(EHTTP_ERROR); + break; + case VC_EVENT_EOS: + // It possible the transform quit + // before the producer finished. If this is true + // we need shut down the producer if it doesn't + // have other consumers to serve or else it will + // fill up buffer and get hung + if (c->producer->alive && c->producer->num_consumers == 1) { + // Send a tunnel detach event to the producer + // to shut it down but indicates it should not abort + // downstream (on the other side of the transform) + // cache writes + tunnel.producer_handler(HTTP_TUNNEL_EVENT_CONSUMER_DETACH, c->producer); + } + // FALLTHROUGH + case VC_EVENT_WRITE_COMPLETE: + // write to transform complete - shutdown the write side + c->write_success = true; + c->vc->do_io_shutdown(IO_SHUTDOWN_WRITE); + + // If the read side has not started up yet, then the + // this transform_vc is no longer owned by the tunnel + if (c->self_producer == nullptr) { + i->entry->in_tunnel = false; + } else if (c->self_producer->alive == false) { + // The read side of the Transform + // has already completed (possible when the + // transform intentionally truncates the response). + // So close it + c->vc->do_io_close(); + } + break; + default: + ink_release_assert(0); + } + + // attribute the size written to the transform from various sources + // NOTE: the range transform is excluded from this accounting and + // is instead handled in L4RSM::tunnel_handler_ua + // + // the reasoning is that the range transform is internal functionality + // in support of HTTP 1.1 compliance, therefore part of "normal" operation + // all other transforms are plugin driven and the difference between + // source data and final data should represent the transformation delta + // + if (t_state.range_setup == HttpTransact::RANGE_NONE) { + switch (t_state.pre_transform_source) { + case HttpTransact::SOURCE_HTTP_ORIGIN_SERVER: + server_response_body_bytes = client_response_body_bytes; + break; + case HttpTransact::SOURCE_CACHE: + cache_response_body_bytes = client_response_body_bytes; + break; + default: + break; + } + } + + return 0; +} + +int +L4RSM::tunnel_handler_transform_read(int event, HttpTunnelProducer *p) +{ + STATE_ENTER(&L4RSM::tunnel_handler_transform_read, event); + + ink_assert(p->vc == transform_info.vc || p->vc == post_transform_info.vc); + + switch (event) { + case VC_EVENT_ERROR: + // Transform error + tunnel.chain_abort_all(p->self_consumer->producer); + break; + case VC_EVENT_EOS: + // If we did not get enough data from the transform abort the + // cache write otherwise fallthrough to the transform + // completing successfully + if (t_state.hdr_info.transform_response_cl != HTTP_UNDEFINED_CL && + p->read_vio->nbytes < t_state.hdr_info.transform_response_cl) { + tunnel.abort_cache_write_finish_others(p); + break; + } + // FALL-THROUGH + case VC_EVENT_READ_COMPLETE: + case HTTP_TUNNEL_EVENT_PRECOMPLETE: + // Transform complete + p->read_success = true; + tunnel.local_finish_all(p); + break; + default: + ink_release_assert(0); + } + + // it's possible that the write side of the + // transform hasn't detached yet. If it is still alive, + // don't close the transform vc + if (p->self_consumer->alive == false) { + p->vc->do_io_close(); + } + p->handler_state = HTTP_SM_TRANSFORM_CLOSED; + + return 0; +} + +int +L4RSM::tunnel_handler_plugin_agent(int event, HttpTunnelConsumer *c) +{ + STATE_ENTER(&L4RSM::tunnel_handler_plugin_client, event); + + switch (event) { + case VC_EVENT_ERROR: + c->vc->do_io_close(EHTTP_ERROR); // close up + // Signal producer if we're the last consumer. + if (c->producer->alive && c->producer->num_consumers == 1) { + tunnel.producer_handler(HTTP_TUNNEL_EVENT_CONSUMER_DETACH, c->producer); + } + break; + case VC_EVENT_EOS: + if (c->producer->alive && c->producer->num_consumers == 1) { + tunnel.producer_handler(HTTP_TUNNEL_EVENT_CONSUMER_DETACH, c->producer); + } + // FALLTHROUGH + case VC_EVENT_WRITE_COMPLETE: + c->write_success = true; + c->vc->do_io_close(); + break; + default: + ink_release_assert(0); + } + + return 0; +} + +int +L4RSM::state_srv_lookup(int event, void *data) +{ + STATE_ENTER(&L4RSM::state_srv_lookup, event); + + ink_assert(t_state.req_flavor == HttpTransact::REQ_FLAVOR_SCHEDULED_UPDATE || + t_state.req_flavor == HttpTransact::REQ_FLAVOR_REVPROXY || ua_entry->vc != nullptr); + + switch (event) { + case EVENT_SRV_LOOKUP: + pending_action = nullptr; + process_srv_info((HostDBInfo *)data); + break; + case EVENT_SRV_IP_REMOVED: + ink_assert(!"Unexpected SRV event from HostDB. What up, Eric?"); + break; + default: + ink_assert(!"Unexpected event"); + } + + return 0; +} + +int +L4RSM::state_remap_request(int event, void * /* data ATS_UNUSED */) +{ + STATE_ENTER(&L4RSM::state_remap_request, event); + + switch (event) { + case EVENT_REMAP_ERROR: { + ink_assert(!"this doesn't happen"); + pending_action = nullptr; + Error("error remapping request [see previous errors]"); + call_transact_and_set_next_state(HttpTransact::HandleRequest); // HandleRequest skips EndRemapRequest + break; + } + + case EVENT_REMAP_COMPLETE: { + pending_action = nullptr; + SMDebug("url_rewrite", "completed processor-based remapping request for [%" PRId64 "]", sm_id); + t_state.url_remap_success = remapProcessor.finish_remap(&t_state, m_remap); + call_transact_and_set_next_state(nullptr); + break; + } + + default: + ink_assert("Unexpected event inside state_remap_request"); + break; + } + + return 0; +} + +void +L4RSM::do_remap_request(bool run_inline) +{ + SMDebug("http_seq", "[L4RSM::do_remap_request] Remapping request"); + SMDebug("url_rewrite", "Starting a possible remapping for request [%" PRId64 "]", sm_id); + bool ret = remapProcessor.setup_for_remap(&t_state, m_remap); + + // Preserve effective url before remap + t_state.unmapped_url.create(t_state.hdr_info.client_request.url_get()->m_heap); + t_state.unmapped_url.copy(t_state.hdr_info.client_request.url_get()); + // Depending on a variety of factors the HOST field may or may not have been promoted to the + // client request URL. The unmapped URL should always have that promotion done. If the HOST field + // is not already there, promote it only in the unmapped_url. This avoids breaking any logic that + // depends on the lack of promotion in the client request URL. + if (!t_state.unmapped_url.m_url_impl->m_ptr_host) { + MIMEField *host_field = t_state.hdr_info.client_request.field_find(MIME_FIELD_HOST, MIME_LEN_HOST); + if (host_field) { + int host_len; + const char *host_name = host_field->value_get(&host_len); + if (host_name && host_len) { + t_state.unmapped_url.host_set(host_name, host_len); + } + } + } + + if (!ret) { + SMDebug("url_rewrite", "Could not find a valid remapping entry for this request [%" PRId64 "]", sm_id); + if (!run_inline) { + handleEvent(EVENT_REMAP_COMPLETE, nullptr); + } + return; + } + + SMDebug("url_rewrite", "Found a remap map entry for [%" PRId64 "], attempting to remap request and call any plugins", sm_id); + Action *remap_action_handle = remapProcessor.perform_remap(this, &t_state); + + if (remap_action_handle != ACTION_RESULT_DONE) { + SMDebug("url_rewrite", "Still more remapping needed for [%" PRId64 "]", sm_id); + ink_assert(!pending_action); + pending_action = remap_action_handle; + } + + return; +} + +void +L4RSM::do_hostdb_lookup() +{ + /* + ////////////////////////////////////////// + // if a connection to the origin server // + // is currently opened --- close it. // + ////////////////////////////////////////// + if (m_origin_server_vc != 0) { + origin_server_close(CLOSE_CONNECTION); + if (m_response_body_tunnel_buffer_.buf() != 0) + m_response_body_tunnel_buffer_.reset(); + } + */ + + ink_assert(t_state.dns_info.lookup_name != nullptr); + ink_assert(pending_action == nullptr); + + milestones[TS_MILESTONE_DNS_LOOKUP_BEGIN] = Thread::get_hrtime(); + + if (t_state.txn_conf->srv_enabled) { + char d[MAXDNAME]; + + // Look at the next_hop_scheme to determine what scheme to put in the SRV lookup + unsigned int scheme_len = sprintf(d, "_%s._tcp.", hdrtoken_index_to_wks(t_state.next_hop_scheme)); + ink_strlcpy(d + scheme_len, t_state.server_info.name, sizeof(d) - scheme_len); + + SMDebug("dns_srv", "Beginning lookup of SRV records for origin %s", d); + + HostDBProcessor::Options opt; + if (t_state.api_txn_dns_timeout_value != -1) { + opt.timeout = t_state.api_txn_dns_timeout_value; + } + Action *srv_lookup_action_handle = + hostDBProcessor.getSRVbyname_imm(this, (process_srv_info_pfn)&L4RSM::process_srv_info, d, 0, opt); + + if (srv_lookup_action_handle != ACTION_RESULT_DONE) { + ink_assert(!pending_action); + pending_action = srv_lookup_action_handle; + } else { + char *host_name = t_state.dns_info.srv_lookup_success ? t_state.dns_info.srv_hostname : t_state.dns_info.lookup_name; + opt.port = t_state.dns_info.srv_lookup_success ? + t_state.dns_info.srv_port : + t_state.server_info.dst_addr.isValid() ? t_state.server_info.dst_addr.host_order_port() : + t_state.hdr_info.client_request.port_get(); + opt.flags = (t_state.cache_info.directives.does_client_permit_dns_storing) ? HostDBProcessor::HOSTDB_DO_NOT_FORCE_DNS : + HostDBProcessor::HOSTDB_FORCE_DNS_RELOAD; + opt.timeout = (t_state.api_txn_dns_timeout_value != -1) ? t_state.api_txn_dns_timeout_value : 0; + opt.host_res_style = ua_txn->get_host_res_style(); + + Action *dns_lookup_action_handle = + hostDBProcessor.getbyname_imm(this, (process_hostdb_info_pfn)&L4RSM::process_hostdb_info, host_name, 0, opt); + if (dns_lookup_action_handle != ACTION_RESULT_DONE) { + ink_assert(!pending_action); + pending_action = dns_lookup_action_handle; + } else { + call_transact_and_set_next_state(nullptr); + } + } + return; + } else { /* we aren't using SRV stuff... */ + SMDebug("http_seq", "[L4RSM::do_hostdb_lookup] Doing DNS Lookup"); + + // If there is not a current server, we must be looking up the origin + // server at the beginning of the transaction + int server_port = t_state.current.server ? + t_state.current.server->dst_addr.host_order_port() : + t_state.server_info.dst_addr.isValid() ? t_state.server_info.dst_addr.host_order_port() : + t_state.hdr_info.client_request.port_get(); + + if (t_state.api_txn_dns_timeout_value != -1) { + SMDebug("http_timeout", "beginning DNS lookup. allowing %d mseconds for DNS lookup", t_state.api_txn_dns_timeout_value); + } + + HostDBProcessor::Options opt; + opt.port = server_port; + opt.flags = (t_state.cache_info.directives.does_client_permit_dns_storing) ? HostDBProcessor::HOSTDB_DO_NOT_FORCE_DNS : + HostDBProcessor::HOSTDB_FORCE_DNS_RELOAD; + opt.timeout = (t_state.api_txn_dns_timeout_value != -1) ? t_state.api_txn_dns_timeout_value : 0; + opt.host_res_style = ua_txn->get_host_res_style(); + + Action *dns_lookup_action_handle = hostDBProcessor.getbyname_imm(this, (process_hostdb_info_pfn)&L4RSM::process_hostdb_info, + t_state.dns_info.lookup_name, 0, opt); + + if (dns_lookup_action_handle != ACTION_RESULT_DONE) { + ink_assert(!pending_action); + pending_action = dns_lookup_action_handle; + } else { + call_transact_and_set_next_state(nullptr); + } + return; + } + ink_assert(!"not reached"); + return; +} + +void +L4RSM::do_hostdb_reverse_lookup() +{ + ink_assert(t_state.dns_info.lookup_name != nullptr); + ink_assert(pending_action == nullptr); + + SMDebug("http_seq", "[L4RSM::do_hostdb_reverse_lookup] Doing reverse DNS Lookup"); + + IpEndpoint addr; + ats_ip_pton(t_state.dns_info.lookup_name, &addr.sa); + Action *dns_lookup_action_handle = hostDBProcessor.getbyaddr_re(this, &addr.sa); + + if (dns_lookup_action_handle != ACTION_RESULT_DONE) { + ink_assert(!pending_action); + pending_action = dns_lookup_action_handle; + } + return; +} + +void +L4RSM::do_hostdb_update_if_necessary() +{ + int issue_update = 0; + + if (t_state.current.server == nullptr || plugin_tunnel_type != HTTP_NO_PLUGIN_TUNNEL) { + // No server, so update is not necessary + return; + } + // If we failed back over to the origin server, we don't have our + // hostdb information anymore which means we shouldn't update the hostdb + if (!ats_ip_addr_eq(&t_state.current.server->dst_addr.sa, t_state.host_db_info.ip())) { + SMDebug("http", "[%" PRId64 "] skipping hostdb update due to server failover", sm_id); + return; + } + + if (t_state.updated_server_version != HostDBApplicationInfo::HTTP_VERSION_UNDEFINED) { + // we may have incorrectly assumed that the hostdb had the wrong version of + // http for the server because our first few connect attempts to the server + // failed, causing us to downgrade our requests to a lower version and changing + // our information about the server version. + // + // This test therefore just issues the update only if the hostdb version is + // in fact different from the version we want the value to be updated to. + if (t_state.host_db_info.app.http_data.http_version != t_state.updated_server_version) { + t_state.host_db_info.app.http_data.http_version = t_state.updated_server_version; + issue_update |= 1; + } + + t_state.updated_server_version = HostDBApplicationInfo::HTTP_VERSION_UNDEFINED; + } + // Check to see if we need to report or clear a connection failure + if (t_state.current.server->had_connect_fail()) { + issue_update |= 1; + mark_host_failure(&t_state.host_db_info, t_state.client_request_time); + } else { + if (t_state.host_db_info.app.http_data.last_failure != 0) { + t_state.host_db_info.app.http_data.last_failure = 0; + issue_update |= 1; + char addrbuf[INET6_ADDRPORTSTRLEN]; + SMDebug("http", "[%" PRId64 "] hostdb update marking IP: %s as up", sm_id, + ats_ip_nptop(&t_state.current.server->dst_addr.sa, addrbuf, sizeof(addrbuf))); + } + + if (t_state.dns_info.srv_lookup_success && t_state.dns_info.srv_app.http_data.last_failure != 0) { + t_state.dns_info.srv_app.http_data.last_failure = 0; + hostDBProcessor.setby_srv(t_state.dns_info.lookup_name, 0, t_state.dns_info.srv_hostname, &t_state.dns_info.srv_app); + SMDebug("http", "[%" PRId64 "] hostdb update marking SRV: %s as up", sm_id, t_state.dns_info.srv_hostname); + } + } + + if (issue_update) { + hostDBProcessor.setby(t_state.current.server->name, strlen(t_state.current.server->name), &t_state.current.server->dst_addr.sa, + &t_state.host_db_info.app); + } + + char addrbuf[INET6_ADDRPORTSTRLEN]; + SMDebug("http", "server info = %s", ats_ip_nptop(&t_state.current.server->dst_addr.sa, addrbuf, sizeof(addrbuf))); + return; +} + +/* + * range entry valid [a,b] (a >= 0 and b >= 0 and a <= b) + * HttpTransact::RANGE_NONE if the content length of cached copy is zero or + * no range entry + * HttpTransact::RANGE_NOT_SATISFIABLE iff all range entries are valid but + * none overlap the current extent of the cached copy + * HttpTransact::RANGE_NOT_HANDLED if out-of-order Range entries or + * the cached copy`s content_length is INT64_MAX (e.g. read_from_writer and trunked) + * HttpTransact::RANGE_REQUESTED if all sub range entries are valid and + * in order (remove the entries that not overlap the extent of cache copy) + */ +void +L4RSM::parse_range_and_compare(MIMEField *field, int64_t content_length) +{ + int prev_good_range = -1; + const char *value; + int value_len; + int n_values; + int nr = 0; // number of valid ranges, also index to range array. + int not_satisfy = 0; + HdrCsvIter csv; + const char *s, *e, *tmp; + RangeRecord *ranges = nullptr; + int64_t start, end; + + ink_assert(field != nullptr && t_state.range_setup == HttpTransact::RANGE_NONE && t_state.ranges == nullptr); + + if (content_length <= 0) { + return; + } + + // ToDo: Can this really happen? + if (content_length == INT64_MAX) { + t_state.range_setup = HttpTransact::RANGE_NOT_HANDLED; + return; + } + + if (parse_range_done) { + Debug("http_range", "parse_range already done, t_state.range_setup %d", t_state.range_setup); + return; + } + parse_range_done = true; + + n_values = 0; + value = csv.get_first(field, &value_len); + while (value) { + ++n_values; + value = csv.get_next(&value_len); + } + + value = csv.get_first(field, &value_len); + if (n_values <= 0 || ptr_len_ncmp(value, value_len, "bytes=", 6)) { + return; + } + + ranges = new RangeRecord[n_values]; + value += 6; // skip leading 'bytes=' + value_len -= 6; + + // assume range_in_cache + t_state.range_in_cache = true; + + for (; value; value = csv.get_next(&value_len)) { + if (!(tmp = (const char *)memchr(value, '-', value_len))) { + t_state.range_setup = HttpTransact::RANGE_NONE; + goto Lfaild; + } + + // process start value + s = value; + e = tmp; + // skip leading white spaces + for (; s < e && ParseRules::is_ws(*s); ++s) { + ; + } + + if (s >= e) { + start = -1; + } else { + for (start = 0; s < e && *s >= '0' && *s <= '9'; ++s) { + start = start * 10 + (*s - '0'); + } + // skip last white spaces + for (; s < e && ParseRules::is_ws(*s); ++s) { + ; + } + + if (s < e || start < 0) { + t_state.range_setup = HttpTransact::RANGE_NONE; + goto Lfaild; + } + } + + // process end value + s = tmp + 1; + e = value + value_len; + // skip leading white spaces + for (; s < e && ParseRules::is_ws(*s); ++s) { + ; + } + + if (s >= e) { + if (start < 0) { + t_state.range_setup = HttpTransact::RANGE_NONE; + goto Lfaild; + } else if (start >= content_length) { + not_satisfy++; + continue; + } + end = content_length - 1; + } else { + for (end = 0; s < e && *s >= '0' && *s <= '9'; ++s) { + end = end * 10 + (*s - '0'); + } + // skip last white spaces + for (; s < e && ParseRules::is_ws(*s); ++s) { + ; + } + + if (s < e || end < 0) { + t_state.range_setup = HttpTransact::RANGE_NONE; + goto Lfaild; + } + + if (start < 0) { + if (end >= content_length) { + end = content_length; + } + start = content_length - end; + end = content_length - 1; + } else if (start >= content_length && start <= end) { + not_satisfy++; + continue; + } + + if (end >= content_length) { + end = content_length - 1; + } + } + + if (start > end) { + t_state.range_setup = HttpTransact::RANGE_NONE; + goto Lfaild; + } + + if (prev_good_range >= 0 && start <= ranges[prev_good_range]._end) { + t_state.range_setup = HttpTransact::RANGE_NOT_HANDLED; + goto Lfaild; + } + + ink_assert(start >= 0 && end >= 0 && start < content_length && end < content_length); + + prev_good_range = nr; + ranges[nr]._start = start; + ranges[nr]._end = end; + ++nr; + + if (!cache_sm.cache_read_vc->is_pread_capable() && cache_config_read_while_writer == 2) { + // write in progress, check if request range not in cache yet + HTTPInfo::FragOffset *frag_offset_tbl = t_state.cache_info.object_read->get_frag_table(); + int frag_offset_cnt = t_state.cache_info.object_read->get_frag_offset_count(); + + if (!frag_offset_tbl || !frag_offset_cnt || (frag_offset_tbl[frag_offset_cnt - 1] < (uint64_t)end)) { + Debug("http_range", "request range in cache, end %" PRId64 ", frg_offset_cnt %d" PRId64, end, frag_offset_cnt); + t_state.range_in_cache = false; + } + } + } + + if (nr > 0) { + t_state.range_setup = HttpTransact::RANGE_REQUESTED; + t_state.ranges = ranges; + t_state.num_range_fields = nr; + return; + } + + if (not_satisfy) { + t_state.range_setup = HttpTransact::RANGE_NOT_SATISFIABLE; + } + +Lfaild: + t_state.range_in_cache = false; + t_state.num_range_fields = -1; + delete[] ranges; + return; +} + +void +L4RSM::calculate_output_cl(int64_t num_chars_for_ct, int64_t num_chars_for_cl) +{ + int i; + + if (t_state.range_setup != HttpTransact::RANGE_REQUESTED && t_state.range_setup != HttpTransact::RANGE_NOT_TRANSFORM_REQUESTED) { + return; + } + + ink_assert(t_state.ranges); + + if (t_state.num_range_fields == 1) { + t_state.range_output_cl = t_state.ranges[0]._end - t_state.ranges[0]._start + 1; + } else { + for (i = 0; i < t_state.num_range_fields; i++) { + if (t_state.ranges[i]._start >= 0) { + t_state.range_output_cl += boundary_size; + t_state.range_output_cl += sub_header_size + num_chars_for_ct; + t_state.range_output_cl += + num_chars_for_int(t_state.ranges[i]._start) + num_chars_for_int(t_state.ranges[i]._end) + num_chars_for_cl + 2; + t_state.range_output_cl += t_state.ranges[i]._end - t_state.ranges[i]._start + 1; + t_state.range_output_cl += 2; + } + } + + t_state.range_output_cl += boundary_size + 2; + } + + Debug("http_range", "Pre-calculated Content-Length for Range response is %" PRId64, t_state.range_output_cl); +} + +void +L4RSM::do_range_parse(MIMEField *range_field) +{ + int num_chars_for_ct = 0; + t_state.cache_info.object_read->response_get()->value_get(MIME_FIELD_CONTENT_TYPE, MIME_LEN_CONTENT_TYPE, &num_chars_for_ct); + + int64_t content_length = t_state.cache_info.object_read->object_size_get(); + int64_t num_chars_for_cl = num_chars_for_int(content_length); + + parse_range_and_compare(range_field, content_length); + calculate_output_cl(num_chars_for_ct, num_chars_for_cl); +} + +// this function looks for any Range: headers, parses them and either +// sets up a transform processor to handle the request OR defers to the +// HttpTunnel +void +L4RSM::do_range_setup_if_necessary() +{ + MIMEField *field; + INKVConnInternal *range_trans; + int field_content_type_len = -1; + const char *content_type; + + ink_assert(t_state.cache_info.object_read != nullptr); + + field = t_state.hdr_info.client_request.field_find(MIME_FIELD_RANGE, MIME_LEN_RANGE); + ink_assert(field != nullptr); + + t_state.range_setup = HttpTransact::RANGE_NONE; + + if (t_state.method == HTTP_WKSIDX_GET && t_state.hdr_info.client_request.version_get() == HTTPVersion(1, 1)) { + do_range_parse(field); + + if (t_state.range_setup == HttpTransact::RANGE_REQUESTED) { + bool do_transform = false; + + if (!t_state.range_in_cache) { + Debug("http_range", "range can't be satisfied from cache, force origin request"); + t_state.cache_lookup_result = HttpTransact::CACHE_LOOKUP_MISS; + return; + } + + if (t_state.num_range_fields > 1) { + if (0 == t_state.txn_conf->allow_multi_range) { + t_state.range_setup = HttpTransact::RANGE_NONE; // No Range required (not allowed) + t_state.hdr_info.client_request.field_delete(MIME_FIELD_RANGE, MIME_LEN_RANGE); // ... and nuke the Range header too + t_state.num_range_fields = 0; + } else if (1 == t_state.txn_conf->allow_multi_range) { + do_transform = true; + } else { + t_state.num_range_fields = 0; + t_state.range_setup = HttpTransact::RANGE_NOT_SATISFIABLE; + } + } else { + if (cache_sm.cache_read_vc->is_pread_capable()) { + // If only one range entry and pread is capable, no need transform range + t_state.range_setup = HttpTransact::RANGE_NOT_TRANSFORM_REQUESTED; + } else { + do_transform = true; + } + } + + // We have to do the transform on (allowed) multi-range request, *or* if the VC is not pread capable + if (do_transform) { + if (api_hooks.get(TS_HTTP_RESPONSE_TRANSFORM_HOOK) == nullptr) { + Debug("http_trans", "Unable to accelerate range request, fallback to transform"); + content_type = t_state.cache_info.object_read->response_get()->value_get(MIME_FIELD_CONTENT_TYPE, MIME_LEN_CONTENT_TYPE, + &field_content_type_len); + // create a Range: transform processor for requests of type Range: bytes=1-2,4-5,10-100 (eg. multiple ranges) + range_trans = transformProcessor.range_transform( + mutex.get(), t_state.ranges, t_state.num_range_fields, &t_state.hdr_info.transform_response, content_type, + field_content_type_len, t_state.cache_info.object_read->object_size_get()); + api_hooks.append(TS_HTTP_RESPONSE_TRANSFORM_HOOK, range_trans); + } else { + // ToDo: Do we do something here? The theory is that multiple transforms do not behave well with + // the range transform needed here. + } + } + } + } +} + +void +L4RSM::do_cache_lookup_and_read() +{ + // TODO decide whether to uncomment after finish testing redirect + // ink_assert(server_session == NULL); + ink_assert(pending_action == nullptr); + + HTTP_INCREMENT_DYN_STAT(http_cache_lookups_stat); + + milestones[TS_MILESTONE_CACHE_OPEN_READ_BEGIN] = Thread::get_hrtime(); + t_state.cache_lookup_result = HttpTransact::CACHE_LOOKUP_NONE; + t_state.cache_info.lookup_count++; + // YTS Team, yamsat Plugin + // Changed the lookup_url to c_url which enables even + // the new redirect url to perform a CACHE_LOOKUP + URL *c_url; + if (t_state.redirect_info.redirect_in_process && !t_state.txn_conf->redirect_use_orig_cache_key) { + c_url = t_state.hdr_info.client_request.url_get(); + } else { + c_url = t_state.cache_info.lookup_url; + } + + SMDebug("http_seq", "[L4RSM::do_cache_lookup_and_read] [%" PRId64 "] Issuing cache lookup for URL %s", sm_id, + c_url->string_get(&t_state.arena)); + + HttpCacheKey key; + Cache::generate_key(&key, c_url, t_state.txn_conf->cache_generation_number); + + Action *cache_action_handle = + cache_sm.open_read(&key, c_url, &t_state.hdr_info.client_request, t_state.txn_conf, + (time_t)((t_state.cache_control.pin_in_cache_for < 0) ? 0 : t_state.cache_control.pin_in_cache_for)); + // + // pin_in_cache value is an open_write parameter. + // It is passed in open_read to allow the cluster to + // optimize the typical open_read/open_read failed/open_write + // sequence. + // + if (cache_action_handle != ACTION_RESULT_DONE) { + ink_assert(!pending_action); + pending_action = cache_action_handle; + } + REMEMBER((long)pending_action, reentrancy_count); + + return; +} + +void +L4RSM::do_cache_delete_all_alts(Continuation *cont) +{ + // Do not delete a non-existant object. + ink_assert(t_state.cache_info.object_read); + + SMDebug("http_seq", "[L4RSM::do_cache_delete_all_alts] Issuing cache delete for %s", + t_state.cache_info.lookup_url->string_get_ref()); + + Action *cache_action_handle = nullptr; + + HttpCacheKey key; + Cache::generate_key(&key, t_state.cache_info.lookup_url, t_state.txn_conf->cache_generation_number); + cache_action_handle = cacheProcessor.remove(cont, &key); + if (cont != nullptr) { + if (cache_action_handle != ACTION_RESULT_DONE) { + ink_assert(!pending_action); + pending_action = cache_action_handle; + } + } + + return; +} + +inline void +L4RSM::do_cache_prepare_write() +{ + milestones[TS_MILESTONE_CACHE_OPEN_WRITE_BEGIN] = Thread::get_hrtime(); + do_cache_prepare_action(&cache_sm, t_state.cache_info.object_read, true); +} + +inline void +L4RSM::do_cache_prepare_write_transform() +{ + if (cache_sm.cache_write_vc != nullptr || tunnel.has_cache_writer()) { + do_cache_prepare_action(&transform_cache_sm, nullptr, false, true); + } else { + do_cache_prepare_action(&transform_cache_sm, nullptr, false); + } +} + +void +L4RSM::do_cache_prepare_update() +{ + if (t_state.cache_info.object_read != nullptr && t_state.cache_info.object_read->valid() && + t_state.cache_info.object_store.valid() && t_state.cache_info.object_store.response_get() != nullptr && + t_state.cache_info.object_store.response_get()->valid() && + t_state.hdr_info.client_request.method_get_wksidx() == HTTP_WKSIDX_GET) { + t_state.cache_info.object_store.request_set(t_state.cache_info.object_read->request_get()); + // t_state.cache_info.object_read = NULL; + // cache_sm.close_read(); + + t_state.transact_return_point = HttpTransact::HandleUpdateCachedObject; + ink_assert(cache_sm.cache_write_vc == nullptr); + L4R_SM_SET_DEFAULT_HANDLER(&L4RSM::state_cache_open_write); + // don't retry read for update + do_cache_prepare_action(&cache_sm, t_state.cache_info.object_read, false); + } else { + t_state.api_modifiable_cached_resp = false; + call_transact_and_set_next_state(HttpTransact::HandleApiErrorJump); + } +} + +void +L4RSM::do_cache_prepare_action(HttpCacheSM *c_sm, CacheHTTPInfo *object_read_info, bool retry, bool allow_multiple) +{ + URL *o_url, *c_url, *s_url; + bool restore_client_request = false; + + ink_assert(!pending_action); + + if (t_state.redirect_info.redirect_in_process) { + o_url = &(t_state.redirect_info.original_url); + ink_assert(o_url->valid()); + restore_client_request = true; + s_url = o_url; + } else { + o_url = &(t_state.cache_info.original_url); + if (o_url->valid()) { + s_url = o_url; + } else { + s_url = t_state.cache_info.lookup_url; + } + } + + // modify client request to make it have the url we are going to + // store into the cache + if (restore_client_request) { + c_url = t_state.hdr_info.client_request.url_get(); + s_url->copy(c_url); + } + + ink_assert(s_url != nullptr && s_url->valid()); + SMDebug("http_cache_write", "[%" PRId64 "] writing to cache with URL %s", sm_id, s_url->string_get(&t_state.arena)); + + HttpCacheKey key; + Cache::generate_key(&key, s_url, t_state.txn_conf->cache_generation_number); + + Action *cache_action_handle = c_sm->open_write( + &key, s_url, &t_state.hdr_info.client_request, object_read_info, + (time_t)((t_state.cache_control.pin_in_cache_for < 0) ? 0 : t_state.cache_control.pin_in_cache_for), retry, allow_multiple); + + if (cache_action_handle != ACTION_RESULT_DONE) { + ink_assert(!pending_action); + pending_action = cache_action_handle; + } +} + +void +L4RSM::send_origin_throttled_response() +{ + t_state.current.attempts = t_state.txn_conf->connect_attempts_max_retries; + t_state.current.state = HttpTransact::CONNECTION_ERROR; + call_transact_and_set_next_state(HttpTransact::HandleResponse); +} + +////////////////////////////////////////////////////////////////////////// +// +// L4RSM::do_http_server_open() +// +////////////////////////////////////////////////////////////////////////// +void +L4RSM::do_http_server_open(bool raw) +{ + int ip_family = t_state.current.server->dst_addr.sa.sa_family; + auto fam_name = ats_ip_family_name(ip_family); + SMDebug("http_track", "entered inside do_http_server_open ][%.*s]", static_cast(fam_name.size()), fam_name.data()); + + // Make sure we are on the "right" thread + if (ua_txn) { + if ((pending_action = ua_txn->adjust_thread(this, EVENT_INTERVAL, nullptr))) { + return; // Go away if we reschedule + } + } + pending_action = nullptr; + ink_assert(server_entry == nullptr); + + // ua_entry can be null if a scheduled update is also a reverse proxy + // request. Added REVPROXY to the assert below, and then changed checks + // to be based on ua_txn != NULL instead of req_flavor value. + ink_assert(ua_entry != nullptr || t_state.req_flavor == HttpTransact::REQ_FLAVOR_SCHEDULED_UPDATE || + t_state.req_flavor == HttpTransact::REQ_FLAVOR_REVPROXY); + + ink_assert(pending_action == nullptr); + + if (false == t_state.api_server_addr_set) { + ink_assert(t_state.current.server->dst_addr.host_order_port() > 0); + } else { + ink_assert(t_state.current.server->dst_addr.port() != 0); // verify the plugin set it to something. + } + + char addrbuf[INET6_ADDRPORTSTRLEN]; + SMDebug("http", "[%" PRId64 "] open connection to %s: %s", sm_id, t_state.current.server->name, + ats_ip_nptop(&t_state.current.server->dst_addr.sa, addrbuf, sizeof(addrbuf))); + + if (plugin_tunnel) { + PluginVCCore *t = plugin_tunnel; + plugin_tunnel = nullptr; + Action *pvc_action_handle = t->connect_re(this); + + // This connect call is always reentrant + ink_release_assert(pvc_action_handle == ACTION_RESULT_DONE); + return; + } + + SMDebug("http_seq", "[L4RSM::do_http_server_open] Sending request to server"); + + milestones[TS_MILESTONE_SERVER_CONNECT] = Thread::get_hrtime(); + if (milestones[TS_MILESTONE_SERVER_FIRST_CONNECT] == 0) { + milestones[TS_MILESTONE_SERVER_FIRST_CONNECT] = milestones[TS_MILESTONE_SERVER_CONNECT]; + } + + // Check for remap rule. If so, only apply ip_allow filter if it is activated (ip_allow_check_enabled_p set). + // Otherwise, if no remap rule is defined, apply the ip_allow filter. + if (!t_state.url_remap_success || t_state.url_map.getMapping()->ip_allow_check_enabled_p) { + // Method allowed on dest IP address check + sockaddr *server_ip = &t_state.current.server->dst_addr.sa; + IpAllow::scoped_config ip_allow; + + if (ip_allow) { + const AclRecord *acl_record = ip_allow->match(server_ip, IpAllow::DEST_ADDR); + bool deny_request = false; // default is fail open. + int method = t_state.hdr_info.server_request.method_get_wksidx(); + + if (acl_record) { + // If empty, nothing is allowed, deny. Conversely if all methods are allowed it's OK, do not deny. + // Otherwise the method has to be checked specifically. + if (acl_record->isEmpty()) { + deny_request = true; + } else if (acl_record->_method_mask != AclRecord::ALL_METHOD_MASK) { + if (method != -1) { + deny_request = !acl_record->isMethodAllowed(method); + } else { + int method_str_len; + const char *method_str = t_state.hdr_info.server_request.method_get(&method_str_len); + deny_request = !acl_record->isNonstandardMethodAllowed(std::string(method_str, method_str_len)); + } + } + } + + if (deny_request) { + if (is_debug_tag_set("ip-allow")) { + ip_text_buffer ipb; + Warning("server '%s' prohibited by ip-allow policy", ats_ip_ntop(server_ip, ipb, sizeof(ipb))); + Debug("ip-allow", "Denial on %s:%s with mask %x", ats_ip_ntop(&t_state.current.server->dst_addr.sa, ipb, sizeof(ipb)), + hdrtoken_index_to_wks(method), acl_record ? acl_record->_method_mask : 0x0); + } + t_state.current.attempts = t_state.txn_conf->connect_attempts_max_retries; // prevent any more retries with this IP + call_transact_and_set_next_state(HttpTransact::Forbidden); + return; + } + } + } + + // If this is not a raw connection, we try to get a session from the + // shared session pool. Raw connections are for SSLs tunnel and + // require a new connection + // + + // This problem with POST requests is a bug. Because of the issue of the + // race with us sending a request after server has closed but before the FIN + // gets to us, we should open a new connection for POST. I believe TS used + // to do this but as far I can tell the code that prevented keep-alive if + // there is a request body has been removed. + + // If we are sending authorizations headers, mark the connection private + // + // We do this here because it means that we will not waste a connection from the pool if we already + // know that the session will be private. This is overridable meaning that if a plugin later decides + // it shouldn't be private it can still be returned to a shared pool. + // + if (t_state.txn_conf->auth_server_session_private == 1 && + t_state.hdr_info.server_request.presence(MIME_PRESENCE_AUTHORIZATION | MIME_PRESENCE_PROXY_AUTHORIZATION | + MIME_PRESENCE_WWW_AUTHENTICATE)) { + SMDebug("http_ss_auth", "Setting server session to private for authorization header"); + will_be_private_ss = true; + } + + if (t_state.method == HTTP_WKSIDX_POST || t_state.method == HTTP_WKSIDX_PUT) { + // don't share the session if keep-alive for post is not on + if (t_state.txn_conf->keep_alive_post_out == 0) { + SMDebug("http_ss", "Setting server session to private because of keep-alive post out"); + will_be_private_ss = true; + } + } + + // If there is already an attached server session mark it as private. + if (server_session != nullptr && will_be_private_ss) { + set_server_session_private(true); + } + + if (raw == false && TS_SERVER_SESSION_SHARING_MATCH_NONE != t_state.txn_conf->server_session_sharing_match && + (t_state.txn_conf->keep_alive_post_out == 1 || t_state.hdr_info.request_content_length == 0) && !is_private() && + ua_txn != nullptr) { + HSMresult_t shared_result; + shared_result = httpSessionManager.acquire_session(this, // state machine + &t_state.current.server->dst_addr.sa, // ip + port + t_state.current.server->name, // hostname + ua_txn, // has ptr to bound ua sessions + this // sm + ); + + switch (shared_result) { + case HSM_DONE: + lsm_release_assert(server_session != nullptr); + handle_http_server_open(); + return; + case HSM_NOT_FOUND: + lsm_release_assert(server_session == nullptr); + break; + case HSM_RETRY: + // Could not get shared pool lock + // FIX: should retry lock + break; + default: + lsm_release_assert(0); + } + } + // Avoid a problem where server session sharing is disabled and we have keep-alive, we are trying to open a new server + // session when we already have an attached server session. + else if ((TS_SERVER_SESSION_SHARING_MATCH_NONE == t_state.txn_conf->server_session_sharing_match || is_private()) && + (ua_txn != nullptr)) { + HttpServerSession *existing_ss = ua_txn->get_server_session(); + + if (existing_ss) { + // [amc] Not sure if this is the best option, but we don't get here unless session sharing is disabled + // so there's no point in further checking on the match or pool values. But why check anything? The + // client has already exchanged a request with this specific origin server and has sent another one + // shouldn't we just automatically keep the association? + if (ats_ip_addr_port_eq(&existing_ss->get_server_ip().sa, &t_state.current.server->dst_addr.sa)) { + ua_txn->attach_server_session(nullptr); + existing_ss->state = HSS_ACTIVE; + this->attach_server_session(existing_ss); + lsm_release_assert(server_session != nullptr); + handle_http_server_open(); + return; + } else { + // As this is in the non-sharing configuration, we want to close + // the existing connection and call connect_re to get a new one + existing_ss->get_netvc()->set_inactivity_timeout(HRTIME_SECONDS(t_state.txn_conf->keep_alive_no_activity_timeout_out)); + existing_ss->release(); + ua_txn->attach_server_session(nullptr); + } + } + } + // Otherwise, we release the existing connection and call connect_re + // to get a new one. + // ua_txn is null when t_state.req_flavor == REQ_FLAVOR_SCHEDULED_UPDATE + else if (ua_txn != nullptr) { + HttpServerSession *existing_ss = ua_txn->get_server_session(); + if (existing_ss) { + existing_ss->get_netvc()->set_inactivity_timeout(HRTIME_SECONDS(t_state.txn_conf->keep_alive_no_activity_timeout_out)); + existing_ss->release(); + ua_txn->attach_server_session(nullptr); + } + } + // Check to see if we have reached the max number of connections. + // Atomically read the current number of connections and check to see + // if we have gone above the max allowed. + if (t_state.http_config_param->server_max_connections > 0) { + int64_t sum; + + HTTP_READ_GLOBAL_DYN_SUM(http_current_server_connections_stat, sum); + + // Note that there is a potential race condition here where + // the value of the http_current_server_connections_stat gets changed + // between the statement above and the check below. + // If this happens, we might go over the max by 1 but this is ok. + if (sum >= t_state.http_config_param->server_max_connections) { + httpSessionManager.purge_keepalives(); + // Eventually may want to have a queue as the origin_max_connection does to allow for a combination + // of retries and errors. But at this point, we are just going to allow the error case. + t_state.current.state = HttpTransact::CONNECTION_ERROR; + call_transact_and_set_next_state(HttpTransact::HandleResponse); + return; + } + } + // Check to see if we have reached the max number of connections on this + // host. + if (t_state.txn_conf->origin_max_connections > 0) { + ConnectionCount *connections = ConnectionCount::getInstance(); + + CryptoHash hostname_hash; + CryptoContext().hash_immediate(hostname_hash, static_cast(t_state.current.server->name), + static_cast(strlen(t_state.current.server->name))); + + auto ccount = connections->getCount(t_state.current.server->dst_addr, hostname_hash, + (TSServerSessionSharingMatchType)t_state.txn_conf->server_session_sharing_match); + if (ccount >= t_state.txn_conf->origin_max_connections) { + ip_port_text_buffer addrbuf; + ats_ip_nptop(&t_state.current.server->dst_addr.sa, addrbuf, sizeof(addrbuf)); + SMDebug("http", "[%" PRId64 "] too many connections (%d) for this host (%" PRId64 "): %s", sm_id, ccount, + t_state.txn_conf->origin_max_connections, addrbuf); + Warning("[%" PRId64 "] too many connections (%d) for this host (%" PRId64 "): %s", sm_id, ccount, + t_state.txn_conf->origin_max_connections, addrbuf); + ink_assert(pending_action == nullptr); + + // if we were previously queued, or the queue is disabled-- just reschedule + if (t_state.origin_request_queued || t_state.txn_conf->origin_max_connections_queue < 0) { + pending_action = eventProcessor.schedule_in(this, HRTIME_MSECONDS(100)); + return; + } else if (t_state.txn_conf->origin_max_connections_queue > 0) { // If we have a queue, lets see if there is a slot + ConnectionCountQueue *waiting_connections = ConnectionCountQueue::getInstance(); + // if there is space in the queue + if (waiting_connections->getCount(t_state.current.server->dst_addr, hostname_hash, + (TSServerSessionSharingMatchType)t_state.txn_conf->server_session_sharing_match) < + t_state.txn_conf->origin_max_connections_queue) { + t_state.origin_request_queued = true; + Debug("http", "[%" PRId64 "] queued for this host: %s", sm_id, + ats_ip_ntop(&t_state.current.server->dst_addr.sa, addrbuf, sizeof(addrbuf))); + waiting_connections->incrementCount(t_state.current.server->dst_addr, hostname_hash, + (TSServerSessionSharingMatchType)t_state.txn_conf->server_session_sharing_match, 1); + pending_action = eventProcessor.schedule_in(this, HRTIME_MSECONDS(100)); + } else { // the queue is full + HTTP_INCREMENT_DYN_STAT(http_origin_connections_throttled_stat); + send_origin_throttled_response(); + } + } else { // the queue is set to 0 + HTTP_INCREMENT_DYN_STAT(http_origin_connections_throttled_stat); + send_origin_throttled_response(); + } + return; + } + } + + // We did not manage to get an existing session + // and need to open a new connection + Action *connect_action_handle; + + NetVCOptions opt; + opt.f_blocking_connect = false; + opt.set_sock_param(t_state.txn_conf->sock_recv_buffer_size_out, t_state.txn_conf->sock_send_buffer_size_out, + t_state.txn_conf->sock_option_flag_out, t_state.txn_conf->sock_packet_mark_out, + t_state.txn_conf->sock_packet_tos_out); + + opt.ip_family = ip_family; + + if (ua_txn) { + opt.local_port = ua_txn->get_outbound_port(); + + const IpAddr &outbound_ip = AF_INET6 == ip_family ? ua_txn->get_outbound_ip6() : ua_txn->get_outbound_ip4(); + if (outbound_ip.isValid()) { + opt.addr_binding = NetVCOptions::INTF_ADDR; + opt.local_ip = outbound_ip; + } else if (ua_txn->is_outbound_transparent()) { + opt.addr_binding = NetVCOptions::FOREIGN_ADDR; + opt.local_ip = t_state.client_info.src_addr; + /* If the connection is server side transparent, we can bind to the + port that the client chose instead of randomly assigning one at + the proxy. This is controlled by the 'use_client_source_port' + configuration parameter. + */ + + NetVConnection *client_vc = ua_txn->get_netvc(); + if (t_state.http_config_param->use_client_source_port && nullptr != client_vc) { + opt.local_port = client_vc->get_remote_port(); + } + } + } + + int scheme_to_use = t_state.scheme; // get initial scheme + + if (!t_state.is_websocket) { // if not websocket, then get scheme from server request + int new_scheme_to_use = t_state.hdr_info.server_request.url_get()->scheme_get_wksidx(); + // if the server_request url scheme was never set, try the client_request + if (new_scheme_to_use < 0) { + new_scheme_to_use = t_state.hdr_info.client_request.url_get()->scheme_get_wksidx(); + } + if (new_scheme_to_use >= 0) { // found a new scheme, use it + scheme_to_use = new_scheme_to_use; + } + } + + // draft-stenberg-httpbis-tcp recommends only enabling TFO on indempotent methods or + // those with intervening protocol layers (eg. TLS). + + if (scheme_to_use == URL_WKSIDX_HTTPS || HttpTransactHeaders::is_method_idempotent(t_state.method)) { + opt.f_tcp_fastopen = (t_state.txn_conf->sock_option_flag_out & NetVCOptions::SOCK_OPT_TCP_FAST_OPEN); + } + + if (scheme_to_use == URL_WKSIDX_HTTPS) { + SMDebug("http", "calling sslNetProcessor.connect_re"); + + int len = 0; + const char *host = t_state.hdr_info.server_request.host_get(&len); + if (host && len > 0) { + opt.set_sni_servername(host, len); + } + + SSLConfig::scoped_config params; + // check if the overridden client cert filename is already attached to an existing ssl context + if (t_state.txn_conf->client_cert_filepath && t_state.txn_conf->client_cert_filename) { + ats_scoped_str clientCert( + Layout::relative_to(t_state.txn_conf->client_cert_filepath, t_state.txn_conf->client_cert_filename)); + if (clientCert != nullptr) { + auto tCTX = params->getCTX(clientCert); + + if (tCTX == nullptr) { + // make new client ctx and add it to the ctx list + Debug("ssl", "adding new cert for client cert %s", (char *)clientCert); + auto tctx = params->getNewCTX(clientCert); + params->InsertCTX(clientCert, tctx); + } + opt.set_client_certname(clientCert); + } + } + connect_action_handle = sslNetProcessor.connect_re(this, // state machine + &t_state.current.server->dst_addr.sa, // addr + port + &opt); + } else if (t_state.method != HTTP_WKSIDX_CONNECT && t_state.method != HTTP_WKSIDX_POST && t_state.method != HTTP_WKSIDX_PUT) { + SMDebug("http", "calling netProcessor.connect_re"); + connect_action_handle = netProcessor.connect_re(this, // state machine + &t_state.current.server->dst_addr.sa, // addr + port + &opt); + } else { + // The request transform would be applied to POST and/or PUT request. + // The server_vc should be established (writeable) before request transform start. + // The CheckConnect is created by connect_s, + // It will callback NET_EVENT_OPEN to L4RSM if server_vc is WRITE_READY, + // Otherwise NET_EVENT_OPEN_FAILED is callbacked. + MgmtInt connect_timeout; + + ink_assert(t_state.method == HTTP_WKSIDX_CONNECT || t_state.method == HTTP_WKSIDX_POST || t_state.method == HTTP_WKSIDX_PUT); + + // Set the inactivity timeout to the connect timeout so that we + // we fail this server if it doesn't start sending the response + // header + if (t_state.method == HTTP_WKSIDX_POST || t_state.method == HTTP_WKSIDX_PUT) { + connect_timeout = t_state.txn_conf->post_connect_attempts_timeout; + } else if (t_state.current.server == &t_state.parent_info) { + connect_timeout = t_state.txn_conf->parent_connect_timeout; + } else { + connect_timeout = t_state.txn_conf->connect_attempts_timeout; + } + + SMDebug("http", "calling netProcessor.connect_s"); + connect_action_handle = netProcessor.connect_s(this, // state machine + &t_state.current.server->dst_addr.sa, // addr + port + connect_timeout, &opt); + } + + if (connect_action_handle != ACTION_RESULT_DONE) { + ink_assert(!pending_action); + pending_action = connect_action_handle; + } + + return; +} + +void +L4RSM::do_api_callout_internal() +{ + if (t_state.backdoor_request) { + handle_api_return(); + return; + } + + switch (t_state.api_next_action) { + case HttpTransact::SM_ACTION_API_SM_START: + cur_hook_id = TS_HTTP_TXN_START_HOOK; + break; + case HttpTransact::SM_ACTION_API_PRE_REMAP: + cur_hook_id = TS_HTTP_PRE_REMAP_HOOK; + break; + case HttpTransact::SM_ACTION_API_POST_REMAP: + cur_hook_id = TS_HTTP_POST_REMAP_HOOK; + break; + case HttpTransact::SM_ACTION_API_READ_REQUEST_HDR: + cur_hook_id = TS_HTTP_READ_REQUEST_HDR_HOOK; + break; + case HttpTransact::SM_ACTION_REQUEST_BUFFER_READ_COMPLETE: + cur_hook_id = TS_HTTP_REQUEST_BUFFER_READ_COMPLETE_HOOK; + break; + case HttpTransact::SM_ACTION_API_OS_DNS: + cur_hook_id = TS_HTTP_OS_DNS_HOOK; + break; + case HttpTransact::SM_ACTION_API_SEND_REQUEST_HDR: + cur_hook_id = TS_HTTP_SEND_REQUEST_HDR_HOOK; + break; + case HttpTransact::SM_ACTION_API_READ_CACHE_HDR: + cur_hook_id = TS_HTTP_READ_CACHE_HDR_HOOK; + break; + case HttpTransact::SM_ACTION_API_CACHE_LOOKUP_COMPLETE: + cur_hook_id = TS_HTTP_CACHE_LOOKUP_COMPLETE_HOOK; + break; + case HttpTransact::SM_ACTION_API_READ_RESPONSE_HDR: + cur_hook_id = TS_HTTP_READ_RESPONSE_HDR_HOOK; + break; + case HttpTransact::SM_ACTION_API_SEND_RESPONSE_HDR: + cur_hook_id = TS_HTTP_SEND_RESPONSE_HDR_HOOK; + milestones[TS_MILESTONE_UA_BEGIN_WRITE] = Thread::get_hrtime(); + break; + case HttpTransact::SM_ACTION_API_SM_SHUTDOWN: + if (callout_state == HTTP_API_IN_CALLOUT || callout_state == HTTP_API_DEFERED_SERVER_ERROR) { + callout_state = HTTP_API_DEFERED_CLOSE; + return; + } else { + cur_hook_id = TS_HTTP_TXN_CLOSE_HOOK; + } + break; + default: + cur_hook_id = (TSHttpHookID)-1; + ink_assert(!"not reached"); + } + + cur_hook = nullptr; + cur_hooks = 0; + state_api_callout(0, nullptr); +} + +VConnection * +L4RSM::do_post_transform_open() +{ + ink_assert(post_transform_info.vc == nullptr); + + if (is_action_tag_set("http_post_nullt")) { + txn_hook_prepend(TS_HTTP_REQUEST_TRANSFORM_HOOK, transformProcessor.null_transform(mutex.get())); + } + + post_transform_info.vc = transformProcessor.open(this, api_hooks.get(TS_HTTP_REQUEST_TRANSFORM_HOOK)); + if (post_transform_info.vc) { + // Record the transform VC in our table + post_transform_info.entry = vc_table.new_entry(); + post_transform_info.entry->vc = post_transform_info.vc; + post_transform_info.entry->vc_type = HTTP_TRANSFORM_VC; + } + + return post_transform_info.vc; +} + +VConnection * +L4RSM::do_transform_open() +{ + ink_assert(transform_info.vc == nullptr); + APIHook *hooks; + + if (is_action_tag_set("http_nullt")) { + txn_hook_prepend(TS_HTTP_RESPONSE_TRANSFORM_HOOK, transformProcessor.null_transform(mutex.get())); + } + + hooks = api_hooks.get(TS_HTTP_RESPONSE_TRANSFORM_HOOK); + if (hooks) { + transform_info.vc = transformProcessor.open(this, hooks); + + // Record the transform VC in our table + transform_info.entry = vc_table.new_entry(); + transform_info.entry->vc = transform_info.vc; + transform_info.entry->vc_type = HTTP_TRANSFORM_VC; + } else { + transform_info.vc = nullptr; + } + + return transform_info.vc; +} + +void +L4RSM::mark_host_failure(HostDBInfo *info, time_t time_down) +{ + char addrbuf[INET6_ADDRPORTSTRLEN]; + + if (info->app.http_data.last_failure == 0) { + char *url_str = t_state.hdr_info.client_request.url_string_get(&t_state.arena, nullptr); + Log::error("CONNECT: could not connect to %s " + "for '%s' (setting last failure time) connect_result=%d", + ats_ip_ntop(&t_state.current.server->dst_addr.sa, addrbuf, sizeof(addrbuf)), url_str ? url_str : "", + t_state.current.server->connect_result); + + if (url_str) { + t_state.arena.str_free(url_str); + } + } + + info->app.http_data.last_failure = time_down; + +#ifdef DEBUG + ink_assert(ink_local_time() + t_state.txn_conf->down_server_timeout > time_down); +#endif + + SMDebug("http", "[%" PRId64 "] hostdb update marking IP: %s as down", sm_id, + ats_ip_nptop(&t_state.current.server->dst_addr.sa, addrbuf, sizeof(addrbuf))); +} + +void +L4RSM::set_ua_abort(HttpTransact::AbortState_t ua_abort, int event) +{ + t_state.client_info.abort = ua_abort; + + switch (ua_abort) { + case HttpTransact::ABORTED: + case HttpTransact::MAYBE_ABORTED: + // More detailed client side abort logging based on event + switch (event) { + case VC_EVENT_ERROR: + t_state.squid_codes.log_code = SQUID_LOG_ERR_CLIENT_READ_ERROR; + break; + case VC_EVENT_EOS: + case VC_EVENT_ACTIVE_TIMEOUT: // Won't matter. Server will hangup + case VC_EVENT_INACTIVITY_TIMEOUT: // Won't matter. Send back 408 + // Fall-through + default: + t_state.squid_codes.log_code = SQUID_LOG_ERR_CLIENT_ABORT; + break; + } + break; + default: + // Handled here: + // HttpTransact::ABORT_UNDEFINED, HttpTransact::DIDNOT_ABORT + break; + } + + // Set the connection attribute code for the client so that + // we log the client finish code correctly + switch (event) { + case VC_EVENT_ACTIVE_TIMEOUT: + t_state.client_info.state = HttpTransact::ACTIVE_TIMEOUT; + break; + case VC_EVENT_INACTIVITY_TIMEOUT: + t_state.client_info.state = HttpTransact::INACTIVE_TIMEOUT; + break; + case VC_EVENT_ERROR: + t_state.client_info.state = HttpTransact::CONNECTION_ERROR; + break; + } +} + +void +L4RSM::mark_server_down_on_client_abort() +{ + ///////////////////////////////////////////////////// + // Check see if the client aborted because the // + // origin server was too slow in sending the // + // response header. If so, mark that // + // server as down so other clients won't try to // + // for revalidation or select it from a round // + // robin set // + // // + // Note: we do not want to mark parent // + // proxies as down with this metric because // + // that upstream proxy may be working but // + // the actual origin server is one that is hung // + ///////////////////////////////////////////////////// + if (t_state.current.request_to == HttpTransact::ORIGIN_SERVER && t_state.hdr_info.request_content_length <= 0) { + if (milestones[TS_MILESTONE_SERVER_FIRST_CONNECT] != 0 && milestones[TS_MILESTONE_SERVER_FIRST_READ] == 0) { + // Check to see if client waited for the threshold + // to declare the origin server as down + ink_hrtime wait = Thread::get_hrtime() - milestones[TS_MILESTONE_SERVER_FIRST_CONNECT]; + if (wait < 0) { + wait = 0; + } + if (ink_hrtime_to_sec(wait) > t_state.txn_conf->client_abort_threshold) { + t_state.current.server->set_connect_fail(ETIMEDOUT); + do_hostdb_update_if_necessary(); + } + } + } +} + +// void L4RSM::release_server_session() +// +// Called when we are not tunneling a response from the +// server. If the session is keep alive, release it back to the +// shared pool, otherwise close it +// +void +L4RSM::release_server_session(bool serve_from_cache) +{ + if (server_session == nullptr) { + return; + } + + if (TS_SERVER_SESSION_SHARING_MATCH_NONE != t_state.txn_conf->server_session_sharing_match && t_state.current.server != nullptr && + t_state.current.server->keep_alive == HTTP_KEEPALIVE && t_state.hdr_info.server_response.valid() && + t_state.hdr_info.server_request.valid() && + (t_state.hdr_info.server_response.status_get() == HTTP_STATUS_NOT_MODIFIED || + (t_state.hdr_info.server_request.method_get_wksidx() == HTTP_WKSIDX_HEAD && + t_state.www_auth_content != HttpTransact::CACHE_AUTH_NONE)) && + plugin_tunnel_type == HTTP_NO_PLUGIN_TUNNEL) { + HTTP_DECREMENT_DYN_STAT(http_current_server_transactions_stat); + server_session->server_trans_stat--; + server_session->attach_hostname(t_state.current.server->name); + if (t_state.www_auth_content == HttpTransact::CACHE_AUTH_NONE || serve_from_cache == false) { + // Must explicitly set the keep_alive_no_activity time before doing the release + server_session->get_netvc()->set_inactivity_timeout(HRTIME_SECONDS(t_state.txn_conf->keep_alive_no_activity_timeout_out)); + server_session->release(); + } else { + // an authenticated server connection - attach to the local client + // we are serving from cache for the current transaction + t_state.www_auth_content = HttpTransact::CACHE_AUTH_SERVE; + ua_txn->attach_server_session(server_session, false); + } + } else { + server_session->do_io_close(); + } + + ink_assert(server_entry->vc == server_session); + server_entry->in_tunnel = true; + vc_table.cleanup_entry(server_entry); + server_entry = nullptr; + server_session = nullptr; +} + +// void L4RSM::handle_post_failure() +// +// We failed in our attempt post (or put) a document +// to the server. Two cases happen here. The normal +// one is the server died, in which case we ought to +// return an error to the client. The second one is +// stupid. The server returned a response without reading +// all the post data. In order to be as transparent as +// possible process the server's response +void +L4RSM::handle_post_failure() +{ + STATE_ENTER(&L4RSM::handle_post_failure, VC_EVENT_NONE); + + ink_assert(ua_entry->vc == ua_txn); + ink_assert(is_waiting_for_full_body || server_entry->eos == true); + + if (is_waiting_for_full_body) { + call_transact_and_set_next_state(HttpTransact::Forbidden); + return; + } + // First order of business is to clean up from + // the tunnel + // note: since the tunnel is providing the buffer for a lingering + // client read (for abort watching purposes), we need to stop + // the read + if (false == t_state.redirect_info.redirect_in_process) { + ua_entry->read_vio = ua_txn->do_io_read(this, 0, nullptr); + } + ua_entry->in_tunnel = false; + server_entry->in_tunnel = false; + + // disable redirection in case we got a partial response and then EOS, because the buffer might not + // have the full post and it's deallocating the post buffers here + this->disable_redirect(); + + // Don't even think about doing keep-alive after this debacle + t_state.client_info.keep_alive = HTTP_NO_KEEPALIVE; + t_state.current.server->keep_alive = HTTP_NO_KEEPALIVE; + + if (server_buffer_reader->read_avail() > 0) { + tunnel.deallocate_buffers(); + tunnel.reset(); + // There's data from the server so try to read the header + // Read response is already set up + } else { + tunnel.deallocate_buffers(); + tunnel.reset(); + // Server died + t_state.current.state = HttpTransact::CONNECTION_CLOSED; + call_transact_and_set_next_state(HttpTransact::HandleResponse); + } +} + +// void L4RSM::handle_http_server_open() +// +// The server connection is now open. If there is a POST or PUT, +// we need setup a transform is there is one otherwise we need +// to send the request header +// +void +L4RSM::handle_http_server_open() +{ + // if we were a queued request, we need to decrement the queue size-- as we got a connection + if (t_state.origin_request_queued) { + CryptoHash hostname_hash; + CryptoContext().hash_immediate(hostname_hash, static_cast(t_state.current.server->name), + strlen(t_state.current.server->name)); + + ConnectionCountQueue *waiting_connections = ConnectionCountQueue::getInstance(); + waiting_connections->incrementCount(t_state.current.server->dst_addr, hostname_hash, + (TSServerSessionSharingMatchType)t_state.txn_conf->server_session_sharing_match, -1); + // The request is now not queued. This is important if the request will ever retry, the t_state is re-used + t_state.origin_request_queued = false; + } + + // [bwyatt] applying per-transaction OS netVC options here + // IFF they differ from the netVC's current options. + // This should keep this from being redundant on a + // server session's first transaction. + if (nullptr != server_session) { + NetVConnection *vc = server_session->get_netvc(); + if (vc != nullptr && (vc->options.sockopt_flags != t_state.txn_conf->sock_option_flag_out || + vc->options.packet_mark != t_state.txn_conf->sock_packet_mark_out || + vc->options.packet_tos != t_state.txn_conf->sock_packet_tos_out || + vc->options.clientVerificationFlag != t_state.txn_conf->ssl_client_verify_server)) { + vc->options.sockopt_flags = t_state.txn_conf->sock_option_flag_out; + vc->options.packet_mark = t_state.txn_conf->sock_packet_mark_out; + vc->options.packet_tos = t_state.txn_conf->sock_packet_tos_out; + vc->options.clientVerificationFlag = t_state.txn_conf->ssl_client_verify_server; + vc->apply_options(); + } + } + + int method = t_state.hdr_info.server_request.method_get_wksidx(); + if (method != HTTP_WKSIDX_TRACE && + (t_state.hdr_info.request_content_length > 0 || t_state.client_info.transfer_encoding == HttpTransact::CHUNKED_ENCODING) && + do_post_transform_open()) { + do_setup_post_tunnel(HTTP_TRANSFORM_VC); + // Start up read response in parallel in case of early error response + setup_server_read_response_header(); + } else { + setup_server_send_request_api(); + } +} + +// void L4RSM::handle_server_setup_error(int event, void* data) +// +// Handles setting t_state.current.state and calling +// Transact in between opening an origin server connection +// and receiving the response header (in the case of the +// POST, a post tunnel happens in between the sending +// request header and reading the response header +// +void +L4RSM::handle_server_setup_error(int event, void *data) +{ + VIO *vio = (VIO *)data; + ink_assert(vio != nullptr); + + STATE_ENTER(&L4RSM::handle_server_setup_error, event); + + // If there is POST or PUT tunnel wait for the tunnel + // to figure out that things have gone to hell + + if (tunnel.is_tunnel_active()) { + ink_assert(server_entry->read_vio == data || server_entry->write_vio == data); + SMDebug("http", + "[%" PRId64 "] [handle_server_setup_error] " + "forwarding event %s to post tunnel", + sm_id, HttpDebugNames::get_event_name(event)); + HttpTunnelConsumer *c = tunnel.get_consumer(server_entry->vc); + // it is possible only user agent post->post transform is set up + // this happened for Linux iocore where NET_EVENT_OPEN was returned + // for a non-existing listening port. the hack is to pass the error + // event for server connection to post_transform_info + if (c == nullptr && post_transform_info.vc) { + c = tunnel.get_consumer(post_transform_info.vc); + // c->handler_state = HTTP_SM_TRANSFORM_FAIL; + + // No point in proceeding if there is no consumer + // Do we need to do additional clean up in the c == NULL case? + if (c != nullptr) { + HttpTunnelProducer *ua_producer = c->producer; + ink_assert(ua_entry->vc == ua_producer->vc); + + ua_entry->vc_handler = &L4RSM::state_watch_for_client_abort; + ua_entry->read_vio = ua_producer->vc->do_io_read(this, INT64_MAX, c->producer->read_buffer); + ua_producer->vc->do_io_shutdown(IO_SHUTDOWN_READ); + + ua_producer->alive = false; + ua_producer->handler_state = HTTP_SM_POST_SERVER_FAIL; + tunnel.handleEvent(VC_EVENT_ERROR, c->write_vio); + return; + } + } else { + // c could be null here as well + if (c != nullptr) { + tunnel.handleEvent(event, c->write_vio); + return; + } + } + // If there is no consumer, let the event pass through to shutdown + } else { + if (post_transform_info.vc) { + HttpTunnelConsumer *c = tunnel.get_consumer(post_transform_info.vc); + if (c && c->handler_state == HTTP_SM_TRANSFORM_OPEN) { + vc_table.cleanup_entry(post_transform_info.entry); + post_transform_info.entry = nullptr; + tunnel.deallocate_buffers(); + tunnel.reset(); + } + } + } + + switch (event) { + case VC_EVENT_EOS: + t_state.current.state = HttpTransact::CONNECTION_CLOSED; + break; + case VC_EVENT_ERROR: + t_state.current.state = HttpTransact::CONNECTION_ERROR; + t_state.cause_of_death_errno = server_session->get_netvc()->lerrno; + break; + case VC_EVENT_ACTIVE_TIMEOUT: + t_state.current.state = HttpTransact::ACTIVE_TIMEOUT; + break; + + case VC_EVENT_INACTIVITY_TIMEOUT: + // If we're writing the request and get an inactivity timeout + // before any bytes are written, the connection to the + // server failed + // In case of TIMEOUT, the iocore sends back + // server_entry->read_vio instead of the write_vio + if (server_entry->write_vio && server_entry->write_vio->nbytes > 0 && server_entry->write_vio->ndone == 0) { + t_state.current.state = HttpTransact::CONNECTION_ERROR; + } else { + t_state.current.state = HttpTransact::INACTIVE_TIMEOUT; + } + break; + default: + ink_release_assert(0); + } + + if (event == VC_EVENT_INACTIVITY_TIMEOUT || event == VC_EVENT_ERROR) { + // Clean up the vc_table entry so any events in play to the timed out server vio + // don't get handled. The connection isn't there. + if (server_entry) { + ink_assert(server_entry->vc_type == HTTP_SERVER_VC); + vc_table.cleanup_entry(server_entry); + server_entry = nullptr; + server_session = nullptr; + } + } + + // Closedown server connection and deallocate buffers + ink_assert(!server_entry || server_entry->in_tunnel == false); + + // if we are waiting on a plugin callout for + // HTTP_API_SEND_REQUEST_HDR defer calling transact until + // after we've finished processing the plugin callout + switch (callout_state) { + case HTTP_API_NO_CALLOUT: + // Normal fast path case, no api callouts in progress + break; + case HTTP_API_IN_CALLOUT: + case HTTP_API_DEFERED_SERVER_ERROR: + // Callout in progress note that we are in deferring + // the server error + callout_state = HTTP_API_DEFERED_SERVER_ERROR; + return; + case HTTP_API_DEFERED_CLOSE: + // The user agent has shutdown killing the sm + // but we are stuck waiting for the server callout + // to finish so do nothing here. We don't care + // about the server connection at this and are + // just waiting till we can execute the close hook + return; + default: + ink_release_assert(0); + } + + call_transact_and_set_next_state(HttpTransact::HandleResponse); +} + +void +L4RSM::setup_transform_to_server_transfer() +{ + ink_assert(post_transform_info.vc != nullptr); + ink_assert(post_transform_info.entry->vc == post_transform_info.vc); + + int64_t nbytes = t_state.hdr_info.transform_request_cl; + int64_t alloc_index = buffer_size_to_index(nbytes); + MIOBuffer *post_buffer = new_MIOBuffer(alloc_index); + IOBufferReader *buf_start = post_buffer->alloc_reader(); + + L4R_SM_SET_DEFAULT_HANDLER(&L4RSM::tunnel_handler_post); + + HttpTunnelConsumer *c = tunnel.get_consumer(post_transform_info.vc); + + HttpTunnelProducer *p = tunnel.add_producer(post_transform_info.vc, nbytes, buf_start, &L4RSM::tunnel_handler_transform_read, + HT_TRANSFORM, "post transform"); + tunnel.chain(c, p); + post_transform_info.entry->in_tunnel = true; + + tunnel.add_consumer(server_entry->vc, post_transform_info.vc, &L4RSM::tunnel_handler_post_server, HT_HTTP_SERVER, + "http server post"); + server_entry->in_tunnel = true; + + tunnel.tunnel_run(p); +} + +void +L4RSM::do_drain_request_body() +{ + int64_t content_length = t_state.hdr_info.client_request.get_content_length(); + int64_t avail = ua_buffer_reader->read_avail(); + + if (t_state.client_info.transfer_encoding == HttpTransact::CHUNKED_ENCODING) { + SMDebug("http", "Chunked body, setting the response to non-keepalive"); + goto close_connection; + } + + if (content_length > 0) { + if (avail >= content_length) { + SMDebug("http", "entire body is in the buffer, consuming"); + int64_t act_on = (avail < content_length) ? avail : content_length; + client_request_body_bytes = act_on; + ua_buffer_reader->consume(act_on); + } else { + SMDebug("http", "entire body is not in the buffer, setting the response to non-keepalive"); + goto close_connection; + } + } + return; + +close_connection: + t_state.client_info.keep_alive = HTTP_NO_KEEPALIVE; + t_state.hdr_info.client_response.value_set(MIME_FIELD_CONNECTION, MIME_LEN_CONNECTION, "close", 5); +} + +void +L4RSM::do_setup_post_tunnel(HttpVC_t to_vc_type) +{ + bool chunked = (t_state.client_info.transfer_encoding == HttpTransact::CHUNKED_ENCODING); + bool post_redirect = false; + + HttpTunnelProducer *p = nullptr; + // YTS Team, yamsat Plugin + // if redirect_in_process and redirection is enabled add static producer + + if (is_using_post_buffer || + (t_state.redirect_info.redirect_in_process && enable_redirection && this->_postbuf.postdata_copy_buffer_start != nullptr)) { + post_redirect = true; + // copy the post data into a new producer buffer for static producer + MIOBuffer *postdata_producer_buffer = new_empty_MIOBuffer(); + IOBufferReader *postdata_producer_reader = postdata_producer_buffer->alloc_reader(); + + postdata_producer_buffer->write(this->_postbuf.postdata_copy_buffer_start); + int64_t post_bytes = postdata_producer_reader->read_avail(); + transfered_bytes = post_bytes; + p = tunnel.add_producer(HTTP_TUNNEL_STATIC_PRODUCER, post_bytes, postdata_producer_reader, (HttpProducerHandler) nullptr, + HT_STATIC, "redirect static agent post"); + } else { + int64_t alloc_index; + // content length is undefined, use default buffer size + if (t_state.hdr_info.request_content_length == HTTP_UNDEFINED_CL) { + alloc_index = (int)t_state.txn_conf->default_buffer_size_index; + if (alloc_index < MIN_CONFIG_BUFFER_SIZE_INDEX || alloc_index > MAX_BUFFER_SIZE_INDEX) { + alloc_index = DEFAULT_REQUEST_BUFFER_SIZE_INDEX; + } + } else { + alloc_index = buffer_size_to_index(t_state.hdr_info.request_content_length); + } + MIOBuffer *post_buffer = new_MIOBuffer(alloc_index); + IOBufferReader *buf_start = post_buffer->alloc_reader(); + int64_t post_bytes = chunked ? INT64_MAX : t_state.hdr_info.request_content_length; + + if (enable_redirection) { + this->_postbuf.init(post_buffer->clone_reader(buf_start)); + } + + // Note: Many browsers, Netscape and IE included send two extra + // bytes (CRLF) at the end of the post. We just ignore those + // bytes since the sending them is not spec + + // Next order of business if copy the remaining data from the + // header buffer into new buffer + client_request_body_bytes = post_buffer->write(ua_buffer_reader, chunked ? ua_buffer_reader->read_avail() : post_bytes); + + ua_buffer_reader->consume(client_request_body_bytes); + p = tunnel.add_producer(ua_entry->vc, post_bytes - transfered_bytes, buf_start, &L4RSM::tunnel_handler_post_ua, HT_HTTP_CLIENT, + "user agent post"); + } + ua_entry->in_tunnel = true; + + switch (to_vc_type) { + case HTTP_TRANSFORM_VC: + L4R_SM_SET_DEFAULT_HANDLER(&L4RSM::state_request_wait_for_transform_read); + ink_assert(post_transform_info.entry != nullptr); + ink_assert(post_transform_info.entry->vc == post_transform_info.vc); + tunnel.add_consumer(post_transform_info.entry->vc, ua_entry->vc, &L4RSM::tunnel_handler_transform_write, HT_TRANSFORM, + "post transform"); + post_transform_info.entry->in_tunnel = true; + break; + case HTTP_SERVER_VC: + // YTS Team, yamsat Plugin + // When redirect in process is true and redirection is enabled + // add http server as the consumer + if (post_redirect) { + L4R_SM_SET_DEFAULT_HANDLER(&L4RSM::tunnel_handler_for_partial_post); + tunnel.add_consumer(server_entry->vc, HTTP_TUNNEL_STATIC_PRODUCER, &L4RSM::tunnel_handler_post_server, HT_HTTP_SERVER, + "redirect http server post"); + } else { + L4R_SM_SET_DEFAULT_HANDLER(&L4RSM::tunnel_handler_post); + tunnel.add_consumer(server_entry->vc, ua_entry->vc, &L4RSM::tunnel_handler_post_server, HT_HTTP_SERVER, "http server post"); + } + server_entry->in_tunnel = true; + break; + default: + ink_release_assert(0); + break; + } + + // The user agent may support chunked (HTTP/1.1) or not (HTTP/2) + // In either case, the server will support chunked (HTTP/1.1) + if (chunked) { + if (ua_txn->is_chunked_encoding_supported()) { + tunnel.set_producer_chunking_action(p, 0, TCA_PASSTHRU_CHUNKED_CONTENT); + } else { + tunnel.set_producer_chunking_action(p, 0, TCA_CHUNK_CONTENT); + tunnel.set_producer_chunking_size(p, 0); + } + } + + ua_txn->set_inactivity_timeout(HRTIME_SECONDS(t_state.txn_conf->transaction_no_activity_timeout_in)); + server_session->get_netvc()->set_inactivity_timeout(HRTIME_SECONDS(t_state.txn_conf->transaction_no_activity_timeout_out)); + + tunnel.tunnel_run(p); + + // If we're half closed, we got a FIN from the client. Forward it on to the origin server + // now that we have the tunnel operational. + // HttpTunnel could broken due to bad chunked data and close all vc by chain_abort_all(). + if (p->handler_state != HTTP_SM_POST_UA_FAIL && ua_txn->get_half_close_flag()) { + p->vc->do_io_shutdown(IO_SHUTDOWN_READ); + } +} + +// void L4RSM::perform_transform_cache_write_action() +// +// Called to do cache write from the transform +// +void +L4RSM::perform_transform_cache_write_action() +{ + SMDebug("http", "[%" PRId64 "] perform_transform_cache_write_action %s", sm_id, + HttpDebugNames::get_cache_action_name(t_state.cache_info.action)); + + if (t_state.range_setup) { + return; + } + + switch (t_state.cache_info.transform_action) { + case HttpTransact::CACHE_DO_NO_ACTION: { + // Nothing to do + transform_cache_sm.end_both(); + break; + } + + case HttpTransact::CACHE_DO_WRITE: { + transform_cache_sm.close_read(); + t_state.cache_info.transform_write_status = HttpTransact::CACHE_WRITE_IN_PROGRESS; + setup_cache_write_transfer(&transform_cache_sm, transform_info.entry->vc, &t_state.cache_info.transform_store, + client_response_hdr_bytes, "cache write t"); + break; + } + + default: + ink_release_assert(0); + break; + } +} + +// void L4RSM::perform_cache_write_action() +// +// Called to do cache write, delete and updates based +// on s->cache_info.action. Does not setup cache +// read tunnels +// +void +L4RSM::perform_cache_write_action() +{ + SMDebug("http", "[%" PRId64 "] perform_cache_write_action %s", sm_id, + HttpDebugNames::get_cache_action_name(t_state.cache_info.action)); + + switch (t_state.cache_info.action) { + case HttpTransact::CACHE_DO_NO_ACTION: + + { + // Nothing to do + cache_sm.end_both(); + break; + } + + case HttpTransact::CACHE_DO_SERVE: { + cache_sm.abort_write(); + break; + } + + case HttpTransact::CACHE_DO_DELETE: { + // Write close deletes the old alternate + cache_sm.close_write(); + cache_sm.close_read(); + break; + } + + case HttpTransact::CACHE_DO_SERVE_AND_DELETE: { + // FIX ME: need to set up delete for after cache write has + // completed + break; + } + + case HttpTransact::CACHE_DO_SERVE_AND_UPDATE: { + issue_cache_update(); + break; + } + + case HttpTransact::CACHE_DO_UPDATE: { + cache_sm.close_read(); + issue_cache_update(); + break; + } + + case HttpTransact::CACHE_DO_WRITE: + case HttpTransact::CACHE_DO_REPLACE: + // Fix need to set up delete for after cache write has + // completed + if (transform_info.entry == nullptr || t_state.api_info.cache_untransformed == true) { + cache_sm.close_read(); + t_state.cache_info.write_status = HttpTransact::CACHE_WRITE_IN_PROGRESS; + setup_cache_write_transfer(&cache_sm, server_entry->vc, &t_state.cache_info.object_store, client_response_hdr_bytes, + "cache write"); + } else { + // We are not caching the untransformed. We might want to + // use the cache writevc to cache the transformed copy + ink_assert(transform_cache_sm.cache_write_vc == nullptr); + transform_cache_sm.cache_write_vc = cache_sm.cache_write_vc; + cache_sm.cache_write_vc = nullptr; + } + break; + + default: + ink_release_assert(0); + break; + } +} + +void +L4RSM::issue_cache_update() +{ + ink_assert(cache_sm.cache_write_vc != nullptr); + if (cache_sm.cache_write_vc) { + t_state.cache_info.object_store.request_sent_time_set(t_state.request_sent_time); + t_state.cache_info.object_store.response_received_time_set(t_state.response_received_time); + ink_assert(t_state.cache_info.object_store.request_sent_time_get() > 0); + ink_assert(t_state.cache_info.object_store.response_received_time_get() > 0); + cache_sm.cache_write_vc->set_http_info(&t_state.cache_info.object_store); + t_state.cache_info.object_store.clear(); + } + // Now close the write which commits the update + cache_sm.close_write(); +} + +int +L4RSM::write_header_into_buffer(HTTPHdr *h, MIOBuffer *b) +{ + int bufindex; + int dumpoffset; + int done, tmp; + IOBufferBlock *block; + + dumpoffset = 0; + do { + bufindex = 0; + tmp = dumpoffset; + block = b->get_current_block(); + ink_assert(block->write_avail() > 0); + done = h->print(block->start(), block->write_avail(), &bufindex, &tmp); + dumpoffset += bufindex; + ink_assert(bufindex > 0); + b->fill(bufindex); + if (!done) { + b->add_block(); + } + } while (!done); + + return dumpoffset; +} + +void +L4RSM::attach_server_session(HttpServerSession *s) +{ + lsm_release_assert(server_session == nullptr); + lsm_release_assert(server_entry == nullptr); + lsm_release_assert(s->state == HSS_ACTIVE); + server_session = s; + server_transact_count = server_session->transact_count++; + // Propagate the per client IP debugging + if (ua_txn) { + s->get_netvc()->control_flags.set_flags(get_cont_flags().get_flags()); + } else { // If there is no ua_txn no sense in continuing to attach the server session + return; + } + + // Set the mutex so that we have something to update + // stats with + server_session->mutex = this->mutex; + + HTTP_INCREMENT_DYN_STAT(http_current_server_transactions_stat); + ++s->server_trans_stat; + + // Record the VC in our table + server_entry = vc_table.new_entry(); + server_entry->vc = server_session; + server_entry->vc_type = HTTP_SERVER_VC; + server_entry->vc_handler = &L4RSM::state_send_server_request_header; + + // es - is this a concern here in L4RSM? Does it belong somewhere else? + // Get server and client connections + UnixNetVConnection *server_vc = dynamic_cast(server_session->get_netvc()); + UnixNetVConnection *client_vc = (UnixNetVConnection *)(ua_txn->get_netvc()); + SSLNetVConnection *ssl_vc = dynamic_cast(client_vc); + + // Verifying that the user agent and server sessions/transactions are operating on the same thread. + ink_release_assert(!server_vc || !client_vc || server_vc->thread == client_vc->thread); + bool associated_connection = false; + if (server_vc) { // if server_vc isn't a PluginVC + if (ssl_vc) { // if incoming connection is SSL + bool client_trace = ssl_vc->getSSLTrace(); + if (client_trace) { + // get remote address and port to mark corresponding traces + const sockaddr *remote_addr = ssl_vc->get_remote_addr(); + uint16_t remote_port = ssl_vc->get_remote_port(); + server_vc->setOriginTrace(true); + server_vc->setOriginTraceAddr(remote_addr); + server_vc->setOriginTracePort(remote_port); + associated_connection = true; + } + } + } + if (!associated_connection && server_vc) { + server_vc->setOriginTrace(false); + server_vc->setOriginTraceAddr(nullptr); + server_vc->setOriginTracePort(0); + } + + // set flag for server session is SSL + SSLNetVConnection *server_ssl_vc = dynamic_cast(server_vc); + if (server_ssl_vc) { + server_connection_is_ssl = true; + } + + // Initiate a read on the session so that the SM and not + // session manager will get called back if the timeout occurs + // or the server closes on us. The IO Core now requires us to + // do the read with a buffer and a size so preallocate the + // buffer + server_buffer_reader = server_session->get_reader(); + // ts-3189 We are only setting up an empty read at this point. This + // is sufficient to have the timeout errors directed to the appropriate + // SM handler, but we don't want to read any data until the tunnel has + // been set up. This isn't such a big deal with GET results, since + // if no tunnels are set up, there is no danger of data being delivered + // to the wrong tunnel's consumer handler. But for post and other + // methods that send data after the request, two tunnels are created in + // series, and with a full read set up at this point, the EOS from the + // first tunnel was sometimes behind handled by the consumer of the + // first tunnel instead of the producer of the second tunnel. + // The real read is setup in setup_server_read_response_header() + // + server_entry->read_vio = server_session->do_io_read(this, 0, server_session->read_buffer); + + // Transfer control of the write side as well + server_entry->write_vio = server_session->do_io_write(this, 0, nullptr); + + // Setup the timeouts + // Set the inactivity timeout to the connect timeout so that we + // we fail this server if it doesn't start sending the response + // header + MgmtInt connect_timeout; + + if (t_state.method == HTTP_WKSIDX_POST || t_state.method == HTTP_WKSIDX_PUT) { + connect_timeout = t_state.txn_conf->post_connect_attempts_timeout; + } else if (t_state.current.server == &t_state.parent_info) { + connect_timeout = t_state.txn_conf->parent_connect_timeout; + } else { + connect_timeout = t_state.txn_conf->connect_attempts_timeout; + } + + if (t_state.api_txn_connect_timeout_value != -1) { + server_session->get_netvc()->set_inactivity_timeout(HRTIME_MSECONDS(t_state.api_txn_connect_timeout_value)); + } else { + server_session->get_netvc()->set_inactivity_timeout(HRTIME_SECONDS(connect_timeout)); + } + + if (t_state.api_txn_active_timeout_value != -1) { + server_session->get_netvc()->set_active_timeout(HRTIME_MSECONDS(t_state.api_txn_active_timeout_value)); + } else { + server_session->get_netvc()->set_active_timeout(HRTIME_SECONDS(t_state.txn_conf->transaction_active_timeout_out)); + } + + if (plugin_tunnel_type != HTTP_NO_PLUGIN_TUNNEL || will_be_private_ss) { + SMDebug("http_ss", "Setting server session to private"); + set_server_session_private(true); + } +} + +void +L4RSM::setup_server_send_request_api() +{ + t_state.api_next_action = HttpTransact::SM_ACTION_API_SEND_REQUEST_HDR; + do_api_callout(); +} + +void +L4RSM::setup_server_send_request() +{ + int hdr_length; + int64_t msg_len = 0; /* lv: just make gcc happy */ + + lsm_release_assert(server_entry != nullptr); + lsm_release_assert(server_session != nullptr); + lsm_release_assert(server_entry->vc == server_session); + + // Send the request header + server_entry->vc_handler = &L4RSM::state_send_server_request_header; + server_entry->write_buffer = new_MIOBuffer(HTTP_HEADER_BUFFER_SIZE_INDEX); + + if (t_state.api_server_request_body_set) { + msg_len = t_state.internal_msg_buffer_size; + t_state.hdr_info.server_request.value_set_int64(MIME_FIELD_CONTENT_LENGTH, MIME_LEN_CONTENT_LENGTH, msg_len); + } + + DUMP_HEADER("http_hdrs", &(t_state.hdr_info.server_request), t_state.state_machine_id, "Proxy's Request after hooks"); + + // We need a reader so bytes don't fall off the end of + // the buffer + IOBufferReader *buf_start = server_entry->write_buffer->alloc_reader(); + server_request_hdr_bytes = hdr_length = write_header_into_buffer(&t_state.hdr_info.server_request, server_entry->write_buffer); + + // the plugin decided to append a message to the request + if (t_state.api_server_request_body_set) { + SMDebug("http", "[%" PRId64 "] appending msg of %" PRId64 " bytes to request %s", sm_id, msg_len, t_state.internal_msg_buffer); + hdr_length += server_entry->write_buffer->write(t_state.internal_msg_buffer, msg_len); + server_request_body_bytes = msg_len; + } + + milestones[TS_MILESTONE_SERVER_BEGIN_WRITE] = Thread::get_hrtime(); + server_entry->write_vio = server_entry->vc->do_io_write(this, hdr_length, buf_start); +} + +void +L4RSM::setup_server_read_response_header() +{ + ink_assert(server_session != nullptr); + ink_assert(server_entry != nullptr); + // REQ_FLAVOR_SCHEDULED_UPDATE can be transformed in REQ_FLAVOR_REVPROXY + ink_assert(ua_txn != nullptr || t_state.req_flavor == HttpTransact::REQ_FLAVOR_SCHEDULED_UPDATE || + t_state.req_flavor == HttpTransact::REQ_FLAVOR_REVPROXY); + + // We should have set the server_buffer_reader + // we sent the request header + ink_assert(server_buffer_reader != nullptr); + + // Now that we've got the ability to read from the + // server, setup to read the response header + server_entry->vc_handler = &L4RSM::state_read_server_response_header; + + t_state.current.state = HttpTransact::STATE_UNDEFINED; + t_state.current.server->state = HttpTransact::STATE_UNDEFINED; + + // Note: we must use destroy() here since clear() + // does not free the memory from the header + t_state.hdr_info.server_response.destroy(); + t_state.hdr_info.server_response.create(HTTP_TYPE_RESPONSE); + http_parser_clear(&http_parser); + server_response_hdr_bytes = 0; + milestones[TS_MILESTONE_SERVER_READ_HEADER_DONE] = 0; + + // We already done the READ when we setup the connection to + // read the request header + ink_assert(server_entry->read_vio); + + // The tunnel from OS to UA is now setup. Ready to read the response + server_entry->read_vio = server_session->do_io_read(this, INT64_MAX, server_buffer_reader->mbuf); + + // If there is anything in the buffer call the parsing routines + // since if the response is finished, we won't get any + // additional callbacks + + if (server_buffer_reader->read_avail() > 0) { + state_read_server_response_header((server_entry->eos) ? VC_EVENT_EOS : VC_EVENT_READ_READY, server_entry->read_vio); + } +} + +HttpTunnelProducer * +L4RSM::setup_cache_read_transfer() +{ + int64_t alloc_index, hdr_size; + int64_t doc_size; + + ink_assert(cache_sm.cache_read_vc != nullptr); + + doc_size = t_state.cache_info.object_read->object_size_get(); + alloc_index = buffer_size_to_index(doc_size + index_to_buffer_size(HTTP_HEADER_BUFFER_SIZE_INDEX)); + +#ifndef USE_NEW_EMPTY_MIOBUFFER + MIOBuffer *buf = new_MIOBuffer(alloc_index); +#else + MIOBuffer *buf = new_empty_MIOBuffer(alloc_index); + buf->append_block(HTTP_HEADER_BUFFER_SIZE_INDEX); +#endif + + buf->water_mark = (int)t_state.txn_conf->default_buffer_water_mark; + + IOBufferReader *buf_start = buf->alloc_reader(); + + // Now dump the header into the buffer + ink_assert(t_state.hdr_info.client_response.status_get() != HTTP_STATUS_NOT_MODIFIED); + client_response_hdr_bytes = hdr_size = write_response_header_into_buffer(&t_state.hdr_info.client_response, buf); + cache_response_hdr_bytes = client_response_hdr_bytes; + + L4R_SM_SET_DEFAULT_HANDLER(&L4RSM::tunnel_handler); + + if (doc_size != INT64_MAX) { + doc_size += hdr_size; + } + + HttpTunnelProducer *p = tunnel.add_producer(cache_sm.cache_read_vc, doc_size, buf_start, &L4RSM::tunnel_handler_cache_read, + HT_CACHE_READ, "cache read"); + tunnel.add_consumer(ua_entry->vc, cache_sm.cache_read_vc, &L4RSM::tunnel_handler_ua, HT_HTTP_CLIENT, "user agent"); + // if size of a cached item is not known, we'll do chunking for keep-alive HTTP/1.1 clients + // this only applies to read-while-write cases where origin server sends a dynamically generated chunked content + // w/o providing a Content-Length header + if (t_state.client_info.receive_chunked_response) { + tunnel.set_producer_chunking_action(p, client_response_hdr_bytes, TCA_CHUNK_CONTENT); + tunnel.set_producer_chunking_size(p, t_state.txn_conf->http_chunking_size); + } + ua_entry->in_tunnel = true; + cache_sm.cache_read_vc = nullptr; + return p; +} + +HttpTunnelProducer * +L4RSM::setup_cache_transfer_to_transform() +{ + int64_t alloc_index; + int64_t doc_size; + + ink_assert(cache_sm.cache_read_vc != nullptr); + ink_assert(transform_info.vc != nullptr); + ink_assert(transform_info.entry->vc == transform_info.vc); + + // grab this here + cache_response_hdr_bytes = t_state.hdr_info.cache_response.length_get(); + + doc_size = t_state.cache_info.object_read->object_size_get(); + alloc_index = buffer_size_to_index(doc_size); + MIOBuffer *buf = new_MIOBuffer(alloc_index); + IOBufferReader *buf_start = buf->alloc_reader(); + + L4R_SM_SET_DEFAULT_HANDLER(&L4RSM::state_response_wait_for_transform_read); + + HttpTunnelProducer *p = tunnel.add_producer(cache_sm.cache_read_vc, doc_size, buf_start, &L4RSM::tunnel_handler_cache_read, + HT_CACHE_READ, "cache read"); + + tunnel.add_consumer(transform_info.vc, cache_sm.cache_read_vc, &L4RSM::tunnel_handler_transform_write, HT_TRANSFORM, + "transform write"); + transform_info.entry->in_tunnel = true; + cache_sm.cache_read_vc = nullptr; + + return p; +} + +void +L4RSM::setup_cache_write_transfer(HttpCacheSM *c_sm, VConnection *source_vc, HTTPInfo *store_info, int64_t skip_bytes, + const char *name) +{ + ink_assert(c_sm->cache_write_vc != nullptr); + ink_assert(t_state.request_sent_time > 0); + ink_assert(t_state.response_received_time > 0); + + store_info->request_sent_time_set(t_state.request_sent_time); + store_info->response_received_time_set(t_state.response_received_time); + + c_sm->cache_write_vc->set_http_info(store_info); + store_info->clear(); + + tunnel.add_consumer(c_sm->cache_write_vc, source_vc, &L4RSM::tunnel_handler_cache_write, HT_CACHE_WRITE, name, skip_bytes); + + c_sm->cache_write_vc = nullptr; +} + +void +L4RSM::setup_100_continue_transfer() +{ + MIOBuffer *buf = new_MIOBuffer(HTTP_HEADER_BUFFER_SIZE_INDEX); + IOBufferReader *buf_start = buf->alloc_reader(); + + // First write the client response header into the buffer + ink_assert(t_state.client_info.http_version != HTTPVersion(0, 9)); + client_response_hdr_bytes = write_header_into_buffer(&t_state.hdr_info.client_response, buf); + ink_assert(client_response_hdr_bytes > 0); + + L4R_SM_SET_DEFAULT_HANDLER(&L4RSM::tunnel_handler_100_continue); + + // Clear the decks before we set up new producers. As things stand, we cannot have two static operators + // at once + tunnel.reset(); + + // Setup the tunnel to the client + HttpTunnelProducer *p = tunnel.add_producer(HTTP_TUNNEL_STATIC_PRODUCER, client_response_hdr_bytes, buf_start, + (HttpProducerHandler) nullptr, HT_STATIC, "internal msg - 100 continue"); + tunnel.add_consumer(ua_entry->vc, HTTP_TUNNEL_STATIC_PRODUCER, &L4RSM::tunnel_handler_100_continue_ua, HT_HTTP_CLIENT, + "user agent"); + + // Make sure the half_close is not set. + ua_txn->set_half_close_flag(false); + ua_entry->in_tunnel = true; + tunnel.tunnel_run(p); +} + +////////////////////////////////////////////////////////////////////////// +// +// L4RSM::setup_error_transfer() +// +// The proxy has generated an error message which it +// is sending to the client. For some cases, however, +// such as when the proxy is transparent, returning +// a proxy-generated error message exposes the proxy, +// destroying transparency. The HttpBodyFactory code, +// therefore, does not generate an error message body +// in such cases. This function checks for the presence +// of an error body. If its not present, it closes the +// connection to the user, else it simply calls +// setup_write_proxy_internal, which is the standard +// routine for setting up proxy-generated responses. +// +////////////////////////////////////////////////////////////////////////// +void +L4RSM::setup_error_transfer() +{ + if (t_state.internal_msg_buffer || is_response_body_precluded(t_state.http_return_code)) { + // Since we need to send the error message, call the API + // function + ink_assert(t_state.internal_msg_buffer_size > 0 || is_response_body_precluded(t_state.http_return_code)); + t_state.api_next_action = HttpTransact::SM_ACTION_API_SEND_RESPONSE_HDR; + do_api_callout(); + } else { + SMDebug("http", "[setup_error_transfer] Now closing connection ..."); + vc_table.cleanup_entry(ua_entry); + ua_entry = nullptr; + // ua_txn = NULL; + terminate_sm = true; + t_state.source = HttpTransact::SOURCE_INTERNAL; + } +} + +void +L4RSM::setup_internal_transfer(L4RSMHandler handler_arg) +{ + bool is_msg_buf_present; + + if (t_state.internal_msg_buffer) { + is_msg_buf_present = true; + ink_assert(t_state.internal_msg_buffer_size > 0); + + // Set the content length here since a plugin + // may have changed the error body + t_state.hdr_info.client_response.set_content_length(t_state.internal_msg_buffer_size); + t_state.hdr_info.client_response.field_delete(MIME_FIELD_TRANSFER_ENCODING, MIME_LEN_TRANSFER_ENCODING); + + // set internal_msg_buffer_type if available + if (t_state.internal_msg_buffer_type) { + int len = strlen(t_state.internal_msg_buffer_type); + + if (len > 0) { + t_state.hdr_info.client_response.value_set(MIME_FIELD_CONTENT_TYPE, MIME_LEN_CONTENT_TYPE, t_state.internal_msg_buffer_type, + len); + } + ats_free(t_state.internal_msg_buffer_type); + t_state.internal_msg_buffer_type = nullptr; + } else { + t_state.hdr_info.client_response.value_set(MIME_FIELD_CONTENT_TYPE, MIME_LEN_CONTENT_TYPE, "text/html", 9); + } + } else { + is_msg_buf_present = false; + + // If we are sending a response that can have a body + // but doesn't have a body add a content-length of zero. + // Needed for keep-alive on PURGE requests + if (!is_response_body_precluded(t_state.hdr_info.client_response.status_get(), t_state.method)) { + t_state.hdr_info.client_response.set_content_length(0); + t_state.hdr_info.client_response.field_delete(MIME_FIELD_TRANSFER_ENCODING, MIME_LEN_TRANSFER_ENCODING); + } + } + + t_state.source = HttpTransact::SOURCE_INTERNAL; + + int64_t buf_size = + index_to_buffer_size(HTTP_HEADER_BUFFER_SIZE_INDEX) + (is_msg_buf_present ? t_state.internal_msg_buffer_size : 0); + + MIOBuffer *buf = new_MIOBuffer(buffer_size_to_index(buf_size)); + IOBufferReader *buf_start = buf->alloc_reader(); + + // First write the client response header into the buffer + client_response_hdr_bytes = write_response_header_into_buffer(&t_state.hdr_info.client_response, buf); + int64_t nbytes = client_response_hdr_bytes; + + // Next append the message onto the MIOBuffer + + // From HTTP/1.1 RFC: + // "The HEAD method is identical to GET except that the server + // MUST NOT return a message-body in the response. The metainformation + // in the HTTP headers in response to a HEAD request SHOULD be + // identical to the information sent in response to a GET request." + // --> do not append the message onto the MIOBuffer and keep our pointer + // to it so that it can be freed. + + if (is_msg_buf_present && t_state.method != HTTP_WKSIDX_HEAD) { + nbytes += t_state.internal_msg_buffer_size; + + if (t_state.internal_msg_buffer_fast_allocator_size < 0) { + buf->append_xmalloced(t_state.internal_msg_buffer, t_state.internal_msg_buffer_size); + } else { + buf->append_fast_allocated(t_state.internal_msg_buffer, t_state.internal_msg_buffer_size, + t_state.internal_msg_buffer_fast_allocator_size); + } + + // The IOBufferBlock will free the msg buffer when necessary so + // eliminate our pointer to it + t_state.internal_msg_buffer = nullptr; + t_state.internal_msg_buffer_size = 0; + } + + L4R_SM_SET_DEFAULT_HANDLER(handler_arg); + + // Clear the decks before we setup the new producers + // As things stand, we cannot have two static producers operating at + // once + tunnel.reset(); + + // Setup the tunnel to the client + HttpTunnelProducer *p = + tunnel.add_producer(HTTP_TUNNEL_STATIC_PRODUCER, nbytes, buf_start, (HttpProducerHandler) nullptr, HT_STATIC, "internal msg"); + tunnel.add_consumer(ua_entry->vc, HTTP_TUNNEL_STATIC_PRODUCER, &L4RSM::tunnel_handler_ua, HT_HTTP_CLIENT, "user agent"); + + ua_entry->in_tunnel = true; + tunnel.tunnel_run(p); +} + +// int L4RSM::find_http_resp_buffer_size(int cl) +// +// Returns the allocation index for the buffer for +// a response based on the content length +// +int +L4RSM::find_http_resp_buffer_size(int64_t content_length) +{ + int64_t buf_size; + int64_t alloc_index; + + if (content_length == HTTP_UNDEFINED_CL) { + // Try use our configured default size. Otherwise pick + // the default size + alloc_index = (int)t_state.txn_conf->default_buffer_size_index; + if (alloc_index < MIN_CONFIG_BUFFER_SIZE_INDEX || alloc_index > DEFAULT_MAX_BUFFER_SIZE) { + alloc_index = DEFAULT_RESPONSE_BUFFER_SIZE_INDEX; + } + } else { +#ifdef WRITE_AND_TRANSFER + buf_size = HTTP_HEADER_BUFFER_SIZE + content_length - index_to_buffer_size(HTTP_SERVER_RESP_HDR_BUFFER_INDEX); +#else + buf_size = index_to_buffer_size(HTTP_HEADER_BUFFER_SIZE_INDEX) + content_length; +#endif + alloc_index = buffer_size_to_index(buf_size); + } + + return alloc_index; +} + +// int L4RSM::server_transfer_init() +// +// Moves data from the header buffer into the reply buffer +// and return the number of bytes we should use for initiating the +// tunnel +// +int64_t +L4RSM::server_transfer_init(MIOBuffer *buf, int hdr_size) +{ + int64_t nbytes; + int64_t to_copy = INT64_MAX; + + ink_assert(t_state.current.server != nullptr); // should have been set up if we're doing a transfer. + + if (server_entry->eos == true) { + // The server has shutdown on us already so the only data + // we'll get is already in the buffer + nbytes = server_buffer_reader->read_avail() + hdr_size; + } else if (t_state.hdr_info.response_content_length == HTTP_UNDEFINED_CL) { + nbytes = -1; + } else { + // Set to copy to the number of bytes we want to write as + // if the server is sending us a bogus response we have to + // truncate it as we've already decided to trust the content + // length + to_copy = t_state.hdr_info.response_content_length; + nbytes = t_state.hdr_info.response_content_length + hdr_size; + } + + // Next order of business if copy the remaining data from the + // header buffer into new buffer. + + int64_t server_response_pre_read_bytes = +#ifdef WRITE_AND_TRANSFER + /* relinquish the space in server_buffer and let + the tunnel use the trailing space + */ + buf->write_and_transfer_left_over_space(server_buffer_reader, to_copy); +#else + buf->write(server_buffer_reader, to_copy); +#endif + server_buffer_reader->consume(server_response_pre_read_bytes); + + // If we know the length & copied the entire body + // of the document out of the header buffer make + // sure the server isn't screwing us by having sent too + // much. If it did, we want to close the server connection + if (server_response_pre_read_bytes == to_copy && server_buffer_reader->read_avail() > 0) { + t_state.current.server->keep_alive = HTTP_NO_KEEPALIVE; + } +#ifdef LAZY_BUF_ALLOC + // reset the server session buffer + server_session->reset_read_buffer(); +#endif + return nbytes; +} + +HttpTunnelProducer * +L4RSM::setup_server_transfer_to_transform() +{ + int64_t alloc_index; + int64_t nbytes; + + alloc_index = find_server_buffer_size(); + MIOBuffer *buf = new_MIOBuffer(alloc_index); + IOBufferReader *buf_start = buf->alloc_reader(); + nbytes = server_transfer_init(buf, 0); + + L4R_SM_SET_DEFAULT_HANDLER(&L4RSM::state_response_wait_for_transform_read); + + HttpTunnelProducer *p = + tunnel.add_producer(server_entry->vc, nbytes, buf_start, &L4RSM::tunnel_handler_server, HT_HTTP_SERVER, "http server"); + + tunnel.add_consumer(transform_info.vc, server_entry->vc, &L4RSM::tunnel_handler_transform_write, HT_TRANSFORM, + "transform write"); + + server_entry->in_tunnel = true; + transform_info.entry->in_tunnel = true; + + if (t_state.current.server->transfer_encoding == HttpTransact::CHUNKED_ENCODING) { + client_response_hdr_bytes = 0; // fixed by YTS Team, yamsat + tunnel.set_producer_chunking_action(p, client_response_hdr_bytes, TCA_DECHUNK_CONTENT); + } + + return p; +} + +HttpTunnelProducer * +L4RSM::setup_transfer_from_transform() +{ + int64_t alloc_index = find_server_buffer_size(); + + // TODO change this call to new_empty_MIOBuffer() + MIOBuffer *buf = new_MIOBuffer(alloc_index); + buf->water_mark = (int)t_state.txn_conf->default_buffer_water_mark; + IOBufferReader *buf_start = buf->alloc_reader(); + + HttpTunnelConsumer *c = tunnel.get_consumer(transform_info.vc); + ink_assert(c != nullptr); + ink_assert(c->vc == transform_info.vc); + ink_assert(c->vc_type == HT_TRANSFORM); + + // Now dump the header into the buffer + ink_assert(t_state.hdr_info.client_response.status_get() != HTTP_STATUS_NOT_MODIFIED); + client_response_hdr_bytes = write_response_header_into_buffer(&t_state.hdr_info.client_response, buf); + + L4R_SM_SET_DEFAULT_HANDLER(&L4RSM::tunnel_handler); + + HttpTunnelProducer *p = tunnel.add_producer(transform_info.vc, INT64_MAX, buf_start, &L4RSM::tunnel_handler_transform_read, + HT_TRANSFORM, "transform read"); + tunnel.chain(c, p); + + tunnel.add_consumer(ua_entry->vc, transform_info.vc, &L4RSM::tunnel_handler_ua, HT_HTTP_CLIENT, "user agent"); + + transform_info.entry->in_tunnel = true; + ua_entry->in_tunnel = true; + + this->setup_plugin_agents(p); + + if (t_state.client_info.receive_chunked_response) { + tunnel.set_producer_chunking_action(p, client_response_hdr_bytes, TCA_CHUNK_CONTENT); + tunnel.set_producer_chunking_size(p, t_state.txn_conf->http_chunking_size); + } + + return p; +} + +HttpTunnelProducer * +L4RSM::setup_transfer_from_transform_to_cache_only() +{ + int64_t alloc_index = find_server_buffer_size(); + MIOBuffer *buf = new_MIOBuffer(alloc_index); + IOBufferReader *buf_start = buf->alloc_reader(); + + HttpTunnelConsumer *c = tunnel.get_consumer(transform_info.vc); + ink_assert(c != nullptr); + ink_assert(c->vc == transform_info.vc); + ink_assert(c->vc_type == HT_TRANSFORM); + + L4R_SM_SET_DEFAULT_HANDLER(&L4RSM::tunnel_handler); + + HttpTunnelProducer *p = tunnel.add_producer(transform_info.vc, INT64_MAX, buf_start, &L4RSM::tunnel_handler_transform_read, + HT_TRANSFORM, "transform read"); + tunnel.chain(c, p); + + transform_info.entry->in_tunnel = true; + + ink_assert(t_state.cache_info.transform_action == HttpTransact::CACHE_DO_WRITE); + + perform_transform_cache_write_action(); + + return p; +} + +void +L4RSM::setup_server_transfer_to_cache_only() +{ + TunnelChunkingAction_t action; + int64_t alloc_index; + int64_t nbytes; + + alloc_index = find_server_buffer_size(); + MIOBuffer *buf = new_MIOBuffer(alloc_index); + IOBufferReader *buf_start = buf->alloc_reader(); + + action = (t_state.current.server && t_state.current.server->transfer_encoding == HttpTransact::CHUNKED_ENCODING) ? + TCA_DECHUNK_CONTENT : + TCA_PASSTHRU_DECHUNKED_CONTENT; + + nbytes = server_transfer_init(buf, 0); + + L4R_SM_SET_DEFAULT_HANDLER(&L4RSM::tunnel_handler); + + HttpTunnelProducer *p = + tunnel.add_producer(server_entry->vc, nbytes, buf_start, &L4RSM::tunnel_handler_server, HT_HTTP_SERVER, "http server"); + + tunnel.set_producer_chunking_action(p, 0, action); + tunnel.set_producer_chunking_size(p, t_state.txn_conf->http_chunking_size); + + setup_cache_write_transfer(&cache_sm, server_entry->vc, &t_state.cache_info.object_store, 0, "cache write"); + + server_entry->in_tunnel = true; +} + +HttpTunnelProducer * +L4RSM::setup_server_transfer() +{ + SMDebug("http", "Setup Server Transfer"); + int64_t alloc_index, hdr_size; + int64_t nbytes; + + alloc_index = find_server_buffer_size(); +#ifndef USE_NEW_EMPTY_MIOBUFFER + MIOBuffer *buf = new_MIOBuffer(alloc_index); +#else + MIOBuffer *buf = new_empty_MIOBuffer(alloc_index); + buf->append_block(HTTP_HEADER_BUFFER_SIZE_INDEX); +#endif + buf->water_mark = (int)t_state.txn_conf->default_buffer_water_mark; + IOBufferReader *buf_start = buf->alloc_reader(); + + // we need to know if we are going to chunk the response or not + // before we write the response header into buffer + TunnelChunkingAction_t action; + if (t_state.client_info.receive_chunked_response == false) { + if (t_state.current.server->transfer_encoding == HttpTransact::CHUNKED_ENCODING) { + action = TCA_DECHUNK_CONTENT; + } else { + action = TCA_PASSTHRU_DECHUNKED_CONTENT; + } + } else { + if (t_state.current.server->transfer_encoding != HttpTransact::CHUNKED_ENCODING) { + if (t_state.client_info.http_version == HTTPVersion(0, 9)) { + action = TCA_PASSTHRU_DECHUNKED_CONTENT; // send as-is + } else { + action = TCA_CHUNK_CONTENT; + } + } else { + action = TCA_PASSTHRU_CHUNKED_CONTENT; + } + } + if (action == TCA_CHUNK_CONTENT || action == TCA_PASSTHRU_CHUNKED_CONTENT) { // remove Content-Length + t_state.hdr_info.client_response.field_delete(MIME_FIELD_CONTENT_LENGTH, MIME_LEN_CONTENT_LENGTH); + } + // Now dump the header into the buffer + ink_assert(t_state.hdr_info.client_response.status_get() != HTTP_STATUS_NOT_MODIFIED); + client_response_hdr_bytes = hdr_size = write_response_header_into_buffer(&t_state.hdr_info.client_response, buf); + + nbytes = server_transfer_init(buf, hdr_size); + + if (t_state.negative_caching && t_state.hdr_info.server_response.status_get() == HTTP_STATUS_NO_CONTENT) { + int s = sizeof("No Content") - 1; + buf->write("No Content", s); + nbytes += s; + } + + L4R_SM_SET_DEFAULT_HANDLER(&L4RSM::tunnel_handler); + + HttpTunnelProducer *p = + tunnel.add_producer(server_entry->vc, nbytes, buf_start, &L4RSM::tunnel_handler_server, HT_HTTP_SERVER, "http server"); + + tunnel.add_consumer(ua_entry->vc, server_entry->vc, &L4RSM::tunnel_handler_ua, HT_HTTP_CLIENT, "user agent"); + + ua_entry->in_tunnel = true; + server_entry->in_tunnel = true; + + this->setup_plugin_agents(p); + + // If the incoming server response is chunked and the client does not + // expect a chunked response, then dechunk it. Otherwise, if the + // incoming response is not chunked and the client expects a chunked + // response, then chunk it. + /* + // this block is moved up so that we know if we need to remove + // Content-Length field from response header before writing the + // response header into buffer bz50730 + TunnelChunkingAction_t action; + if (t_state.client_info.receive_chunked_response == false) { + if (t_state.current.server->transfer_encoding == + HttpTransact::CHUNKED_ENCODING) + action = TCA_DECHUNK_CONTENT; + else action = TCA_PASSTHRU_DECHUNKED_CONTENT; + } + else { + if (t_state.current.server->transfer_encoding != + HttpTransact::CHUNKED_ENCODING) + action = TCA_CHUNK_CONTENT; + else action = TCA_PASSTHRU_CHUNKED_CONTENT; + } + */ + tunnel.set_producer_chunking_action(p, client_response_hdr_bytes, action); + tunnel.set_producer_chunking_size(p, t_state.txn_conf->http_chunking_size); + return p; +} + +HttpTunnelProducer * +L4RSM::setup_push_transfer_to_cache() +{ + int64_t nbytes, alloc_index; + + alloc_index = find_http_resp_buffer_size(t_state.hdr_info.request_content_length); + MIOBuffer *buf = new_MIOBuffer(alloc_index); + IOBufferReader *buf_start = buf->alloc_reader(); + + ink_release_assert(t_state.hdr_info.request_content_length != HTTP_UNDEFINED_CL); + nbytes = t_state.hdr_info.request_content_length - pushed_response_hdr_bytes; + ink_release_assert(nbytes >= 0); + + if (ua_entry->eos == true) { + // The ua has shutdown on us already so the only data + // we'll get is already in the buffer. Make sure it + // fulfills the stated length + int64_t avail = ua_buffer_reader->read_avail(); + + if (avail < nbytes) { + // Client failed to send the body, it's gone. Kill the + // state machine + terminate_sm = true; + return nullptr; + } + } + // Next order of business is copy the remaining data from the + // header buffer into new buffer. + pushed_response_body_bytes = buf->write(ua_buffer_reader, nbytes); + ua_buffer_reader->consume(pushed_response_body_bytes); + client_request_body_bytes += pushed_response_body_bytes; + + L4R_SM_SET_DEFAULT_HANDLER(&L4RSM::tunnel_handler_push); + + HttpTunnelProducer *p = + tunnel.add_producer(ua_entry->vc, nbytes, buf_start, &L4RSM::tunnel_handler_ua_push, HT_HTTP_CLIENT, "user_agent"); + setup_cache_write_transfer(&cache_sm, ua_entry->vc, &t_state.cache_info.object_store, 0, "cache write"); + + ua_entry->in_tunnel = true; + return p; +} + +void +L4RSM::setup_blind_tunnel(bool send_response_hdr, IOBufferReader *initial) +{ + HttpTunnelConsumer *c_ua; + HttpTunnelConsumer *c_os; + HttpTunnelProducer *p_ua; + HttpTunnelProducer *p_os; + MIOBuffer *from_ua_buf = new_MIOBuffer(BUFFER_SIZE_INDEX_32K); + MIOBuffer *to_ua_buf = new_MIOBuffer(BUFFER_SIZE_INDEX_32K); + IOBufferReader *r_from = from_ua_buf->alloc_reader(); + IOBufferReader *r_to = to_ua_buf->alloc_reader(); + + milestones[TS_MILESTONE_SERVER_BEGIN_WRITE] = Thread::get_hrtime(); + if (send_response_hdr) { + client_response_hdr_bytes = write_response_header_into_buffer(&t_state.hdr_info.client_response, to_ua_buf); + if (initial && initial->read_avail()) { + int64_t avail = initial->read_avail(); + to_ua_buf->write(initial, avail); + initial->consume(avail); + } + } else { + client_response_hdr_bytes = 0; + } + + client_request_body_bytes = 0; + if (ua_raw_buffer_reader != nullptr) { + client_request_body_bytes += from_ua_buf->write(ua_raw_buffer_reader, client_request_hdr_bytes); + ua_raw_buffer_reader->dealloc(); + ua_raw_buffer_reader = nullptr; + } + + // Next order of business if copy the remaining data from the + // header buffer into new buffer + client_request_body_bytes += from_ua_buf->write(ua_buffer_reader); + + L4R_SM_SET_DEFAULT_HANDLER(&L4RSM::tunnel_handler); + + p_os = + tunnel.add_producer(server_entry->vc, -1, r_to, &L4RSM::tunnel_handler_ssl_producer, HT_HTTP_SERVER, "http server - tunnel"); + + c_ua = tunnel.add_consumer(ua_entry->vc, server_entry->vc, &L4RSM::tunnel_handler_ssl_consumer, HT_HTTP_CLIENT, + "user agent - tunnel"); + + p_ua = tunnel.add_producer(ua_entry->vc, -1, r_from, &L4RSM::tunnel_handler_ssl_producer, HT_HTTP_CLIENT, "user agent - tunnel"); + + c_os = tunnel.add_consumer(server_entry->vc, ua_entry->vc, &L4RSM::tunnel_handler_ssl_consumer, HT_HTTP_SERVER, + "http server - tunnel"); + + // Make the tunnel aware that the entries are bi-directional + tunnel.chain(c_os, p_os); + tunnel.chain(c_ua, p_ua); + + ua_entry->in_tunnel = true; + server_entry->in_tunnel = true; + + tunnel.tunnel_run(); + + // If we're half closed, we got a FIN from the client. Forward it on to the origin server + // now that we have the tunnel operational. + if (ua_txn && ua_txn->get_half_close_flag()) { + p_ua->vc->do_io_shutdown(IO_SHUTDOWN_READ); + } +} + +void +L4RSM::setup_plugin_agents(HttpTunnelProducer *p) +{ + APIHook *agent = txn_hook_get(TS_HTTP_RESPONSE_CLIENT_HOOK); + has_active_plugin_agents = agent != nullptr; + while (agent) { + INKVConnInternal *contp = static_cast(agent->m_cont); + tunnel.add_consumer(contp, p->vc, &L4RSM::tunnel_handler_plugin_agent, HT_HTTP_CLIENT, "plugin agent"); + // We don't put these in the SM VC table because the tunnel + // will clean them up in do_io_close(). + agent = agent->next(); + } +} + +inline void +L4RSM::transform_cleanup(TSHttpHookID hook, HttpTransformInfo *info) +{ + APIHook *t_hook = api_hooks.get(hook); + if (t_hook && info->vc == nullptr) { + do { + VConnection *t_vcon = t_hook->m_cont; + t_vcon->do_io_close(); + t_hook = t_hook->m_link.next; + } while (t_hook != nullptr); + } +} + +void +L4RSM::plugin_agents_cleanup() +{ + // If this is set then all of the plugin agent VCs were put in + // the VC table and cleaned up there. This handles the case where + // something went wrong early. + if (!has_active_plugin_agents) { + APIHook *agent = txn_hook_get(TS_HTTP_RESPONSE_CLIENT_HOOK); + while (agent) { + INKVConnInternal *contp = static_cast(agent->m_cont); + contp->do_io_close(); + agent = agent->next(); + } + } +} + +////////////////////////////////////////////////////////////////////////// +// +// L4RSM::kill_this() +// +// This function has two phases. One before we call the asynchronous +// clean up routines (api and list removal) and one after. +// The state about which phase we are in is kept in +// L4RSM::kill_this_async_done +// +////////////////////////////////////////////////////////////////////////// +void +L4RSM::kill_this() +{ + ink_release_assert(reentrancy_count == 1); + this->postbuf_clear(); + enable_redirection = false; + + if (kill_this_async_done == false) { + //////////////////////////////// + // cancel uncompleted actions // + //////////////////////////////// + // The action should be cancelled only if + // the state machine is in HTTP_API_NO_CALLOUT + // state. This is because we are depending on the + // callout to complete for the state machine to + // get killed. + if (callout_state == HTTP_API_NO_CALLOUT && pending_action) { + pending_action->cancel(); + pending_action = nullptr; + } else if (pending_action) { + ink_assert(pending_action == nullptr); + } + + cache_sm.end_both(); + transform_cache_sm.end_both(); + vc_table.cleanup_all(); + + // tunnel.deallocate_buffers(); + // Why don't we just kill the tunnel? Might still be + // active if the state machine is going down hard, + // and we should clean it up. + tunnel.kill_tunnel(); + + // It possible that a plugin added transform hook + // but the hook never executed due to a client abort + // In that case, we need to manually close all the + // transforms to prevent memory leaks (INKqa06147) + if (hooks_set) { + transform_cleanup(TS_HTTP_RESPONSE_TRANSFORM_HOOK, &transform_info); + transform_cleanup(TS_HTTP_REQUEST_TRANSFORM_HOOK, &post_transform_info); + plugin_agents_cleanup(); + } + // It's also possible that the plugin_tunnel vc was never + // executed due to not contacting the server + if (plugin_tunnel) { + plugin_tunnel->kill_no_connect(); + plugin_tunnel = nullptr; + } + + server_session = nullptr; + + // So we don't try to nuke the state machine + // if the plugin receives event we must reset + // the terminate_flag + terminate_sm = false; + t_state.api_next_action = HttpTransact::SM_ACTION_API_SM_SHUTDOWN; + do_api_callout(); + } + // The reentrancy_count is still valid up to this point since + // the api shutdown hook is asynchronous and double frees can + // happen if the reentrancy count is not still valid until + // after all asynch callouts have completed + // + // Once we get to this point, we could be waiting for async + // completion in which case we need to decrement the reentrancy + // count since the entry points can't do it for us since they + // don't know if the state machine has been destroyed. In the + // case we really are done with asynch callouts, decrement the + // reentrancy count since it seems tacky to destruct a state + // machine with non-zero count + reentrancy_count--; + ink_release_assert(reentrancy_count == 0); + + // If the api shutdown & list removal was synchronous + // then the value of kill_this_async_done has changed so + // we must check it again + if (kill_this_async_done == true) { + ink_assert(pending_action == nullptr); + if (t_state.http_config_param->enable_http_stats) { + update_stats(); + } + + if (ua_txn) { + ua_txn->transaction_done(); + } + + // In the async state, the plugin could have been + // called resulting in the creation of a plugin_tunnel. + // So it needs to be deleted now. + if (plugin_tunnel) { + plugin_tunnel->kill_no_connect(); + plugin_tunnel = nullptr; + } + + ink_assert(pending_action == nullptr); + ink_release_assert(vc_table.is_table_clear() == true); + ink_release_assert(tunnel.is_tunnel_active() == false); + + L4R_SM_SET_DEFAULT_HANDLER(nullptr); + + ////////////// + // Log Data // + ////////////// + SMDebug("http_seq", "[L4RSM::update_stats] Logging transaction"); + if (Log::transaction_logging_enabled() && t_state.api_info.logging_enabled) { + LogAccessHttp accessor(this); + + int ret = Log::access(&accessor); + + if (ret & Log::FULL) { + SMDebug("http", "[update_stats] Logging system indicates FULL."); + } + if (ret & Log::FAIL) { + Log::error("failed to log transaction for at least one log object"); + } + } + + if (redirect_url != nullptr) { + ats_free((void *)redirect_url); + redirect_url = nullptr; + redirect_url_len = 0; + } + +#ifdef USE_HTTP_DEBUG_LISTS + ink_mutex_acquire(&debug_sm_list_mutex); + debug_sm_list.remove(this); + ink_mutex_release(&debug_sm_list_mutex); +#endif + + SMDebug("http", "[%" PRId64 "] deallocating sm", sm_id); + // authAdapter.destroyState(); + HTTP_DECREMENT_DYN_STAT(http_current_client_transactions_stat); + destroy(); + } +} + +void +L4RSM::update_stats() +{ + milestones[TS_MILESTONE_SM_FINISH] = Thread::get_hrtime(); + + if (is_action_tag_set("bad_length_state_dump")) { + if (t_state.hdr_info.client_response.valid() && t_state.hdr_info.client_response.status_get() == HTTP_STATUS_OK) { + int64_t p_resp_cl = t_state.hdr_info.client_response.get_content_length(); + int64_t resp_size = client_response_body_bytes; + if (!((p_resp_cl == -1 || p_resp_cl == resp_size || resp_size == 0))) { + Error("[%" PRId64 "] Truncated content detected", sm_id); + dump_state_on_assert(); + } + } else if (client_request_hdr_bytes == 0) { + Error("[%" PRId64 "] Zero length request header received", sm_id); + dump_state_on_assert(); + } + } + + if (is_action_tag_set("assert_jtest_length")) { + if (t_state.hdr_info.client_response.valid() && t_state.hdr_info.client_response.status_get() == HTTP_STATUS_OK) { + int64_t p_resp_cl = t_state.hdr_info.client_response.get_content_length(); + int64_t resp_size = client_response_body_bytes; + ink_release_assert(p_resp_cl == -1 || p_resp_cl == resp_size || resp_size == 0); + } + } + + ink_hrtime total_time = milestones.elapsed(TS_MILESTONE_SM_START, TS_MILESTONE_SM_FINISH); + + // ua_close will not be assigned properly in some exceptional situation. + // TODO: Assign ua_close with suitable value when HttpTunnel terminates abnormally. + if (milestones[TS_MILESTONE_UA_CLOSE] == 0 && milestones[TS_MILESTONE_UA_READ_HEADER_DONE] > 0) { + milestones[TS_MILESTONE_UA_CLOSE] = Thread::get_hrtime(); + } + + // request_process_time = The time after the header is parsed to the completion of the transaction + ink_hrtime request_process_time = milestones[TS_MILESTONE_UA_CLOSE] - milestones[TS_MILESTONE_UA_READ_HEADER_DONE]; + + HttpTransact::client_result_stat(&t_state, total_time, request_process_time); + + ink_hrtime ua_write_time; + if (milestones[TS_MILESTONE_UA_BEGIN_WRITE] != 0 && milestones[TS_MILESTONE_UA_CLOSE] != 0) { + ua_write_time = milestones.elapsed(TS_MILESTONE_UA_BEGIN_WRITE, TS_MILESTONE_UA_CLOSE); + } else { + ua_write_time = -1; + } + + ink_hrtime os_read_time; + if (milestones[TS_MILESTONE_SERVER_READ_HEADER_DONE] != 0 && milestones[TS_MILESTONE_SERVER_CLOSE] != 0) { + os_read_time = milestones.elapsed(TS_MILESTONE_SERVER_READ_HEADER_DONE, TS_MILESTONE_SERVER_CLOSE); + } else { + os_read_time = -1; + } + + HttpTransact::update_size_and_time_stats( + &t_state, total_time, ua_write_time, os_read_time, client_request_hdr_bytes, client_request_body_bytes, + client_response_hdr_bytes, client_response_body_bytes, server_request_hdr_bytes, server_request_body_bytes, + server_response_hdr_bytes, server_response_body_bytes, pushed_response_hdr_bytes, pushed_response_body_bytes, milestones); + /* + if (is_action_tag_set("http_handler_times")) { + print_all_http_handler_times(); + } + */ + + // print slow requests if the threshold is set (> 0) and if we are over the time threshold + if (t_state.txn_conf->slow_log_threshold != 0 && ink_hrtime_from_msec(t_state.txn_conf->slow_log_threshold) < total_time) { + char url_string[256] = ""; + int offset = 0; + int skip = 0; + + t_state.hdr_info.client_request.url_print(url_string, sizeof(url_string) - 1, &offset, &skip); + url_string[offset] = 0; // NULL terminate the string + + // unique id + char unique_id_string[128] = ""; + int length = 0; + const char *field = t_state.hdr_info.client_request.value_get(MIME_FIELD_X_ID, MIME_LEN_X_ID, &length); + if (field != nullptr && length > 0) { + length = std::min(length, static_cast(sizeof(unique_id_string)) - 1); + memcpy(unique_id_string, field, length); + unique_id_string[length] = 0; // NULL terminate the string + } + + // set the fd for the request + int fd = 0; + NetVConnection *vc = nullptr; + if (ua_txn != nullptr) { + vc = ua_txn->get_netvc(); + if (vc != nullptr) { + fd = vc->get_socket(); + } else { + fd = -1; + } + } + // get the status code, lame that we have to check to see if it is valid or we will assert in the method call + int status = 0; + if (t_state.hdr_info.client_response.valid()) { + status = t_state.hdr_info.client_response.status_get(); + } + char client_ip[INET6_ADDRSTRLEN]; + ats_ip_ntop(&t_state.client_info.src_addr, client_ip, sizeof(client_ip)); + Error("[%" PRId64 "] Slow Request: " + "client_ip: %s:%u " + "protocol: %s " + "url: %s " + "status: %d " + "unique id: %s " + "redirection_tries: %d " + "bytes: %" PRId64 " " + "fd: %d " + "client state: %d " + "server state: %d " + "ua_begin: %.3f " + "ua_first_read: %.3f " + "ua_read_header_done: %.3f " + "cache_open_read_begin: %.3f " + "cache_open_read_end: %.3f " + "dns_lookup_begin: %.3f " + "dns_lookup_end: %.3f " + "server_connect: %.3f " + "server_connect_end: %.3f " + "server_first_read: %.3f " + "server_read_header_done: %.3f " + "server_close: %.3f " + "ua_write: %.3f " + "ua_close: %.3f " + "sm_finish: %.3f " + "plugin_active: %.3f " + "plugin_total: %.3f", + sm_id, client_ip, t_state.client_info.src_addr.host_order_port(), ua_txn ? ua_txn->get_protocol_string() : "-1", + url_string, status, unique_id_string, redirection_tries, client_response_body_bytes, fd, t_state.client_info.state, + t_state.server_info.state, milestones.difference_sec(TS_MILESTONE_SM_START, TS_MILESTONE_UA_BEGIN), + milestones.difference_sec(TS_MILESTONE_SM_START, TS_MILESTONE_UA_FIRST_READ), + milestones.difference_sec(TS_MILESTONE_SM_START, TS_MILESTONE_UA_READ_HEADER_DONE), + milestones.difference_sec(TS_MILESTONE_SM_START, TS_MILESTONE_CACHE_OPEN_READ_BEGIN), + milestones.difference_sec(TS_MILESTONE_SM_START, TS_MILESTONE_CACHE_OPEN_READ_END), + milestones.difference_sec(TS_MILESTONE_SM_START, TS_MILESTONE_DNS_LOOKUP_BEGIN), + milestones.difference_sec(TS_MILESTONE_SM_START, TS_MILESTONE_DNS_LOOKUP_END), + milestones.difference_sec(TS_MILESTONE_SM_START, TS_MILESTONE_SERVER_CONNECT), + milestones.difference_sec(TS_MILESTONE_SM_START, TS_MILESTONE_SERVER_CONNECT_END), + milestones.difference_sec(TS_MILESTONE_SM_START, TS_MILESTONE_SERVER_FIRST_READ), + milestones.difference_sec(TS_MILESTONE_SM_START, TS_MILESTONE_SERVER_READ_HEADER_DONE), + milestones.difference_sec(TS_MILESTONE_SM_START, TS_MILESTONE_SERVER_CLOSE), + milestones.difference_sec(TS_MILESTONE_SM_START, TS_MILESTONE_UA_BEGIN_WRITE), + milestones.difference_sec(TS_MILESTONE_SM_START, TS_MILESTONE_UA_CLOSE), + milestones.difference_sec(TS_MILESTONE_SM_START, TS_MILESTONE_SM_FINISH), + milestones.difference_sec(TS_MILESTONE_SM_START, TS_MILESTONE_PLUGIN_ACTIVE), + milestones.difference_sec(TS_MILESTONE_SM_START, TS_MILESTONE_PLUGIN_TOTAL)); + } +} + +// +// void L4RSM::dump_state_on_assert +// Debugging routine to dump the state machine's history +// and other state on an assertion failure +// We use Diags::Status instead of stderr since +// Diags works both on UNIX & NT +// +void +L4RSM::dump_state_on_assert() +{ + Error("[%" PRId64 "] ------- begin http state dump -------", sm_id); + + if (history.overflowed()) { + Error(" History Wrap around. history size: %d", history.size()); + } + // Loop through the history and dump it + for (unsigned int i = 0; i < history.size(); i++) { + char buf[256]; + int r = history[i].reentrancy; + int e = history[i].event; + Error("%d %d %s", e, r, history[i].location.str(buf, sizeof(buf))); + } + + // Dump the via string + Error("Via String: [%s]\n", t_state.via_string); + + // Dump header info + dump_state_hdr(&t_state.hdr_info.client_request, "Client Request"); + dump_state_hdr(&t_state.hdr_info.server_request, "Server Request"); + dump_state_hdr(&t_state.hdr_info.server_response, "Server Response"); + dump_state_hdr(&t_state.hdr_info.transform_response, "Transform Response"); + dump_state_hdr(&t_state.hdr_info.client_response, "Client Response"); + + Error("[%" PRId64 "] ------- end http state dump ---------", sm_id); +} + +void +L4RSM::dump_state_hdr(HTTPHdr *h, const char *s) +{ + // Dump the client request if available + if (h->valid()) { + int l = h->length_get(); + char *hdr_buf = (char *)ats_malloc(l + 1); + int index = 0; + int offset = 0; + + h->print(hdr_buf, l, &index, &offset); + + hdr_buf[l] = '\0'; + Error(" ---- %s [%" PRId64 "] ----\n%s\n", s, sm_id, hdr_buf); + ats_free(hdr_buf); + } +} + +/***************************************************************************** + ***************************************************************************** + **** **** + **** HttpTransact Interface **** + **** **** + ***************************************************************************** + *****************************************************************************/ +////////////////////////////////////////////////////////////////////////// +// +// L4RSM::call_transact_and_set_next_state(f) +// +// This routine takes an HttpTransact function , calls the function +// to perform some actions on the current HttpTransact::State, and +// then uses the HttpTransact return action code to set the next +// handler (state) for the state machine. HttpTransact could have +// returned the handler directly, but returns action codes in hopes of +// making a cleaner separation between the state machine and the +// HttpTransact logic. +// +////////////////////////////////////////////////////////////////////////// + +// Where is the goatherd? + +void +L4RSM::call_transact_and_set_next_state(TransactEntryFunc_t f) +{ + last_action = t_state.next_action; // remember where we were + + // The callee can either specify a method to call in to Transact, + // or call with NULL which indicates that Transact should use + // its stored entry point. + if (f == nullptr) { + ink_release_assert(t_state.transact_return_point != nullptr); + t_state.transact_return_point(&t_state); + } else { + f(&t_state); + } + + SMDebug("http", "[%" PRId64 "] State Transition: %s -> %s", sm_id, HttpDebugNames::get_action_name(last_action), + HttpDebugNames::get_action_name(t_state.next_action)); + + set_next_state(); + + return; +} + +////////////////////////////////////////////////////////////////////////////// +// +// L4RSM::set_next_state() +// +// call_transact_and_set_next_state() was broken into two parts, one +// which calls the HttpTransact method and the second which sets the +// next state. In a case which set_next_state() was not completed, +// the state function calls set_next_state() to retry setting the +// state. +// +////////////////////////////////////////////////////////////////////////////// +void +L4RSM::set_next_state() +{ + /////////////////////////////////////////////////////////////////////// + // Use the returned "next action" code to set the next state handler // + /////////////////////////////////////////////////////////////////////// + switch (t_state.next_action) { + case HttpTransact::SM_ACTION_API_PRE_REMAP: + case HttpTransact::SM_ACTION_API_POST_REMAP: + case HttpTransact::SM_ACTION_API_READ_REQUEST_HDR: + case HttpTransact::SM_ACTION_REQUEST_BUFFER_READ_COMPLETE: + case HttpTransact::SM_ACTION_API_OS_DNS: + case HttpTransact::SM_ACTION_API_SEND_REQUEST_HDR: + case HttpTransact::SM_ACTION_API_READ_CACHE_HDR: + case HttpTransact::SM_ACTION_API_READ_RESPONSE_HDR: + case HttpTransact::SM_ACTION_API_SEND_RESPONSE_HDR: + case HttpTransact::SM_ACTION_API_CACHE_LOOKUP_COMPLETE: { + t_state.api_next_action = t_state.next_action; + do_api_callout(); + break; + } + + case HttpTransact::SM_ACTION_POST_REMAP_SKIP: { + call_transact_and_set_next_state(nullptr); + break; + } + + case HttpTransact::SM_ACTION_REMAP_REQUEST: { + if (!remapProcessor.using_separate_thread()) { + do_remap_request(true); /* run inline */ + SMDebug("url_rewrite", "completed inline remapping request for [%" PRId64 "]", sm_id); + t_state.url_remap_success = remapProcessor.finish_remap(&t_state, m_remap); + if (t_state.next_action == HttpTransact::SM_ACTION_SEND_ERROR_CACHE_NOOP && t_state.transact_return_point == nullptr) { + // It appears that we can now set the next_action to error and transact_return_point to nullptr when + // going through do_remap_request presumably due to a plugin setting an error. In that case, it seems + // that the error message has already been setup, so we can just return and avoid the further + // call_transact_and_set_next_state + } else { + call_transact_and_set_next_state(nullptr); + } + } else { + L4R_SM_SET_DEFAULT_HANDLER(&L4RSM::state_remap_request); + do_remap_request(false); /* dont run inline (iow on another thread) */ + } + break; + } + + case HttpTransact::SM_ACTION_DNS_LOOKUP: { + sockaddr const *addr; + + if (t_state.api_server_addr_set) { + /* If the API has set the server address before the OS DNS lookup + * then we can skip the lookup + */ + ip_text_buffer ipb; + SMDebug("dns", "[HttpTransact::HandleRequest] Skipping DNS lookup for API supplied target %s.", + ats_ip_ntop(&t_state.server_info.dst_addr, ipb, sizeof(ipb))); + // this seems wasteful as we will just copy it right back + ats_ip_copy(t_state.host_db_info.ip(), &t_state.server_info.dst_addr); + t_state.dns_info.lookup_success = true; + call_transact_and_set_next_state(nullptr); + break; + } else if (0 == ats_ip_pton(t_state.dns_info.lookup_name, t_state.host_db_info.ip()) && + ats_is_ip_loopback(t_state.host_db_info.ip())) { + // If it's 127.0.0.1 or ::1 don't bother with hostdb + SMDebug("dns", "[HttpTransact::HandleRequest] Skipping DNS lookup for %s because it's loopback", + t_state.dns_info.lookup_name); + t_state.dns_info.lookup_success = true; + call_transact_and_set_next_state(nullptr); + break; + } else if (t_state.http_config_param->use_client_target_addr == 2 && !t_state.url_remap_success && + t_state.parent_result.result != PARENT_SPECIFIED && t_state.client_info.is_transparent && + t_state.dns_info.os_addr_style == HttpTransact::DNSLookupInfo::OS_Addr::OS_ADDR_TRY_DEFAULT && + ats_is_ip(addr = t_state.state_machine->ua_txn->get_netvc()->get_local_addr())) { + /* If the connection is client side transparent and the URL + * was not remapped/directed to parent proxy, we can use the + * client destination IP address instead of doing a DNS + * lookup. This is controlled by the 'use_client_target_addr' + * configuration parameter. + */ + if (is_debug_tag_set("dns")) { + ip_text_buffer ipb; + SMDebug("dns", "[HttpTransact::HandleRequest] Skipping DNS lookup for client supplied target %s.", + ats_ip_ntop(addr, ipb, sizeof(ipb))); + } + ats_ip_copy(t_state.host_db_info.ip(), addr); + if (t_state.hdr_info.client_request.version_get() == HTTPVersion(0, 9)) { + t_state.host_db_info.app.http_data.http_version = HostDBApplicationInfo::HTTP_VERSION_09; + } else if (t_state.hdr_info.client_request.version_get() == HTTPVersion(1, 0)) { + t_state.host_db_info.app.http_data.http_version = HostDBApplicationInfo::HTTP_VERSION_10; + } else { + t_state.host_db_info.app.http_data.http_version = HostDBApplicationInfo::HTTP_VERSION_11; + } + + t_state.dns_info.lookup_success = true; + // cache this result so we don't have to unreliably duplicate the + // logic later if the connect fails. + t_state.dns_info.os_addr_style = HttpTransact::DNSLookupInfo::OS_Addr::OS_ADDR_TRY_CLIENT; + call_transact_and_set_next_state(nullptr); + break; + } else if (t_state.parent_result.result == PARENT_UNDEFINED && t_state.dns_info.lookup_success) { + // Already set, and we don't have a parent proxy to lookup + ink_assert(ats_is_ip(t_state.host_db_info.ip())); + SMDebug("dns", "[HttpTransact::HandleRequest] Skipping DNS lookup, provided by plugin"); + call_transact_and_set_next_state(nullptr); + break; + } else if (t_state.dns_info.looking_up == HttpTransact::ORIGIN_SERVER && t_state.http_config_param->no_dns_forward_to_parent && + t_state.parent_result.result != PARENT_UNDEFINED) { + t_state.dns_info.lookup_success = true; + call_transact_and_set_next_state(nullptr); + break; + } + + L4R_SM_SET_DEFAULT_HANDLER(&L4RSM::state_hostdb_lookup); + + // We need to close the previous attempt + // Because it could be a server side retry by DNS rr + if (server_entry) { + ink_assert(server_entry->vc_type == HTTP_SERVER_VC); + vc_table.cleanup_entry(server_entry); + server_entry = nullptr; + server_session = nullptr; + } else { + // Now that we have gotten the user agent request, we can cancel + // the inactivity timeout associated with it. Note, however, that + // we must not cancel the inactivity timeout if the message + // contains a body (as indicated by the non-zero request_content_length + // field). This indicates that a POST operation is taking place and + // that the client is still sending data to the origin server. The + // origin server cannot reply until the entire request is received. In + // light of this dependency, TS must ensure that the client finishes + // sending its request and for this reason, the inactivity timeout + // cannot be cancelled. + if (ua_txn && !t_state.hdr_info.request_content_length) { + ua_txn->cancel_inactivity_timeout(); + } else if (!ua_txn) { + terminate_sm = true; + return; // Give up if there is no session + } + } + + ink_assert(t_state.dns_info.looking_up != HttpTransact::UNDEFINED_LOOKUP); + do_hostdb_lookup(); + break; + } + + case HttpTransact::SM_ACTION_DNS_REVERSE_LOOKUP: { + L4R_SM_SET_DEFAULT_HANDLER(&L4RSM::state_hostdb_reverse_lookup); + do_hostdb_reverse_lookup(); + break; + } + + case HttpTransact::SM_ACTION_CACHE_LOOKUP: { + L4R_SM_SET_DEFAULT_HANDLER(&L4RSM::state_cache_open_read); + do_cache_lookup_and_read(); + break; + } + + case HttpTransact::SM_ACTION_ORIGIN_SERVER_OPEN: { + L4R_SM_SET_DEFAULT_HANDLER(&L4RSM::state_http_server_open); + + // We need to close the previous attempt + if (server_entry) { + ink_assert(server_entry->vc_type == HTTP_SERVER_VC); + vc_table.cleanup_entry(server_entry); + server_entry = nullptr; + server_session = nullptr; + } else { + // Now that we have gotten the user agent request, we can cancel + // the inactivity timeout associated with it. Note, however, that + // we must not cancel the inactivity timeout if the message + // contains a body (as indicated by the non-zero request_content_length + // field). This indicates that a POST operation is taking place and + // that the client is still sending data to the origin server. The + // origin server cannot reply until the entire request is received. In + // light of this dependency, TS must ensure that the client finishes + // sending its request and for this reason, the inactivity timeout + // cannot be cancelled. + if (ua_txn && !t_state.hdr_info.request_content_length) { + ua_txn->cancel_inactivity_timeout(); + } else if (!ua_txn) { + terminate_sm = true; + return; // Give up if there is no session + } + } + + do_http_server_open(); + break; + } + + case HttpTransact::SM_ACTION_SERVER_PARSE_NEXT_HDR: { + setup_server_read_response_header(); + break; + } + + case HttpTransact::SM_ACTION_INTERNAL_100_RESPONSE: { + setup_100_continue_transfer(); + break; + } + + case HttpTransact::SM_ACTION_SERVER_READ: { + t_state.source = HttpTransact::SOURCE_HTTP_ORIGIN_SERVER; + + if (transform_info.vc) { + ink_assert(t_state.hdr_info.client_response.valid() == 0); + ink_assert((t_state.hdr_info.transform_response.valid() ? true : false) == true); + HttpTunnelProducer *p = setup_server_transfer_to_transform(); + perform_cache_write_action(); + tunnel.tunnel_run(p); + } else { + ink_assert((t_state.hdr_info.client_response.valid() ? true : false) == true); + t_state.api_next_action = HttpTransact::SM_ACTION_API_SEND_RESPONSE_HDR; + + // check to see if we are going to handle the redirection from server response and if there is a plugin hook set + if (hooks_set && is_redirect_required() == false) { + do_api_callout_internal(); + } else { + do_redirect(); + handle_api_return(); + } + } + break; + } + + case HttpTransact::SM_ACTION_SERVE_FROM_CACHE: { + ink_assert(t_state.cache_info.action == HttpTransact::CACHE_DO_SERVE || + t_state.cache_info.action == HttpTransact::CACHE_DO_SERVE_AND_DELETE || + t_state.cache_info.action == HttpTransact::CACHE_DO_SERVE_AND_UPDATE); + release_server_session(true); + t_state.source = HttpTransact::SOURCE_CACHE; + + do_drain_request_body(); + + if (transform_info.vc) { + ink_assert(t_state.hdr_info.client_response.valid() == 0); + ink_assert((t_state.hdr_info.transform_response.valid() ? true : false) == true); + t_state.hdr_info.cache_response.create(HTTP_TYPE_RESPONSE); + t_state.hdr_info.cache_response.copy(&t_state.hdr_info.transform_response); + + HttpTunnelProducer *p = setup_cache_transfer_to_transform(); + perform_cache_write_action(); + tunnel.tunnel_run(p); + } else { + ink_assert((t_state.hdr_info.client_response.valid() ? true : false) == true); + t_state.hdr_info.cache_response.create(HTTP_TYPE_RESPONSE); + t_state.hdr_info.cache_response.copy(&t_state.hdr_info.client_response); + + perform_cache_write_action(); + t_state.api_next_action = HttpTransact::SM_ACTION_API_SEND_RESPONSE_HDR; + + // check to see if we are going to handle the redirection from server response and if there is a plugin hook set + if (hooks_set && is_redirect_required() == false) { + do_api_callout_internal(); + } else { + do_redirect(); + handle_api_return(); + } + } + break; + } + + case HttpTransact::SM_ACTION_CACHE_ISSUE_WRITE: { + ink_assert((cache_sm.cache_write_vc == nullptr) || t_state.redirect_info.redirect_in_process); + L4R_SM_SET_DEFAULT_HANDLER(&L4RSM::state_cache_open_write); + + do_cache_prepare_write(); + break; + } + + case HttpTransact::SM_ACTION_INTERNAL_CACHE_WRITE: { + t_state.api_next_action = HttpTransact::SM_ACTION_API_SEND_RESPONSE_HDR; + do_api_callout(); + break; + } + + case HttpTransact::SM_ACTION_INTERNAL_CACHE_NOOP: { + if (server_entry != nullptr && server_entry->in_tunnel == false) { + release_server_session(); + } + // If we're in state SEND_API_RESPONSE_HDR, it means functions + // registered to hook SEND_RESPONSE_HDR have already been called. So we do not + // need to call do_api_callout. Otherwise TS loops infinitely in this state ! + if (t_state.api_next_action == HttpTransact::SM_ACTION_API_SEND_RESPONSE_HDR) { + handle_api_return(); + } else { + t_state.api_next_action = HttpTransact::SM_ACTION_API_SEND_RESPONSE_HDR; + do_api_callout(); + } + break; + } + + case HttpTransact::SM_ACTION_INTERNAL_CACHE_DELETE: { + // Nuke all the alternates since this is mostly likely + // the result of a delete method + cache_sm.end_both(); + do_cache_delete_all_alts(nullptr); + + release_server_session(); + t_state.api_next_action = HttpTransact::SM_ACTION_API_SEND_RESPONSE_HDR; + do_api_callout(); + break; + } + + case HttpTransact::SM_ACTION_INTERNAL_CACHE_UPDATE_HEADERS: { + issue_cache_update(); + cache_sm.close_read(); + + release_server_session(); + t_state.api_next_action = HttpTransact::SM_ACTION_API_SEND_RESPONSE_HDR; + do_api_callout(); + break; + } + + case HttpTransact::SM_ACTION_SEND_ERROR_CACHE_NOOP: { + setup_error_transfer(); + break; + } + + case HttpTransact::SM_ACTION_INTERNAL_REQUEST: { + L4R_SM_SET_DEFAULT_HANDLER(&L4RSM::state_handle_stat_page); + Action *action_handle = statPagesManager.handle_http(this, &t_state.hdr_info.client_request); + + if (action_handle != ACTION_RESULT_DONE) { + ink_assert(pending_action == nullptr); + pending_action = action_handle; + } + + break; + } + + case HttpTransact::SM_ACTION_ORIGIN_SERVER_RR_MARK_DOWN: { + L4R_SM_SET_DEFAULT_HANDLER(&L4RSM::state_mark_os_down); + + ink_assert(t_state.dns_info.looking_up == HttpTransact::ORIGIN_SERVER); + + // TODO: This might not be optimal (or perhaps even correct), but it will + // effectively mark the host as down. What's odd is that state_mark_os_down + // above isn't triggering. + L4RSM::do_hostdb_update_if_necessary(); + + do_hostdb_lookup(); + break; + } + + case HttpTransact::SM_ACTION_SSL_TUNNEL: { + t_state.api_next_action = HttpTransact::SM_ACTION_API_SEND_RESPONSE_HDR; + do_api_callout(); + break; + } + + case HttpTransact::SM_ACTION_ORIGIN_SERVER_RAW_OPEN: { + L4R_SM_SET_DEFAULT_HANDLER(&L4RSM::state_raw_http_server_open); + + ink_assert(server_entry == nullptr); + do_http_server_open(true); + break; + } + + case HttpTransact::SM_ACTION_CACHE_ISSUE_WRITE_TRANSFORM: { + ink_assert(t_state.cache_info.transform_action == HttpTransact::CACHE_PREPARE_TO_WRITE); + + if (transform_cache_sm.cache_write_vc) { + // We've already got the write_vc that + // didn't use for the untransformed copy + ink_assert(cache_sm.cache_write_vc == nullptr); + ink_assert(t_state.api_info.cache_untransformed == false); + t_state.cache_info.write_lock_state = HttpTransact::CACHE_WL_SUCCESS; + call_transact_and_set_next_state(nullptr); + } else { + L4R_SM_SET_DEFAULT_HANDLER(&L4RSM::state_cache_open_write); + + do_cache_prepare_write_transform(); + } + break; + } + + case HttpTransact::SM_ACTION_TRANSFORM_READ: { + t_state.api_next_action = HttpTransact::SM_ACTION_API_SEND_RESPONSE_HDR; + do_api_callout(); + break; + } + + case HttpTransact::SM_ACTION_READ_PUSH_HDR: { + setup_push_read_response_header(); + break; + } + + case HttpTransact::SM_ACTION_STORE_PUSH_BODY: { + // This can return NULL - do we really want to run the tunnel in that case? + // But that's how it was before this change. + HttpTunnelProducer *p = setup_push_transfer_to_cache(); + tunnel.tunnel_run(p); + break; + } + + case HttpTransact::SM_ACTION_CACHE_PREPARE_UPDATE: { + ink_assert(t_state.api_update_cached_object == HttpTransact::UPDATE_CACHED_OBJECT_CONTINUE); + do_cache_prepare_update(); + break; + } + case HttpTransact::SM_ACTION_CACHE_ISSUE_UPDATE: { + if (t_state.api_update_cached_object == HttpTransact::UPDATE_CACHED_OBJECT_ERROR) { + t_state.cache_info.object_read = nullptr; + cache_sm.close_read(); + } + issue_cache_update(); + call_transact_and_set_next_state(nullptr); + break; + } + + case HttpTransact::SM_ACTION_WAIT_FOR_FULL_BODY: { + wait_for_full_body(); + break; + } + + case HttpTransact::SM_ACTION_CONTINUE: { + ink_release_assert(!"Not implemented"); + break; + } + + default: { + ink_release_assert("!Unknown next action"); + } + } +} + +void +clear_http_handler_times() +{ +} + +// YTS Team, yamsat Plugin + +void +L4RSM::do_redirect() +{ + SMDebug("http_redirect", "[L4RSM::do_redirect]"); + if (!enable_redirection || redirection_tries > t_state.txn_conf->number_of_redirections) { + this->postbuf_clear(); + return; + } + + // if redirect_url is set by an user's plugin, yts will redirect to this url anyway. + if (is_redirect_required()) { + if (redirect_url != nullptr || t_state.hdr_info.client_response.field_find(MIME_FIELD_LOCATION, MIME_LEN_LOCATION)) { + if (Log::transaction_logging_enabled() && t_state.api_info.logging_enabled) { + LogAccessHttp accessor(this); + if (redirect_url == nullptr) { + if (t_state.squid_codes.log_code == SQUID_LOG_TCP_HIT) { + t_state.squid_codes.log_code = SQUID_LOG_TCP_HIT_REDIRECT; + } else { + t_state.squid_codes.log_code = SQUID_LOG_TCP_MISS_REDIRECT; + } + } else { + if (t_state.squid_codes.log_code == SQUID_LOG_TCP_HIT) { + t_state.squid_codes.log_code = SQUID_LOG_TCP_HIT_X_REDIRECT; + } else { + t_state.squid_codes.log_code = SQUID_LOG_TCP_MISS_X_REDIRECT; + } + } + + int ret = Log::access(&accessor); + + if (ret & Log::FULL) { + SMDebug("http", "[update_stats] Logging system indicates FULL."); + } + if (ret & Log::FAIL) { + Log::error("failed to log transaction for at least one log object"); + } + } + + ++redirection_tries; + if (redirect_url != nullptr) { + redirect_request(redirect_url, redirect_url_len); + ats_free((void *)redirect_url); + redirect_url = nullptr; + redirect_url_len = 0; + HTTP_INCREMENT_DYN_STAT(http_total_x_redirect_stat); + } else { + // get the location header and setup the redirect + int redir_len; + char *redir_url = (char *)t_state.hdr_info.client_response.value_get(MIME_FIELD_LOCATION, MIME_LEN_LOCATION, &redir_len); + redirect_request(redir_url, redir_len); + } + + } else { + enable_redirection = false; + } + } else { + enable_redirection = false; + } +} + +void +L4RSM::redirect_request(const char *redirect_url, const int redirect_len) +{ + SMDebug("http_redirect", "[L4RSM::redirect_request]"); + // get a reference to the client request header and client url and check to see if the url is valid + HTTPHdr &clientRequestHeader = t_state.hdr_info.client_request; + URL &clientUrl = *clientRequestHeader.url_get(); + if (!clientUrl.valid()) { + return; + } + + bool valid_origHost = true; + int origHost_len, origMethod_len; + char origHost[MAXDNAME]; + char origMethod[255]; + int origPort = 80; + + if (t_state.hdr_info.server_request.valid()) { + char *tmpOrigHost; + + origPort = t_state.hdr_info.server_request.port_get(); + tmpOrigHost = (char *)t_state.hdr_info.server_request.value_get(MIME_FIELD_HOST, MIME_LEN_HOST, &origHost_len); + + if (tmpOrigHost) { + memcpy(origHost, tmpOrigHost, origHost_len); + origHost[std::min(origHost_len, MAXDNAME - 1)] = '\0'; + } else { + valid_origHost = false; + } + + char *tmpOrigMethod = (char *)t_state.hdr_info.server_request.method_get(&origMethod_len); + if (tmpOrigMethod) { + memcpy(origMethod, tmpOrigMethod, std::min(origMethod_len, static_cast(sizeof(origMethod)))); + } else { + valid_origHost = false; + } + } else { + SMDebug("http_redir_error", "t_state.hdr_info.server_request not valid"); + valid_origHost = false; + } + + t_state.redirect_info.redirect_in_process = true; + + // set the passed in location url and parse it + URL &redirectUrl = t_state.redirect_info.redirect_url; + if (!redirectUrl.valid()) { + redirectUrl.create(nullptr); + } + + // reset the path from previous redirects (if any) + t_state.redirect_info.redirect_url.path_set(nullptr, 0); + + // redirectUrl.user_set(redirect_url, redirect_len); + redirectUrl.parse(redirect_url, redirect_len); + { + int _scheme_len = -1; + int _host_len = -1; + if (redirectUrl.scheme_get(&_scheme_len) == nullptr && redirectUrl.host_get(&_host_len) != nullptr && redirect_url[0] != '/') { + // RFC7230 ยง 5.5 + // The redirect URL lacked a scheme and so it is a relative URL. + // The redirect URL did not begin with a slash, so we parsed some or all + // of the the relative URI path as the host. + // Prepend a slash and parse again. + char redirect_url_leading_slash[redirect_len + 1]; + redirect_url_leading_slash[0] = '/'; + memcpy(redirect_url_leading_slash + 1, redirect_url, redirect_len + 1); + url_nuke_proxy_stuff(redirectUrl.m_url_impl); + redirectUrl.parse(redirect_url_leading_slash, redirect_len + 1); + } + } + + // copy the client url to the original url + URL &origUrl = t_state.redirect_info.original_url; + if (!origUrl.valid()) { + origUrl.create(nullptr); + origUrl.copy(&clientUrl); + } + // copy the redirect url to the client url + clientUrl.copy(&redirectUrl); + + //(bug 2540703) Clear the previous response if we will attempt the redirect + if (t_state.hdr_info.client_response.valid()) { + // XXX - doing a destroy() for now, we can do a fileds_clear() if we have performance issue + t_state.hdr_info.client_response.destroy(); + } + + int scheme = t_state.next_hop_scheme; + int scheme_len = hdrtoken_index_to_length(scheme); + const char *next_hop_scheme = hdrtoken_index_to_wks(scheme); + char scheme_str[scheme_len + 1]; + + if (next_hop_scheme) { + memcpy(scheme_str, next_hop_scheme, scheme_len); + } else { + valid_origHost = false; + } + + t_state.hdr_info.server_request.destroy(); + + // we want to close the server session + // will do that in handle_api_return under the + // HttpTransact::SM_ACTION_REDIRECT_READ state + t_state.parent_result.reset(); + t_state.request_sent_time = 0; + t_state.response_received_time = 0; + t_state.cache_info.write_lock_state = HttpTransact::CACHE_WL_INIT; + t_state.next_action = HttpTransact::SM_ACTION_REDIRECT_READ; + // we have a new OS and need to have DNS lookup the new OS + t_state.dns_info.lookup_success = false; + t_state.force_dns = false; + t_state.server_info.clear(); + t_state.parent_info.clear(); + + if (t_state.txn_conf->cache_http) { + t_state.cache_info.object_read = nullptr; + } + + bool noPortInHost = HttpConfig::m_master.redirection_host_no_port; + + bool isRedirectUrlOriginForm = clientUrl.m_url_impl->m_len_scheme <= 0 && clientUrl.m_url_impl->m_len_user <= 0 && + clientUrl.m_url_impl->m_len_password <= 0 && clientUrl.m_url_impl->m_len_host <= 0 && + clientUrl.m_url_impl->m_len_port <= 0; + + // check to see if the client request passed a host header, if so copy the host and port from the redirect url and + // make a new host header + if (t_state.hdr_info.client_request.presence(MIME_PRESENCE_HOST)) { + int host_len; + const char *host = clientUrl.host_get(&host_len); + + if (host != nullptr) { + int port = clientUrl.port_get(); + int redirectSchemeLen; + const char *redirectScheme = clientUrl.scheme_get(&redirectSchemeLen); + + if (redirectScheme == nullptr) { + clientUrl.scheme_set(scheme_str, scheme_len); + SMDebug("http_redirect", "[L4RSM::redirect_request] URL without scheme %.*s", redirectSchemeLen, redirectScheme); + } + + if (noPortInHost) { + int redirectSchemeIdx = clientUrl.scheme_get_wksidx(); + bool defaultPort = + (((redirectSchemeIdx == URL_WKSIDX_HTTP) && (port == 80)) || ((redirectSchemeIdx == URL_WKSIDX_HTTPS) && (port == 443))); + + if (!defaultPort) { + noPortInHost = false; + } + } + + if (!noPortInHost) { + char buf[host_len + 7]; // 5 + 1 + 1 ("12345" + ':' + '\0') + + host_len = snprintf(buf, host_len + 7, "%.*s:%d", host_len, host, port); + t_state.hdr_info.client_request.value_set(MIME_FIELD_HOST, MIME_LEN_HOST, buf, host_len); + } else { + t_state.hdr_info.client_request.value_set(MIME_FIELD_HOST, MIME_LEN_HOST, host, host_len); + } + t_state.hdr_info.client_request.m_target_cached = false; + t_state.hdr_info.server_request.m_target_cached = false; + } else { + // the client request didn't have a host, so use the current origin host + if (valid_origHost) { + char *saveptr = nullptr; + + // the client request didn't have a host, so use the current origin host + SMDebug("http_redirect", "[L4RSM::redirect_request] keeping client request host %s://%s", next_hop_scheme, origHost); + char *origHostNoPort = strtok_r(origHost, ":", &saveptr); + + if (origHostNoPort == nullptr) { + goto LhostError; + } + + host_len = strlen(origHostNoPort); + if (noPortInHost) { + int redirectSchemeIdx = t_state.next_hop_scheme; + bool defaultPort = (((redirectSchemeIdx == URL_WKSIDX_HTTP) && (origPort == 80)) || + ((redirectSchemeIdx == URL_WKSIDX_HTTPS) && (origPort == 443))); + + if (!defaultPort) { + noPortInHost = false; + } + } + + if (!noPortInHost) { + char buf[host_len + 7]; // 5 + 1 + 1 ("12345" + ':' + '\0') + + host_len = snprintf(buf, host_len + 7, "%s:%d", origHostNoPort, origPort); + t_state.hdr_info.client_request.value_set(MIME_FIELD_HOST, MIME_LEN_HOST, buf, host_len); + } else { + t_state.hdr_info.client_request.value_set(MIME_FIELD_HOST, MIME_LEN_HOST, origHostNoPort, host_len); + } + + // Cleanup of state etc. + url_nuke_proxy_stuff(clientUrl.m_url_impl); + url_nuke_proxy_stuff(t_state.hdr_info.client_request.m_url_cached.m_url_impl); + t_state.hdr_info.client_request.method_set(origMethod, std::min(origMethod_len, static_cast(sizeof(origMethod)))); + t_state.hdr_info.client_request.m_target_cached = false; + t_state.hdr_info.server_request.m_target_cached = false; + clientUrl.scheme_set(scheme_str, scheme_len); + if (isRedirectUrlOriginForm) { + // build the rest of the effictive URL: the authority part + clientUrl.user_set(origUrl.m_url_impl->m_ptr_user, origUrl.m_url_impl->m_len_user); + clientUrl.password_set(origUrl.m_url_impl->m_ptr_password, origUrl.m_url_impl->m_len_password); + clientUrl.host_set(origUrl.m_url_impl->m_ptr_host, origUrl.m_url_impl->m_len_host); + clientUrl.port_set(origUrl.port_get()); + } + } else { + LhostError: + // the server request didn't have a host, so remove it from the headers + t_state.hdr_info.client_request.field_delete(MIME_FIELD_HOST, MIME_LEN_HOST); + } + } + } + + DUMP_HEADER("http_hdrs", &t_state.hdr_info.client_request, sm_id, "Framed Client Request..checking"); +} + +void +L4RSM::set_http_schedule(Continuation *contp) +{ + L4R_SM_SET_DEFAULT_HANDLER(&L4RSM::get_http_schedule); + schedule_cont = contp; +} + +int +L4RSM::get_http_schedule(int event, void * /* data ATS_UNUSED */) +{ + bool plugin_lock; + Ptr plugin_mutex; + if (schedule_cont->mutex) { + plugin_mutex = schedule_cont->mutex; + plugin_lock = MUTEX_TAKE_TRY_LOCK(schedule_cont->mutex, mutex->thread_holding); + + if (!plugin_lock) { + L4R_SM_SET_DEFAULT_HANDLER(&L4RSM::get_http_schedule); + ink_assert(pending_action == nullptr); + pending_action = mutex->thread_holding->schedule_in(this, HRTIME_MSECONDS(10)); + return 0; + } else { + pending_action = nullptr; // if there was a pending action, it'll get freed after this returns so clear it. + } + } else { + plugin_lock = false; + } + + // handle Mutex; + schedule_cont->handleEvent(event, this); + if (plugin_lock) { + Mutex_unlock(plugin_mutex, mutex->thread_holding); + } + + return 0; +} + +bool +L4RSM::set_server_session_private(bool private_session) +{ + if (server_session != nullptr) { + server_session->private_session = private_session; + return true; + } + return false; +} + +inline bool +L4RSM::is_private() +{ + bool res = false; + if (server_session) { + res = server_session->private_session; + } else if (ua_txn) { + HttpServerSession *ss = ua_txn->get_server_session(); + if (ss) { + res = ss->private_session; + } else if (will_be_private_ss) { + res = will_be_private_ss; + } + } + return res; +} + +// check to see if redirection is enabled and less than max redirections tries or if a plugin enabled redirection +inline bool +L4RSM::is_redirect_required() +{ + bool redirect_required = (enable_redirection && (redirection_tries <= t_state.txn_conf->number_of_redirections)); + + SMDebug("http_redirect", "is_redirect_required %u", redirect_required); + + if (redirect_required == true) { + HTTPStatus status = t_state.hdr_info.client_response.status_get(); + // check to see if the response from the orgin was a 301, 302, or 303 + switch (status) { + case HTTP_STATUS_MULTIPLE_CHOICES: // 300 + case HTTP_STATUS_MOVED_PERMANENTLY: // 301 + case HTTP_STATUS_MOVED_TEMPORARILY: // 302 + case HTTP_STATUS_SEE_OTHER: // 303 + case HTTP_STATUS_USE_PROXY: // 305 + case HTTP_STATUS_TEMPORARY_REDIRECT: // 307 + redirect_required = true; + break; + default: + redirect_required = false; + break; + } + + // if redirect_url is set by an user's plugin, ats will redirect to this url anyway. + if (redirect_url != nullptr) { + redirect_required = true; + } + } + return redirect_required; +} + +// Fill in the client protocols used. Return the number of entries returned +int +L4RSM::populate_client_protocol(std::string_view *result, int n) const +{ + int retval = 0; + if (n > 0) { + std::string_view proto = L4RSM::find_proto_string(t_state.hdr_info.client_request.version_get()); + if (!proto.empty()) { + result[retval++] = proto; + if (n > retval && ua_txn) { + retval += ua_txn->populate_protocol(result + retval, n - retval); + } + } + } + return retval; +} + +// Look for a specific protocol +const char * +L4RSM::client_protocol_contains(std::string_view tag_prefix) const +{ + const char *retval = nullptr; + std::string_view proto = L4RSM::find_proto_string(t_state.hdr_info.client_request.version_get()); + if (!proto.empty()) { + std::string_view prefix(tag_prefix); + if (prefix.size() <= proto.size() && 0 == strncmp(proto.data(), prefix.data(), prefix.size())) { + retval = proto.data(); + } else if (ua_txn) { + retval = ua_txn->protocol_contains(prefix); + } + } + return retval; +} + +std::string_view +L4RSM::find_proto_string(HTTPVersion version) const +{ + if (version == HTTPVersion(1, 1)) { + return IP_PROTO_TAG_HTTP_1_1; + } else if (version == HTTPVersion(1, 0)) { + return IP_PROTO_TAG_HTTP_1_0; + } else if (version == HTTPVersion(0, 9)) { + return IP_PROTO_TAG_HTTP_0_9; + } + return {}; +} + +// YTS Team, yamsat Plugin +// Function to copy the partial Post data while tunnelling +void +PostDataBuffers::copy_partial_post_data() +{ + if (post_data_buffer_done) { + return; + } + Debug("http_redirect", "[PostDataBuffers::copy_partial_post_data] wrote %" PRId64 " bytes to buffers %" PRId64 "", + this->ua_buffer_reader->read_avail(), this->postdata_copy_buffer_start->read_avail()); + this->postdata_copy_buffer->write(this->ua_buffer_reader); + this->ua_buffer_reader->consume(this->ua_buffer_reader->read_avail()); +} + +IOBufferReader * +PostDataBuffers::get_post_data_buffer_clone_reader() +{ + return this->postdata_copy_buffer->clone_reader(this->postdata_copy_buffer_start); +} + +// YTS Team, yamsat Plugin +// Allocating the post data buffers +void +PostDataBuffers::init(IOBufferReader *ua_reader) +{ + Debug("http_redirect", "[PostDataBuffers::init]"); + + this->ua_buffer_reader = ua_reader; + + if (this->postdata_copy_buffer == nullptr) { + this->post_data_buffer_done = false; + ink_assert(this->postdata_copy_buffer_start == nullptr); + this->postdata_copy_buffer = new_empty_MIOBuffer(BUFFER_SIZE_INDEX_4K); + this->postdata_copy_buffer_start = this->postdata_copy_buffer->alloc_reader(); + } + + ink_assert(this->ua_buffer_reader != nullptr); +} + +// YTS Team, yamsat Plugin +// Deallocating the post data buffers +void +PostDataBuffers::clear() +{ + Debug("http_redirect", "[PostDataBuffers::clear]"); + + if (this->postdata_copy_buffer != nullptr) { + free_MIOBuffer(this->postdata_copy_buffer); + this->postdata_copy_buffer = nullptr; + this->postdata_copy_buffer_start = nullptr; // deallocated by the buffer + } + this->post_data_buffer_done = false; +} + +PostDataBuffers::~PostDataBuffers() +{ + this->clear(); +} diff --git a/proxy/l4r/L4RSM.h b/proxy/l4r/L4RSM.h new file mode 100644 index 00000000000..2fd0048bded --- /dev/null +++ b/proxy/l4r/L4RSM.h @@ -0,0 +1,550 @@ +/** @file + + A brief file description + + @section license License + + Licensed to the Apache Software Foundation (ASF) under one + or more contributor license agreements. See the NOTICE file + distributed with this work for additional information + regarding copyright ownership. The ASF licenses this file + to you under the Apache License, Version 2.0 (the + "License"); you may not use this file except in compliance + with the License. You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + */ + +/**************************************************************************** + + L4RSM.h + + Description: + + + ****************************************************************************/ + +#pragma once + +#include "ts/ink_platform.h" +#include "P_EventSystem.h" +//#include "HttpCacheSM.h" +//#include "HttpTransact.h" +//#include "UrlRewrite.h" +//#include "HttpTunnel.h" +#include "InkAPIInternal.h" +#include "../ProxyClientTransaction.h" +//#include "HdrUtils.h" +#include +#include +//#include "AuthHttpAdapter.h" + +class L4RServerSession; + +/* Enable LAZY_BUF_ALLOC to delay allocation of buffers until they + * are actually required. + * Enabling LAZY_BUF_ALLOC, stop Http code from allocation space + * for header buffer and tunnel buffer. The allocation is done by + * the net code in read_from_net when data is actually written into + * the buffer. By allocating memory only when it is required we can + * reduce the memory consumed by TS process. + * + * IMPORTANT NOTE: enable/disable LAZY_BUF_ALLOC in HttpServerSession.h + * as well. + */ +#define LAZY_BUF_ALLOC + +#define L4R_API_CONTINUE (INK_API_EVENT_EVENTS_START + 0) +#define L4R_API_ERROR (INK_API_EVENT_EVENTS_START + 1) + +// The default size for http header buffers when we don't +// need to include extra space for the document +static size_t const HTTP_HEADER_BUFFER_SIZE_INDEX = CLIENT_CONNECTION_FIRST_READ_BUFFER_SIZE_INDEX; + +// We want to use a larger buffer size when reading response +// headers from the origin server since we want to get +// as much of the document as possible on the first read +// Marco benchmarked about 3% ops/second improvement using +// the larger buffer size +static size_t const HTTP_SERVER_RESP_HDR_BUFFER_INDEX = BUFFER_SIZE_INDEX_8K; + +class HttpServerSession; +class AuthHttpAdapter; + +class L4RSM; +typedef int (L4RSM::*L4RSMHandler)(int event, void *data); + +enum HttpVC_t { + HTTP_UNKNOWN = 0, + HTTP_UA_VC, + HTTP_SERVER_VC, + HTTP_TRANSFORM_VC, + HTTP_CACHE_READ_VC, + HTTP_CACHE_WRITE_VC, + HTTP_RAW_SERVER_VC +}; + +enum BackgroundFill_t { + BACKGROUND_FILL_NONE = 0, + BACKGROUND_FILL_STARTED, + BACKGROUND_FILL_ABORTED, + BACKGROUND_FILL_COMPLETED, +}; + +extern ink_mutex debug_sm_list_mutex; + +struct L4RVCTableEntry { + VConnection *vc; + MIOBuffer *read_buffer; + MIOBuffer *write_buffer; + VIO *read_vio; + VIO *write_vio; + L4RSMHandler vc_handler; + HttpVC_t vc_type; + bool eos; + bool in_tunnel; +}; + +struct L4RVCTable { + static const int vc_table_max_entries = 4; + L4RVCTable(); + + L4RVCTableEntry *new_entry(); + L4RVCTableEntry *find_entry(VConnection *); + L4RVCTableEntry *find_entry(VIO *); + void remove_entry(L4RVCTableEntry *); + void cleanup_entry(L4RVCTableEntry *); + void cleanup_all(); + bool is_table_clear() const; + +private: + L4RVCTableEntry vc_table[vc_table_max_entries]; +}; + +inline bool +L4RVCTable::is_table_clear() const +{ + for (const auto &i : vc_table) { + if (i.vc != nullptr) { + return false; + } + } + return true; +} + +struct HttpTransformInfo { + L4RVCTableEntry *entry; + VConnection *vc; + + HttpTransformInfo() : entry(nullptr), vc(nullptr) {} +}; + +enum { + HTTP_SM_MAGIC_ALIVE = 0x0000FEED, + HTTP_SM_MAGIC_DEAD = 0xDEADFEED, +}; + +enum { + HTTP_SM_POST_UNKNOWN = 0, + HTTP_SM_POST_UA_FAIL = 1, + HTTP_SM_POST_SERVER_FAIL = 2, + HTTP_SM_POST_SUCCESS = 3, +}; + +enum { + HTTP_SM_TRANSFORM_OPEN = 0, + HTTP_SM_TRANSFORM_CLOSED = 1, + HTTP_SM_TRANSFORM_FAIL = 2, +}; + +enum L4RApiState_t { + L4R_API_NO_CALLOUT, + L4R_API_IN_CALLOUT, + L4R_API_DEFERED_CLOSE, + L4R_API_DEFERED_SERVER_ERROR, +}; + +enum HttpPluginTunnel_t { + HTTP_NO_PLUGIN_TUNNEL = 0, + HTTP_PLUGIN_AS_SERVER, + HTTP_PLUGIN_AS_INTERCEPT, +}; + +class CoreUtils; +class PluginVCCore; + +class PostDataBuffers +{ +public: + PostDataBuffers() { Debug("http_redirect", "[PostDataBuffers::PostDataBuffers]"); } + MIOBuffer *postdata_copy_buffer = nullptr; + IOBufferReader *postdata_copy_buffer_start = nullptr; + IOBufferReader *ua_buffer_reader = nullptr; + bool post_data_buffer_done = false; + + void clear(); + void init(IOBufferReader *ua_reader); + void copy_partial_post_data(); + IOBufferReader *get_post_data_buffer_clone_reader(); + void + set_post_data_buffer_done(bool done) + { + post_data_buffer_done = done; + } + bool + get_post_data_buffer_done() + { + return post_data_buffer_done; + } + bool + is_valid() + { + return postdata_copy_buffer_start != nullptr; + } + + ~PostDataBuffers(); +}; + +class L4RSM : public Continuation +{ + //friend class HttpPagesHandler; + friend class CoreUtils; + +public: + L4RSM(); + void cleanup(); + virtual void destroy(); + + static L4RSM *allocate(); + //L4RVCTableEntry *get_ua_entry(); // Added to get the ua_entry pointer - YTS-TEAM + + void init(); + + void attach_client_session(BasicProxyClientTransaction *client_vc_arg, IOBufferReader *buffer_reader); + + // Called by httpSessionManager so that we can reset + // the session timeouts and initiate a read while + // holding the lock for the server session + void attach_server_session(L4RServerSession *s); + + // Used to read attributes of + // the current active server session + L4RServerSession * + get_server_session() + { + return server_session; + } + + // Called by transact to prevent reset problems + // failed PUSH requests + void set_ua_half_close_flag(); + + // Debugging routines to dump the SM history, hdrs + void dump_state_on_assert(); + + // Functions for manipulating api hooks + void txn_hook_append(TSHttpHookID id, INKContInternal *cont); + void txn_hook_prepend(TSHttpHookID id, INKContInternal *cont); + APIHook *txn_hook_get(TSHttpHookID id); + + bool is_private(); + + /// Get the protocol stack for the inbound (client, user agent) connection. + /// @arg result [out] Array to store the results + /// @arg n [in] Size of the array @a result. + int populate_client_protocol(std::string_view *result, int n) const; + const char *client_protocol_contains(std::string_view tag_prefix) const; + std::string_view find_proto_string(HTTPVersion version) const; + + int64_t sm_id = -1; + unsigned int magic = HTTP_SM_MAGIC_DEAD; + + // YTS Team, yamsat Plugin + bool enable_redirection = false; // To check if redirection is enabled + char *redirect_url = nullptr; // url for force redirect (provide users a functionality to redirect to another url when needed) + int redirect_url_len = 0; + int redirection_tries = 0; // To monitor number of redirections + int64_t transfered_bytes = 0; // Added to calculate POST data + bool post_failed = false; // Added to identify post failure + bool debug_on = false; // Transaction specific debug flag + +protected: + int reentrancy_count = 0; + + L4RVCTable vc_table; + + L4RVCTableEntry *ua_entry = nullptr; + void remove_ua_entry(); + +public: + L4RClientTransaction *ua_txn = nullptr; + BackgroundFill_t background_fill = BACKGROUND_FILL_NONE; + // AuthHttpAdapter authAdapter; + void set_http_schedule(Continuation *); + int get_http_schedule(int event, void *data); + + History history; + +protected: + IOBufferReader *ua_buffer_reader = nullptr; + IOBufferReader *ua_raw_buffer_reader = nullptr; + + L4RVCTableEntry *server_entry = nullptr; + L4RServerSession *server_session = nullptr; + + /* Because we don't want to take a session from a shared pool if we know that it will be private, + * but we cannot set it to private until we have an attached server session. + * So we use this variable to indicate that + * we should create a new connection and then once we attach the session we'll mark it as private. + */ + bool will_be_private_ss = false; + int shared_session_retries = 0; + IOBufferReader *server_buffer_reader = nullptr; + void remove_server_entry(); + + HttpTransformInfo transform_info; + HttpTransformInfo post_transform_info; + /// Set if plugin client / user agents are active. + /// Need primarily for cleanup. + bool has_active_plugin_agents = false; + + L4RSMHandler default_handler = nullptr; + Action *pending_action = nullptr; + Continuation *schedule_cont = nullptr; + + void start_sub_sm(); + + int main_handler(int event, void *data); + int tunnel_handler(int event, void *data); + int tunnel_handler_push(int event, void *data); + int tunnel_handler_post(int event, void *data); + + // YTS Team, yamsat Plugin + int tunnel_handler_for_partial_post(int event, void *data); + + //void tunnel_handler_post_or_put(HttpTunnelProducer *p); + + int tunnel_handler_100_continue(int event, void *data); + int tunnel_handler_cache_fill(int event, void *data); + int state_read_client_request_header(int event, void *data); + int state_watch_for_client_abort(int event, void *data); + int state_read_push_response_header(int event, void *data); + int state_srv_lookup(int event, void *data); + int state_hostdb_lookup(int event, void *data); + int state_hostdb_reverse_lookup(int event, void *data); + int state_mark_os_down(int event, void *data); + int state_handle_stat_page(int event, void *data); + int state_auth_callback(int event, void *data); + int state_add_to_list(int event, void *data); + int state_remove_from_list(int event, void *data); + + // Y! ebalsa: remap handlers + int state_remap_request(int event, void *data); + void do_remap_request(bool); + + // Cache Handlers + int state_cache_open_read(int event, void *data); + int state_cache_open_write(int event, void *data); + + // Http Server Handlers + int state_http_server_open(int event, void *data); + int state_raw_http_server_open(int event, void *data); + int state_send_server_request_header(int event, void *data); + int state_acquire_server_read(int event, void *data); + int state_read_server_response_header(int event, void *data); + + // API + int state_request_wait_for_transform_read(int event, void *data); + int state_response_wait_for_transform_read(int event, void *data); + int state_common_wait_for_transform_read(HttpTransformInfo *t_info, L4RSMHandler tunnel_handler, int event, void *data); + + // Tunnel event handlers + //int tunnel_handler_server(int event, HttpTunnelProducer *p); + //int tunnel_handler_ua(int event, HttpTunnelConsumer *c); + //int tunnel_handler_ua_push(int event, HttpTunnelProducer *p); + //int tunnel_handler_100_continue_ua(int event, HttpTunnelConsumer *c); + //int tunnel_handler_cache_write(int event, HttpTunnelConsumer *c); + //int tunnel_handler_cache_read(int event, HttpTunnelProducer *p); + //int tunnel_handler_post_ua(int event, HttpTunnelProducer *c); + //int tunnel_handler_post_server(int event, HttpTunnelConsumer *c); + //int tunnel_handler_ssl_producer(int event, HttpTunnelProducer *p); + //int tunnel_handler_ssl_consumer(int event, HttpTunnelConsumer *p); + //int tunnel_handler_transform_write(int event, HttpTunnelConsumer *c); + //int tunnel_handler_transform_read(int event, HttpTunnelProducer *p); + //int tunnel_handler_plugin_agent(int event, HttpTunnelConsumer *c); + + void do_hostdb_lookup(); + void do_hostdb_reverse_lookup(); + void do_cache_lookup_and_read(); + void do_http_server_open(bool raw = false); + void send_origin_throttled_response(); + void do_setup_post_tunnel(HttpVC_t to_vc_type); + void do_auth_callout(); + void do_api_callout(); + void do_api_callout_internal(); + void do_redirect(); + void redirect_request(const char *redirect_url, const int redirect_len); + void do_drain_request_body(); + + void wait_for_full_body(); + + virtual void handle_api_return(); + void handle_server_setup_error(int event, void *data); + void handle_http_server_open(); + void handle_post_failure(); + void mark_host_failure(HostDBInfo *info, time_t time_down); + void mark_server_down_on_client_abort(); + void release_server_session(bool serve_from_cache = false); + void set_ua_abort(HttpTransact::AbortState_t ua_abort, int event); + int write_header_into_buffer(HTTPHdr *h, MIOBuffer *b); + int write_response_header_into_buffer(HTTPHdr *h, MIOBuffer *b); + void setup_blind_tunnel_port(); + void setup_client_header_nca(); + void setup_client_read_request_header(); + void setup_push_read_response_header(); + void setup_server_read_response_header(); + void setup_cache_lookup_complete_api(); + void setup_server_send_request(); + void setup_server_send_request_api(); + //HttpTunnelProducer *setup_server_transfer(); + void setup_server_transfer_to_cache_only(); + //HttpTunnelProducer *setup_cache_read_transfer(); + void setup_internal_transfer(L4RSMHandler handler); + void setup_error_transfer(); + void setup_100_continue_transfer(); + //HttpTunnelProducer *setup_push_transfer_to_cache(); + void setup_transform_to_server_transfer(); + void setup_cache_write_transfer(HttpCacheSM *c_sm, VConnection *source_vc, HTTPInfo *store_info, int64_t skip_bytes, + const char *name); + void issue_cache_update(); + void perform_cache_write_action(); + void perform_transform_cache_write_action(); + void perform_nca_cache_action(); + void setup_blind_tunnel(bool send_response_hdr, IOBufferReader *initial = nullptr); + //HttpTunnelProducer *setup_server_transfer_to_transform(); + //HttpTunnelProducer *setup_transfer_from_transform(); + //HttpTunnelProducer *setup_cache_transfer_to_transform(); + //HttpTunnelProducer *setup_transfer_from_transform_to_cache_only(); + //void setup_plugin_agents(HttpTunnelProducer *p); + + HttpTransact::StateMachineAction_t last_action = HttpTransact::SM_ACTION_UNDEFINED; + int (L4RSM::*m_last_state)(int event, void *data) = nullptr; + virtual void set_next_state(); + void call_transact_and_set_next_state(TransactEntryFunc_t f); + + //bool is_http_server_eos_truncation(HttpTunnelProducer *); + bool is_bg_fill_necessary(HttpTunnelConsumer *c); + int find_server_buffer_size(); + int find_http_resp_buffer_size(int64_t cl); + int64_t server_transfer_init(MIOBuffer *buf, int hdr_size); + +public: + // TODO: Now that bodies can be empty, should the body counters be set to -1 ? TS-2213 + // Stats & Logging Info + int client_request_hdr_bytes = 0; + int64_t client_request_body_bytes = 0; + int server_request_hdr_bytes = 0; + int64_t server_request_body_bytes = 0; + int server_response_hdr_bytes = 0; + int64_t server_response_body_bytes = 0; + int client_response_hdr_bytes = 0; + int64_t client_response_body_bytes = 0; + int cache_response_hdr_bytes = 0; + int64_t cache_response_body_bytes = 0; + int pushed_response_hdr_bytes = 0; + int64_t pushed_response_body_bytes = 0; + bool client_tcp_reused = false; + // Info about client's SSL connection. + bool client_ssl_reused = false; + bool client_connection_is_ssl = false; + const char *client_protocol = "-"; + const char *client_sec_protocol = "-"; + const char *client_cipher_suite = "-"; + int server_transact_count = 0; + bool server_connection_is_ssl = false; + bool is_waiting_for_full_body = false; + bool is_using_post_buffer = false; + + TransactionMilestones milestones; + ink_hrtime api_timer = 0; + // The next two enable plugins to tag the state machine for + // the purposes of logging so the instances can be correlated + // with the source plugin. + const char *plugin_tag = nullptr; + int64_t plugin_id = 0; + + // hooks_set records whether there are any hooks relevant + // to this transaction. Used to avoid costly calls + // do_api_callout_internal() + bool hooks_set = false; + +protected: + TSHttpHookID cur_hook_id = TS_HTTP_LAST_HOOK; + APIHook *cur_hook = nullptr; + + // + // Continuation time keeper + int64_t prev_hook_start_time = 0; + + int cur_hooks = 0; + L4RApiState_t callout_state = L4R_API_NO_CALLOUT; + + // api_hooks must not be changed directly + // Use txn_hook_{ap,pre}pend so hooks_set is + // updated + HttpAPIHooks api_hooks; + + // The terminate flag is set by handlers and checked by the + // main handler who will terminate the state machine + // when the flag is set + bool terminate_sm = false; + bool kill_this_async_done = false; + bool parse_range_done = false; + virtual int kill_this_async_hook(int event, void *data); + void kill_this(); + void update_stats(); + void transform_cleanup(TSHttpHookID hook, HttpTransformInfo *info); + bool is_transparent_passthrough_allowed(); + void plugin_agents_cleanup(); + +public: + LINK(L4RSM, debug_link); + +public: + bool set_server_session_private(bool private_session); + bool + is_dying() const + { + return terminate_sm; + } + + int + client_connection_id() const + { + return _client_connection_id; + } + + int + client_transaction_id() const + { + return _client_transaction_id; + } + +private: + //PostDataBuffers _postbuf; + int _client_connection_id = -1, _client_transaction_id = -1; +}; + +inline L4RSM * +L4RSM::allocate() +{ + extern ClassAllocator l4rSMAllocator; + return l4rSMAllocator.alloc(); +} + diff --git a/proxy/l4r/L4RServerSession.cc b/proxy/l4r/L4RServerSession.cc new file mode 100644 index 00000000000..212c859bc54 --- /dev/null +++ b/proxy/l4r/L4RServerSession.cc @@ -0,0 +1,197 @@ +/** @file + + A brief file description + + @section license License + + Licensed to the Apache Software Foundation (ASF) under one + or more contributor license agreements. See the NOTICE file + distributed with this work for additional information + regarding copyright ownership. The ASF licenses this file + to you under the Apache License, Version 2.0 (the + "License"); you may not use this file except in compliance + with the License. You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + */ + +/**************************************************************************** + + L4RServerSession.cc + + Description: + + ****************************************************************************/ +#include "ts/ink_config.h" +#include "ts/Allocator.h" +#include "L4RServerSession.h" +#include "HttpSessionManager.h" +//#include "HttpSM.h" + +static int64_t next_ss_id = (int64_t)0; +ClassAllocator l4rServerSessionAllocator("l4rServerSessionAllocator"); + +void +L4RServerSession::destroy() +{ + ink_release_assert(server_vc == nullptr); + ink_assert(read_buffer); + ink_assert(server_trans_stat == 0); + magic = L4R_SS_MAGIC_DEAD; + if (read_buffer) { + free_MIOBuffer(read_buffer); + read_buffer = nullptr; + } + + mutex.clear(); + if (TS_SERVER_SESSION_SHARING_POOL_THREAD == sharing_pool) { + THREAD_FREE(this, l4rServerSessionAllocator, this_thread()); + } else { + l4rServerSessionAllocator.free(this); + } +} + +void +L4RServerSession::new_connection(NetVConnection *new_vc) +{ + ink_assert(new_vc != nullptr); + server_vc = new_vc; + + // Used to do e.g. mutex = new_vc->thread->mutex; when per-thread pools enabled + mutex = new_vc->mutex; + + // Unique client session identifier. + con_id = ink_atomic_increment((int64_t *)(&next_ss_id), 1); + + magic = L4R_SS_MAGIC_ALIVE; + //HTTP_SUM_GLOBAL_DYN_STAT(http_current_server_connections_stat, 1); // Update the true global stat + //HTTP_INCREMENT_DYN_STAT(http_total_server_connections_stat); + // Check to see if we are limiting the number of connections + // per host + //if (enable_origin_connection_limiting == true) { + // if (connection_count == nullptr) { + // connection_count = ConnectionCount::getInstance(); + // } + // connection_count->incrementCount(get_server_ip(), hostname_hash, sharing_match); + // ip_port_text_buffer addrbuf; + // Debug("l4r_ss", "[%" PRId64 "] new connection, ip: %s, count: %u", con_id, + // ats_ip_nptop(&get_server_ip().sa, addrbuf, sizeof(addrbuf)), + // connection_count->getCount(get_server_ip(), hostname_hash, sharing_match)); + //} +#ifdef LAZY_BUF_ALLOC + read_buffer = new_empty_MIOBuffer(BUFFER_SIZE_INDEX_8K); +#else + read_buffer = new_MIOBuffer(HTTP_SERVER_RESP_HDR_BUFFER_INDEX); +#endif + buf_reader = read_buffer->alloc_reader(); + Debug("l4r_ss", "[%" PRId64 "] session born, netvc %p", con_id, new_vc); + state = LSS_INIT; + + new_vc->set_tcp_congestion_control(SERVER_SIDE); +} + +VIO * +L4RServerSession::do_io_read(Continuation *c, int64_t nbytes, MIOBuffer *buf) +{ + return server_vc->do_io_read(c, nbytes, buf); +} + +VIO * +L4RServerSession::do_io_write(Continuation *c, int64_t nbytes, IOBufferReader *buf, bool owner) +{ + return server_vc->do_io_write(c, nbytes, buf, owner); +} + +void +L4RServerSession::do_io_shutdown(ShutdownHowTo_t howto) +{ + server_vc->do_io_shutdown(howto); +} + +void +L4RServerSession::do_io_close(int alerrno) +{ + if (state == LSS_ACTIVE) { + //HTTP_DECREMENT_DYN_STAT(http_current_server_transactions_stat); + this->server_trans_stat--; + } + + Debug("l4r_ss", "[%" PRId64 "] session closing, netvc %p", con_id, server_vc); + + //HTTP_SUM_GLOBAL_DYN_STAT(http_current_server_connections_stat, -1); // Make sure to work on the global stat + //HTTP_SUM_DYN_STAT(http_transactions_per_server_con, transact_count); + + // Check to see if we are limiting the number of connections + // per host + //if (enable_origin_connection_limiting == true) { + // if (connection_count->getCount(get_server_ip(), hostname_hash, sharing_match) >= 0) { + // connection_count->incrementCount(get_server_ip(), hostname_hash, sharing_match, -1); + // ip_port_text_buffer addrbuf; + // Debug("l4r_ss", "[%" PRId64 "] connection closed, ip: %s, count: %u", con_id, + // ats_ip_nptop(&get_server_ip().sa, addrbuf, sizeof(addrbuf)), + // connection_count->getCount(get_server_ip(), hostname_hash, sharing_match)); + // } else { + // Error("[%" PRId64 "] number of connections should be greater than or equal to zero: %u", con_id, + // connection_count->getCount(get_server_ip(), hostname_hash, sharing_match)); + // } + //} + + if (server_vc) { + server_vc->do_io_close(alerrno); + } + server_vc = nullptr; + + destroy(); +} + +void +L4RServerSession::reenable(VIO *vio) +{ + server_vc->reenable(vio); +} + +// void L4RServerSession::release() +// +// Releases the session for K-A reuse +// +void +L4RServerSession::release() +{ + Debug("l4r_ss", "Releasing session, private_session=%d, sharing_match=%d", private_session, sharing_match); + // Set our state to KA for stat issues + state = LSS_KA_SHARED; + + server_vc->control_flags.set_flags(0); + + // Private sessions are never released back to the shared pool + if (private_session || TS_SERVER_SESSION_SHARING_MATCH_NONE == sharing_match) { + this->do_io_close(); + return; + } + + // Make sure the vios for the current SM are cleared + server_vc->do_io_read(nullptr, 0, nullptr); + server_vc->do_io_write(nullptr, 0, nullptr); + +#if 0 + HSMresult_t r = httpSessionManager.release_session(this); + + if (r == HSM_RETRY) { + // Session could not be put in the session manager + // due to lock contention + // FIX: should retry instead of closing + this->do_io_close(); + } else { + // The session was successfully put into the session + // manager and it will manage it + // (Note: should never get HSM_NOT_FOUND here) + ink_assert(r == HSM_DONE); + } +#endif +} diff --git a/proxy/l4r/L4RServerSession.h b/proxy/l4r/L4RServerSession.h new file mode 100644 index 00000000000..4a533f20d55 --- /dev/null +++ b/proxy/l4r/L4RServerSession.h @@ -0,0 +1,213 @@ +/** @file + + A brief file description + + @section license License + + Licensed to the Apache Software Foundation (ASF) under one + or more contributor license agreements. See the NOTICE file + distributed with this work for additional information + regarding copyright ownership. The ASF licenses this file + to you under the Apache License, Version 2.0 (the + "License"); you may not use this file except in compliance + with the License. You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + */ + +/**************************************************************************** + + L4RServerSession.h + + Description: + + + ****************************************************************************/ + +#pragma once +/* Enable LAZY_BUF_ALLOC to delay allocation of buffers until they + * are actually required. + * Enabling LAZY_BUF_ALLOC, stop Http code from allocation space + * for header buffer and tunnel buffer. The allocation is done by + * the net code in read_from_net when data is actually written into + * the buffer. By allocating memory only when it is required we can + * reduce the memory consumed by TS process. + * + * IMPORTANT NOTE: enable/disable LAZY_BUF_ALLOC in HttpSM.h as well. + */ +#define LAZY_BUF_ALLOC + +#include "P_Net.h" + +//#include "HttpConnectionCount.h" +#include "HttpProxyAPIEnums.h" + +//class HttpSM; +class MIOBuffer; +class IOBufferReader; + +enum { + L4R_SS_MAGIC_ALIVE = 0x0123FEED, + L4R_SS_MAGIC_DEAD = 0xDEADFEED, +}; + +enum LSS_State { + LSS_INIT, + LSS_ACTIVE, + LSS_KA_CLIENT_SLAVE, + LSS_KA_SHARED, +}; + +#if 0 +enum { + HTTP_SS_MAGIC_ALIVE = 0x0123FEED, + HTTP_SS_MAGIC_DEAD = 0xDEADFEED, +}; +#endif + +class L4RServerSession : public VConnection +{ +public: + L4RServerSession() + : VConnection(nullptr), + hostname_hash(), + con_id(0), + transact_count(0), + state(LSS_INIT), + server_trans_stat(0), + private_session(false), + sharing_match(TS_SERVER_SESSION_SHARING_MATCH_BOTH), + sharing_pool(TS_SERVER_SESSION_SHARING_POOL_GLOBAL), + enable_origin_connection_limiting(false), + //connection_count(nullptr), + read_buffer(nullptr), + server_vc(nullptr), + magic(L4R_SS_MAGIC_DEAD), + buf_reader(nullptr) + { + } + + void destroy(); + void new_connection(NetVConnection *new_vc); + + void + reset_read_buffer(void) + { + ink_assert(read_buffer->_writer); + ink_assert(buf_reader != nullptr); + read_buffer->dealloc_all_readers(); + read_buffer->_writer = nullptr; + buf_reader = read_buffer->alloc_reader(); + } + + IOBufferReader * + get_reader() + { + return buf_reader; + }; + + VIO *do_io_read(Continuation *c, int64_t nbytes = INT64_MAX, MIOBuffer *buf = nullptr) override; + + VIO *do_io_write(Continuation *c = nullptr, int64_t nbytes = INT64_MAX, IOBufferReader *buf = nullptr, + bool owner = false) override; + + void do_io_close(int lerrno = -1) override; + void do_io_shutdown(ShutdownHowTo_t howto) override; + + void reenable(VIO *vio) override; + + void release(); + void attach_hostname(const char *hostname); + NetVConnection * + get_netvc() const + { + return server_vc; + }; + void + set_netvc(NetVConnection *new_vc) + { + server_vc = new_vc; + } + + // Keys for matching hostnames + IpEndpoint const & + get_server_ip() const + { + ink_release_assert(server_vc != nullptr); + return server_vc->get_remote_endpoint(); + } + + CryptoHash hostname_hash; + + int64_t con_id; + int transact_count; + LSS_State state; + + // Used to verify we are recording the server + // transaction stat properly + int server_trans_stat; + + // Sessions become if authentication headers + // are sent over them + bool private_session; + + // Copy of the owning SM's server session sharing settings + TSServerSessionSharingMatchType sharing_match; + TSServerSessionSharingPoolType sharing_pool; + // int share_session; + + LINK(L4RServerSession, ip_hash_link); + LINK(L4RServerSession, host_hash_link); + + // Keep track of connection limiting and a pointer to the + // singleton that keeps track of the connection counts. + bool enable_origin_connection_limiting; + //ConnectionCount *connection_count; + + // The ServerSession owns the following buffer which use + // for parsing the headers. The server session needs to + // own the buffer so we can go from a keep-alive state + // to being acquired and parsing the header without + // changing the buffer we are doing I/O on. We can + // not change the buffer for I/O without issuing a + // an asyncronous cancel on NT + MIOBuffer *read_buffer; + + virtual int + populate_protocol(std::string_view *result, int size) const + { + auto vc = this->get_netvc(); + return vc ? vc->populate_protocol(result, size) : 0; + } + + virtual const char * + protocol_contains(std::string_view tag_prefix) const + { + auto vc = this->get_netvc(); + return vc ? vc->protocol_contains(tag_prefix) : nullptr; + } + +private: + L4RServerSession(L4RServerSession &); + + NetVConnection *server_vc; + int magic; + + IOBufferReader *buf_reader; +}; + +extern ClassAllocator l4rServerSessionAllocator; + +inline void +L4RServerSession::attach_hostname(const char *hostname) +{ + if (CRYPTO_HASH_ZERO == hostname_hash) { + CryptoContext().hash_immediate(hostname_hash, (unsigned char *)hostname, strlen(hostname)); + } +} diff --git a/proxy/l4r/L4RSessionAccept.cc b/proxy/l4r/L4RSessionAccept.cc new file mode 100644 index 00000000000..7832553cf93 --- /dev/null +++ b/proxy/l4r/L4RSessionAccept.cc @@ -0,0 +1,116 @@ +/** @file + + A brief file description + + @section license License + + Licensed to the Apache Software Foundation (ASF) under one + or more contributor license agreements. See the NOTICE file + distributed with this work for additional information + regarding copyright ownership. The ASF licenses this file + to you under the Apache License, Version 2.0 (the + "License"); you may not use this file except in compliance + with the License. You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + */ + +#include "L4RSessionAccept.h" +#include "IPAllow.h" +//#include "Http1ClientSession.h" +#include "I_Machine.h" + +bool +L4RSessionAccept::accept(NetVConnection *netvc, MIOBuffer *iobuf, IOBufferReader *reader) +{ + sockaddr const *client_ip = netvc->get_remote_addr(); + const AclRecord *acl_record = nullptr; + ip_port_text_buffer ipb; + + // The backdoor port is now only bound to "localhost", so no + // reason to check for if it's incoming from "localhost" or not. +#if 0 + if (backdoor) { + acl_record = IpAllow::AllMethodAcl(); + } else { + #endif + acl_record = testIpAllowPolicy(client_ip); + if (!acl_record) { + //////////////////////////////////////////////////// + // if client address forbidden, close immediately // + //////////////////////////////////////////////////// + Warning("client '%s' prohibited by ip-allow policy", ats_ip_ntop(client_ip, ipb, sizeof(ipb))); + return false; + } + #if 0 + } +#endif + + // Set the transport type if not already set + if (HttpProxyPort::TRANSPORT_NONE == netvc->attributes) { + //netvc->attributes = transport_type; + } + + if (is_debug_tag_set("http_seq")) { + Debug("http_seq", "[L4RSessionAccept:mainEvent %p] accepted connection from %s transport type = %d", netvc, + ats_ip_nptop(client_ip, ipb, sizeof(ipb)), netvc->attributes); + } + + //L4RClientSession *new_session = THREAD_ALLOC_INIT(l4rClientSessionAllocator, this_ethread()); + + //// copy over session related data. + //new_session->f_outbound_transparent = f_outbound_transparent; + //new_session->f_transparent_passthrough = f_transparent_passthrough; + //new_session->outbound_ip4 = outbound_ip4; + //new_session->outbound_ip6 = outbound_ip6; + //new_session->outbound_port = outbound_port; + //new_session->host_res_style = ats_host_res_from(client_ip->sa_family, host_res_preference); + //new_session->acl_record = acl_record; + + //new_session->new_connection(netvc, iobuf, reader, backdoor); + + return true; +} + +int +L4RSessionAccept::mainEvent(int event, void *data) +{ + NetVConnection *netvc; + ink_release_assert(event == NET_EVENT_ACCEPT || event == EVENT_ERROR); + ink_release_assert((event == NET_EVENT_ACCEPT) ? (data != nullptr) : (1)); + + if (event == NET_EVENT_ACCEPT) { + netvc = static_cast(data); + if (!this->accept(netvc, nullptr, nullptr)) { + netvc->do_io_close(); + } + return EVENT_CONT; + } + + ///////////////// + // EVENT_ERROR // + ///////////////// + if (((long)data) == -ECONNABORTED) { + ///////////////////////////////////////////////// + // Under Solaris, when accept() fails and sets // + // errno to EPROTO, it means the client has // + // sent a TCP reset before the connection has // + // been accepted by the server... Note that // + // in 2.5.1 with the Internet Server Supplement// + // and also in 2.6 the errno for this case has // + // changed from EPROTO to ECONNABORTED. // + ///////////////////////////////////////////////// + + // FIX: add time to user_agent_hangup + //HTTP_SUM_DYN_STAT(http_ua_msecs_counts_errors_pre_accept_hangups_stat, 0); + } + + ink_abort("HTTP accept received fatal error: errno = %d", -((int)(intptr_t)data)); + return EVENT_CONT; +} diff --git a/proxy/l4r/L4RSessionAccept.h b/proxy/l4r/L4RSessionAccept.h new file mode 100644 index 00000000000..7e20cd0643f --- /dev/null +++ b/proxy/l4r/L4RSessionAccept.h @@ -0,0 +1,214 @@ +/** @file + + A brief file description + + @section license License + + Licensed to the Apache Software Foundation (ASF) under one + or more contributor license agreements. See the NOTICE file + distributed with this work for additional information + regarding copyright ownership. The ASF licenses this file + to you under the Apache License, Version 2.0 (the + "License"); you may not use this file except in compliance + with the License. You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + */ + +#pragma once + +#include "ts/ink_platform.h" +#include "records/I_RecHttp.h" +#include "P_EventSystem.h" +//#include "HttpConfig.h" +//#include "HTTP.h" +#include "I_Net.h" +#include + +namespace detail +{ +/** Options for @c L4RSessionAccept. + + @internal This is done as a separate class for two reasons. + + The first is that in current usage many instances are created + with the same options so (for the client) this is easier and + more efficient than passing options directly to the @c + L4RSessionAccept or calling setters. + + The second is that @c L4RSessionAccept is not provided with any thread + safety because it is intended as an immutable object. Putting + the setters here and not there makes that clearer. + + We don't do this directly as nested class because we want to + inherit the data members rather than duplicate the declarations + and initializations. + */ +class L4RSessionAcceptOptions +{ +private: + typedef L4RSessionAcceptOptions self; ///< Self reference type. +public: + L4RSessionAcceptOptions(); + + // Connection type (HttpProxyPort::TransportType) + int transport_type; + /// Set the transport type. + self &setTransportType(int); + /// Local address to bind for outbound connections. + IpAddr outbound_ip4; + /// Local address to bind for outbound connections. + IpAddr outbound_ip6; + /// Set the outbound IP address to @a ip. + self &setOutboundIp(IpAddr &ip); + /// Set the outbound IP address to @a ip. + self &setOutboundIp(IpEndpoint *ip); + /// Local port for outbound connection. + uint16_t outbound_port; + /// Set outbound port. + self &setOutboundPort(uint16_t); + /// Outbound transparent. + bool f_outbound_transparent; + /// Set outbound transparency. + self &setOutboundTransparent(bool); + /// Transparent pass-through. + bool f_transparent_passthrough; + /// Set transparent passthrough. + self &setTransparentPassthrough(bool); + /// Accepting backdoor connections. + bool backdoor; + /// Set backdoor accept. + self &setBackdoor(bool); + /// Host address resolution preference order. + HostResPreferenceOrder host_res_preference; + /// Set the host query preference. + self &setHostResPreference(HostResPreferenceOrder const); + /// Acceptable session protocols. + SessionProtocolSet session_protocol_preference; + /// Set the session protocol preference. + self &setSessionProtocolPreference(SessionProtocolSet const &); +}; + +inline L4RSessionAcceptOptions::L4RSessionAcceptOptions() + : transport_type(0), outbound_port(0), f_outbound_transparent(false), f_transparent_passthrough(false), backdoor(false) +{ + memcpy(host_res_preference, host_res_default_preference_order, sizeof(host_res_preference)); +} + +inline L4RSessionAcceptOptions & +L4RSessionAcceptOptions::setTransportType(int type) +{ + transport_type = type; + return *this; +} + +inline L4RSessionAcceptOptions & +L4RSessionAcceptOptions::setOutboundIp(IpAddr &ip) +{ + if (ip.isIp4()) + outbound_ip4 = ip; + else if (ip.isIp6()) + outbound_ip6 = ip; + return *this; +} + +inline L4RSessionAcceptOptions & +L4RSessionAcceptOptions::setOutboundIp(IpEndpoint *ip) +{ + if (ip->isIp4()) + outbound_ip4 = *ip; + else if (ip->isIp6()) + outbound_ip6 = *ip; + return *this; +} + +inline L4RSessionAcceptOptions & +L4RSessionAcceptOptions::setOutboundPort(uint16_t port) +{ + outbound_port = port; + return *this; +} + +inline L4RSessionAcceptOptions & +L4RSessionAcceptOptions::setOutboundTransparent(bool flag) +{ + f_outbound_transparent = flag; + return *this; +} + +inline L4RSessionAcceptOptions & +L4RSessionAcceptOptions::setTransparentPassthrough(bool flag) +{ + f_transparent_passthrough = flag; + return *this; +} + +inline L4RSessionAcceptOptions & +L4RSessionAcceptOptions::setBackdoor(bool flag) +{ + backdoor = flag; + return *this; +} + +inline L4RSessionAcceptOptions & +L4RSessionAcceptOptions::setHostResPreference(HostResPreferenceOrder const order) +{ + memcpy(host_res_preference, order, sizeof(host_res_preference)); + return *this; +} + +inline L4RSessionAcceptOptions & +L4RSessionAcceptOptions::setSessionProtocolPreference(SessionProtocolSet const &sp_set) +{ + session_protocol_preference = sp_set; + return *this; +} +} // namespace detail + +/** + The continuation mutex is NULL to allow parellel accepts in NT. No + state is recorded by the handler and values are required to be set + during construction via the @c Options struct and never changed. So + a NULL mutex is safe. + + Most of the state is simply passed on to the @c ClientSession after + an accept. It is done here because this is the least bad pathway + from the top level configuration to the HTTP session. +*/ + +class L4RSessionAccept : public SessionAccept//, private detail::L4RSessionAcceptOptions +{ +private: + typedef L4RSessionAccept self; ///< Self reference type. +public: + /** Construction options. + Provide an easier to remember typedef for clients. + */ + typedef detail::L4RSessionAcceptOptions Options; + + /** Default constructor. + @internal We don't use a static default options object because of + initialization order issues. It is important to pick up data that is read + from the config file and a static is initialized long before that point. + */ + //L4RSessionAccept(Options const &opt = Options()) : SessionAccept(nullptr), detail::L4RSessionAcceptOptions(opt) // copy these. + L4RSessionAccept() : SessionAccept(nullptr) + { + SET_HANDLER(&L4RSessionAccept::mainEvent); + return; + } + + ~L4RSessionAccept() override { return; } + bool accept(NetVConnection *, MIOBuffer *, IOBufferReader *) override; + int mainEvent(int event, void *netvc) override; + + // noncopyable + L4RSessionAccept(const L4RSessionAccept &) = delete; + L4RSessionAccept &operator=(const L4RSessionAccept &) = delete; +}; diff --git a/proxy/l4r/Makefile.am b/proxy/l4r/Makefile.am new file mode 100644 index 00000000000..c733ae3e33d --- /dev/null +++ b/proxy/l4r/Makefile.am @@ -0,0 +1,50 @@ +# Makefile.am for Http +# +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you under the Apache License, Version 2.0 (the +# "License"); you may not use this file except in compliance +# with the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +include $(top_srcdir)/build/tidy.mk + +AM_CPPFLAGS += \ + $(iocore_include_dirs) \ + -I$(abs_top_srcdir)/proxy \ + -I$(abs_top_srcdir)/lib \ + -I$(abs_top_srcdir)/lib/records \ + -I$(abs_top_srcdir)/mgmt \ + -I$(abs_top_srcdir)/mgmt/utils \ + -I$(abs_top_srcdir)/proxy/shared \ + -I$(abs_top_srcdir)/proxy/logging \ + -I$(abs_top_srcdir)/proxy/hdrs \ + -I$(abs_top_srcdir)/proxy/http \ + $(TS_INCLUDES) + +#noinst_HEADERS = HttpProxyServerMain.h +noinst_LIBRARIES = libl4r.a + +libl4r_a_SOURCES = \ + L4RProxyServerMain.cc \ + L4RProxyServerMain.h \ + L4RClientSession.cc \ + L4RClientSession.h \ + L4RClientTransaction.cc \ + L4RClientTransaction.h \ + L4RServerSession.cc \ + L4RServerSession.h \ + L4RSM.cc \ + L4RSM.h \ + L4RSessionAccept.cc \ + L4RSessionAccept.h + diff --git a/src/traffic_server/Makefile.inc b/src/traffic_server/Makefile.inc index b264afddc2f..7a61145715b 100644 --- a/src/traffic_server/Makefile.inc +++ b/src/traffic_server/Makefile.inc @@ -31,6 +31,7 @@ traffic_server_traffic_server_CPPFLAGS = \ -I$(abs_top_srcdir)/proxy/http/remap \ -I$(abs_top_srcdir)/proxy/hdrs \ -I$(abs_top_srcdir)/proxy/shared \ + -I$(abs_top_srcdir)/proxy/l4r \ -I$(abs_top_srcdir)/mgmt \ -I$(abs_top_srcdir)/mgmt/utils \ $(TS_INCLUDES) \ @@ -64,6 +65,7 @@ traffic_server_traffic_server_LDADD = \ $(top_builddir)/proxy/logging/liblogcollation.a \ $(top_builddir)/proxy/hdrs/libhdrs.a \ $(top_builddir)/proxy/shared/libdiagsconfig.a \ + $(top_builddir)/proxy/l4r/libl4r.a \ $(top_builddir)/mgmt/libmgmt_p.la \ $(top_builddir)/iocore/utils/libinkutils.a \ $(top_builddir)/iocore/dns/libinkdns.a \ diff --git a/src/traffic_server/traffic_server.cc b/src/traffic_server/traffic_server.cc index e444097b8a2..7b96367e099 100644 --- a/src/traffic_server/traffic_server.cc +++ b/src/traffic_server/traffic_server.cc @@ -75,6 +75,7 @@ extern "C" int plock(int); #include "ProcessManager.h" #include "ProxyConfig.h" #include "HttpProxyServerMain.h" +#include "L4RProxyServerMain.h" #include "HttpBodyFactory.h" #include "ProxyClientSession.h" #include "logging/Log.h" @@ -1934,6 +1935,7 @@ main(int /* argc ATS_UNUSED */, const char **argv) ////////////////////////////////////// init_accept_HttpProxyServer(num_accept_threads); + init_accept_L4RProxyServer(num_accept_threads); transformProcessor.start(); int http_enabled = 1; @@ -1962,6 +1964,7 @@ main(int /* argc ATS_UNUSED */, const char **argv) Debug("http_listen", "Delaying listen, waiting for cache initialization"); } else { start_HttpProxyServer(); // PORTS_READY_HOOK called from in here + start_L4RProxyServer(); } } SNIConfig::cloneProtoSet();