From 6c7a91733469f76381487f9ca78bdece6825c8c9 Mon Sep 17 00:00:00 2001 From: htuch Date: Sun, 6 May 2018 06:03:27 -0400 Subject: [PATCH] tap/fuzz: transport socket extension for traffic capture. (#3244) * tap/fuzz: transport socket extension for traffic capture. This PR introduces a transport socket extension that wraps a given transport socket, interposes on its plain text traffic and records it into a proto trace file on the filesystem. This can be used for a number of purposes: 1. As a corpus for fuzzing the data plane. 2. Converted to PCAP using a soon-to-be-written utility, allowing existing tools such as Wireshark to be used to decode L4/L7 protocol history in the trace. Essentially this lets us take advantage of the PCAP ecosystem. Relates to #1413 and #508. Risk Level: Low (opt-in). Testing: New SSL integration tests, demonstrating plain text intercept. Signed-off-by: Harvey Tuch --- .circleci/config.yml | 2 +- api/docs/BUILD | 2 + api/envoy/api/v2/BUILD | 1 + .../transport_socket/capture/v2alpha/BUILD | 11 ++ .../capture/v2alpha/capture.proto | 45 +++++++ api/envoy/extensions/common/tap/v2alpha/BUILD | 9 ++ .../common/tap/v2alpha/capture.proto | 57 +++++++++ api/tools/BUILD | 24 ++++ api/tools/capture2pcap.py | 91 ++++++++++++++ api/tools/capture2pcap_test.py | 27 ++++ api/tools/data/capture2pcap_h2_ipv4.pb_text | 68 ++++++++++ api/tools/data/capture2pcap_h2_ipv4.txt | 6 + ci/build_setup.sh | 2 +- ci/do_ci.sh | 3 +- docs/build.sh | 2 + docs/root/api-v2/api.rst | 1 + .../common_messages/common_messages.rst | 1 + .../transport_socket/transport_socket.rst | 8 ++ docs/root/intro/version_history.rst | 2 + docs/root/operations/operations.rst | 1 + docs/root/operations/traffic_capture.rst | 75 +++++++++++ source/common/network/BUILD | 1 + source/common/network/connection_impl.cc | 3 +- source/common/network/connection_impl.h | 3 + source/extensions/extensions_build_config.bzl | 6 + .../transport_sockets/capture/BUILD | 41 ++++++ .../transport_sockets/capture/capture.cc | 115 +++++++++++++++++ .../transport_sockets/capture/capture.h | 62 +++++++++ .../transport_sockets/capture/config.cc | 68 ++++++++++ .../transport_sockets/capture/config.h | 46 +++++++ .../transport_sockets/well_known_names.h | 1 + test/integration/BUILD | 4 + test/integration/http_integration.h | 1 + test/integration/ssl_integration_test.cc | 118 ++++++++++++++++++ 34 files changed, 902 insertions(+), 5 deletions(-) create mode 100644 api/envoy/config/transport_socket/capture/v2alpha/BUILD create mode 100644 api/envoy/config/transport_socket/capture/v2alpha/capture.proto create mode 100644 api/envoy/extensions/common/tap/v2alpha/BUILD create mode 100644 api/envoy/extensions/common/tap/v2alpha/capture.proto create mode 100644 api/tools/capture2pcap.py create mode 100644 api/tools/capture2pcap_test.py create mode 100644 api/tools/data/capture2pcap_h2_ipv4.pb_text create mode 100644 api/tools/data/capture2pcap_h2_ipv4.txt create mode 100644 docs/root/api-v2/config/transport_socket/transport_socket.rst create mode 100644 docs/root/operations/traffic_capture.rst create mode 100644 source/extensions/transport_sockets/capture/BUILD create mode 100644 source/extensions/transport_sockets/capture/capture.cc create mode 100644 source/extensions/transport_sockets/capture/capture.h create mode 100644 source/extensions/transport_sockets/capture/config.cc create mode 100644 source/extensions/transport_sockets/capture/config.h diff --git a/.circleci/config.yml b/.circleci/config.yml index 3007e12b6e61..368324972009 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -1,6 +1,6 @@ references: envoy-build-image: &envoy-build-image - envoyproxy/envoy-build:220e5cb537b5185c953de1aac7d0613f8cf155ac + envoyproxy/envoy-build:7f7f5666c72e00ac7c1909b4fc9a2121d772c859 version: 2 jobs: diff --git a/api/docs/BUILD b/api/docs/BUILD index 149f241069e5..b7d0880ed829 100644 --- a/api/docs/BUILD +++ b/api/docs/BUILD @@ -48,6 +48,8 @@ proto_library( "//envoy/config/metrics/v2:stats", "//envoy/config/ratelimit/v2:rls", "//envoy/config/trace/v2:trace", + "//envoy/config/transport_socket/capture/v2alpha:capture", + "//envoy/extensions/common/tap/v2alpha:capture", "//envoy/service/discovery/v2:ads", "//envoy/service/load_stats/v2:lrs", "//envoy/service/metrics/v2:metrics_service", diff --git a/api/envoy/api/v2/BUILD b/api/envoy/api/v2/BUILD index 42a89c91e18c..7d6c7c48abcc 100644 --- a/api/envoy/api/v2/BUILD +++ b/api/envoy/api/v2/BUILD @@ -11,6 +11,7 @@ package_group( "//envoy/admin/...", "//envoy/api/v2", "//envoy/config/...", + "//envoy/extensions/...", "//envoy/service/...", ], ) diff --git a/api/envoy/config/transport_socket/capture/v2alpha/BUILD b/api/envoy/config/transport_socket/capture/v2alpha/BUILD new file mode 100644 index 000000000000..1786d008b9e7 --- /dev/null +++ b/api/envoy/config/transport_socket/capture/v2alpha/BUILD @@ -0,0 +1,11 @@ +load("//bazel:api_build_system.bzl", "api_proto_library") + +licenses(["notice"]) # Apache 2 + +api_proto_library( + name = "capture", + srcs = ["capture.proto"], + deps = [ + "//envoy/api/v2/core:base", + ], +) diff --git a/api/envoy/config/transport_socket/capture/v2alpha/capture.proto b/api/envoy/config/transport_socket/capture/v2alpha/capture.proto new file mode 100644 index 000000000000..07b2802d5857 --- /dev/null +++ b/api/envoy/config/transport_socket/capture/v2alpha/capture.proto @@ -0,0 +1,45 @@ +syntax = "proto3"; + +package envoy.config.transport_socket.capture.v2alpha; +option go_package = "v2"; + +// [#protodoc-title: Capture] + +import "envoy/api/v2/core/base.proto"; + +// File sink. +// +// .. warning:: +// +// The current file sink implementation buffers the entire trace in memory +// prior to writing. This will OOM for long lived sockets and/or where there +// is a large amount of traffic on the socket. +message FileSink { + // Path prefix. The output file will be of the form _.pb, where is an + // identifier distinguishing the recorded trace for individual socket instances (the Envoy + // connection ID). + string path_prefix = 1; + + // File format. + enum Format { + // Binary proto format as per :ref:`Trace + // `. + PROTO_BINARY = 0; + // Text proto format as per :ref:`Trace + // `. + PROTO_TEXT = 1; + } + Format format = 2; +} + +// Configuration for capture transport socket. This wraps another transport socket, providing the +// ability to interpose and record in plain text any traffic that is surfaced to Envoy. +message Capture { + oneof sink_selector { + // Trace is to be written to a file sink. + FileSink file_sink = 1; + } + + // The underlying transport socket being wrapped. + api.v2.core.TransportSocket transport_socket = 2; +} diff --git a/api/envoy/extensions/common/tap/v2alpha/BUILD b/api/envoy/extensions/common/tap/v2alpha/BUILD new file mode 100644 index 000000000000..2211bb37ca5b --- /dev/null +++ b/api/envoy/extensions/common/tap/v2alpha/BUILD @@ -0,0 +1,9 @@ +load("//bazel:api_build_system.bzl", "api_proto_library") + +licenses(["notice"]) # Apache 2 + +api_proto_library( + name = "capture", + srcs = ["capture.proto"], + deps = ["//envoy/api/v2/core:address"], +) diff --git a/api/envoy/extensions/common/tap/v2alpha/capture.proto b/api/envoy/extensions/common/tap/v2alpha/capture.proto new file mode 100644 index 000000000000..cde6791b895a --- /dev/null +++ b/api/envoy/extensions/common/tap/v2alpha/capture.proto @@ -0,0 +1,57 @@ +syntax = "proto3"; + +// [#protodoc-title: Common TAP] +// Trace capture format for the capture transport socket extension. This dumps plain text read/write +// sequences on a socket. + +package envoy.extensions.common.tap.v2alpha; +option go_package = "v2"; + +import "envoy/api/v2/core/address.proto"; + +import "google/protobuf/timestamp.proto"; + +// Connection properties. +message Connection { + // Global unique connection ID for Envoy session. Matches connection IDs used + // in Envoy logs. + uint64 id = 1; + // Local address. + envoy.api.v2.core.Address local_address = 2; + // Remote address. + envoy.api.v2.core.Address remote_address = 3; +} + +// Event in a capture trace. +message Event { + // Timestamp for event. + google.protobuf.Timestamp timestamp = 1; + // Data read by Envoy from the transport socket. + message Read { + // Binary data read. + bytes data = 1; + // TODO(htuch): Half-close for reads. + } + // Data written by Envoy to the transport socket. + message Write { + // Binary data written. + bytes data = 1; + // Stream was half closed after this write. + bool end_stream = 2; + } + // Read or write with content as bytes string. + oneof event_selector { + Read read = 2; + Write write = 3; + } +} + +// Sequence of read/write events that constitute a captured trace on a socket. +// Multiple Trace messages might be emitted for a given connection ID, with the +// sink (e.g. file set, network) responsible for later reassembly. +message Trace { + // Connection properties. + Connection connection = 1; + // Sequence of observed events. + repeated Event events = 2; +} diff --git a/api/tools/BUILD b/api/tools/BUILD index 668ea6fedb93..a3336f1961dd 100644 --- a/api/tools/BUILD +++ b/api/tools/BUILD @@ -1,5 +1,29 @@ licenses(["notice"]) # Apache 2 +py_binary( + name = "capture2pcap", + srcs = ["capture2pcap.py"], + licenses = ["notice"], # Apache 2 + visibility = ["//visibility:public"], + deps = [ + "//envoy/extensions/common/tap/v2alpha:capture_py", + ], +) + +py_test( + name = "capture2pcap_test", + srcs = ["capture2pcap_test.py"], + data = [ + "data/capture2pcap_h2_ipv4.pb_text", + "data/capture2pcap_h2_ipv4.txt", + ], + # Don't run this by default, since we don't want to force local dependency on Wireshark/tshark, + # will explicitly invoke in CI. + tags = ["manual"], + visibility = ["//visibility:public"], + deps = [":capture2pcap"], +) + py_binary( name = "generate_listeners", srcs = ["generate_listeners.py"], diff --git a/api/tools/capture2pcap.py b/api/tools/capture2pcap.py new file mode 100644 index 000000000000..52770b9f399d --- /dev/null +++ b/api/tools/capture2pcap.py @@ -0,0 +1,91 @@ +"""Tool to convert Envoy capture trace format to PCAP. + +Uses od and text2pcap (part of Wireshark) utilities to translate the Envoy +capture trace proto format to a PCAP file suitable for consuming in Wireshark +and other tools in the PCAP ecosystem. The TCP stream in the output PCAP is +synthesized based on the known IP/port/timestamps that Envoy produces in its +capture files; it is not a literal wire capture. + +Usage: + +bazel run @envoy_api//tools:capture2pcap + +Known issues: +- IPv6 PCAP generation has malformed TCP packets. This appears to be a text2pcap +issue. + +TODO(htuch): +- Figure out IPv6 PCAP issue above, or file a bug once the root cause is clear. +""" + +import datetime +import socket +import StringIO +import subprocess as sp +import sys +import time + +from google.protobuf import text_format + +from envoy.extensions.common.tap.v2alpha import capture_pb2 + + +def DumpEvent(direction, timestamp, data): + dump = StringIO.StringIO() + dump.write('%s\n' % direction) + # Adjust to local timezone + adjusted_dt = timestamp.ToDatetime() - datetime.timedelta( + seconds=time.altzone) + dump.write('%s\n' % adjusted_dt) + od = sp.Popen( + ['od', '-Ax', '-tx1', '-v'], + stdout=sp.PIPE, + stdin=sp.PIPE, + stderr=sp.PIPE) + packet_dump = od.communicate(data)[0] + dump.write(packet_dump) + return dump.getvalue() + + +def Capture2Pcap(capture_path, pcap_path): + trace = capture_pb2.Trace() + if capture_path.endswith('.pb_text'): + with open(capture_path, 'r') as f: + text_format.Merge(f.read(), trace) + else: + with open(capture_path, 'r') as f: + trace.ParseFromString(f.read()) + + local_address = trace.connection.local_address.socket_address.address + local_port = trace.connection.local_address.socket_address.port_value + remote_address = trace.connection.remote_address.socket_address.address + remote_port = trace.connection.remote_address.socket_address.port_value + + dumps = [] + for event in trace.events: + if event.HasField('read'): + dumps.append(DumpEvent('I', event.timestamp, event.read.data)) + elif event.HasField('write'): + dumps.append(DumpEvent('O', event.timestamp, event.write.data)) + + ipv6 = False + try: + socket.inet_pton(socket.AF_INET6, local_address) + ipv6 = True + except socket.error: + pass + + text2pcap_args = [ + 'text2pcap', '-D', '-t', '%Y-%m-%d %H:%M:%S.', '-6' if ipv6 else '-4', + '%s,%s' % (remote_address, local_address), '-T', + '%d,%d' % (remote_port, local_port), '-', pcap_path + ] + text2pcap = sp.Popen(text2pcap_args, stdout=sp.PIPE, stdin=sp.PIPE) + text2pcap.communicate('\n'.join(dumps)) + + +if __name__ == '__main__': + if len(sys.argv) != 3: + print 'Usage: %s ' % sys.argv[0] + sys.exit(1) + Capture2Pcap(sys.argv[1], sys.argv[2]) diff --git a/api/tools/capture2pcap_test.py b/api/tools/capture2pcap_test.py new file mode 100644 index 000000000000..3a9fcfcd2ea0 --- /dev/null +++ b/api/tools/capture2pcap_test.py @@ -0,0 +1,27 @@ +"""Tests for capture2pcap.""" + +import os +import subprocess as sp +import sys + +import capture2pcap + +# Validate that the captured trace when run through capture2cap | tshark matches +# a golden output file for the tshark dump. Since we run capture2pcap in a +# subshell with a limited environment, the inferred time zone should be UTC. +if __name__ == '__main__': + srcdir = os.path.join(os.getenv('TEST_SRCDIR'), 'envoy_api') + capture_path = os.path.join(srcdir, 'tools/data/capture2pcap_h2_ipv4.pb_text') + expected_path = os.path.join(srcdir, 'tools/data/capture2pcap_h2_ipv4.txt') + pcap_path = os.path.join(os.getenv('TEST_TMPDIR'), 'generated.pcap') + + capture2pcap.Capture2Pcap(capture_path, pcap_path) + actual_output = sp.check_output( + ['tshark', '-r', pcap_path, '-d', 'tcp.port==10000,http2', '-P']) + with open(expected_path, 'r') as f: + expected_output = f.read() + if actual_output != expected_output: + print 'Mismatch' + print 'Expected: %s' % expected_output + print 'Actual: %s' % actual_output + sys.exit(1) diff --git a/api/tools/data/capture2pcap_h2_ipv4.pb_text b/api/tools/data/capture2pcap_h2_ipv4.pb_text new file mode 100644 index 000000000000..f5e2449902d9 --- /dev/null +++ b/api/tools/data/capture2pcap_h2_ipv4.pb_text @@ -0,0 +1,68 @@ +connection { + local_address { + socket_address { + address: "127.0.0.1" + port_value: 10000 + } + } + remote_address { + socket_address { + address: "127.0.0.1" + port_value: 53288 + } + } +} +events { + timestamp { + seconds: 1525207293 + nanos: 216737962 + } + read { + data: "PRI * HTTP/2.0\r\n\r\nSM\r\n\r\n\000\000\022\004\000\000\000\000\000\000\003\000\000\000d\000\004@\000\000\000\000\002\000\000\000\000\000\000\004\010\000\000\000\000\000?\377\000\001\000\000\036\001\005\000\000\000\001\202\204\206A\212\240\344\035\023\235\t\270\020\000\000z\210%\266P\303\253\266\362\340S\003*/*" + } +} +events { + timestamp { + seconds: 1525207293 + nanos: 230450657 + } + write { + data: "\000\000\006\004\000\000\000\000\000\000\004\020\000\000\000\000\000\000\004\001\000\000\000\000\000\000\004\010\000\000\000\000\000\017\377\000\001" + } +} +events { + timestamp { + seconds: 1525207293 + nanos: 230558250 + } + read { + data: "\000\000\000\004\001\000\000\000\000" + } +} +events { + timestamp { + seconds: 1525207293 + nanos: 345386933 + } + write { + data: "\000\025\223\001\004\000\000\000\001\210@\217\362\264\307<\324\025d\025\0101\352X\325J\177\211\3056\316p\232l\371!\301\000\216\362\264\307<\324\025b\371\254\266\032\222\324\237\377\277 \023n6\357\320\200\027]o\350@\013\300s\350@\013\302\177\351\326\302\333\241\372u\2612\363\237Ae\260\205\327>\202\313b\003\301\372\013-\211\226\333\372\013-\211\226\335\372\013-\211\267\033\372\013-\211\366[\372\013-\211\367\001\364\026[d\017\271\364\026[d/=\364\026[dL\271\364\026[e\221;\364\026[e\267\031\372\013-\262\353N\375\005\226\331x\014\375\005\226\331x\r}\005\226\331x \372\013-\262\360^\372\013-\262\373\200\372\013-\262\373\354\375\005\226\332\003\356}\005\226\332\013\216\375\005\226\332\013\316\375\005\226\332\020\232\372\013-\264&\203\364\026[hN\273\364\026[i\220;\364\026[i\226\231\372\013-\264\350\003\364\026[i\326\335\372\013-\264\360[\372\013-\264\363m}\005\226\332|-\375\005\226\332}\347~\202\313m\201\227~\202\313m\210\"\372\013-\266\'\032\372\013-\266\'\303\364\026[m\246\335\372\013-\266\330]\372\013-\266\333\356\375\005\226\333m\367\376\202\313m\270\340\276\202\313m\272\340~\202\313m\272\350~\202\313m\272\363?Ae\266\335y\377\240\262\333o8\017\240\262\333o\211\237\240\262\333\200\020>\202\313n\000L\375\005\226\334\003N}\005\226\334\003\257\375\005\226\334\010^\372\013-\270\027\201\364\026[p/\013\350,\266\340\234o\350,\266\343 o\350,\266\343L\271\364\026[q\246\201\364\026[q\300\207\350,\266\343\201o\350,\266\343\201w\350,\266\343\201\177\350,\266\343\216\003\350,\266\343\217\273\364\026[q\3215\364\026[q\326\235\372\013-\270\353\257\375\005\226\334u\367>\202\313n<\333?Ae\267\036u\377\240\262\333\217\200?Ae\267\037i\377\240\262\333\240e\317\240\262\333\240x_Ae\267B\313\337Ae\267B\320\276\202\313n\205\307\276\202\313n\205\347>\202\313n\210\017\375\005\226\335\020_\372\013-\272\313m\375\005\226\335h\017\375\005\226\335h!\372\013-\272\320\203\364\026[u\247\237\372\013-\272\343\301\372\013-\272\363-}\005\226\335|.\375\005\226\335}\227~\202\313n\276\343?Ae\267_}\237\240\262\333\300q\257\240\262\333\300t?Ae\267\200\353?Ae\267\200\370~\202\313o\004\017}\005\226\336\010C\364\026[x!s\350,\266\360\204\357\320Ym\3416\037\240\262\333\302}\237\240\262\333\314\205\377\240\262\333\314\272\377\320Ym\346\200\017\240\262\333\315\005\257\240\262\333\315\010?Ae\267\232\023\177Ae\267\234\020~\202\313o8\313\337Ae\267\234h_Ae\267\234}\377\240\262\333\317>\017\240\262\333\317\205\377\240\262\333\340\013\377Ae\267\300\313\337Ae\267\304\360\276\204\330\002&\335\372h\002e\240\277\364\320\004\373\301o\351\2402\020\201\377\246\200\310B\343_M\001\226\231d?M\001\226\236h\037M\001\227\237}\237\246\200\320\002\313\177M\001\246B\370>\232\003L\264\370\276\232\003M\274\320~\232\003N\270\360\276\232\003O2\323\277M\001\261:\340~\232\003m\262\343\177M\001\267\032p\037M\001\267\202\323\337M\001\267\336u\317\246\200\340\237y\277\246\200\343\254\270\327\323@q\346\337\027\323@q\360>\317\323@q\360>\337\323@q\367\031k\351\2408\373\214\271\364\320\034}\347\034\372h\016\204\016\203\351\240:\026\\\007\323@t.\274/\246\200\353o\005\317\246\200\353\217\274\347\323@u\360\t\357\246\200\353\342\003\237M\001\327\334m\317\246\200\353\3568\037\246\200\353\3568\377\323@x\014\211\257\246\200\360\033\013\337M\001\3406&\276\232\003\301\003/}4\007\204 \277\364\320\036d-;\364\320\036e\366\301\364\320\036i\240\263\364\320\036i\2417\364\320\036i\320\273\364\320\036m\327\201\364\320\036q\367]\372h\017>\026\305\364\320\037\000\202/\246\200\370\031\010>\232\003\355\276\020}4\007\334\023\315\3754\007\334\023\316}4\007\334y\306~\232\003\356>\323\337M\001\367B\333?M\001\367Zq\377\246\200\373\257\210_M\001\367_\023\377M\001\367\202\320>\232\003\3574\340>\232\003\3578\373\377M\001\367\235\020~\232\003\357:\'\276\232\010\000!9\364\320@\021\004?M\004\014\205\366~\232\010\031i\340\3754\0204\363b\372h m\226\\\372h m\247\303\364\320@\343-;\364\320@\343\2179\364\320@\343\317\203\351\240\201\326\302\337\323A\003\255\262\017\246\202\007_\013_M\004\017<\006\376\232\010\036y\227~\232\010\037d.}4\020>\323\340\372h!\013\340k\351\240\204/\266\337\323A\010_p?M\004-4\343\277M\004-6\353_M\004-6\373\277M\004-\211\340\3754\020\266\320A\364\320B\333\217\273\364\320B\343 o\351\240\205\306@\357\323A\013\241p?M\004.\205\320\3754\020\272\330Y\372h!u\360\265\364\320B\360\037\027\323A\013\315\264\367\323A\013\340d_M\004/\201\226\276\232\010_\003.}4\020\276\006\201\364\320B\370\035k\351\240\205\366\331g\351\240\205\366\336k\351\240\210\014\262/\246\202 6\373?M\004@q\300}4\021\004.\263\364\320D\020\272\337\323A\020Zi\277\246\202 \264\373?M\004Al/}4\021\005\307\334\372h\"\020\236\027\323A\023.4\037\246\202&\204\323\177M\004M2\370~\232\010\232}\340\3754\0216\323\257}4\0216\363.\3754\0216\373\242\372h\"p@\007\323A\023\202\023\337M\004N:\333_M\004N\201\347>\232\010\235\023L\3754\021:\310\032\372h\"u\2207\364\320D\353A{\351\240\211\326\302\337\323A\023\301\013\277M\004O\010-}4\021< \271\364\320D\363-\265\364\320D\363@\027\323A\023\315\272\017\246\202\'\233y\317\246\202\'\333\020>\232\010\237q\226~\232\010\237u\367>\232\013 \020\232\372h,\201\227\234\372h,\201\267\001\364\320Y\003n?\364\320Y\010B\327\323Ad.2/\246\202\310]e\337\246\202\310^e\257\246\202\310\200\320~\232\013\"\013\355\3754\026D\320^\372h,\211\246\205\364\320Y\023o9\364\320Y\023\217\275\364\320Y\023\317\275\364\320Yd.\013\351\240\262\313-\277\364\320Ye\240\275\364\320Ye\247\003\364\320Ye\260?\364\320Ye\306\\\372h,\262\343/}4\026Yy\266\276\232\013-2\343?M\005\226\232\023\337M\005\226\233q\237\246\202\313`\023\177M\005\226\300\363\337M\005\226\304\323\377M\005\226\332m\337\246\202\313m8\037\246\202\313m\272\347\323Ae\300>/\246\202\313\201e\277\246\202\313\201m\277\246\202\313\201|_M\005\227Z\013\177M\005\227Zq\257\246\202\313\255\205\237\246\202\313\255\276\357\323Ae\327D\357\323Ae\346\233o\351\240\262\363\341k\351\240\262\363\357\007\351\240\262\373L\271\364\320Z\000\002/\246\202\320\000&~\232\013@\003@\372h-\000M3\364\320Z\003\216\267\364\320Z\003\240k\351\240\264\007Yg\351\240\264\007\333\177\351\240\264\0204\377\323Ah,\262/\246\202\320Yi\317\246\202\320Ze\337\246\202\320[\020~\232\013Am\326\376\232\013Am\347\276\232\013Aq\346~\232\013At\016\3754\026\202\370[\372h-\005\366\331\372h-\t\221=\364\320Z\023\314\265\364\320Z\023\340w\351\240\264\310@\317\323Ai\220\205\237\246\202\323\"u\357\246\202\323,\274\337\323Ai\226\200\377\323Ai\227\033g\351\240\264\313\256\275\364\320Ze\327\205\364\320Ze\327\231\372h-2\370\005\364\320Ze\367\035\372h-4&Y\372h-4\310\233\372h-4\330\235\372h-4\333L\3754\026\232p\002\372h-4\340_\372h-4\353B\372h-6\020?\364\320Zm\247\003\364\320Zm\247\233\372h-6\330\005\364\320Zm\327E\364\320Zm\346\\\372h-8\007^\372h-8\007\201\364\320Zp-9\364\320Zp/3\364\320Zq\246\201\364\320Zq\246\205\364\320Zq\247]\372h-8\343!\372h-8\343\316\3754\026\234}\366\376\232\013N\201\267~\232\013N\264\373\337M\005\247_\000>\232\013N\276!}4\026\236\000]\372h-<\007\003\364\320Zx\016;\364\320ZxL\207\351\240\264\360\234{\351\240\264\363 k\351\240\264\363\"s\351\240\264\363\"{\351\240\264\363\"\177\351\240\264\363,\213\351\240\264\363O\275\364\320Zy\261\003\351\240\264\363b\027\323Ai\346\335g\351\240\264\363\255=\364\320Zy\347Y\372h-<\373b\372h->\000\273\364\320Z|\r\275\364\320Z|L\277\364\320Z}\226\303\364\320Z}\266\331\372h->\333\340\372h->\333\341\372h->\333\355\3754\026\237m\367\276\232\013O\270\'\376\232\013O\270\363?M\005\247\336\003\377M\005\247\336\013?M\005\247\336d\037M\005\247\337\013?M\005\260\000.\3754\026\300\006C\364\320[\000\037\007\323Al\r\005\257\246\202\330\032l?M\005\260<\007>\232\013`x.}4\026\302\'\034\372h-\204O\267\364\320[\010\237\177\351\240\266\026_\017\323Al.<\357\323Al.\266/\246\202\330]m\357\246\202\330^m\317\246\202\330^q\277\246\202\330^u\277\246\202\330_\013\377M\005\261\001\247\376\232\013b\013N\3754\026\304\333`\372h-\211\327\201\364\320[\023\341o\351\240\266\'\302\357\323AlO\266\017\246\202\333 i\377\246\202\333 q\357\246\202\333\"\000~\232\013l\210B\372h-\262\'^\372h-\262\313B\372h-\262\320\205\364\320[e\2605\364\320[e\301=\364\320[e\306\332\372h-\262\350\\\372h-\262\373b\372h-\262\373n\3754\026\332\000\201\364\320[h\016\213\351\240\266\320\036\017\323Am\246@\367\323Am\246[k\351\240\266\323\355\277\364\320[l,\277\364\320[l/\265\364\320[m\260\267\364\320[m\267\035\372h-\266\333\217\3754\026\333q\366\376\232\013m\272!}4\026\333u\220}4\026\333u\320}4\026\334\003O}4\026\334\003\356\3754\026\334\010\032\372h-\270\0207\364\320[p w\351\240\266\340Yw\351\240\266\340^\017\323Am\3014\327\323Am\306\202\337\323Am\307\033s\351\240\266\343\240k\351\240\266\343\240s\351\240\266\343\257\003\351\240\266\343\3173\364\320[t\016\207\351\240\266\350Y\177\351\240\266\353-\013\351\240\266\353-\203\351\240\266\353/\007\351\240\266\353/3\364\320[u\2405\364\320[u\306\336\372h-\272\350Z\372h-\272\353\201\372h-\272\353\356}4\026\335u\367\376\232\013n\274\000\3754\026\335y\227~\232\013n\274\363\277M\005\267_\023\377M\005\267\200\313\177M\005\267\200\343_M\005\267\202\373?M\005\267\231\003\177M\005\267\231\003\377M\005\267\234m\337\246\202\333\316\211\257\246\202\333\3176\327\323Am\347\304\017\246\202\333\355\272\017\246\202\333\356\t\277\246\202\333\3564/\246\202\333\356>\317\323Am\367]\007\323Am\367]g\351\240\266\373\256\273\364\320[}\360\013\351\240\266\373\355\007\351\240\266\373\356\273\364\320\\\000[\177\351\240\270\001\010\037M\005\3002\027>\232\013\200h\017}4\027\000\320_\372h.\001\2413\364\320\\\003N3\364\320\\\003O\213\351\240\270\007\304\317\323Ap\017\211\277\246\202\340\037i\317\246\202\340\037y\357\246\202\340@\007~\232\013\201\013\316\3754\027\002\313o}4\027\002\320Y\372h.\005\247\203\364\320\\\013O7\364\320\\\013O?\364\320\\\013a\027\323Ap-\211\317\246\202\340[i\357\246\202\340[|_M\005\300\266\373\177M\005\300\272\373_M\005\300\274\353\237M\005\300\276\027\276\232\013\201}\240\3754\027\004\006\331\372h.\010\017\003\351\240\270 \270/\246\202\340\204\'>\232\013\202\020\236\372h.\t\226C\364\320\\\023A\007\323ApM6\317\323ApM\204_M\005\3018 \3754\027\004\343\201\372h.\t\307\234\372h.2\007\034\372h.2\007\035\372h.2\026\237\372h.2 \013\351\240\270\313-9\364\320\\e\226\236\372h.2\313`\372h.2\313\357\3754\027\031l\017}4\027\031}\247\276\232\013\2152\313\337M\005\306\231q\377\246\202\343M:\017\246\202\343M<\037\246\202\343N\211\357\246\202\343N\270\327\323Aq\247\336\017\323Aq\247\337k\351\240\270\330\002\347\323Aq\2604\037\246\202\343`l?M\005\306\302\350~\232\013\215\210\r}4\027\033\023/}4\027\033\023`\372h.6\310\001\364\320\\m\220\263\364\320\\m\221?\364\320\\m\227\305\364\320\\m\247_\372h.6\330\037\372h.6\340\003\364\320\\m\3003\364\320\\m\300\277\364\320\\m\326\305\364\320\\m\327\205\364\320\\m\340?\364\320\\m\347Y\372h.6\363\255\3754\027\033y\327\276\232\013\215\276\006\376\232\013\215\276\007\276\232\013\216\000N\3754\027\034\020\034\372h.8\'\337\372h.8\333\315}4\027\034q\346\276\232\013\216:\373\337M\005\307\036d_M\005\307B\027\276\232\013\216\205\226~\232\013\216\205\240\3754\027\035\013L\3754\027\035\013M\3754\027\035\013\355}4\027\035\023-}4\027\035\023\357\3754\027\035e\326~\232\013\216\262\373?M\005\307Y}\277\246\202\343\255\005\257\246\202\343\255\205\257\246\202\343\256\266\367\323Aq\327\204\327\323Aq\327\231g\351\240\270\353\315\013\351\240\270\360\032w\351\240\270\360\032{\351\240\270\360\033\007\323Aq\3408\017\246\202\343\301y\277\246\202\343\301|?M\005\307\231}\237\246\202\343\315\205\277\246\202\343\315\266\037\246\202\343\315\266/\246\202\343\315\274/\246\202\343\316\201\257\246\202\343\342h\037M\005\307\304\353\277M\005\307\304\363_M\005\307\333}\357\246\202\343\356\001\337\246\202\343\356\270\367\323Aq\367\234o\351\240\270\373\354\273\364\320\\}\366^\372h.\200\r?\364\320]\000\034\177\351\240\272\000\264\017\246\202\350\002\333\277M\005\3204\323?M\005\320<\340~\232\013\240y\327\376\232\013\240|M\3754\027@\373\254\3758\330D\343\355}8\330D\360\001\364\343ad\r\267\364\343ae\267\301\364\343ae\301\007\351\346@\006\202\317\323\314\200\r<\327\323\314\200\r>\327\323\314\200\r>\357\323\314\200\r\270\347\320@ \000\0173\364\020\010\001}\267>\202\001\000@|\037A\000\200 \266\317\320@ \010M\013\350 \020\004\310\034\372\010\004\0012\310>\202\001\000M4\337\320@ \t\246\335\372\010\004\0016\313\237A\000\200&\332g\350 \020\004\333\217\375\004\002\000\233u\317\240\200@\023n\275\364\020\010\002p@\372\010\004\0018\'\276\202\001\000N2\357\320@ \t\306_\372\010\004\0018\333\177A\000\200\'\034w\350 \020\004\343\256\375\004\002\000\234u\357\240\262\330\000\000\273\364\026\336\003\256\270\327\320\\\020\000\026\\\372\013\202\000\002\353\337Ap@\000\204\357\320_\000\000\'\236\372\013\340\000\032\023?A|\000\003Bw\350/\200\001}\367\376\202\370\000&\332{\350/\200\002y\340}\005\360\001\226D\037\240\276\0002\310\231\372\013\340\003,\211\277\240\276\0002\310\234\372\013\340\003,\211\357\240\276\0002\310\237\372\013\340\003,\262\017\240\276\0002\320\035\372\013\340\003-\001\357\240\276\0002\320\037\372\013\340\003L\201\377\240\276\0004\330\\\372\013\340\003M\205\337\240\276\0004\330^\372\013\340\003M\205\377\240\276\0004\330\201\364\027\300\006\233\020~\202\370\000\323\317\213\350/\200\r>\000\375\005\360\001\261\001\277\240\276\0006 9\364\027\300\006\304\026\276\202\370\000\330\202\337\320_\000\033\020]\372\013\340\003b\013\337A|\000lA\177\350/\200\r\270\343\177A|\000m\307E\364\027\300\006\335e\317\240\276\0006\353M}4\026B\350[oa\226\337i~\224\000T\320?J\010\001yA\002\343A\270\313*b\321\277d\002-1X\215\256\303w\032K\364\245#\362\260\346,\000_\226I|\245\211\323M\037j\022q\330\202\246\014\233\265,\363\315\276\260\177@\230\362\264\307<\324\025i\245*\321\214\235KT\213X^\326\225\tX\325J\177\224)\244\202)/\237\225\203\361\203\261\223\026\301\372\232\274M_\361@\224\362\264\307<\324\025i\274!h\315P\354\364\267r\330\203\036\257\207\013\355\005\246\\m\357@\003p3p\257\275\256\017\347|\346B\206B\225\035*\rMl\353R\263\320bz\376\024\334R\2512\344;\025\263\\\345\242\265%=\212R{\n\241\252\224\353\377?@\236\362\264\307<\324\025i\245*\304\266\313\013RV\260\275\255*\022\261\016\204\255-\207\245i\274#\204\013K\264\017@\217\362\264\307<\324\025i\006\221\255\334\266 \307\253\207\013\355>\333\302m\277v\204-]\317\353@\217\362\264\307<\324\025j\212\232OR\324\0162\321\240b:\220\307k\030\214\366L\307k\030\216\24417\204-]\207\221\211\274 +\240\266w+\016\274\017@\214\362\267\224!j\354:JD\230\365\177\212\017\332\224\236B\301\035\007\'_@\213\362\264\266\016\222\254z\322c\324\217\211\335\016\214\032\266\344\305\223O@\223\362\264\307<\324\025i\245*\326\027\265\245BVM\203!\177\303\031)\350\027\2564\323?]\254\242\240\267q\367\231k\351\210\352C\035\254b3\3313\035\254b:\220\304\336\020\265v\036F&\360\200\256\202\331\334\254:\360>\273YEAn\343\3572\327\320\311O@\275q\246\231@\217\362\264\307<\324\025i\221Dk \266w1\013\003web\017(\300\016\270\262\303\266\001\000/,\006\326\000V\020>\324/\232\315aQ\006\371\355\372Q\220\255\240~\226\020\002\362\202\005\306\203q\226T\305\243\177\332\225\2153\300\307\332\222\036\221\232\250\027\230\347\232\202\256C\323\017(\377\'\323\222\374\001\023\360\037\036L\272\274\305D\276\254\237/\237yd\333\370\224\003\036\275\035w\352\247N\276\306j\251\346\240\277\226\001\313\315\343[\266I\315E\355\274\031\330W,\246\350*\375\351\230\217\342\277\336\034ZC\2435\323BO\323\311\023fK\306\316\314m;\0179\331\313\223\006\216\267\253\353\217e\373\037\305\2012\343\272/\034)\333]\267r\375\250_5\232\302\242\016E\223\351FB\266\242%a\000/( \\h7\031eLZ7\375\251X\323<\014}\251!\351\031\252\201y\216y\250*\344=?jcJk\325U\036\277@\215\362\264\307<\324\025h\306N\245\252D\177\320b:\220\307k\030\214\366L\307k\030\216\24417\204-]\207\221\211\274 +\240\266w+\016\274\017\256\326QP[\270\373\314\265\364\304u!\216\3261\031\354\231\216\3261\035Hbo\010Z\273\017#\023x@W\232R\260\363\237C%=\002\365\306\232g@\222\362\264\307<\324\025i\016\205\220[;\230\205Y6\014\205\207\244f\252\344\347\244\277@\214\362\264\307<\324\025d\026\316\346!\177\207\361\343\307\324\347\244\277@\234\362\264\307<\324\025i\245*\326\025\025\236\244\025b\036B\255!R3P\205\223`\310_\250\260\250\254\365 \261\020\362\026$\0251G\352(\306N\245\252O\253\n\212\317R\013\021\017!bAS\024~\242\214d\352Z\244@\236\362\264\307<\324\025i\245*\326\025\025\236\244\025h\317\'\245\223`\352D\247\262\221\244\307\251\037\215\232\332\275\232\272\313\'\321\'\266\256\245\223@\222\362\264\307<\324\025dNZ(\224\310\235d$i\265\037\207\275\010&\273\202\037_@\205\035\tY\035\311\354\237\264\037\315\306\232g\371\373R\221\300&\337\020\000\017\265;Zb@\330Y\003-2\317\332\235\2551 l,\201\226\231\027\332\235\2551 l,\201\226\231\017\332\235\2551 l,\201\226Y\177\355N\326\230\2206\026@\313,\267\365\332\323\022\017\346\343M3\374\375\251H\340\023o\210\000\007\332\235\340\376Zg\351\241}4\037\246_\372e\277\347@\223\362\264\307<\324\025i\245*\310-\235\314B\254\233\006B\377\207\361\343\307\324\347\244\277R\203\250\365\027{\213\204\204-i[\005D<\206\252o@\225\362\261j\356\177K[Z\023aGJ\310-\235\314B\254\223R_\202\010Z" + } +} +events { + timestamp { + seconds: 1525207293 + nanos: 346744029 + } + write { + data: "\000\035V\000\000\000\000\000\001Google

\"Google\"

 

Advanced searchLanguage tools

© 2018 - Privacy - Terms

\000\000\000\000\001\000\000\000\001" + } +} diff --git a/api/tools/data/capture2pcap_h2_ipv4.txt b/api/tools/data/capture2pcap_h2_ipv4.txt new file mode 100644 index 000000000000..92b44df8fda1 --- /dev/null +++ b/api/tools/data/capture2pcap_h2_ipv4.txt @@ -0,0 +1,6 @@ + 1 0.000000 127.0.0.1 → 127.0.0.1 HTTP2 157 Magic, SETTINGS, WINDOW_UPDATE, HEADERS + 2 0.013713 127.0.0.1 → 127.0.0.1 HTTP2 91 SETTINGS, SETTINGS, WINDOW_UPDATE + 3 0.013820 127.0.0.1 → 127.0.0.1 HTTP2 63 SETTINGS + 4 0.128649 127.0.0.1 → 127.0.0.1 HTTP2 5586 HEADERS + 5 0.130006 127.0.0.1 → 127.0.0.1 HTTP2 7573 DATA + 6 0.131044 127.0.0.1 → 127.0.0.1 HTTP2 3152 DATA, DATA diff --git a/ci/build_setup.sh b/ci/build_setup.sh index 2a5f7547222e..cd8fe503cae6 100755 --- a/ci/build_setup.sh +++ b/ci/build_setup.sh @@ -116,7 +116,7 @@ mkdir -p "${ENVOY_CI_DIR}"/bazel ln -sf "${ENVOY_SRCDIR}"/bazel/get_workspace_status "${ENVOY_FILTER_EXAMPLE_SRCDIR}"/bazel/ ln -sf "${ENVOY_SRCDIR}"/bazel/get_workspace_status "${ENVOY_CI_DIR}"/bazel/ -export BUILDIFIER_BIN="/usr/lib/go/bin/buildifier" +export BUILDIFIER_BIN="/usr/local/bin/buildifier" function cleanup() { # Remove build artifacts. This doesn't mess with incremental builds as these diff --git a/ci/do_ci.sh b/ci/do_ci.sh index b3de0bf6b5a5..42a4ec87e260 100755 --- a/ci/do_ci.sh +++ b/ci/do_ci.sh @@ -117,7 +117,8 @@ elif [[ "$1" == "bazel.api" ]]; then echo "Building API..." bazel --batch build ${BAZEL_BUILD_OPTIONS} -c fastbuild @envoy_api//envoy/... echo "Testing API..." - bazel --batch test ${BAZEL_TEST_OPTIONS} -c fastbuild @envoy_api//test/... @envoy_api//tools/... + bazel --batch test ${BAZEL_TEST_OPTIONS} -c fastbuild @envoy_api//test/... @envoy_api//tools/... \ + @envoy_api//tools:capture2pcap_test exit 0 elif [[ "$1" == "bazel.coverage" ]]; then setup_gcc_toolchain diff --git a/docs/build.sh b/docs/build.sh index 0e934f005d2a..1694797fe2b2 100755 --- a/docs/build.sh +++ b/docs/build.sh @@ -91,6 +91,8 @@ PROTO_RST=" /envoy/config/filter/network/redis_proxy/v2/redis_proxy/envoy/config/filter/network/redis_proxy/v2/redis_proxy.proto.rst /envoy/config/filter/network/tcp_proxy/v2/tcp_proxy/envoy/config/filter/network/tcp_proxy/v2/tcp_proxy.proto.rst /envoy/config/health_checker/redis/v2/redis/envoy/config/health_checker/redis/v2/redis.proto.rst + /envoy/config/transport_socket/capture/v2alpha/capture/envoy/config/transport_socket/capture/v2alpha/capture.proto.rst + /envoy/extensions/common/tap/v2alpha/capture/envoy/extensions/common/tap/v2alpha/capture.proto.rst /envoy/type/percent/envoy/type/percent.proto.rst /envoy/type/range/envoy/type/range.proto.rst " diff --git a/docs/root/api-v2/api.rst b/docs/root/api-v2/api.rst index f6f01b1310d5..5c0308f34f9c 100644 --- a/docs/root/api-v2/api.rst +++ b/docs/root/api-v2/api.rst @@ -13,5 +13,6 @@ v2 API reference http_routes/http_routes config/filter/filter config/health_checker/health_checker + config/transport_socket/transport_socket common_messages/common_messages types/types diff --git a/docs/root/api-v2/common_messages/common_messages.rst b/docs/root/api-v2/common_messages/common_messages.rst index 3e9adab19baa..531d6ccd53a6 100644 --- a/docs/root/api-v2/common_messages/common_messages.rst +++ b/docs/root/api-v2/common_messages/common_messages.rst @@ -13,3 +13,4 @@ Common messages ../api/v2/core/grpc_service.proto ../api/v2/auth/cert.proto ../api/v2/ratelimit/ratelimit.proto + ../extensions/common/tap/v2alpha/capture.proto diff --git a/docs/root/api-v2/config/transport_socket/transport_socket.rst b/docs/root/api-v2/config/transport_socket/transport_socket.rst new file mode 100644 index 000000000000..b1e462c0630a --- /dev/null +++ b/docs/root/api-v2/config/transport_socket/transport_socket.rst @@ -0,0 +1,8 @@ +Transport sockets +================= + +.. toctree:: + :glob: + :maxdepth: 1 + + */v2alpha/* diff --git a/docs/root/intro/version_history.rst b/docs/root/intro/version_history.rst index a3fa93fbacbe..cafbc593e2af 100644 --- a/docs/root/intro/version_history.rst +++ b/docs/root/intro/version_history.rst @@ -57,6 +57,8 @@ Version history * logger: added the ability to optionally set the log format via the :option:`--log-format` option. * logger: all :ref:`logging levels ` can be configured at run-time: trace debug info warning error critical. +* sockets: added :ref:`capture transport socket extension ` to support + recording plain text traffic and PCAP generation. * sockets: added `IP_FREEBIND` socket option support for :ref:`listeners ` and upstream connections via :ref:`cluster manager wide diff --git a/docs/root/operations/operations.rst b/docs/root/operations/operations.rst index 8f813eff3eb9..ca6b5cdbc425 100644 --- a/docs/root/operations/operations.rst +++ b/docs/root/operations/operations.rst @@ -12,3 +12,4 @@ Operations and administration stats_overview runtime fs_flags + traffic_capture diff --git a/docs/root/operations/traffic_capture.rst b/docs/root/operations/traffic_capture.rst new file mode 100644 index 000000000000..b607cdfa72b3 --- /dev/null +++ b/docs/root/operations/traffic_capture.rst @@ -0,0 +1,75 @@ +.. _operations_traffic_capture: + +Traffic capture +=============== + +Envoy currently provides an experimental :ref:`transport socket extension +` that can intercept traffic and write to a :ref:`protobuf +capture file `. + +.. warning:: + This feature is experimental and has a known limitation that it will OOM for large traces on a + given socket. It can also be disabled in the build if there are security concerns, see + https://github.com/envoyproxy/envoy/blob/master/bazel/README.md#disabling-extensions. + +Configuration +------------- + +Capture can be configured on :ref:`Listener +` and :ref:`Cluster +` transport sockets, providing the ability to interpose on +downstream and upstream L4 connections respectively. + +To configure traffic capture, add an `envoy.transport_sockets.capture` transport socket +:ref:`configuration ` to the listener +or cluster. For a plain text socket this might look like: + +.. code-block:: yaml + + transport_socket: + name: envoy.transport_sockets.capture + config: + file_sink: + path_prefix: /some/capture/path + transport_socket: + name: raw_buffer + +For a TLS socket, this will be: + +.. code-block:: yaml + + transport_socket: + name: envoy.transport_sockets.capture + config: + file_sink: + path_prefix: /some/capture/path + transport_socket: + name: ssl + config: + +where the TLS context configuration replaces any existing :ref:`downstream +` or :ref:`upstream +` +TLS configuration on the listener or cluster, respectively. + +Each unique socket instance will generate a trace file prefixed with `path_prefix`. E.g. +`/some/capture/path_0.pb`. + +PCAP generation +--------------- + +The generated trace file can be converted to `libpcap format +`_, suitable for +analysis with tools such as `Wireshark `_ with the +`capture2pcap` utility, e.g.: + +.. code-block:: bash + + bazel run @envoy_api//tools:capture2pcap /some/capture/path_0.pb path_0.pcap + tshark -r path_0.pcap -d "tcp.port==10000,http2" -P + 1 0.000000 127.0.0.1 → 127.0.0.1 HTTP2 157 Magic, SETTINGS, WINDOW_UPDATE, HEADERS + 2 0.013713 127.0.0.1 → 127.0.0.1 HTTP2 91 SETTINGS, SETTINGS, WINDOW_UPDATE + 3 0.013820 127.0.0.1 → 127.0.0.1 HTTP2 63 SETTINGS + 4 0.128649 127.0.0.1 → 127.0.0.1 HTTP2 5586 HEADERS + 5 0.130006 127.0.0.1 → 127.0.0.1 HTTP2 7573 DATA + 6 0.131044 127.0.0.1 → 127.0.0.1 HTTP2 3152 DATA, DATA diff --git a/source/common/network/BUILD b/source/common/network/BUILD index 5214b2f60d42..94ccfdd1b78c 100644 --- a/source/common/network/BUILD +++ b/source/common/network/BUILD @@ -4,6 +4,7 @@ load( "//bazel:envoy_build_system.bzl", "envoy_cc_library", "envoy_package", + "envoy_proto_library", ) envoy_package() diff --git a/source/common/network/connection_impl.cc b/source/common/network/connection_impl.cc index 16a355c9a05e..8b35ce40ca24 100644 --- a/source/common/network/connection_impl.cc +++ b/source/common/network/connection_impl.cc @@ -49,8 +49,7 @@ ConnectionImpl::ConnectionImpl(Event::Dispatcher& dispatcher, ConnectionSocketPt socket_(std::move(socket)), write_buffer_(dispatcher.getWatermarkFactory().create( [this]() -> void { this->onLowWatermark(); }, [this]() -> void { this->onHighWatermark(); })), - dispatcher_(dispatcher), id_(++next_global_id_) { - + dispatcher_(dispatcher), id_(next_global_id_++) { // Treat the lack of a valid fd (which in practice only happens if we run out of FDs) as an OOM // condition and just crash. RELEASE_ASSERT(fd() != -1); diff --git a/source/common/network/connection_impl.h b/source/common/network/connection_impl.h index 688571fb00c1..f0f32516a6bd 100644 --- a/source/common/network/connection_impl.h +++ b/source/common/network/connection_impl.h @@ -111,6 +111,9 @@ class ConnectionImpl : public virtual Connection, // Reconsider how to make fairness happen. void setReadBufferReady() override { file_event_->activate(Event::FileReadyType::Read); } + // Obtain global next connection ID. This should only be used in tests. + static uint64_t nextGlobalIdForTest() { return next_global_id_; } + protected: void closeSocket(ConnectionEvent close_type); diff --git a/source/extensions/extensions_build_config.bzl b/source/extensions/extensions_build_config.bzl index 26b2bf199980..3ee7988e56f5 100644 --- a/source/extensions/extensions_build_config.bzl +++ b/source/extensions/extensions_build_config.bzl @@ -75,4 +75,10 @@ EXTENSIONS = { "envoy.tracers.dynamic_ot": "//source/extensions/tracers/dynamic_ot:config", "envoy.tracers.lightstep": "//source/extensions/tracers/lightstep:config", "envoy.tracers.zipkin": "//source/extensions/tracers/zipkin:config", + + # + # Transport sockets + # + + "envoy.transport_sockets.capture": "//source/extensions/transport_sockets/capture:config", } diff --git a/source/extensions/transport_sockets/capture/BUILD b/source/extensions/transport_sockets/capture/BUILD new file mode 100644 index 000000000000..3054da41cfdd --- /dev/null +++ b/source/extensions/transport_sockets/capture/BUILD @@ -0,0 +1,41 @@ +licenses(["notice"]) # Apache 2 +# Capture wrapper around raw_buffer sockets. + +load( + "//bazel:envoy_build_system.bzl", + "envoy_cc_library", + "envoy_package", +) + +envoy_package() + +envoy_cc_library( + name = "capture_lib", + srcs = ["capture.cc"], + hdrs = ["capture.h"], + deps = [ + "//include/envoy/network:transport_socket_interface", + "//source/common/buffer:buffer_lib", + "//source/common/common:assert_lib", + "//source/common/network:utility_lib", + "//source/common/protobuf", + "@envoy_api//envoy/config/transport_socket/capture/v2alpha:capture_cc", + "@envoy_api//envoy/extensions/common/tap/v2alpha:capture_cc", + ], +) + +envoy_cc_library( + name = "config", + srcs = ["config.cc"], + hdrs = ["config.h"], + deps = [ + ":capture_lib", + "//include/envoy/network:transport_socket_interface", + "//include/envoy/registry", + "//include/envoy/server:transport_socket_config_interface", + "//source/common/config:utility_lib", + "//source/common/protobuf:utility_lib", + "//source/extensions/transport_sockets:well_known_names", + "@envoy_api//envoy/config/transport_socket/capture/v2alpha:capture_cc", + ], +) diff --git a/source/extensions/transport_sockets/capture/capture.cc b/source/extensions/transport_sockets/capture/capture.cc new file mode 100644 index 000000000000..26e8aef21a0b --- /dev/null +++ b/source/extensions/transport_sockets/capture/capture.cc @@ -0,0 +1,115 @@ +#include "extensions/transport_sockets/capture/capture.h" + +#include "common/buffer/buffer_impl.h" +#include "common/common/assert.h" +#include "common/common/fmt.h" +#include "common/network/utility.h" +#include "common/protobuf/protobuf.h" + +namespace Envoy { +namespace Extensions { +namespace TransportSockets { +namespace Capture { + +CaptureSocket::CaptureSocket( + const std::string& path_prefix, + envoy::config::transport_socket::capture::v2alpha::FileSink::Format format, + Network::TransportSocketPtr&& transport_socket) + : path_prefix_(path_prefix), format_(format), transport_socket_(std::move(transport_socket)) {} + +void CaptureSocket::setTransportSocketCallbacks(Network::TransportSocketCallbacks& callbacks) { + callbacks_ = &callbacks; + transport_socket_->setTransportSocketCallbacks(callbacks); +} + +std::string CaptureSocket::protocol() const { return transport_socket_->protocol(); } + +bool CaptureSocket::canFlushClose() { return transport_socket_->canFlushClose(); } + +void CaptureSocket::closeSocket(Network::ConnectionEvent event) { + // The caller should have invoked setTransportSocketCallbacks() prior to this. + ASSERT(callbacks_ != nullptr); + auto* connection = trace_.mutable_connection(); + connection->set_id(callbacks_->connection().id()); + Network::Utility::addressToProtobufAddress(*callbacks_->connection().localAddress(), + *connection->mutable_local_address()); + Network::Utility::addressToProtobufAddress(*callbacks_->connection().remoteAddress(), + *connection->mutable_remote_address()); + const bool text_format = + format_ == envoy::config::transport_socket::capture::v2alpha::FileSink::PROTO_TEXT; + const std::string path = fmt::format("{}_{}.{}", path_prefix_, callbacks_->connection().id(), + text_format ? "pb_text" : "pb"); + ENVOY_LOG_MISC(debug, "Writing socket trace for [C{}] to {}", callbacks_->connection().id(), + path); + ENVOY_LOG_MISC(trace, "Socket trace for [C{}]: {}", callbacks_->connection().id(), + trace_.DebugString()); + std::ofstream proto_stream(path); + if (text_format) { + proto_stream << trace_.DebugString(); + } else { + trace_.SerializeToOstream(&proto_stream); + } + transport_socket_->closeSocket(event); +} + +Network::IoResult CaptureSocket::doRead(Buffer::Instance& buffer) { + Network::IoResult result = transport_socket_->doRead(buffer); + if (result.bytes_processed_ > 0) { + // TODO(htuch): avoid linearizing + char* data = static_cast(buffer.linearize(buffer.length())) + + (buffer.length() - result.bytes_processed_); + auto* event = trace_.add_events(); + event->mutable_timestamp()->MergeFrom(Protobuf::util::TimeUtil::NanosecondsToTimestamp( + std::chrono::duration_cast( + std::chrono::system_clock::now().time_since_epoch()) + .count())); + event->mutable_read()->set_data(data, result.bytes_processed_); + } + + return result; +} + +Network::IoResult CaptureSocket::doWrite(Buffer::Instance& buffer, bool end_stream) { + // TODO(htuch): avoid copy. + Buffer::OwnedImpl copy(buffer); + Network::IoResult result = transport_socket_->doWrite(buffer, end_stream); + if (result.bytes_processed_ > 0) { + // TODO(htuch): avoid linearizing. + char* data = static_cast(copy.linearize(result.bytes_processed_)); + auto* event = trace_.add_events(); + event->mutable_timestamp()->MergeFrom(Protobuf::util::TimeUtil::NanosecondsToTimestamp( + std::chrono::duration_cast( + std::chrono::system_clock::now().time_since_epoch()) + .count())); + event->mutable_write()->set_data(data, result.bytes_processed_); + event->mutable_write()->set_end_stream(end_stream); + } + return result; +} + +void CaptureSocket::onConnected() { transport_socket_->onConnected(); } + +Ssl::Connection* CaptureSocket::ssl() { return transport_socket_->ssl(); } + +const Ssl::Connection* CaptureSocket::ssl() const { return transport_socket_->ssl(); } + +CaptureSocketFactory::CaptureSocketFactory( + const std::string& path_prefix, + envoy::config::transport_socket::capture::v2alpha::FileSink::Format format, + Network::TransportSocketFactoryPtr&& transport_socket_factory) + : path_prefix_(path_prefix), format_(format), + transport_socket_factory_(std::move(transport_socket_factory)) {} + +Network::TransportSocketPtr CaptureSocketFactory::createTransportSocket() const { + return std::make_unique(path_prefix_, format_, + transport_socket_factory_->createTransportSocket()); +} + +bool CaptureSocketFactory::implementsSecureTransport() const { + return transport_socket_factory_->implementsSecureTransport(); +} + +} // namespace Capture +} // namespace TransportSockets +} // namespace Extensions +} // namespace Envoy diff --git a/source/extensions/transport_sockets/capture/capture.h b/source/extensions/transport_sockets/capture/capture.h new file mode 100644 index 000000000000..bb9b3e4d49bd --- /dev/null +++ b/source/extensions/transport_sockets/capture/capture.h @@ -0,0 +1,62 @@ +#pragma once + +#include + +#include "envoy/config/transport_socket/capture/v2alpha/capture.pb.h" +#include "envoy/extensions/common/tap/v2alpha/capture.pb.h" +#include "envoy/network/transport_socket.h" + +namespace Envoy { +namespace Extensions { +namespace TransportSockets { +namespace Capture { + +class CaptureSocket : public Network::TransportSocket { +public: + CaptureSocket(const std::string& path_prefix, + envoy::config::transport_socket::capture::v2alpha::FileSink::Format format, + Network::TransportSocketPtr&& transport_socket); + + // Network::TransportSocket + void setTransportSocketCallbacks(Network::TransportSocketCallbacks& callbacks) override; + std::string protocol() const override; + bool canFlushClose() override; + void closeSocket(Network::ConnectionEvent event) override; + Network::IoResult doRead(Buffer::Instance& buffer) override; + Network::IoResult doWrite(Buffer::Instance& buffer, bool end_stream) override; + void onConnected() override; + Ssl::Connection* ssl() override; + const Ssl::Connection* ssl() const override; + +private: + const std::string& path_prefix_; + const envoy::config::transport_socket::capture::v2alpha::FileSink::Format format_; + // TODO(htuch): Buffering the entire trace until socket close won't scale to + // long lived connections or large transfers. We could emit multiple capture + // files with bounded size, with identical connection ID to allow later + // reassembly. + envoy::extensions::common::tap::v2alpha::Trace trace_; + Network::TransportSocketPtr transport_socket_; + Network::TransportSocketCallbacks* callbacks_{}; +}; + +class CaptureSocketFactory : public Network::TransportSocketFactory { +public: + CaptureSocketFactory(const std::string& path_prefix, + envoy::config::transport_socket::capture::v2alpha::FileSink::Format format, + Network::TransportSocketFactoryPtr&& transport_socket_factory); + + // Network::TransportSocketFactory + Network::TransportSocketPtr createTransportSocket() const override; + bool implementsSecureTransport() const override; + +private: + const std::string path_prefix_; + const envoy::config::transport_socket::capture::v2alpha::FileSink::Format format_; + Network::TransportSocketFactoryPtr transport_socket_factory_; +}; + +} // namespace Capture +} // namespace TransportSockets +} // namespace Extensions +} // namespace Envoy diff --git a/source/extensions/transport_sockets/capture/config.cc b/source/extensions/transport_sockets/capture/config.cc new file mode 100644 index 000000000000..9ed2eb759c8d --- /dev/null +++ b/source/extensions/transport_sockets/capture/config.cc @@ -0,0 +1,68 @@ +#include "extensions/transport_sockets/capture/config.h" + +#include "envoy/config/transport_socket/capture/v2alpha/capture.pb.h" +#include "envoy/config/transport_socket/capture/v2alpha/capture.pb.validate.h" +#include "envoy/registry/registry.h" + +#include "common/config/utility.h" +#include "common/protobuf/utility.h" + +#include "extensions/transport_sockets/capture/capture.h" + +namespace Envoy { +namespace Extensions { +namespace TransportSockets { +namespace Capture { + +Network::TransportSocketFactoryPtr UpstreamCaptureSocketConfigFactory::createTransportSocketFactory( + const Protobuf::Message& message, + Server::Configuration::TransportSocketFactoryContext& context) { + const auto& outer_config = MessageUtil::downcastAndValidate< + const envoy::config::transport_socket::capture::v2alpha::Capture&>(message); + auto& inner_config_factory = Config::Utility::getAndCheckFactory< + Server::Configuration::UpstreamTransportSocketConfigFactory>( + outer_config.transport_socket().name()); + ProtobufTypes::MessagePtr inner_factory_config = Config::Utility::translateToFactoryConfig( + outer_config.transport_socket(), inner_config_factory); + auto inner_transport_factory = + inner_config_factory.createTransportSocketFactory(*inner_factory_config, context); + return std::make_unique(outer_config.file_sink().path_prefix(), + outer_config.file_sink().format(), + std::move(inner_transport_factory)); +} + +Network::TransportSocketFactoryPtr +DownstreamCaptureSocketConfigFactory::createTransportSocketFactory( + const std::string& name, const std::vector& server_names, + bool skip_ssl_context_update, const Protobuf::Message& message, + Server::Configuration::TransportSocketFactoryContext& context) { + const auto& outer_config = MessageUtil::downcastAndValidate< + const envoy::config::transport_socket::capture::v2alpha::Capture&>(message); + auto& inner_config_factory = Config::Utility::getAndCheckFactory< + Server::Configuration::DownstreamTransportSocketConfigFactory>( + outer_config.transport_socket().name()); + ProtobufTypes::MessagePtr inner_factory_config = Config::Utility::translateToFactoryConfig( + outer_config.transport_socket(), inner_config_factory); + auto inner_transport_factory = inner_config_factory.createTransportSocketFactory( + name, server_names, skip_ssl_context_update, *inner_factory_config, context); + return std::make_unique(outer_config.file_sink().path_prefix(), + outer_config.file_sink().format(), + std::move(inner_transport_factory)); +} + +ProtobufTypes::MessagePtr CaptureSocketConfigFactory::createEmptyConfigProto() { + return std::make_unique(); +} + +static Registry::RegisterFactory + upstream_registered_; + +static Registry::RegisterFactory + downstream_registered_; + +} // namespace Capture +} // namespace TransportSockets +} // namespace Extensions +} // namespace Envoy diff --git a/source/extensions/transport_sockets/capture/config.h b/source/extensions/transport_sockets/capture/config.h new file mode 100644 index 000000000000..f44db3f54e9d --- /dev/null +++ b/source/extensions/transport_sockets/capture/config.h @@ -0,0 +1,46 @@ +#pragma once + +#include "envoy/server/transport_socket_config.h" + +#include "extensions/transport_sockets/well_known_names.h" + +namespace Envoy { +namespace Extensions { +namespace TransportSockets { +namespace Capture { + +/** + * Config registration for the capture wrapper for transport socket factory. + * @see TransportSocketConfigFactory. + */ +class CaptureSocketConfigFactory + : public virtual Server::Configuration::TransportSocketConfigFactory { +public: + virtual ~CaptureSocketConfigFactory() {} + std::string name() const override { return TransportSocketNames::get().CAPTURE; } + ProtobufTypes::MessagePtr createEmptyConfigProto() override; +}; + +class UpstreamCaptureSocketConfigFactory + : public Server::Configuration::UpstreamTransportSocketConfigFactory, + public CaptureSocketConfigFactory { +public: + Network::TransportSocketFactoryPtr createTransportSocketFactory( + const Protobuf::Message& config, + Server::Configuration::TransportSocketFactoryContext& context) override; +}; + +class DownstreamCaptureSocketConfigFactory + : public Server::Configuration::DownstreamTransportSocketConfigFactory, + public CaptureSocketConfigFactory { +public: + Network::TransportSocketFactoryPtr createTransportSocketFactory( + const std::string& listener_name, const std::vector& server_names, + bool skip_context_update, const Protobuf::Message& config, + Server::Configuration::TransportSocketFactoryContext& context) override; +}; + +} // namespace Capture +} // namespace TransportSockets +} // namespace Extensions +} // namespace Envoy diff --git a/source/extensions/transport_sockets/well_known_names.h b/source/extensions/transport_sockets/well_known_names.h index 712a6dd517a6..bac2b8ead9a3 100644 --- a/source/extensions/transport_sockets/well_known_names.h +++ b/source/extensions/transport_sockets/well_known_names.h @@ -12,6 +12,7 @@ namespace TransportSockets { */ class TransportSocketNameValues { public: + const std::string CAPTURE = "envoy.transport_sockets.capture"; const std::string RAW_BUFFER = "raw_buffer"; const std::string SSL = "ssl"; }; diff --git a/test/integration/BUILD b/test/integration/BUILD index 7a4a7201a366..c5f3e8036304 100644 --- a/test/integration/BUILD +++ b/test/integration/BUILD @@ -239,6 +239,7 @@ envoy_cc_test_library( "//source/common/thread_local:thread_local_lib", "//source/common/upstream:upstream_includes", "//source/common/upstream:upstream_lib", + "//source/extensions/transport_sockets/capture:config", "//source/extensions/transport_sockets/raw_buffer:config", "//source/server:connection_handler_lib", "//source/server:hot_restart_nop_lib", @@ -360,12 +361,15 @@ envoy_cc_test( ":http_integration_lib", "//source/common/event:dispatcher_includes", "//source/common/event:dispatcher_lib", + "//source/common/network:connection_lib", "//source/common/network:utility_lib", "//source/common/ssl:context_config_lib", "//source/common/ssl:context_lib", "//source/extensions/transport_sockets/ssl:config", "//test/mocks/runtime:runtime_mocks", "//test/test_common:utility_lib", + "@envoy_api//envoy/config/transport_socket/capture/v2alpha:capture_cc", + "@envoy_api//envoy/extensions/common/tap/v2alpha:capture_cc", ], ) diff --git a/test/integration/http_integration.h b/test/integration/http_integration.h index 085044670881..c9dcc1b2245b 100644 --- a/test/integration/http_integration.h +++ b/test/integration/http_integration.h @@ -33,6 +33,7 @@ class IntegrationCodecClient : public Http::CodecClientProd { Http::StreamEncoder& startRequest(const Http::HeaderMap& headers, IntegrationStreamDecoder& response); void waitForDisconnect(); + Network::ClientConnection* connection() const { return connection_.get(); } private: struct ConnectionCallbacks : public Network::ConnectionCallbacks { diff --git a/test/integration/ssl_integration_test.cc b/test/integration/ssl_integration_test.cc index ac3a6f15158c..d149086143b7 100644 --- a/test/integration/ssl_integration_test.cc +++ b/test/integration/ssl_integration_test.cc @@ -3,7 +3,11 @@ #include #include +#include "envoy/config/transport_socket/capture/v2alpha/capture.pb.h" +#include "envoy/extensions/common/tap/v2alpha/capture.pb.h" + #include "common/event/dispatcher_impl.h" +#include "common/network/connection_impl.h" #include "common/network/utility.h" #include "common/ssl/context_config_impl.h" #include "common/ssl/context_manager_impl.h" @@ -12,6 +16,7 @@ #include "test/test_common/network_utility.h" #include "test/test_common/utility.h" +#include "absl/strings/match.h" #include "gmock/gmock.h" #include "gtest/gtest.h" #include "integration.h" @@ -179,5 +184,118 @@ TEST_P(SslIntegrationTest, AltAlpn) { checkStats(); } +class SslCaptureIntegrationTest : public SslIntegrationTest { +public: + void initialize() override { + config_helper_.addConfigModifier([this](envoy::config::bootstrap::v2::Bootstrap& bootstrap) { + auto* filter_chain = + bootstrap.mutable_static_resources()->mutable_listeners(0)->mutable_filter_chains(0); + // Configure inner SSL transport socket based on existing config. + envoy::api::v2::core::TransportSocket ssl_transport_socket; + ssl_transport_socket.set_name("ssl"); + MessageUtil::jsonConvert(filter_chain->tls_context(), *ssl_transport_socket.mutable_config()); + // Configure outer capture transport socket. + auto* transport_socket = filter_chain->mutable_transport_socket(); + transport_socket->set_name("envoy.transport_sockets.capture"); + envoy::config::transport_socket::capture::v2alpha::Capture capture_config; + auto* file_sink = capture_config.mutable_file_sink(); + file_sink->set_path_prefix(path_prefix_); + file_sink->set_format( + text_format_ ? envoy::config::transport_socket::capture::v2alpha::FileSink::PROTO_TEXT + : envoy::config::transport_socket::capture::v2alpha::FileSink::PROTO_BINARY); + capture_config.mutable_transport_socket()->MergeFrom(ssl_transport_socket); + MessageUtil::jsonConvert(capture_config, *transport_socket->mutable_config()); + // Nuke TLS context from legacy location. + filter_chain->clear_tls_context(); + // Rest of TLS initialization. + }); + SslIntegrationTest::initialize(); + } + + std::string path_prefix_ = TestEnvironment::temporaryPath("ssl_trace"); + bool text_format_{}; +}; + +INSTANTIATE_TEST_CASE_P(IpVersions, SslCaptureIntegrationTest, + testing::ValuesIn(TestEnvironment::getIpVersionsForTest()), + TestUtility::ipTestParamsToString); + +// Validate two back-to-back requests with binary proto output. +TEST_P(SslCaptureIntegrationTest, TwoRequestsWithBinaryProto) { + initialize(); + ConnectionCreationFunction creator = [&]() -> Network::ClientConnectionPtr { + return makeSslClientConnection(false, false); + }; + + // First request (ID will be +1 since the client will also bump). + const uint64_t first_id = Network::ConnectionImpl::nextGlobalIdForTest() + 1; + codec_client_ = makeHttpConnection(creator()); + Http::TestHeaderMapImpl post_request_headers{ + {":method", "POST"}, {":path", "/test/long/url"}, {":scheme", "http"}, + {":authority", "host"}, {"x-lyft-user-id", "123"}, {"x-forwarded-for", "10.0.0.1"}}; + sendRequestAndWaitForResponse(post_request_headers, 128, default_response_headers_, 256); + EXPECT_TRUE(upstream_request_->complete()); + EXPECT_EQ(128, upstream_request_->bodyLength()); + ASSERT_TRUE(response_->complete()); + EXPECT_STREQ("200", response_->headers().Status()->value().c_str()); + EXPECT_EQ(256, response_->body().size()); + checkStats(); + envoy::api::v2::core::Address expected_local_address; + Network::Utility::addressToProtobufAddress(*codec_client_->connection()->remoteAddress(), + expected_local_address); + envoy::api::v2::core::Address expected_remote_address; + Network::Utility::addressToProtobufAddress(*codec_client_->connection()->localAddress(), + expected_remote_address); + codec_client_->close(); + test_server_->waitForCounterGe("http.config_test.downstream_cx_destroy", 1); + envoy::extensions::common::tap::v2alpha::Trace trace; + MessageUtil::loadFromFile(fmt::format("{}_{}.pb", path_prefix_, first_id), trace); + // Validate general expected properties in the trace. + EXPECT_EQ(first_id, trace.connection().id()); + EXPECT_THAT(expected_local_address, ProtoEq(trace.connection().local_address())); + EXPECT_THAT(expected_remote_address, ProtoEq(trace.connection().remote_address())); + EXPECT_TRUE(absl::StartsWith(trace.events(0).read().data(), "POST /test/long/url HTTP/1.1")); + EXPECT_TRUE(absl::StartsWith(trace.events(1).write().data(), "HTTP/1.1 200 OK")); + + // Verify a second request hits a different file. + const uint64_t second_id = Network::ConnectionImpl::nextGlobalIdForTest() + 1; + codec_client_ = makeHttpConnection(creator()); + Http::TestHeaderMapImpl get_request_headers{ + {":method", "GET"}, {":path", "/test/long/url"}, {":scheme", "http"}, + {":authority", "host"}, {"x-lyft-user-id", "123"}, {"x-forwarded-for", "10.0.0.1"}}; + sendRequestAndWaitForResponse(get_request_headers, 128, default_response_headers_, 256); + EXPECT_TRUE(upstream_request_->complete()); + EXPECT_EQ(128, upstream_request_->bodyLength()); + ASSERT_TRUE(response_->complete()); + EXPECT_STREQ("200", response_->headers().Status()->value().c_str()); + EXPECT_EQ(256, response_->body().size()); + checkStats(); + codec_client_->close(); + test_server_->waitForCounterGe("http.config_test.downstream_cx_destroy", 2); + MessageUtil::loadFromFile(fmt::format("{}_{}.pb", path_prefix_, second_id), trace); + // Validate second connection ID. + EXPECT_EQ(second_id, trace.connection().id()); + EXPECT_TRUE(absl::StartsWith(trace.events(0).read().data(), "GET /test/long/url HTTP/1.1")); + EXPECT_TRUE(absl::StartsWith(trace.events(1).write().data(), "HTTP/1.1 200 OK")); +} + +// Validate a single request with text proto output. +TEST_P(SslCaptureIntegrationTest, RequestWithTextProto) { + text_format_ = true; + ConnectionCreationFunction creator = [&]() -> Network::ClientConnectionPtr { + return makeSslClientConnection(false, false); + }; + const uint64_t id = Network::ConnectionImpl::nextGlobalIdForTest() + 1; + testRouterRequestAndResponseWithBody(1024, 512, false, &creator); + checkStats(); + codec_client_->close(); + test_server_->waitForCounterGe("http.config_test.downstream_cx_destroy", 1); + envoy::extensions::common::tap::v2alpha::Trace trace; + MessageUtil::loadFromFile(fmt::format("{}_{}.pb_text", path_prefix_, id), trace); + // Test some obvious properties. + EXPECT_TRUE(absl::StartsWith(trace.events(0).read().data(), "POST /test/long/url HTTP/1.1")); + EXPECT_TRUE(absl::StartsWith(trace.events(1).write().data(), "HTTP/1.1 200 OK")); +} + } // namespace Ssl } // namespace Envoy