Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions iocore/net/ProxyProtocol.cc
Original file line number Diff line number Diff line change
Expand Up @@ -356,6 +356,7 @@ proxy_protocol_v1_build(uint8_t *buf, size_t max_buf_len, const ProxyProtocol &p
bw.fill(len);
}

Debug("proxyprotocol_v1", "Proxy Protocol v1: %.*s", static_cast<int>(bw.size()), bw.data());
bw.write("\r\n");

return bw.size();
Expand Down Expand Up @@ -441,6 +442,7 @@ proxy_protocol_v2_build(uint8_t *buf, size_t max_buf_len, const ProxyProtocol &p
// Set len field (number of following bytes part of the header) in the hdr
uint16_t len = htons(bw.size() - PPv2_CONNECTION_HEADER_LEN);
memcpy(buf + len_field_offset, &len, sizeof(uint16_t));
Debug("proxyprotocol_v2", "Proxy Protocol v2 of %zu bytes", bw.size());
return bw.size();
}

Expand Down
11 changes: 9 additions & 2 deletions proxy/http/HttpSM.cc
Original file line number Diff line number Diff line change
Expand Up @@ -127,9 +127,10 @@ do_outbound_proxy_protocol(MIOBuffer *miob, NetVConnection *vc_out, NetVConnecti
// nothing to forward
return 0;
} else {
Debug("proxyprotocol", "vc_in had no Proxy Protocol. Manufacturing from the vc_in socket.");
// set info from incoming NetVConnection
IpEndpoint local = vc_in->get_local_endpoint();
info = ProxyProtocol{pp_version, local.family(), local, vc_in->get_remote_endpoint()};
info = ProxyProtocol{pp_version, local.family(), vc_in->get_remote_endpoint(), local};
}
}

Expand Down Expand Up @@ -6982,7 +6983,13 @@ HttpSM::setup_blind_tunnel(bool send_response_hdr, IOBufferReader *initial)
client_response_hdr_bytes = 0;
}

client_request_body_bytes = 0;
int64_t nbytes = 0;
if (t_state.txn_conf->proxy_protocol_out >= 0) {
nbytes = do_outbound_proxy_protocol(from_ua_buf, static_cast<NetVConnection *>(server_entry->vc), ua_txn->get_netvc(),
t_state.txn_conf->proxy_protocol_out);
}

client_request_body_bytes = nbytes;
if (ua_raw_buffer_reader != nullptr) {
client_request_body_bytes += from_ua_buf->write(ua_raw_buffer_reader, client_request_hdr_bytes);
ua_raw_buffer_reader->dealloc();
Expand Down
14 changes: 13 additions & 1 deletion tests/gold_tests/autest-site/trafficserver.test.ext
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,7 @@ default_log_data = {
def MakeATSProcess(obj, name, command='traffic_server', select_ports=True,
enable_tls=False, enable_cache=True, enable_quic=False,
block_for_debug=False, log_data=default_log_data,
use_traffic_out=True):
use_traffic_out=True, enable_proxy_protocol=False):
#####################################
# common locations

Expand Down Expand Up @@ -328,6 +328,14 @@ def MakeATSProcess(obj, name, command='traffic_server', select_ports=True,
if enable_tls:
get_port(p, "ssl_port")
get_port(p, "ssl_portv6")

if enable_proxy_protocol:
get_port(p, "proxy_protocol_port")
get_port(p, "proxy_protocol_portv6")

if enable_tls:
get_port(p, "proxy_protocol_ssl_port")
get_port(p, "proxy_protocol_ssl_portv6")
else:
p.Variables.port = 8080
p.Variables.portv6 = 8080
Expand Down Expand Up @@ -382,6 +390,10 @@ def MakeATSProcess(obj, name, command='traffic_server', select_ports=True,
if enable_quic:
port_str += " {ssl_port}:quic {ssl_portv6}:quic:ipv6".format(
ssl_port=p.Variables.ssl_port, ssl_portv6=p.Variables.ssl_portv6)
if enable_proxy_protocol:
port_str += f" {p.Variables.proxy_protocol_port}:pp {p.Variables.proxy_protocol_portv6}:pp:ipv6"
if enable_tls:
port_str += f" {p.Variables.proxy_protocol_ssl_port}:pp:ssl {p.Variables.proxy_protocol_ssl_portv6}:pp:ssl:ipv6"
#p.Env['PROXY_CONFIG_HTTP_SERVER_PORTS'] = port_str
p.Disk.records_config.update({
'proxy.config.http.server_ports': port_str,
Expand Down
168 changes: 163 additions & 5 deletions tests/gold_tests/proxy_protocol/proxy_protocol.test.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@
# limitations under the License.

import os
from ports import get_port
import sys

Test.Summary = 'Test PROXY Protocol'
Expand All @@ -27,6 +28,8 @@


class ProxyProtocolTest:
"""Test that ATS can receive Proxy Protocol."""

def __init__(self):
self.setupOriginServer()
self.setupTS()
Expand All @@ -39,7 +42,7 @@ def setupOriginServer(self):
'''

def setupTS(self):
self.ts = Test.MakeATSProcess("ts", enable_tls=True, enable_cache=False)
self.ts = Test.MakeATSProcess("ts_in", enable_tls=True, enable_cache=False, enable_proxy_protocol=True)

self.ts.addDefaultSSLFiles()
self.ts.Disk.ssl_multicert_config.AddLine("dest_ip=* ssl_cert_name=server.pem ssl_key_name=server.key")
Expand All @@ -48,7 +51,6 @@ def setupTS(self):
f"map / http://127.0.0.1:{self.httpbin.Variables.Port}/")

self.ts.Disk.records_config.update({
"proxy.config.http.server_ports": f"{self.ts.Variables.port}:pp {self.ts.Variables.ssl_port}:ssl:pp",
"proxy.config.http.proxy_protocol_allowlist": "127.0.0.1",
"proxy.config.http.insert_forwarded": "for|by=ip|proto",
"proxy.config.ssl.server.cert.path": f"{self.ts.Variables.SSLDir}",
Expand Down Expand Up @@ -76,7 +78,7 @@ def addTestCase0(self):
tr = Test.AddTestRun()
tr.Processes.Default.StartBefore(self.httpbin)
tr.Processes.Default.StartBefore(self.ts)
tr.Processes.Default.Command = f"curl -vs --haproxy-protocol http://localhost:{self.ts.Variables.port}/get | {self.json_printer}"
tr.Processes.Default.Command = f"curl -vs --haproxy-protocol http://localhost:{self.ts.Variables.proxy_protocol_port}/get | {self.json_printer}"
tr.Processes.Default.ReturnCode = 0
tr.Processes.Default.Streams.stdout = "gold/test_case_0_stdout.gold"
tr.Processes.Default.Streams.stderr = "gold/test_case_0_stderr.gold"
Expand All @@ -88,7 +90,7 @@ def addTestCase1(self):
Incoming PROXY Protocol v1 on SSL port
"""
tr = Test.AddTestRun()
tr.Processes.Default.Command = f"curl -vsk --haproxy-protocol --http1.1 https://localhost:{self.ts.Variables.ssl_port}/get | {self.json_printer}"
tr.Processes.Default.Command = f"curl -vsk --haproxy-protocol --http1.1 https://localhost:{self.ts.Variables.proxy_protocol_ssl_port}/get | {self.json_printer}"
tr.Processes.Default.ReturnCode = 0
tr.Processes.Default.Streams.stdout = "gold/test_case_1_stdout.gold"
tr.Processes.Default.Streams.stderr = "gold/test_case_1_stderr.gold"
Expand All @@ -100,7 +102,7 @@ def addTestCase2(self):
Test with netcat
"""
tr = Test.AddTestRun()
tr.Processes.Default.Command = f"echo 'PROXY TCP4 198.51.100.1 198.51.100.2 51137 80\r\nGET /get HTTP/1.1\r\nHost: 127.0.0.1:80\r\n' | nc localhost {self.ts.Variables.port}"
tr.Processes.Default.Command = f"echo 'PROXY TCP4 198.51.100.1 198.51.100.2 51137 80\r\nGET /get HTTP/1.1\r\nHost: 127.0.0.1:80\r\n' | nc localhost {self.ts.Variables.proxy_protocol_port}"
tr.Processes.Default.ReturnCode = 0
tr.Processes.Default.Streams.stdout = "gold/test_case_2_stdout.gold"
tr.StillRunningAfter = self.httpbin
Expand All @@ -127,4 +129,160 @@ def run(self):
self.addTestCase99()


class ProxyProtocolOutTest:
"""Test that ATS can send Proxy Protocol."""

_pp_server = 'proxy_protocol_server.py'

_dns_counter = 0
_server_counter = 0
_ts_counter = 0

def __init__(self, pp_version: int, is_tunnel: bool) -> None:
"""Initialize a ProxyProtocolOutTest.

:param pp_version: The Proxy Protocol version to use (1 or 2).
:param is_tunnel: Whether ATS should tunnel to the origin.
"""

if pp_version not in (-1, 1, 2):
raise ValueError(
f'Invalid Proxy Protocol version (not 1 or 2): {pp_version}')
self._pp_version = pp_version
self._is_tunnel = is_tunnel

def setupOriginServer(self, tr: 'TestRun') -> None:
"""Configure the origin server.

:param tr: The TestRun to associate the origin's Process with.
"""
tr.Setup.CopyAs(self._pp_server, tr.RunDirectory)
cert_file = os.path.join(Test.Variables.AtsTestToolsDir, "ssl", "server.pem")
key_file = os.path.join(Test.Variables.AtsTestToolsDir, "ssl", "server.key")
tr.Setup.Copy(cert_file)
tr.Setup.Copy(key_file)
server = tr.Processes.Process(
f'server-{ProxyProtocolOutTest._server_counter}')
ProxyProtocolOutTest._server_counter += 1
server_port = get_port(server, "external_port")
internal_port = get_port(server, "internal_port")
command = (
f'{sys.executable} {self._pp_server} '
f'server.pem server.key 127.0.0.1 {server_port} {internal_port}')
if not self._is_tunnel:
command += ' --plaintext'
server.Command = command
server.Ready = When.PortOpenv4(server_port)

self._server = server

def setupDNS(self, tr: 'TestRun') -> None:
"""Configure the DNS server.

:param tr: The TestRun to associate the DNS's Process with.
"""
self._dns = tr.MakeDNServer(
f'dns-{ProxyProtocolOutTest._dns_counter}',
default='127.0.0.1')
ProxyProtocolOutTest._dns_counter += 1

def setupTS(self, tr: 'TestRun') -> None:
"""Configure Traffic Server."""
process_name = f'ts-out-{ProxyProtocolOutTest._ts_counter}'
ProxyProtocolOutTest._ts_counter += 1
self._ts = tr.MakeATSProcess(process_name, enable_tls=True,
enable_cache=False)

self._ts.addDefaultSSLFiles()
self._ts.Disk.ssl_multicert_config.AddLine(
"dest_ip=* ssl_cert_name=server.pem ssl_key_name=server.key"
)

self._ts.Disk.remap_config.AddLine(
f"map / http://backend.pp.origin.com:{self._server.Variables.external_port}/")

self._ts.Disk.records_config.update({
"proxy.config.ssl.server.cert.path": f"{self._ts.Variables.SSLDir}",
"proxy.config.ssl.server.private_key.path": f"{self._ts.Variables.SSLDir}",
"proxy.config.diags.debug.enabled": 1,
"proxy.config.diags.debug.tags": "http|proxyprotocol",
"proxy.config.http.proxy_protocol_out": self._pp_version,
"proxy.config.dns.nameservers": f"127.0.0.1:{self._dns.Variables.Port}",
"proxy.config.dns.resolv_conf": 'NULL'
})

if self._is_tunnel:
self._ts.Disk.records_config.update({
"proxy.config.http.connect_ports": f'{self._server.Variables.external_port}',
})

self._ts.Disk.sni_yaml.AddLines([
'sni:',
'- fqdn: pp.origin.com',
f' tunnel_route: backend.pp.origin.com:{self._server.Variables.external_port}',
])

def setLogExpectations(self, tr: 'TestRun') -> None:

tr.Processes.Default.Streams.All += Testers.ContainsExpression(
"HTTP/1.1 200 OK",
"Verify that curl got a 200 response")

if self._pp_version in (1, 2):
expected_pp = (
'PROXY TCP4 127.0.0.1 127.0.0.1 '
rf'\d+ {self._ts.Variables.ssl_port}'
)
self._server.Streams.All += Testers.ContainsExpression(
expected_pp,
"Verify the server got the expected Proxy Protocol string.")

self._server.Streams.All += Testers.ContainsExpression(
f'Received Proxy Protocol v{self._pp_version}',
"Verify the server got the expected Proxy Protocol version.")

if self._pp_version == -1:
self._server.Streams.All += Testers.ContainsExpression(
'No Proxy Protocol string found',
'There should be no Proxy Protocol string.')

def run(self) -> None:
"""Run the test."""
description = f'Proxy Protocol v{self._pp_version} '
if self._is_tunnel:
description += "with blind tunneling"
else:
description += "without blind tunneling"
tr = Test.AddTestRun(description)

self.setupDNS(tr)
self.setupOriginServer(tr)
self.setupTS(tr)

self._ts.StartBefore(self._server)
self._ts.StartBefore(self._dns)
tr.Processes.Default.StartBefore(self._ts)

origin = f'pp.origin.com:{self._ts.Variables.ssl_port}'
command = (
'sleep1; curl -vsk --http1.1 '
f'--resolve "{origin}:127.0.0.1" '
f'https://{origin}/get'
)

tr.Processes.Default.Command = command
tr.Processes.Default.ReturnCode = 0
# Its only one transaction, so this should complete quickly. The test
# server often hangs if there are issues parsing the Proxy Protocol
# string.
tr.TimeOut = 5
self.setLogExpectations(tr)


ProxyProtocolTest().run()

ProxyProtocolOutTest(pp_version=-1, is_tunnel=False).run()
ProxyProtocolOutTest(pp_version=1, is_tunnel=False).run()
ProxyProtocolOutTest(pp_version=2, is_tunnel=False).run()
ProxyProtocolOutTest(pp_version=1, is_tunnel=True).run()
ProxyProtocolOutTest(pp_version=2, is_tunnel=True).run()
Loading