|  | /** @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 "tscore/ink_config.h" | 
|  | #include "P_Net.h" | 
|  | #include "HttpConfig.h" | 
|  | #include "HttpSessionAccept.h" | 
|  | #include "ReverseProxy.h" | 
|  | #include "HttpSessionManager.h" | 
|  | #include "remap/RemapHitCount.h" | 
|  | #ifdef USE_HTTP_DEBUG_LISTS | 
|  | #include "Http1ClientSession.h" | 
|  | #endif | 
|  | #include "HttpPages.h" | 
|  | #include "HttpTunnel.h" | 
|  | #include "tscore/Tokenizer.h" | 
|  | #include "P_SSLNextProtocolAccept.h" | 
|  | #include "ProtocolProbeSessionAccept.h" | 
|  | #include "http2/Http2SessionAccept.h" | 
|  | #include "HttpConnectionCount.h" | 
|  | #include "HttpProxyServerMain.h" | 
|  | #if TS_USE_QUIC == 1 | 
|  | #include "P_QUICNetProcessor.h" | 
|  | #include "P_QUICNextProtocolAccept.h" | 
|  | #include "http3/Http3SessionAccept.h" | 
|  | #endif | 
|  | #include "PreWarmManager.h" | 
|  |  | 
|  | #include <vector> | 
|  |  | 
|  | HttpSessionAccept *plugin_http_accept             = nullptr; | 
|  | HttpSessionAccept *plugin_http_transparent_accept = nullptr; | 
|  | extern std::function<PoolableSession *()> create_h1_server_session; | 
|  | extern std::map<int, std::function<ProxySession *()>> ProtocolSessionCreateMap; | 
|  |  | 
|  | static SLL<SSLNextProtocolAccept> ssl_plugin_acceptors; | 
|  | static Ptr<ProxyMutex> ssl_plugin_mutex; | 
|  |  | 
|  | std::mutex proxyServerMutex; | 
|  | std::condition_variable proxyServerCheck; | 
|  | bool et_net_threads_ready = false; | 
|  |  | 
|  | std::mutex etUdpMutex; | 
|  | std::condition_variable etUdpCheck; | 
|  | bool et_udp_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 HTTP_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; | 
|  | }(); | 
|  |  | 
|  | 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; | 
|  | } | 
|  |  | 
|  | ///////////////////////////////////////////////////////////////// | 
|  | // | 
|  | //  main() | 
|  | // | 
|  | ///////////////////////////////////////////////////////////////// | 
|  |  | 
|  | /** Data about an acceptor. | 
|  |  | 
|  | This is used to separate setting up the proxy ports and | 
|  | starting to accept on them. | 
|  |  | 
|  | */ | 
|  | struct HttpProxyAcceptor { | 
|  | /// Accept continuation. | 
|  | Continuation *_accept = nullptr; | 
|  | /// Options for @c NetProcessor. | 
|  | NetProcessor::AcceptOptions _net_opt; | 
|  |  | 
|  | /// Default constructor. | 
|  | HttpProxyAcceptor() {} | 
|  | }; | 
|  |  | 
|  | /** 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<HttpProxyAcceptor> HttpProxyAcceptors; | 
|  |  | 
|  | // Called from InkAPI.cc | 
|  | NetProcessor::AcceptOptions | 
|  | make_net_accept_options(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"); | 
|  | REC_ReadConfigInteger(net.defer_accept, "proxy.config.net.defer_accept"); | 
|  |  | 
|  | #if TCP_NOTSENT_LOWAT | 
|  | REC_ReadConfigInteger(net.packet_notsent_lowat, "proxy.config.net.sock_notsent_lowat"); | 
|  | #endif | 
|  |  | 
|  | #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.f_mptcp               = port->m_mptcp; | 
|  | net.ip_family             = port->m_family; | 
|  | net.local_port            = port->m_port; | 
|  | net.f_proxy_protocol      = port->m_proxy_protocol; | 
|  |  | 
|  | 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 | 
|  | MakeHttpProxyAcceptor(HttpProxyAcceptor &acceptor, HttpProxyPort &port, unsigned nthreads) | 
|  | { | 
|  | NetProcessor::AcceptOptions &net_opt = acceptor._net_opt; | 
|  | HttpSessionAccept::Options accept_opt; | 
|  |  | 
|  | net_opt = make_net_accept_options(&port, nthreads); | 
|  |  | 
|  | 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; | 
|  | } | 
|  |  | 
|  | // 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(); | 
|  | HttpSessionAccept *http           = nullptr; // don't allocate this unless it will be used. | 
|  | probe->proxyPort                  = &port; | 
|  | probe->proxy_protocol_ipmap       = &HttpConfig::m_master.config_proxy_protocol_ipmap; | 
|  |  | 
|  | if (port.m_session_protocol_preference.intersects(HTTP_PROTOCOL_SET)) { | 
|  | http = new HttpSessionAccept(accept_opt); | 
|  | probe->registerEndpoint(ProtocolProbeSessionAccept::PROTO_HTTP, http); | 
|  | } | 
|  |  | 
|  | if (port.m_session_protocol_preference.intersects(HTTP2_PROTOCOL_SET)) { | 
|  | probe->registerEndpoint(ProtocolProbeSessionAccept::PROTO_HTTP2, new Http2SessionAccept(accept_opt)); | 
|  | } | 
|  | ProtocolSessionCreateMap.insert({TS_ALPN_PROTOCOL_INDEX_HTTP_1_0, create_h1_server_session}); | 
|  | ProtocolSessionCreateMap.insert({TS_ALPN_PROTOCOL_INDEX_HTTP_1_1, create_h1_server_session}); | 
|  |  | 
|  | 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 | 
|  |  | 
|  | ssl->enableProtocols(port.m_session_protocol_preference); | 
|  | ssl->registerEndpoint(TS_ALPN_PROTOCOL_HTTP_1_0, http); | 
|  | ssl->registerEndpoint(TS_ALPN_PROTOCOL_HTTP_1_1, http); | 
|  | ssl->registerEndpoint(TS_ALPN_PROTOCOL_HTTP_2_0, new Http2SessionAccept(accept_opt)); | 
|  |  | 
|  | SCOPED_MUTEX_LOCK(lock, ssl_plugin_mutex, this_ethread()); | 
|  | ssl_plugin_acceptors.push(ssl); | 
|  | ssl->proxyPort   = &port; | 
|  | acceptor._accept = ssl; | 
|  | #if TS_USE_QUIC == 1 | 
|  | } else if (port.isQUIC()) { | 
|  | QUICNextProtocolAccept *quic = new QUICNextProtocolAccept(); | 
|  |  | 
|  | quic->enableProtocols(port.m_session_protocol_preference); | 
|  |  | 
|  | // HTTP/0.9 over QUIC draft-29 (for interop only, will be removed) | 
|  | quic->registerEndpoint(TS_ALPN_PROTOCOL_HTTP_QUIC_D29, new Http3SessionAccept(accept_opt)); | 
|  |  | 
|  | // HTTP/3 draft-29 | 
|  | quic->registerEndpoint(TS_ALPN_PROTOCOL_HTTP_3_D29, new Http3SessionAccept(accept_opt)); | 
|  |  | 
|  | // HTTP/0.9 over QUIC (for interop only, will be removed) | 
|  | quic->registerEndpoint(TS_ALPN_PROTOCOL_HTTP_QUIC, new Http3SessionAccept(accept_opt)); | 
|  |  | 
|  | // HTTP/3 | 
|  | quic->registerEndpoint(TS_ALPN_PROTOCOL_HTTP_3, new Http3SessionAccept(accept_opt)); | 
|  |  | 
|  | quic->proxyPort  = &port; | 
|  | acceptor._accept = quic; | 
|  | #endif | 
|  | } else { | 
|  | acceptor._accept = probe; | 
|  | } | 
|  | } | 
|  |  | 
|  | /// Do all pre-thread initialization / setup. | 
|  | void | 
|  | prep_HttpProxyServer() | 
|  | { | 
|  | httpSessionManager.init(); | 
|  | } | 
|  |  | 
|  | /** Set up all the accepts and sockets. | 
|  | */ | 
|  | void | 
|  | init_accept_HttpProxyServer(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 (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(); | 
|  | } | 
|  |  | 
|  | // Do the configuration defined ports. | 
|  | // Assign temporary empty objects of proxy ports size | 
|  | HttpProxyAcceptors.assign(proxy_ports.size(), HttpProxyAcceptor()); | 
|  | for (int i = 0, n = proxy_ports.size(); i < n; ++i) { | 
|  | MakeHttpProxyAcceptor(HttpProxyAcceptors.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_HttpProxyServer() | 
|  | { | 
|  | if (eventProcessor.has_tg_started(ET_NET)) { | 
|  | std::unique_lock<std::mutex> lock(proxyServerMutex); | 
|  | et_net_threads_ready = true; | 
|  | lock.unlock(); | 
|  | proxyServerCheck.notify_one(); | 
|  | } | 
|  |  | 
|  | #if TS_USE_QUIC == 1 | 
|  | if (eventProcessor.has_tg_started(ET_UDP)) { | 
|  | std::unique_lock<std::mutex> lock(etUdpMutex); | 
|  | et_udp_threads_ready = true; | 
|  | lock.unlock(); | 
|  | etUdpCheck.notify_one(); | 
|  | } | 
|  | #endif | 
|  | } | 
|  |  | 
|  | void | 
|  | start_HttpProxyServer() | 
|  | { | 
|  | static bool called_once           = false; | 
|  | HttpProxyPort::Group &proxy_ports = HttpProxyPort::global(); | 
|  |  | 
|  | /////////////////////////////////// | 
|  | // start accepting connections   // | 
|  | /////////////////////////////////// | 
|  |  | 
|  | ink_assert(!called_once); | 
|  | ink_assert(proxy_ports.size() == HttpProxyAcceptors.size()); | 
|  |  | 
|  | for (int i = 0, n = proxy_ports.size(); i < n; ++i) { | 
|  | HttpProxyAcceptor &acceptor = HttpProxyAcceptors[i]; | 
|  | HttpProxyPort &port         = proxy_ports[i]; | 
|  | if (port.isSSL()) { | 
|  | if (nullptr == sslNetProcessor.main_accept(acceptor._accept, port.m_fd, acceptor._net_opt)) { | 
|  | return; | 
|  | } | 
|  | #if TS_USE_QUIC == 1 | 
|  | } else if (port.isQUIC()) { | 
|  | if (nullptr == quic_NetProcessor.main_accept(acceptor._accept, port.m_fd, acceptor._net_opt)) { | 
|  | return; | 
|  | } | 
|  | #endif | 
|  | } 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. | 
|  | } | 
|  |  | 
|  | // Set up stat page for http connection count | 
|  | statPagesManager.register_http("connection_count", register_ShowConnectionCount); | 
|  | statPagesManager.register_http("remap_hits", register_ShowRemapHitCount); | 
|  |  | 
|  | // 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(); | 
|  | } | 
|  |  | 
|  | prewarmManager.start(); | 
|  | } | 
|  |  | 
|  | void | 
|  | stop_HttpProxyServer() | 
|  | { | 
|  | sslNetProcessor.stop_accept(); | 
|  | netProcessor.stop_accept(); | 
|  | } |