diff --git a/doc/admin-guide/configuration/index.en.rst b/doc/admin-guide/configuration/index.en.rst index cc2e3f603e6..1aecdbe29fe 100644 --- a/doc/admin-guide/configuration/index.en.rst +++ b/doc/admin-guide/configuration/index.en.rst @@ -32,3 +32,4 @@ Proxy Cache Configuration transparent-proxy.en transparent-forward-proxying.en hierachical-caching.en + proxy-protocol.en diff --git a/doc/admin-guide/configuration/proxy-protocol.en.rst b/doc/admin-guide/configuration/proxy-protocol.en.rst new file mode 100644 index 00000000000..2d7673b2914 --- /dev/null +++ b/doc/admin-guide/configuration/proxy-protocol.en.rst @@ -0,0 +1,100 @@ +.. 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:: ../../common.defs + +.. _proxy-protocol: + +Proxy Protocol +**************** + +The `PROXY protocol `_ +provides a means of passing connection information between layers of the proxy +infrastructure. Without the PROXY protocol, |TS| would only have connection +information from the previous hop connecting to |TS| and not the actual +originating client connection information. This can be done over either HTTP or +TLS connections. + +.. note:: + + The current version only supports transforming client IP from PROXY Version 1 + header to the Forwarded: header. + +In the current implementation, the client IP address in the PROXY protocol header +is passed to the origin server via an HTTP `Forwarded: +`_ header. + +The Proxy Protocol must be enabled on each port. See +:ts:cv:`proxy.config.http.server_ports` for information on how to enable the +Proxy Protocol on a port. Once enabled, all incoming requests must be prefaced +with the PROXY v1 header. Any request not preface by this header will be +dropped. + +As a security measure, an optional whitelist of trusted IP addresses may be +configured with :ts:cv:`proxy.config.http.proxy_protocol_whitelist`. + + .. important:: + + If the whitelist is configured, requests will only be accepted from these + IP addressses and must be prefaced with the PROXY v1 header. + +See :ts:cv:`proxy.config.http.insert_forwarded` for configuration information. +Detection of the PROXY protocol header is automatic. If the PROXY header +precludes the request, it will automatically be parse and made available to the +Forwarded: request header sent to the origin server. + +Example +------- + +As an example, consider the following topology: + +.. figure:: ../../static/images/admin/proxy-protocol.png + :align: center + :alt: PROXY protocol transformed into a Forwarded: header + + PROXY protocol header flow + +Without the PROXY protocol header, the client IP would only be reported +accurately to the Load Balancer. |TS| would only see the connection from the +Load Balancer. Similarly, the Web Server would only see the connection from +|TS|. In the example above, if the client initiated a TLS connection, the Web +Server would see the connection originating from |TS| at ``10.0.0.2``: + +.. code-block:: lua + + Forwarded: for=10.0.0.2;by=10.0.0.1;proto=https;host=test000001.com + +If the Load Balancer has the Proxy Protocol enabled, requests sent through the +Load Balancer will be preceded with the PROXY header. |TS| will detect the +PROXY header and transform that into the Forwarded: HTTP header if configured to +insert the Forwarded: header with the ``for`` paramter. In the example above, +if the client initiated a TLS connection, the Web Server can use the Forwarded: +header to determine the TLS connection originated from the client at ``192.168.1.100``: + +.. code-block:: lua + + Forwarded: for=192.168.2.100;by=10.0.0.2;proto=https;host=test000001.com + + +References +========== + +- `The PROXY protocol Versions 1 & 2 + `_ + +- `Forwarded HTTP Extension + `_ diff --git a/doc/admin-guide/files/records.config.en.rst b/doc/admin-guide/files/records.config.en.rst index e2443846b44..c4c02a805ea 100644 --- a/doc/admin-guide/files/records.config.en.rst +++ b/doc/admin-guide/files/records.config.en.rst @@ -610,6 +610,7 @@ HTTP Engine ip-out Value Local outbound IP address. ip-resolve Value IP address resolution style. proto Value List of supported session protocols. + pp Enable Proxy Protocol. ssl SSL terminated. tr-full Fully transparent (inbound and outbound) tr-in Inbound transparent. @@ -646,6 +647,12 @@ proto all available protocols. For non-TLS proxy ports the default is HTTP only. +pp + Enables Proxy Protocol on the port. If Proxy Protocol is enabled on the + port, all incoming requests must be prefaced with the PROXY header. See + :ref:`Proxy Protocol ` for more details on how to configure + this option properly. + tr-full Fully transparent. This is a convenience option and is identical to specifying both ``tr-in`` and ``tr-out``. @@ -1730,6 +1737,30 @@ Proxy User Variables is prohibited by RFC 7239. Currently, for the ``host`` parameter to provide the original host from the incoming client request, `proxy.config.url_remap.pristine_host_hdr`_ must be enabled. +.. ts:cv:: CONFIG proxy.config.http.proxy_protocol_whitelist STRING `````` + + This defines a whitelist of server IPs that are trusted to provide + connections with Proxy Protocol information. This is a comma delimited list + of IP addresses. Addressed may be listed individually, in a range separated + by a dash or by using CIDR notation. + + ======================= =========================================================== + Example Effect + ======================= =========================================================== + ``10.0.2.123`` A single IP Address. + ``10.0.3.1-10.0.3.254`` A range of IP address. + ``10.0.4.0/24`` A range of IP address specified by CIDR notation. + ======================= ============================================================ + + .. important:: + + If Proxy Protocol is enabled on the port, but this directive is not + defined any server may initiate a connection with Proxy Protocol + information. + See :ts:cv:`proxy.config.http.server_ports` for information on how to enable Proxy Protocol on a port. + + See :ref:`proxy-protocol` for more discussion on how |TS| tranforms the `Forwarded: header. + .. ts:cv:: CONFIG proxy.config.http.normalize_ae INT 1 :reloadable: :overridable: @@ -2631,7 +2662,7 @@ HostDB Set the file path for an external host file. If this is set (non-empty) then the file is presumed to be a hosts file in - the standard `host file format `_. + the standard . It is read and the entries there added to the HostDB. The file is periodically checked for a more recent modification date in which case it is reloaded. The interval is set with :ts:cv:`proxy.config.hostdb.host_file.interval`. diff --git a/doc/static/images/admin/proxy-protocol.png b/doc/static/images/admin/proxy-protocol.png new file mode 100644 index 00000000000..e69de29bb2d diff --git a/include/tscore/IpMapConf.h b/include/tscore/IpMapConf.h index 3e96bba6ba7..7d229adf15d 100644 --- a/include/tscore/IpMapConf.h +++ b/include/tscore/IpMapConf.h @@ -27,6 +27,8 @@ class IpMap; // declare in name only. +// Returns 0 if successful, error string otherwise +int read_addr(char *line, int n, int *i, sockaddr *addr, char *err); // Returns 0 if successful, error string otherwise char *Load_IpMap_From_File(IpMap *map, int fd, char const *key_str); // Returns 0 if successful, error string otherwise diff --git a/iocore/net/Connection.cc b/iocore/net/Connection.cc index 2384033a776..1595bb9791a 100644 --- a/iocore/net/Connection.cc +++ b/iocore/net/Connection.cc @@ -241,6 +241,10 @@ Server::setup_fd_for_listen(bool non_blocking, const NetProcessor::AcceptOptions #endif } + if (opt.f_proxy_protocol) { + Debug("proxyprotocol", "Proxy Protocol enabled."); + } + #if defined(TCP_MAXSEG) if (NetProcessor::accept_mss > 0) { if ((res = safe_setsockopt(fd, IPPROTO_TCP, TCP_MAXSEG, (char *)&NetProcessor::accept_mss, sizeof(int))) < 0) { diff --git a/iocore/net/I_NetProcessor.h b/iocore/net/I_NetProcessor.h index acaa1a6515c..b8dee4e9dbc 100644 --- a/iocore/net/I_NetProcessor.h +++ b/iocore/net/I_NetProcessor.h @@ -24,6 +24,7 @@ #pragma once +#include "tscore/IpMap.h" #include "I_EventSystem.h" #include "I_Socks.h" struct socks_conf_struct; @@ -96,6 +97,9 @@ class NetProcessor : public Processor */ bool f_inbound_transparent; + /// Proxy Protocol enabled + bool f_proxy_protocol; + /// Default constructor. /// Instance is constructed with default values. AcceptOptions() { this->reset(); } diff --git a/iocore/net/I_NetVConnection.h b/iocore/net/I_NetVConnection.h index 1a8d6013126..786c7afe708 100644 --- a/iocore/net/I_NetVConnection.h +++ b/iocore/net/I_NetVConnection.h @@ -34,6 +34,8 @@ #include "ts/apidefs.h" #include #include "YamlSNIConfig.h" +#include "tscpp/util/TextView.h" +#include "tscore/IpMap.h" #define CONNECT_SUCCESS 1 #define CONNECT_FAILURE 0 @@ -130,10 +132,12 @@ struct NetVCOptions { @see ip_family */ IpAddr local_ip; + /** Local port for connection. Set to 0 for "don't care" (default). - */ + */ uint16_t local_port; + /// How to bind the local address. /// @note Default is @c ANY_ADDR. addr_bind_style addr_binding; @@ -642,6 +646,9 @@ class NetVConnection : public AnnotatedVConnection /** Set remote sock addr struct. */ virtual void set_remote_addr() = 0; + /** Set remote sock addr struct. */ + virtual void set_remote_addr(const sockaddr *) = 0; + // for InkAPI bool get_is_internal_request() const @@ -668,6 +675,19 @@ class NetVConnection : public AnnotatedVConnection is_transparent = state; } + /// Get the proxy protocol enabled flag + bool + get_is_proxy_protocol() const + { + return is_proxy_protocol; + } + /// Set the proxy protocol enabled flag on the port + void + set_is_proxy_protocol(bool state = true) + { + is_proxy_protocol = state; + } + virtual int populate_protocol(std::string_view *results, int n) const { @@ -684,6 +704,113 @@ class NetVConnection : public AnnotatedVConnection NetVConnection(const NetVConnection &) = delete; NetVConnection &operator=(const NetVConnection &) = delete; + enum class ProxyProtocolVersion { + UNDEFINED, + V1, + V2, + }; + + enum class ProxyProtocolData { + UNDEFINED, + SRC, + DST, + }; + + int + set_proxy_protocol_addr(const ProxyProtocolData src_or_dst, ts::TextView &ip_addr_str) + { + int ret = -1; + + if (src_or_dst == ProxyProtocolData::SRC) { + ret = ats_ip_pton(ip_addr_str, &pp_info.src_addr); + } else { + ret = ats_ip_pton(ip_addr_str, &pp_info.dst_addr); + } + return ret; + } + + int + set_proxy_protocol_src_addr(ts::TextView src) + { + return set_proxy_protocol_addr(ProxyProtocolData::SRC, src); + } + + int + set_proxy_protocol_dst_addr(ts::TextView src) + { + return set_proxy_protocol_addr(ProxyProtocolData::DST, src); + } + + int + set_proxy_protocol_port(const ProxyProtocolData src_or_dst, in_port_t port) + { + if (src_or_dst == ProxyProtocolData::SRC) { + pp_info.src_addr.port() = htons(port); + } else { + pp_info.dst_addr.port() = htons(port); + } + return port; + } + + int + set_proxy_protocol_src_port(in_port_t port) + { + return set_proxy_protocol_port(ProxyProtocolData::SRC, port); + } + + int + set_proxy_protocol_dst_port(in_port_t port) + { + return set_proxy_protocol_port(ProxyProtocolData::DST, port); + } + + void + set_proxy_protocol_version(const ProxyProtocolVersion ver) + { + pp_info.proxy_protocol_version = ver; + } + + ProxyProtocolVersion + get_proxy_protocol_version() + { + return pp_info.proxy_protocol_version; + } + + sockaddr const *get_proxy_protocol_addr(const ProxyProtocolData); + + sockaddr const * + get_proxy_protocol_src_addr() + { + return get_proxy_protocol_addr(ProxyProtocolData::SRC); + } + + uint16_t + get_proxy_protocol_src_port() + { + return ats_ip_port_host_order(this->get_proxy_protocol_addr(ProxyProtocolData::SRC)); + } + + sockaddr const * + get_proxy_protocol_dst_addr() + { + return get_proxy_protocol_addr(ProxyProtocolData::DST); + } + + uint16_t + get_proxy_protocol_dst_port() + { + return ats_ip_port_host_order(this->get_proxy_protocol_addr(ProxyProtocolData::DST)); + }; + + struct ProxyProtocol { + ProxyProtocolVersion proxy_protocol_version = ProxyProtocolVersion::UNDEFINED; + uint16_t ip_family; + IpEndpoint src_addr; + IpEndpoint dst_addr; + }; + + ProxyProtocol pp_info; + protected: IpEndpoint local_addr; IpEndpoint remote_addr; @@ -694,6 +821,8 @@ class NetVConnection : public AnnotatedVConnection bool is_internal_request; /// Set if this connection is transparent. bool is_transparent; + /// Set if proxy protocol is enabled + bool is_proxy_protocol; /// Set if the next write IO that empties the write buffer should generate an event. int write_buffer_empty_event; /// NetVConnection Context. @@ -708,6 +837,7 @@ inline NetVConnection::NetVConnection() got_remote_addr(false), is_internal_request(false), is_transparent(false), + is_proxy_protocol(false), write_buffer_empty_event(0), netvc_context(NET_VCONNECTION_UNSET) { diff --git a/iocore/net/Makefile.am b/iocore/net/Makefile.am index e9efb3c3778..9eb93963b71 100644 --- a/iocore/net/Makefile.am +++ b/iocore/net/Makefile.am @@ -130,6 +130,8 @@ libinknet_a_SOURCES = \ P_UnixNetVConnection.h \ P_UnixPollDescriptor.h \ P_UnixUDPConnection.h \ + ProxyProtocol.h \ + ProxyProtocol.cc \ Socks.cc \ SSLCertLookup.cc \ SSLSessionCache.cc \ diff --git a/iocore/net/P_NetVConnection.h b/iocore/net/P_NetVConnection.h index a81563304ca..3d85e7a1d35 100644 --- a/iocore/net/P_NetVConnection.h +++ b/iocore/net/P_NetVConnection.h @@ -23,24 +23,28 @@ #include "I_NetVConnection.h" -TS_INLINE sockaddr const * +inline sockaddr const * NetVConnection::get_remote_addr() { if (!got_remote_addr) { - set_remote_addr(); + if (pp_info.proxy_protocol_version != ProxyProtocolVersion::UNDEFINED) { + set_remote_addr(get_proxy_protocol_src_addr()); + } else { + set_remote_addr(); + } got_remote_addr = true; } return &remote_addr.sa; } -TS_INLINE IpEndpoint const & +inline IpEndpoint const & NetVConnection::get_remote_endpoint() { get_remote_addr(); // Make sure the vallue is filled in return remote_addr; } -TS_INLINE in_addr_t +inline in_addr_t NetVConnection::get_remote_ip() { sockaddr const *addr = this->get_remote_addr(); @@ -48,13 +52,13 @@ NetVConnection::get_remote_ip() } /// @return The remote port in host order. -TS_INLINE uint16_t +inline uint16_t NetVConnection::get_remote_port() { return ats_ip_port_host_order(this->get_remote_addr()); } -TS_INLINE sockaddr const * +inline sockaddr const * NetVConnection::get_local_addr() { if (!got_local_addr) { @@ -68,7 +72,7 @@ NetVConnection::get_local_addr() return &local_addr.sa; } -TS_INLINE in_addr_t +inline in_addr_t NetVConnection::get_local_ip() { sockaddr const *addr = this->get_local_addr(); @@ -76,8 +80,27 @@ NetVConnection::get_local_ip() } /// @return The local port in host order. -TS_INLINE uint16_t +inline uint16_t NetVConnection::get_local_port() { return ats_ip_port_host_order(this->get_local_addr()); } + +inline sockaddr const * +NetVConnection::get_proxy_protocol_addr(const ProxyProtocolData src_or_dst) +{ + if (src_or_dst == ProxyProtocolData::SRC) { + if ((pp_info.src_addr.isValid() && pp_info.src_addr.port() != 0) || + (ats_is_ip4(&pp_info.src_addr) && INADDR_ANY != ats_ip4_addr_cast(&pp_info.src_addr)) // IPv4 + || (ats_is_ip6(&pp_info.src_addr) && !IN6_IS_ADDR_UNSPECIFIED(&pp_info.src_addr.sin6.sin6_addr))) { + return &pp_info.src_addr.sa; + } + } else { + if ((pp_info.dst_addr.isValid() && pp_info.dst_addr.port() != 0) || + (ats_is_ip4(&pp_info.dst_addr) && INADDR_ANY != ats_ip4_addr_cast(&pp_info.dst_addr)) // IPv4 + || (ats_is_ip6(&pp_info.dst_addr) && !IN6_IS_ADDR_UNSPECIFIED(&pp_info.dst_addr.sin6.sin6_addr))) { + return &pp_info.dst_addr.sa; + } + } + return nullptr; +} diff --git a/iocore/net/P_SSLConfig.h b/iocore/net/P_SSLConfig.h index 5c93abbbd8f..df178660511 100644 --- a/iocore/net/P_SSLConfig.h +++ b/iocore/net/P_SSLConfig.h @@ -36,6 +36,7 @@ #include #include "P_SSLCertLookup.h" #include "YamlSNIConfig.h" +#include struct SSLCertLookup; struct ssl_ticket_key_block; @@ -109,6 +110,8 @@ struct SSLConfigParams : public ConfigInfo { static size_t session_cache_max_bucket_size; static bool session_cache_skip_on_lock_contention; + static IpMap *proxy_protocol_ipmap; + static init_ssl_ctx_func init_ssl_ctx_cb; static load_ssl_file_func load_ssl_file_cb; @@ -130,6 +133,7 @@ struct SSLConfigParams : public ConfigInfo { void initialize(); void cleanup(); void reset(); + void SSLConfigInit(IpMap *global); }; ///////////////////////////////////////////////////////////// diff --git a/iocore/net/P_UnixNetVConnection.h b/iocore/net/P_UnixNetVConnection.h index 0a320319b1c..94c141801b9 100644 --- a/iocore/net/P_UnixNetVConnection.h +++ b/iocore/net/P_UnixNetVConnection.h @@ -41,7 +41,7 @@ class UnixNetVConnection; class NetHandler; struct PollDescriptor; -TS_INLINE void +inline void NetVCOptions::reset() { ip_proto = USE_TCP; @@ -73,7 +73,7 @@ NetVCOptions::reset() ssl_client_ca_cert_name = nullptr; } -TS_INLINE void +inline void NetVCOptions::set_sock_param(int _recv_bufsize, int _send_bufsize, unsigned long _opt_flags, unsigned long _packet_mark, unsigned long _packet_tos) { @@ -293,6 +293,7 @@ class UnixNetVConnection : public NetVConnection void set_local_addr() override; void set_remote_addr() override; + void set_remote_addr(const sockaddr *) override; int set_tcp_init_cwnd(int init_cwnd) override; int set_tcp_congestion_control(int side) override; void apply_options() override; @@ -322,14 +323,21 @@ extern ClassAllocator netVCAllocator; typedef int (UnixNetVConnection::*NetVConnHandler)(int, void *); -TS_INLINE void +inline void UnixNetVConnection::set_remote_addr() { ats_ip_copy(&remote_addr, &con.addr); this->control_flags.set_flag(ContFlags::DEBUG_OVERRIDE, diags->test_override_ip(remote_addr)); } -TS_INLINE void +inline void +UnixNetVConnection::set_remote_addr(const sockaddr *new_sa) +{ + ats_ip_copy(&remote_addr, new_sa); + this->control_flags.set_flag(ContFlags::DEBUG_OVERRIDE, diags->test_override_ip(remote_addr)); +} + +inline void UnixNetVConnection::set_local_addr() { int local_sa_size = sizeof(local_addr); @@ -339,19 +347,19 @@ UnixNetVConnection::set_local_addr() ATS_UNUSED_RETURN(safe_getsockname(con.fd, &local_addr.sa, &local_sa_size)); } -TS_INLINE ink_hrtime +inline ink_hrtime UnixNetVConnection::get_active_timeout() { return active_timeout_in; } -TS_INLINE ink_hrtime +inline ink_hrtime UnixNetVConnection::get_inactivity_timeout() { return inactivity_timeout_in; } -TS_INLINE void +inline void UnixNetVConnection::set_active_timeout(ink_hrtime timeout_in) { Debug("socket", "Set active timeout=%" PRId64 ", NetVC=%p", timeout_in, this); @@ -359,7 +367,7 @@ UnixNetVConnection::set_active_timeout(ink_hrtime timeout_in) next_activity_timeout_at = Thread::get_hrtime() + timeout_in; } -TS_INLINE void +inline void UnixNetVConnection::cancel_inactivity_timeout() { Debug("socket", "Cancel inactive timeout for NetVC=%p", this); @@ -367,7 +375,7 @@ UnixNetVConnection::cancel_inactivity_timeout() set_inactivity_timeout(0); } -TS_INLINE void +inline void UnixNetVConnection::cancel_active_timeout() { Debug("socket", "Cancel active timeout for NetVC=%p", this); @@ -375,7 +383,7 @@ UnixNetVConnection::cancel_active_timeout() next_activity_timeout_at = 0; } -TS_INLINE int +inline int UnixNetVConnection::set_tcp_init_cwnd(int init_cwnd) { #ifdef TCP_INIT_CWND @@ -390,7 +398,7 @@ UnixNetVConnection::set_tcp_init_cwnd(int init_cwnd) #endif } -TS_INLINE int +inline int UnixNetVConnection::set_tcp_congestion_control(int side) { #ifdef TCP_CONGESTION @@ -425,21 +433,21 @@ UnixNetVConnection::set_tcp_congestion_control(int side) #endif } -TS_INLINE UnixNetVConnection::~UnixNetVConnection() {} +inline UnixNetVConnection::~UnixNetVConnection() {} -TS_INLINE SOCKET +inline SOCKET UnixNetVConnection::get_socket() { return con.fd; } -TS_INLINE void +inline void UnixNetVConnection::set_action(Continuation *c) { action_ = c; } -TS_INLINE const Action * +inline const Action * UnixNetVConnection::get_action() const { return &action_; diff --git a/iocore/net/ProxyProtocol.cc b/iocore/net/ProxyProtocol.cc new file mode 100644 index 00000000000..83c3bcf9dfe --- /dev/null +++ b/iocore/net/ProxyProtocol.cc @@ -0,0 +1,179 @@ +/** @file + * + * PROXY protocol definitions and parsers. + * + * @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_assert.h" +#include "tscpp/util/TextView.h" +#include "ProxyProtocol.h" +#include "I_NetVConnection.h" + +bool +ssl_has_proxy_v1(NetVConnection *sslvc, char *buffer, int64_t *bytes_r) +{ + ts::TextView tv; + + tv.assign(buffer, *bytes_r); + + // Client must send at least 15 bytes to get a reasonable match. + if (tv.size() < PROXY_V1_CONNECTION_HEADER_LEN_MIN) { + Debug("proxyprotocol_v1", "ssl_has_proxy_v1: not enough recv'd"); + return false; + } + + // if we don't have the PROXY preface, we don't have a ProxyV1 header + if (0 != memcmp(PROXY_V1_CONNECTION_PREFACE, buffer, PROXY_V1_CONNECTION_PREFACE_LEN)) { + Debug("proxyprotocol_v1", "ssl_has_proxy_v1: failed the memcmp(%s, %s, %lu)", PROXY_V1_CONNECTION_PREFACE, buffer, + PROXY_V1_CONNECTION_PREFACE_LEN); + return false; + } + + // Find the terminating newline + ts::TextView::size_type pos = tv.find('\n'); + if (pos == tv.npos) { + Debug("proxyprotocol_v1", "ssl_has_proxy_v1: newline not found"); + return false; + } + + // Parse the TextView before moving the bytes in the buffer + if (!proxy_protov1_parse(sslvc, tv)) { + *bytes_r = -EAGAIN; + return false; + } + *bytes_r -= pos + 1; + if (*bytes_r <= 0) { + *bytes_r = -EAGAIN; + } else { + Debug("ssl", "Moving %" PRId64 " characters remaining in the buffer from %p to %p", *bytes_r, buffer + pos + 1, buffer); + memmove(buffer, buffer + pos + 1, *bytes_r); + } + return true; +} + +bool +http_has_proxy_v1(IOBufferReader *reader, NetVConnection *netvc) +{ + char buf[PROXY_V1_CONNECTION_HEADER_LEN_MAX + 1]; + ts::TextView tv; + + tv.assign(buf, reader->memcpy(buf, sizeof(buf), 0)); + + // Client must send at least 15 bytes to get a reasonable match. + if (tv.size() < PROXY_V1_CONNECTION_HEADER_LEN_MIN) { + return false; + } + + if (0 != memcmp(PROXY_V1_CONNECTION_PREFACE, buf, PROXY_V1_CONNECTION_PREFACE_LEN)) { + return false; + } + + // Find the terminating LF, which should already be in the buffer. + ts::TextView::size_type pos = tv.find('\n'); + if (pos == tv.npos) { // not found, it's not a proxy protocol header. + return false; + } + reader->consume(pos + 1); // clear out the header. + + // Now that we know we have a valid PROXY V1 preface, let's parse the + // remainder of the header + + return proxy_protov1_parse(netvc, tv); +} + +bool +proxy_protov1_parse(NetVConnection *netvc, ts::TextView hdr) +{ + static const std::string_view PREFACE{PROXY_V1_CONNECTION_PREFACE, PROXY_V1_CONNECTION_PREFACE_LEN}; + ts::TextView token; + in_port_t port; + + // All the cases are special and sequence, might as well unroll them. + + // The header should begin with the PROXY preface + token = hdr.split_prefix_at(' '); + if (0 == token.size() || token != PREFACE) { + Debug("proxyprotocol_v1", "proxy_protov1_parse: header [%.*s] does not start with preface [%.*s]", static_cast(hdr.size()), + hdr.data(), static_cast(PREFACE.size()), PREFACE.data()); + return false; + } + Debug("proxyprotocol_v1", "proxy_protov1_parse: [%.*s] = PREFACE", static_cast(token.size()), token.data()); + + // The INET protocol family - TCP4, TCP6 or UNKNOWN + token = hdr.split_prefix_at(' '); + if (0 == token.size()) { + return false; + } + Debug("proxyprotocol_v1", "proxy_protov1_parse: [%.*s] = INET Family", static_cast(token.size()), token.data()); + + // Next up is the layer 3 source address + // - 255.255.255.255 or ffff:f...f:ffff ffff:f...f:fff + token = hdr.split_prefix_at(' '); + if (0 == token.size()) { + return false; + } + Debug("proxyprotocol_v1", "proxy_protov1_parse: [%.*s] = Source Address", static_cast(token.size()), token.data()); + if (0 != netvc->set_proxy_protocol_src_addr(token)) { + return false; + } + + // Next is the layer3 destination address + // - 255.255.255.255 or ffff:f...f:ffff ffff:f...f:fff + token = hdr.split_prefix_at(' '); + if (0 == token.size()) { + return false; + } + Debug("proxyprotocol_v1", "proxy_protov1_parse: [%.*s] = Destination Address", static_cast(token.size()), token.data()); + if (0 != netvc->set_proxy_protocol_dst_addr(token)) { + return false; + } + + // Next is the TCP source port represented as a decimal number in the range of [0..65535] inclusive. + token = hdr.split_prefix_at(' '); + if (0 == token.size()) { + return false; + } + Debug("proxyprotocol_v1", "proxy_protov1_parse: [%.*s] = Source Port", static_cast(token.size()), token.data()); + + if (0 == (port = ts::svtoi(token))) { + Debug("proxyprotocol_v1", "proxy_protov1_parse: src port [%d] token [%.*s] failed to parse", port, + static_cast(token.size()), token.data()); + return false; + } + netvc->set_proxy_protocol_src_port(port); + + // Next is the TCP destination port represented as a decimal number in the range of [0..65535] inclusive. + // Final trailer is CR LF so split at CR. + token = hdr.split_prefix_at('\r'); + if (0 == token.size()) { + return false; + } + Debug("proxyprotocol_v1", "proxy_protov1_parse: [%.*s] = Destination Port", static_cast(token.size()), token.data()); + if (0 == (port = ts::svtoi(token))) { + Debug("proxyprotocol_v1", "proxy_protov1_parse: dst port [%d] token [%.*s] failed to parse", port, + static_cast(token.size()), token.data()); + return false; + } + netvc->set_proxy_protocol_dst_port(port); + + netvc->set_proxy_protocol_version(NetVConnection::ProxyProtocolVersion::V1); + + return true; +} diff --git a/iocore/net/ProxyProtocol.h b/iocore/net/ProxyProtocol.h new file mode 100644 index 00000000000..b561205f05c --- /dev/null +++ b/iocore/net/ProxyProtocol.h @@ -0,0 +1,55 @@ +/** @file + + PROXY Protocol + + See: https://www.haproxy.org/download/1.8/doc/proxy-protocol.txt + + @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. + */ + +#ifndef ProxyProtocol_H_ +#define ProxyProtocol_H_ + +#include "tscore/ink_defs.h" +#include "tscore/ink_memory.h" +#include +#include +#include "I_VConnection.h" +#include "I_NetVConnection.h" +#include "I_IOBuffer.h" + +// http://www.haproxy.org/download/1.8/doc/proxy-protocol.txt + +extern bool proxy_protov1_parse(NetVConnection *, ts::TextView hdr); +extern bool ssl_has_proxy_v1(NetVConnection *, char *, int64_t *); +extern bool http_has_proxy_v1(IOBufferReader *, NetVConnection *); + +const char *const PROXY_V1_CONNECTION_PREFACE = "\x50\x52\x4F\x58\x59"; +const char *const PROXY_V2_CONNECTION_PREFACE = "\x0D\x0A\x0D\x0A\x00\x0D\x0A\x51\x55\x49\x54\x0A\x02"; + +const size_t PROXY_V1_CONNECTION_PREFACE_LEN = strlen(PROXY_V1_CONNECTION_PREFACE); // 5 +const size_t PROXY_V2_CONNECTION_PREFACE_LEN = 13; + +const size_t PROXY_V1_CONNECTION_HEADER_LEN_MIN = 15; +const size_t PROXY_V2_CONNECTION_HEADER_LEN_MIN = 16; + +const size_t PROXY_V1_CONNECTION_HEADER_LEN_MAX = 108; +const size_t PROXY_V2_CONNECTION_HEADER_LEN_MAX = 16; + +#endif /* ProxyProtocol_H_ */ diff --git a/iocore/net/SSLConfig.cc b/iocore/net/SSLConfig.cc index 2cdeca0ad63..b46ff5f90fb 100644 --- a/iocore/net/SSLConfig.cc +++ b/iocore/net/SSLConfig.cc @@ -41,6 +41,7 @@ #include "P_SSLCertLookup.h" #include "SSLSessionCache.h" #include +#include int SSLConfig::configid = 0; int SSLCertificateConfig::configid = 0; @@ -57,6 +58,7 @@ bool SSLConfigParams::session_cache_skip_on_lock_contention = false; size_t SSLConfigParams::session_cache_max_bucket_size = 100; init_ssl_ctx_func SSLConfigParams::init_ssl_ctx_cb = nullptr; load_ssl_file_func SSLConfigParams::load_ssl_file_cb = nullptr; +IpMap *SSLConfigParams::proxy_protocol_ipmap = nullptr; int SSLConfigParams::async_handshake_enabled = 0; char *SSLConfigParams::engine_conf_file = nullptr; @@ -84,6 +86,12 @@ SSLConfigParams::~SSLConfigParams() ink_mutex_destroy(&ctxMapLock); } +void +SSLConfigInit(IpMap *global) +{ + SSLConfigParams::proxy_protocol_ipmap = global; +} + void SSLConfigParams::reset() { diff --git a/iocore/net/SSLNetVConnection.cc b/iocore/net/SSLNetVConnection.cc index 1e5dccf8cc2..86cc813a1a1 100644 --- a/iocore/net/SSLNetVConnection.cc +++ b/iocore/net/SSLNetVConnection.cc @@ -34,6 +34,8 @@ #include "P_SSLClientUtils.h" #include "P_SSLSNI.h" #include "HttpTunnel.h" +#include "ProxyProtocol.h" +#include #include #include @@ -366,6 +368,49 @@ SSLNetVConnection::read_raw_data() } NET_SUM_DYN_STAT(net_read_bytes_stat, r); + IpMap *pp_ipmap; + pp_ipmap = SSLConfigParams::proxy_protocol_ipmap; + + if (this->get_is_proxy_protocol()) { + Debug("proxyprotocol", "[SSLNetVConnection::read_raw_data] proxy protocol is enabled on this port"); + if (pp_ipmap->count() > 0) { + Debug("proxyprotocol", + "[SSLNetVConnection::read_raw_data] proxy protocol has a configured whitelist of trusted IPs - checking"); + + // At this point, using get_remote_addr() will return the ip of the + // proxy source IP, not the Proxy Protocol client ip. Since we are + // checking the ip of the actual source of this connection, this is + // what we want now. + void *payload = nullptr; + if (!pp_ipmap->contains(get_remote_addr(), &payload)) { + Debug("proxyprotocol", + "[SSLNetVConnection::read_raw_data] proxy protocol src IP is NOT in the configured whitelist of trusted IPs - " + "closing connection"); + r = -ENOTCONN; // Need a quick close/exit here to refuse the connection!!!!!!!!! + goto proxy_protocol_bypass; + } else { + char new_host[INET6_ADDRSTRLEN]; + Debug("proxyprotocol", "[SSLNetVConnection::read_raw_data] Source IP [%s] is in the trusted whitelist for proxy protocol", + ats_ip_ntop(this->get_remote_addr(), new_host, sizeof(new_host))); + } + } else { + Debug("proxyprotocol", + "[SSLNetVConnection::read_raw_data] proxy protocol DOES NOT have a configured whitelist of trusted IPs but " + "proxy protocol is ernabled on this port - processing all connections"); + } + + if (ssl_has_proxy_v1(this, buffer, &r)) { + Debug("proxyprotocol", "[SSLNetVConnection::read_raw_data] ssl has proxy_v1 header"); + set_remote_addr(get_proxy_protocol_src_addr()); + } else { + Debug("proxyprotocol", + "[SSLNetVConnection::read_raw_data] proxy protocol was enabled, but required header was not present in the " + "transaction - closing connection"); + } + } // end of Proxy Protocol processing + +proxy_protocol_bypass: + if (r > 0) { this->handShakeBuffer->fill(r); diff --git a/iocore/net/UnixNetAccept.cc b/iocore/net/UnixNetAccept.cc index 21f39c4ac85..eccef03d38e 100644 --- a/iocore/net/UnixNetAccept.cc +++ b/iocore/net/UnixNetAccept.cc @@ -115,6 +115,7 @@ net_accept(NetAccept *na, void *ep, bool blockable) vc->submit_time = Thread::get_hrtime(); vc->action_ = *na->action_; vc->set_is_transparent(na->opt.f_inbound_transparent); + vc->set_is_proxy_protocol(na->opt.f_proxy_protocol); vc->set_context(NET_VCONNECTION_IN); #ifdef USE_EDGE_TRIGGER // Set the vc as triggered and place it in the read ready queue later in case there is already data on the socket. @@ -347,6 +348,7 @@ NetAccept::do_blocking_accept(EThread *t) vc->submit_time = Thread::get_hrtime(); vc->action_ = *action_; vc->set_is_transparent(opt.f_inbound_transparent); + vc->set_is_proxy_protocol(opt.f_proxy_protocol); vc->options.packet_mark = opt.packet_mark; vc->options.packet_tos = opt.packet_tos; vc->options.ip_family = opt.ip_family; @@ -495,6 +497,7 @@ NetAccept::acceptFastEvent(int event, void *ep) vc->submit_time = Thread::get_hrtime(); vc->action_ = *action_; vc->set_is_transparent(opt.f_inbound_transparent); + vc->set_is_proxy_protocol(opt.f_proxy_protocol); vc->options.packet_mark = opt.packet_mark; vc->options.packet_tos = opt.packet_tos; vc->options.ip_family = opt.ip_family; diff --git a/iocore/net/UnixNetProcessor.cc b/iocore/net/UnixNetProcessor.cc index 3d4771c401d..436e6faac37 100644 --- a/iocore/net/UnixNetProcessor.cc +++ b/iocore/net/UnixNetProcessor.cc @@ -51,6 +51,7 @@ NetProcessor::AcceptOptions::reset() packet_tos = 0; tfo_queue_length = 0; f_inbound_transparent = false; + f_proxy_protocol = false; return *this; } @@ -122,6 +123,10 @@ UnixNetProcessor::accept_internal(Continuation *cont, int fd, AcceptOptions cons Debug("http_tproxy", "Marked accept server %p on port %d as inbound transparent", na, opt.local_port); } + if (opt.f_proxy_protocol) { + Debug("http_tproxy", "Marked accept server %p on port %d for proxy protocol", na, opt.local_port); + } + int should_filter_int = 0; na->server.http_accept_filter = false; REC_ReadConfigInteger(should_filter_int, "proxy.config.net.defer_accept"); diff --git a/lib/records/I_RecHttp.h b/lib/records/I_RecHttp.h index 7e831fc590f..1c95ef72100 100644 --- a/lib/records/I_RecHttp.h +++ b/lib/records/I_RecHttp.h @@ -28,6 +28,7 @@ #include "ts/apidefs.h" #include "ts/apidefs.h" #include "tscore/ink_assert.h" +#include "tscore/IpMap.h" #include #include @@ -37,6 +38,11 @@ void RecHttpLoadIp(const char *name, ///< Name of value in configuration file. IpAddr &ip6 ///< [out] Ipv6 address. ); +/// Load up an IpMap with IP addresses from the configuration file. +void RecHttpLoadIpMap(const char *name, ///< Name of value in configuration file. + IpMap &ipmap ///< [out] IpMap. +); + /** A set of session protocols. This depends on using @c SessionProtocolNameRegistry to get the indices. */ @@ -239,6 +245,8 @@ struct HttpProxyPort { TransportType m_type; ///< Type of connection. in_port_t m_port; ///< Port on which to listen. uint8_t m_family; ///< IP address family. + /// True if proxy protocol is required on incoming requests. + bool m_proxy_protocol; /// True if inbound connects (from client) are transparent. bool m_inbound_transparent_p; /// True if outbound connections (to origin servers) are transparent. @@ -392,6 +400,7 @@ struct HttpProxyPort { static const char *const OPT_TRANSPARENT_FULL; ///< Full transparency. static const char *const OPT_TRANSPARENT_PASSTHROUGH; ///< Pass-through non-HTTP. static const char *const OPT_SSL; ///< SSL (experimental) + static const char *const OPT_PROXY_PROTO; ///< Proxy Protocol static const char *const OPT_PLUGIN; ///< Protocol Plugin handle (experimental) static const char *const OPT_BLIND_TUNNEL; ///< Blind tunnel. static const char *const OPT_COMPRESSED; ///< Compressed. diff --git a/lib/records/RecHttp.cc b/lib/records/RecHttp.cc index c7acb5e0f03..64193b8043f 100644 --- a/lib/records/RecHttp.cc +++ b/lib/records/RecHttp.cc @@ -30,6 +30,7 @@ #include "tscore/ink_inet.h" #include #include +#include SessionProtocolNameRegistry globalSessionProtocolNameRegistry; @@ -123,6 +124,30 @@ RecHttpLoadIp(const char *value_name, IpAddr &ip4, IpAddr &ip6) } } +void +RecHttpLoadIpMap(const char *value_name, IpMap &ipmap) +{ + char value[1024]; + IpAddr laddr; + IpAddr raddr; + void *payload = nullptr; + + if (REC_ERR_OKAY == RecGetRecordString(value_name, value, sizeof(value))) { + Debug("config", "RecHttpLoadIpMap: parsing the name [%s] and value [%s] to an IpMap", value_name, value); + Tokenizer tokens(", "); + int n_addrs = tokens.Initialize(value); + for (int i = 0; i < n_addrs; ++i) { + const char *val = tokens[i]; + + Debug("config", "RecHttpLoadIpMap: marking the value [%s] to an IpMap entry", val); + if (0 == ats_ip_range_parse(val, laddr, raddr)) { + ipmap.fill(laddr, raddr, payload); + } + } + } + Debug("config", "RecHttpLoadIpMap: parsed %zu IpMap entries", ipmap.count()); +} + const char *const HttpProxyPort::DEFAULT_VALUE = "8080"; const char *const HttpProxyPort::PORTS_CONFIG_NAME = "proxy.config.http.server_ports"; @@ -144,6 +169,7 @@ const char *const HttpProxyPort::OPT_TRANSPARENT_OUTBOUND = "tr-out"; const char *const HttpProxyPort::OPT_TRANSPARENT_FULL = "tr-full"; const char *const HttpProxyPort::OPT_TRANSPARENT_PASSTHROUGH = "tr-pass"; const char *const HttpProxyPort::OPT_SSL = "ssl"; +const char *const HttpProxyPort::OPT_PROXY_PROTO = "pp"; const char *const HttpProxyPort::OPT_PLUGIN = "plugin"; const char *const HttpProxyPort::OPT_BLIND_TUNNEL = "blind"; const char *const HttpProxyPort::OPT_COMPRESSED = "compressed"; @@ -176,6 +202,7 @@ HttpProxyPort::HttpProxyPort() m_type(TRANSPORT_DEFAULT), m_port(0), m_family(AF_INET), + m_proxy_protocol(false), m_inbound_transparent_p(false), m_outbound_transparent_p(false), m_transparent_passthrough(false), @@ -358,6 +385,8 @@ HttpProxyPort::processOptions(const char *opts) m_type = TRANSPORT_SSL; } else if (0 == strcasecmp(OPT_PLUGIN, item)) { m_type = TRANSPORT_PLUGIN; + } else if (0 == strcasecmp(OPT_PROXY_PROTO, item)) { + m_proxy_protocol = true; } else if (0 == strcasecmp(OPT_TRANSPARENT_INBOUND, item)) { #if TS_USE_TPROXY m_inbound_transparent_p = true; @@ -558,6 +587,10 @@ HttpProxyPort::print(char *out, size_t n) return n; } + if (m_proxy_protocol) { + zret += snprintf(out + zret, n - zret, ":%s", OPT_PROXY_PROTO); + } + if (m_outbound_transparent_p && m_inbound_transparent_p) { zret += snprintf(out + zret, n - zret, ":%s", OPT_TRANSPARENT_FULL); } else if (m_inbound_transparent_p) { diff --git a/mgmt/LocalManager.cc b/mgmt/LocalManager.cc index d23c913ae60..0d355242fc2 100644 --- a/mgmt/LocalManager.cc +++ b/mgmt/LocalManager.cc @@ -1057,6 +1057,10 @@ LocalManager::bindProxyPort(HttpProxyPort &port) mgmt_fatal(0, "[bindProxyPort] Unable to set socket options: %d : %s\n", port.m_port, strerror(errno)); } + if (port.m_proxy_protocol) { + Debug("lm", "[bindProxyPort] Proxy Protocol enabled"); + } + if (port.m_inbound_transparent_p) { #if TS_USE_TPROXY Debug("http_tproxy", "Listen port %d inbound transparency enabled.", port.m_port); diff --git a/mgmt/RecordsConfig.cc b/mgmt/RecordsConfig.cc index 7c329f1fab0..d4a212262ad 100644 --- a/mgmt/RecordsConfig.cc +++ b/mgmt/RecordsConfig.cc @@ -534,6 +534,8 @@ static const RecordElement RecordsConfig[] = , {RECT_CONFIG, "proxy.config.http.insert_forwarded", RECD_STRING, "none", RECU_DYNAMIC, RR_NULL, RECC_NULL, nullptr, RECA_NULL} , + {RECT_CONFIG, "proxy.config.http.proxy_protocol_whitelist", RECD_STRING, "none", RECU_NULL, RR_NULL, RECC_NULL, nullptr, RECA_NULL} + , {RECT_CONFIG, "proxy.config.http.insert_age_in_response", RECD_INT, "1", RECU_DYNAMIC, RR_NULL, RECC_INT, "[0-1]", RECA_NULL} , {RECT_CONFIG, "proxy.config.http.enable_http_stats", RECD_INT, "1", RECU_DYNAMIC, RR_NULL, RECC_INT, "[0-1]", RECA_NULL} diff --git a/proxy/PluginVC.cc b/proxy/PluginVC.cc index 44d40765e7e..3e488ccc050 100644 --- a/proxy/PluginVC.cc +++ b/proxy/PluginVC.cc @@ -957,6 +957,12 @@ PluginVC::set_remote_addr() } } +void +PluginVC::set_remote_addr(const sockaddr * /* new_sa ATS_UNUSED */) +{ + return; +} + int PluginVC::set_tcp_init_cwnd(int /* init_cwnd ATS_UNUSED */) { diff --git a/proxy/PluginVC.h b/proxy/PluginVC.h index e3603829f60..1e879efcced 100644 --- a/proxy/PluginVC.h +++ b/proxy/PluginVC.h @@ -101,6 +101,7 @@ class PluginVC : public NetVConnection, public PluginIdentity SOCKET get_socket() override; void set_local_addr() override; void set_remote_addr() override; + void set_remote_addr(const sockaddr *) override; int set_tcp_init_cwnd(int init_cwnd) override; int set_tcp_congestion_control(int) override; diff --git a/proxy/ProtocolProbeSessionAccept.cc b/proxy/ProtocolProbeSessionAccept.cc index 8d332941d6d..31b6e52ad4d 100644 --- a/proxy/ProtocolProbeSessionAccept.cc +++ b/proxy/ProtocolProbeSessionAccept.cc @@ -25,6 +25,8 @@ #include "I_Machine.h" #include "ProtocolProbeSessionAccept.h" #include "http2/HTTP2.h" +#include "ProxyProtocol.h" +#include "I_NetVConnection.h" static bool proto_is_http2(IOBufferReader *reader) @@ -90,6 +92,44 @@ struct ProtocolProbeTrampoline : public Continuation, public ProtocolProbeSessio goto done; } + // if proxy_protocol is enabled via port descriptor AND the src IP is in + // the trusted whitelist for proxy protocol, then check to see if it is + // present + + IpMap *pp_ipmap; + pp_ipmap = probeParent->proxy_protocol_ipmap; + + if (netvc->get_is_proxy_protocol()) { + Debug("proxyprotocol", "ioCompletionEvent: proxy protocol is enabled on this port"); + if (pp_ipmap->count() > 0) { + Debug("proxyprotocol", "ioCompletionEvent: proxy protocol has a configured whitelist of trusted IPs - checking"); + void *payload = nullptr; + if (!pp_ipmap->contains(netvc->get_remote_addr(), &payload)) { + Debug("proxyprotocol", + "ioCompletionEvent: proxy protocol src IP is NOT in the configured whitelist of trusted IPs - closing connection"); + goto done; + } else { + char new_host[INET6_ADDRSTRLEN]; + Debug("proxyprotocol", "ioCompletionEvent: Source IP [%s] is trusted in the whitelist for proxy protocol", + ats_ip_ntop(netvc->get_remote_addr(), new_host, sizeof(new_host))); + } + } else { + Debug("proxyprotocol", + "ioCompletionEvent: proxy protocol DOES NOT have a configured whitelist of trusted IPs but proxy protocol is " + "ernabled on this port - processing all connections"); + } + + if (http_has_proxy_v1(reader, netvc)) { + Debug("proxyprotocol", "ioCompletionEvent: http has proxy_v1 header"); + netvc->set_remote_addr(netvc->get_proxy_protocol_src_addr()); + } else { + Debug("proxyprotocol", + "ioCompletionEvent: proxy protocol was enabled, but required header was not present in the transaction - " + "closing connection"); + goto done; + } + } // end of Proxy Protocol processing + if (proto_is_http2(reader)) { key = PROTO_HTTP2; } else { diff --git a/proxy/ProtocolProbeSessionAccept.h b/proxy/ProtocolProbeSessionAccept.h index 8ba84201760..c58dbe31bd2 100644 --- a/proxy/ProtocolProbeSessionAccept.h +++ b/proxy/ProtocolProbeSessionAccept.h @@ -53,6 +53,8 @@ class ProtocolProbeSessionAccept : public SessionAccept, public ProtocolProbeSes ProtocolProbeSessionAccept(const ProtocolProbeSessionAccept &) = delete; // disabled ProtocolProbeSessionAccept &operator=(const ProtocolProbeSessionAccept &) = delete; // disabled + IpMap *proxy_protocol_ipmap = nullptr; + private: int mainEvent(int event, void *netvc) override; diff --git a/proxy/http/HttpConfig.cc b/proxy/http/HttpConfig.cc index 887fe5e2b86..5521233bffc 100644 --- a/proxy/http/HttpConfig.cc +++ b/proxy/http/HttpConfig.cc @@ -961,6 +961,7 @@ load_negative_caching_var(RecRecord const *r, void *cookie) void HttpConfig::startup() { + extern void SSLConfigInit(IpMap * map); http_rsb = RecAllocateRawStatBlock((int)http_stat_count); register_stat_callbacks(); @@ -978,6 +979,8 @@ HttpConfig::startup() RecHttpLoadIp("proxy.local.incoming_ip_to_bind", c.inbound_ip4, c.inbound_ip6); RecHttpLoadIp("proxy.local.outgoing_ip_to_bind", c.outbound_ip4, c.outbound_ip6); + RecHttpLoadIpMap("proxy.config.http.proxy_protocol_whitelist", c.config_proxy_protocol_ipmap); + SSLConfigInit(&c.config_proxy_protocol_ipmap); HttpEstablishStaticConfigLongLong(c.server_max_connections, "proxy.config.http.server_max_connections"); HttpEstablishStaticConfigLongLong(c.max_websocket_connections, "proxy.config.http.websocket.max_number_of_connections"); diff --git a/proxy/http/HttpConfig.h b/proxy/http/HttpConfig.h index 59f0faf7bfa..61092e9b645 100644 --- a/proxy/http/HttpConfig.h +++ b/proxy/http/HttpConfig.h @@ -836,6 +836,8 @@ struct HttpConfigParams : public ConfigInfo { public: IpAddr inbound_ip4, inbound_ip6; IpAddr outbound_ip4, outbound_ip6; + IpAddr proxy_protocol_ip4, proxy_protocol_ip6; + IpMap config_proxy_protocol_ipmap; MgmtInt server_max_connections = 0; MgmtInt origin_min_keep_alive_connections = 0; // TODO: This one really ought to be overridable, but difficult right now. diff --git a/proxy/http/HttpProxyServerMain.cc b/proxy/http/HttpProxyServerMain.cc index a342e276f31..fc68a9a67bb 100644 --- a/proxy/http/HttpProxyServerMain.cc +++ b/proxy/http/HttpProxyServerMain.cc @@ -160,6 +160,7 @@ make_net_accept_options(const HttpProxyPort *port, unsigned nthreads) net.f_inbound_transparent = port->m_inbound_transparent_p; 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; @@ -169,7 +170,6 @@ make_net_accept_options(const HttpProxyPort *port, unsigned nthreads) net.local_ip = HttpConfig::m_master.inbound_ip4; } } - return net; } @@ -209,6 +209,7 @@ MakeHttpProxyAcceptor(HttpProxyAcceptor &acceptor, HttpProxyPort &port, unsigned 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); diff --git a/proxy/http/HttpTransact.cc b/proxy/http/HttpTransact.cc index 2c06b77c909..d97a516f5a4 100644 --- a/proxy/http/HttpTransact.cc +++ b/proxy/http/HttpTransact.cc @@ -5571,7 +5571,12 @@ HttpTransact::initialize_state_variables_from_request(State *s, HTTPHdr *obsolet ats_ip_copy(&s->request_data.src_ip, &s->client_info.src_addr); memset(&s->request_data.dest_ip, 0, sizeof(s->request_data.dest_ip)); if (vc) { - s->request_data.incoming_port = vc->get_local_port(); + s->request_data.incoming_port = vc->get_local_port(); + s->pp_info.proxy_protocol_version = vc->get_proxy_protocol_version(); + if (s->pp_info.proxy_protocol_version != NetVConnection::ProxyProtocolVersion::UNDEFINED) { + ats_ip_copy(s->pp_info.src_addr, vc->pp_info.src_addr); + ats_ip_copy(s->pp_info.dst_addr, vc->pp_info.dst_addr); + } } s->request_data.xact_start = s->client_request_time; s->request_data.api_info = &s->api_info; diff --git a/proxy/http/HttpTransact.h b/proxy/http/HttpTransact.h index 031a0a0a2cb..082e9cdd054 100644 --- a/proxy/http/HttpTransact.h +++ b/proxy/http/HttpTransact.h @@ -921,6 +921,9 @@ class HttpTransact } internal_msg_buffer_size = 0; } + + NetVConnection::ProxyProtocol pp_info; + }; // End of State struct. static void HandleBlindTunnel(State *s);