From e0c8068941aad68512a291436683f3d5bdb0f02f Mon Sep 17 00:00:00 2001 From: Benjamin Kilimnik Date: Wed, 29 Nov 2023 21:24:03 +0000 Subject: [PATCH] Add bpf test for loop limit exceeded Signed-off-by: Benjamin Kilimnik --- .../socket_tracer/BUILD.bazel | 21 +++ .../socket_tracer/nodejs_trace_bpf_test.cc | 165 ++++++++++++++++++ .../testing/container_images/BUILD.bazel | 10 ++ .../container_images/acorn_node14_container.h | 48 +++++ .../testing/containers/BUILD.bazel | 12 ++ .../testing/containers/acornjs/BUILD.bazel | 36 ++++ .../containers/acornjs/acorn_client.js | 43 +++++ .../containers/acornjs/acorn_server.js | 101 +++++++++++ .../testing/containers/nodejs/BUILD.bazel | 36 ++++ .../testing/containers/nodejs/https_client.js | 43 +++++ .../testing/containers/nodejs/https_server.js | 101 +++++++++++ .../socket_tracer/testing/protocol_checkers.h | 15 ++ 12 files changed, 631 insertions(+) create mode 100644 src/stirling/source_connectors/socket_tracer/nodejs_trace_bpf_test.cc create mode 100644 src/stirling/source_connectors/socket_tracer/testing/container_images/acorn_node14_container.h create mode 100644 src/stirling/source_connectors/socket_tracer/testing/containers/acornjs/BUILD.bazel create mode 100644 src/stirling/source_connectors/socket_tracer/testing/containers/acornjs/acorn_client.js create mode 100644 src/stirling/source_connectors/socket_tracer/testing/containers/acornjs/acorn_server.js create mode 100644 src/stirling/source_connectors/socket_tracer/testing/containers/nodejs/BUILD.bazel create mode 100644 src/stirling/source_connectors/socket_tracer/testing/containers/nodejs/https_client.js create mode 100644 src/stirling/source_connectors/socket_tracer/testing/containers/nodejs/https_server.js diff --git a/src/stirling/source_connectors/socket_tracer/BUILD.bazel b/src/stirling/source_connectors/socket_tracer/BUILD.bazel index b908d2a30bf..978125b96d4 100644 --- a/src/stirling/source_connectors/socket_tracer/BUILD.bazel +++ b/src/stirling/source_connectors/socket_tracer/BUILD.bazel @@ -474,6 +474,27 @@ pl_cc_bpf_test( ], ) +pl_cc_bpf_test( + name = "nodejs_trace_bpf_test", + timeout = "long", + srcs = ["nodejs_trace_bpf_test.cc"], + flaky = True, + shard_count = 2, + tags = [ + "cpu:16", + "no_asan", + "requires_bpf", + ], + deps = [ + ":cc_library", + "//src/common/testing/test_utils:cc_library", + "//src/stirling/source_connectors/socket_tracer/testing:cc_library", + "//src/stirling/source_connectors/socket_tracer/testing/container_images:acorn_node14_container", + "//src/stirling/source_connectors/socket_tracer/testing/container_images:curl_container", + "//src/stirling/testing:cc_library", + ], +) + pl_cc_bpf_test( name = "boringssl_trace_bpf_test", timeout = "long", diff --git a/src/stirling/source_connectors/socket_tracer/nodejs_trace_bpf_test.cc b/src/stirling/source_connectors/socket_tracer/nodejs_trace_bpf_test.cc new file mode 100644 index 00000000000..ec579adc0d9 --- /dev/null +++ b/src/stirling/source_connectors/socket_tracer/nodejs_trace_bpf_test.cc @@ -0,0 +1,165 @@ +/* + * Copyright 2018- The Pixie Authors. + * + * Licensed 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. + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#include +#include + +#include + +#include +#include "src/common/base/base.h" +#include "src/common/exec/exec.h" +#include "src/common/testing/test_environment.h" +#include "src/shared/types/column_wrapper.h" +#include "src/shared/types/types.h" +#include "src/stirling/source_connectors/socket_tracer/socket_trace_connector.h" +#include "src/stirling/source_connectors/socket_tracer/testing/container_images/curl_container.h" +#include "src/stirling/source_connectors/socket_tracer/testing/container_images/acorn_node14_container.h" +#include "src/stirling/source_connectors/socket_tracer/testing/protocol_checkers.h" +#include "src/stirling/source_connectors/socket_tracer/testing/socket_trace_bpf_test_fixture.h" +#include "src/stirling/source_connectors/socket_tracer/uprobe_symaddrs.h" +#include "src/stirling/testing/common.h" + +namespace px { +namespace stirling { + +namespace http = protocols::http; + +using ::px::stirling::testing::EqHTTPRecord; +using ::px::stirling::testing::EqHTTPRecordWithBodyRegex; +using ::px::stirling::testing::FindRecordIdxMatchesPID; +using ::px::stirling::testing::GetTargetRecords; +using ::px::stirling::testing::SocketTraceBPFTestFixture; +using ::px::stirling::testing::ToRecordVector; + +using ::testing::StrEq; +using ::testing::Types; +using ::testing::UnorderedElementsAre; + +class AcornNode14ContainerWrapper : public ::px::stirling::testing::AcornNode14Container { + public: + int32_t PID() const { return process_pid(); } +}; + +// Includes all information we need to extract from the trace records, which are used to verify +// against the expected results. +struct TraceRecords { + std::vector http_records; + std::vector remote_address; +}; + +template +class NodeJSTraceTest : public SocketTraceBPFTestFixture { + protected: + NodeJSTraceTest() { + // The container runner will make sure it is in the ready state before unblocking. + // Stirling will run after this unblocks, as part of SocketTraceBPFTest SetUp(). + StatusOr run_result = server_.Run(std::chrono::seconds{60}); + PX_CHECK_OK(run_result); + + // Sleep an additional second, just to be safe. + sleep(1); + } + + // Returns the trace records of the process specified by the input pid. + TraceRecords GetTraceRecords(int pid) { + std::vector tablets = + this->ConsumeRecords(SocketTraceConnector::kHTTPTableNum); + if (tablets.empty()) { + return {}; + } + types::ColumnWrapperRecordBatch record_batch = tablets[0].records; + std::vector server_record_indices = + FindRecordIdxMatchesPID(record_batch, kHTTPUPIDIdx, pid); + std::vector http_records = + ToRecordVector(record_batch, server_record_indices); + std::vector remote_addresses = + testing::GetRemoteAddrs(record_batch, server_record_indices); + return {std::move(http_records), std::move(remote_addresses)}; + } + + TServerContainer server_; +}; + +//----------------------------------------------------------------------------- +// Test Scenarios +//----------------------------------------------------------------------------- + +std::string_view kPartialRespBody = R"delimiter( +295885c4 200 4096 8eee8a560fc1b68e046e9cb5de9a4c64b146667c93280582f5105914c333144 + +8eee8a560fc1b68e046e9cb5de9a4c64b146667c93280582f5105914c333144 +8eee8a560fc1b68e046e9cb5de9a4c64b146667c93280582f5105914c333144 +8eee8a560fc1b68e046e9cb5de9a4c64b146667c93280582f5105914c333144 +8eee8a560fc1b68e046e9cb5de9a4c64b146667c93280582f5105914c333144 +8eee8a560fc1b68e046e9cb5de9a4c64b146667c93280582f5105914c333144 +8eee8a560fc1b68e046e9cb5de9a4c64b146667c93280582f5105914c333144 +8eee8a560fc1b68e046e9cb5de9a4c64b146667c93280... [TRUNCATED])delimiter"; + +http::Record GetExpectedHTTPRecord() { + http::Record expected_record; + expected_record.req.req_method = "GET"; + expected_record.req.req_path = "/"; + expected_record.req.body = ""; + + expected_record.resp.resp_status = 200; + expected_record.resp.resp_message = "OK"; + return expected_record; +} + +using NodeJSServerImplementations = Types; + +TYPED_TEST_SUITE(NodeJSTraceTest, NodeJSServerImplementations); + +TYPED_TEST(NodeJSTraceTest, simple_curl_client) { + FLAGS_stirling_conn_trace_pid = this->server_.process_pid(); + this->StartTransferDataThread(); + + // Run the client in the network of the server, so they can connect to each other. + ::px::stirling::testing::CurlContainer client; + ASSERT_OK(client.Run(std::chrono::seconds{60}, + {absl::Substitute("--network=container:$0", this->server_.container_name())}, + {"-s", "-S", "localhost:8080"})); + client.Wait(); + this->StopTransferDataThread(); + + TraceRecords records = this->GetTraceRecords(this->server_.PID()); + + http::Record expected_record = GetExpectedHTTPRecord(); + + if (LazyParsingEnabled()) { + // We lazily parse the incomplete chunk with a partial body. + // only the consistent part of the response body is checked + EXPECT_THAT(records.http_records, + UnorderedElementsAre(EqHTTPRecordWithBodyRegex(expected_record, ".*200 4096.*"))); + + EXPECT_THAT(records.remote_address, UnorderedElementsAre(StrEq("127.0.0.1"))); + } else { + // No records should be captured because we exceeded the loop limit (iovec has length 257) + // and lazy parsing was disabled. + EXPECT_THAT(records.http_records, UnorderedElementsAre()); + } + + auto& registry = GetMetricsRegistry(); // retrieves global var keeping metrics + auto metrics = registry.Collect(); // samples all metrics + auto metrics_text = prometheus::TextSerializer().Serialize(metrics); // serializes to text + LOG(WARNING) << absl::Substitute("with metric text: $0", metrics_text); +} + +} // namespace stirling +} // namespace px diff --git a/src/stirling/source_connectors/socket_tracer/testing/container_images/BUILD.bazel b/src/stirling/source_connectors/socket_tracer/testing/container_images/BUILD.bazel index 934009dea6d..5146cdbe2c6 100644 --- a/src/stirling/source_connectors/socket_tracer/testing/container_images/BUILD.bazel +++ b/src/stirling/source_connectors/socket_tracer/testing/container_images/BUILD.bazel @@ -230,6 +230,16 @@ pl_cc_test_library( deps = ["//src/common/testing/test_utils:cc_library"], ) +pl_cc_test_library( + name = "acorn_node14_container", + srcs = [], + hdrs = ["acorn_node14_container.h"], + data = [ + "//src/stirling/source_connectors/socket_tracer/testing/containers:nodejs_acorn_image.tar", + ], + deps = ["//src/common/testing/test_utils:cc_library"], +) + pl_cc_test_library( name = "node_client_container", srcs = [], diff --git a/src/stirling/source_connectors/socket_tracer/testing/container_images/acorn_node14_container.h b/src/stirling/source_connectors/socket_tracer/testing/container_images/acorn_node14_container.h new file mode 100644 index 00000000000..dfa592e51fb --- /dev/null +++ b/src/stirling/source_connectors/socket_tracer/testing/container_images/acorn_node14_container.h @@ -0,0 +1,48 @@ +/* + * Copyright 2018- The Pixie Authors. + * + * Licensed 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. + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#pragma once + +#include + +#include "src/common/testing/test_environment.h" +#include "src/common/testing/test_utils/container_runner.h" + +namespace px { +namespace stirling { +namespace testing { + +class AcornNode14Container : public ContainerRunner { + public: + AcornNode14Container() + : ContainerRunner(::px::testing::BazelRunfilePath(kBazelImageTar), kContainerNamePrefix, + kReadyMessage) {} + + private: + static constexpr std::string_view kBazelImageTar = + "src/stirling/source_connectors/socket_tracer/testing/containers/" + "nodejs_acorn_image.tar"; + static constexpr std::string_view kContainerNamePrefix = "acorn_node_server"; + static constexpr std::string_view kReadyMessage = "listening on 8080"; +}; + +// bazel run image to test for same behavior + +} // namespace testing +} // namespace stirling +} // namespace px diff --git a/src/stirling/source_connectors/socket_tracer/testing/containers/BUILD.bazel b/src/stirling/source_connectors/socket_tracer/testing/containers/BUILD.bazel index 1174a83d9c1..523d2435e98 100644 --- a/src/stirling/source_connectors/socket_tracer/testing/containers/BUILD.bazel +++ b/src/stirling/source_connectors/socket_tracer/testing/containers/BUILD.bazel @@ -131,6 +131,18 @@ container_image( ] ] +container_image( + name = "nodejs_acorn_image", + base = "@node_14_18_1_alpine_linux_amd64_image//image", + cmd = [ + "node", + "/etc/node/acorn_server.js", + ], + layers = [ + "//src/stirling/source_connectors/socket_tracer/testing/containers/acornjs:acorn_client_server_layer", + ], +) + container_image( name = "amqp_image", base = "@rabbitmq_3_management//image", diff --git a/src/stirling/source_connectors/socket_tracer/testing/containers/acornjs/BUILD.bazel b/src/stirling/source_connectors/socket_tracer/testing/containers/acornjs/BUILD.bazel new file mode 100644 index 00000000000..aad36e968c4 --- /dev/null +++ b/src/stirling/source_connectors/socket_tracer/testing/containers/acornjs/BUILD.bazel @@ -0,0 +1,36 @@ +# Copyright 2018- The Pixie Authors. +# +# Licensed 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. +# +# SPDX-License-Identifier: Apache-2.0 + +load("@io_bazel_rules_docker//container:container.bzl", "container_layer") +load("@rules_pkg//pkg:tar.bzl", "pkg_tar") + +package(default_visibility = ["//src/stirling:__subpackages__"]) + +pkg_tar( + name = "acorn_client_server", + srcs = [ + "acorn_client.js", + "acorn_server.js", + ], + mode = "0755", + strip_prefix = "/src/stirling/source_connectors/socket_tracer/testing/containers/acornjs", +) + +container_layer( + name = "acorn_client_server_layer", + directory = "/etc/node", + tars = [":acorn_client_server"], +) diff --git a/src/stirling/source_connectors/socket_tracer/testing/containers/acornjs/acorn_client.js b/src/stirling/source_connectors/socket_tracer/testing/containers/acornjs/acorn_client.js new file mode 100644 index 00000000000..3ea5cfdbbb5 --- /dev/null +++ b/src/stirling/source_connectors/socket_tracer/testing/containers/acornjs/acorn_client.js @@ -0,0 +1,43 @@ +/* + * Copyright 2018- The Pixie Authors. + * + * Licensed 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. + * + * SPDX-License-Identifier: Apache-2.0 + */ + +const https = require('https'); + +const options = { + hostname: 'localhost', + port: 443, + path: '/index.html', + method: 'GET' +}; + +// Allows self-signed certs. +process.env["NODE_TLS_REJECT_UNAUTHORIZED"] = 0; + +const req = https.request(options, (res) => { + console.log('statusCode:', res.statusCode); + console.log('headers:', res.headers); + + res.on('data', (d) => { + process.stdout.write(d); + }); +}); + +req.on('error', (e) => { + console.error(e); +}); +req.end(); diff --git a/src/stirling/source_connectors/socket_tracer/testing/containers/acornjs/acorn_server.js b/src/stirling/source_connectors/socket_tracer/testing/containers/acornjs/acorn_server.js new file mode 100644 index 00000000000..013c65d2175 --- /dev/null +++ b/src/stirling/source_connectors/socket_tracer/testing/containers/acornjs/acorn_server.js @@ -0,0 +1,101 @@ +/* + * Copyright 2018- The Pixie Authors. + * + * Licensed 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. + * + * SPDX-License-Identifier: Apache-2.0 + */ + +const http = require('http') +const url = require('url') +const port = process.env.PORT || 8080 +const crypto = require('crypto') + +const id = crypto.randomBytes(4).toString('hex') +const message = crypto.randomBytes(32).toString('hex').substr(0,63)+"\n" +const len = message.length +const targets = [] +const isDebug = process.env.DEBUG === 'true' +const success = parseInt(process.env.SUCCESS_PERCENT,10) || 90 +const errorCodes = [400, 401, 403, 418, 422, 500] + +for ( const k of Object.keys(process.env) ) { + if ( k.startsWith('TARGET_') ) { + let [host, port, bytes, seconds] = process.env[k].split(/[:,]/) + targets.push({host, port, bytes, seconds}) + } +} + +console.log(`[${id}] listening on ${port}`) +for ( const t of targets ) { + debug(`Request ${t.bytes} from ${t.host}:${t.port} every ${t.seconds} seconds`) + setInterval(() => { + const req = http.request(`http://${t.host}:${t.port}/${id}/${t.bytes}`, (res) => { + let received = 0 + let fromId = '' + + res.on('data', (chunk) => { + if ( !received ) { + fromId = chunk.toString().split(' ')[0] + } + received += chunk.length + }) + + res.on('end', () => { + debug(`[${id}] Received ${received} from ${fromId} (${t.host}:${t.port})`) + }) + }) + + req.on('error', (err) => { + console.error(`[${id}] Error: ${err}`) + }) + + req.end() + }, t.seconds * 1000) +} +console.log('===============================') + +http.createServer((req, res) => { + const parsed = url.parse(req.url) + const parts = parsed.path.substring(1).split('/') + let target = parts[0] + let bytes = parseInt(parts[1],10) || 4096 + let status = 200 + + if ( crypto.randomInt(0, 101) > success ) { + status = errorCodes[crypto.randomInt(0,errorCodes.length)] + } + + res.writeHead(status, {'Content-Type': 'text/plain'}) + const intro = `${id} ${status} ${bytes} ${message}\n` + bytes -= intro.length + res.write(intro) + + while ( bytes > len ) { + res.write(message) + bytes -= len + } + + if ( bytes > 0 ) { + res.write(message.substring(0,bytes)) + } + + debug(`[${id}] Sent ${bytes} to ${target}`) + res.end() +}).listen(port) + +function debug(...msg) { + if ( isDebug ) { + console.log(...msg) + } +} diff --git a/src/stirling/source_connectors/socket_tracer/testing/containers/nodejs/BUILD.bazel b/src/stirling/source_connectors/socket_tracer/testing/containers/nodejs/BUILD.bazel new file mode 100644 index 00000000000..fe378179b14 --- /dev/null +++ b/src/stirling/source_connectors/socket_tracer/testing/containers/nodejs/BUILD.bazel @@ -0,0 +1,36 @@ +# Copyright 2018- The Pixie Authors. +# +# Licensed 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. +# +# SPDX-License-Identifier: Apache-2.0 + +load("@io_bazel_rules_docker//container:container.bzl", "container_layer") +load("@rules_pkg//pkg:tar.bzl", "pkg_tar") + +package(default_visibility = ["//src/stirling:__subpackages__"]) + +pkg_tar( + name = "node_client_server", + srcs = [ + "https_client.js", + "https_server.js", + ], + mode = "0755", + strip_prefix = "/src/stirling/source_connectors/socket_tracer/testing/containers/nodejs", +) + +container_layer( + name = "node_client_server_layer", + directory = "/etc/node", + tars = [":node_client_server"], +) diff --git a/src/stirling/source_connectors/socket_tracer/testing/containers/nodejs/https_client.js b/src/stirling/source_connectors/socket_tracer/testing/containers/nodejs/https_client.js new file mode 100644 index 00000000000..3ea5cfdbbb5 --- /dev/null +++ b/src/stirling/source_connectors/socket_tracer/testing/containers/nodejs/https_client.js @@ -0,0 +1,43 @@ +/* + * Copyright 2018- The Pixie Authors. + * + * Licensed 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. + * + * SPDX-License-Identifier: Apache-2.0 + */ + +const https = require('https'); + +const options = { + hostname: 'localhost', + port: 443, + path: '/index.html', + method: 'GET' +}; + +// Allows self-signed certs. +process.env["NODE_TLS_REJECT_UNAUTHORIZED"] = 0; + +const req = https.request(options, (res) => { + console.log('statusCode:', res.statusCode); + console.log('headers:', res.headers); + + res.on('data', (d) => { + process.stdout.write(d); + }); +}); + +req.on('error', (e) => { + console.error(e); +}); +req.end(); diff --git a/src/stirling/source_connectors/socket_tracer/testing/containers/nodejs/https_server.js b/src/stirling/source_connectors/socket_tracer/testing/containers/nodejs/https_server.js new file mode 100644 index 00000000000..013c65d2175 --- /dev/null +++ b/src/stirling/source_connectors/socket_tracer/testing/containers/nodejs/https_server.js @@ -0,0 +1,101 @@ +/* + * Copyright 2018- The Pixie Authors. + * + * Licensed 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. + * + * SPDX-License-Identifier: Apache-2.0 + */ + +const http = require('http') +const url = require('url') +const port = process.env.PORT || 8080 +const crypto = require('crypto') + +const id = crypto.randomBytes(4).toString('hex') +const message = crypto.randomBytes(32).toString('hex').substr(0,63)+"\n" +const len = message.length +const targets = [] +const isDebug = process.env.DEBUG === 'true' +const success = parseInt(process.env.SUCCESS_PERCENT,10) || 90 +const errorCodes = [400, 401, 403, 418, 422, 500] + +for ( const k of Object.keys(process.env) ) { + if ( k.startsWith('TARGET_') ) { + let [host, port, bytes, seconds] = process.env[k].split(/[:,]/) + targets.push({host, port, bytes, seconds}) + } +} + +console.log(`[${id}] listening on ${port}`) +for ( const t of targets ) { + debug(`Request ${t.bytes} from ${t.host}:${t.port} every ${t.seconds} seconds`) + setInterval(() => { + const req = http.request(`http://${t.host}:${t.port}/${id}/${t.bytes}`, (res) => { + let received = 0 + let fromId = '' + + res.on('data', (chunk) => { + if ( !received ) { + fromId = chunk.toString().split(' ')[0] + } + received += chunk.length + }) + + res.on('end', () => { + debug(`[${id}] Received ${received} from ${fromId} (${t.host}:${t.port})`) + }) + }) + + req.on('error', (err) => { + console.error(`[${id}] Error: ${err}`) + }) + + req.end() + }, t.seconds * 1000) +} +console.log('===============================') + +http.createServer((req, res) => { + const parsed = url.parse(req.url) + const parts = parsed.path.substring(1).split('/') + let target = parts[0] + let bytes = parseInt(parts[1],10) || 4096 + let status = 200 + + if ( crypto.randomInt(0, 101) > success ) { + status = errorCodes[crypto.randomInt(0,errorCodes.length)] + } + + res.writeHead(status, {'Content-Type': 'text/plain'}) + const intro = `${id} ${status} ${bytes} ${message}\n` + bytes -= intro.length + res.write(intro) + + while ( bytes > len ) { + res.write(message) + bytes -= len + } + + if ( bytes > 0 ) { + res.write(message.substring(0,bytes)) + } + + debug(`[${id}] Sent ${bytes} to ${target}`) + res.end() +}).listen(port) + +function debug(...msg) { + if ( isDebug ) { + console.log(...msg) + } +} diff --git a/src/stirling/source_connectors/socket_tracer/testing/protocol_checkers.h b/src/stirling/source_connectors/socket_tracer/testing/protocol_checkers.h index f17d7bf0eb0..c332bf22139 100644 --- a/src/stirling/source_connectors/socket_tracer/testing/protocol_checkers.h +++ b/src/stirling/source_connectors/socket_tracer/testing/protocol_checkers.h @@ -93,6 +93,21 @@ inline auto EqHTTPRecord(const protocols::http::Record& x) { Field(&protocols::http::Record::resp, EqHTTPResp(x.resp))); } +inline auto EqHTTPRecordWithBodyRegex(const protocols::http::Record& x, + const std::string& body_regex) { + using ::testing::Field; + using ::testing::MatchesRegex; + + auto EqHTTPRespWithRegex = [&x, &body_regex]() { + return AllOf(Field(&protocols::http::Message::resp_status, x.resp.resp_status), + Field(&protocols::http::Message::resp_message, x.resp.resp_message), + Field(&protocols::http::Message::body, MatchesRegex(body_regex))); + }; + + return AllOf(Field(&protocols::http::Record::req, EqHTTPReq(x.req)), + Field(&protocols::http::Record::resp, EqHTTPRespWithRegex())); +} + inline auto EqMux(const protocols::mux::Frame& x) { using ::testing::Field;