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);